From d8e2eab3b0b0d9c22ea75ddb90d0157d242b8dd4 Mon Sep 17 00:00:00 2001 From: Alexandr Romanov Date: Mon, 11 Nov 2024 23:46:27 +0300 Subject: [PATCH] Develop (#16) * Add new router * Add routing draft * Add router * Upd CI push develop * Test fix * Fix tvOS errors * Fix tvOS support * Fix tvOS * Add wathOS example * Fix ci * Up * Upd * Remove build macOS * Fix support iPad * Up router * Fix iPhone 15 version * Add iPhone 14 Pro build * Add macOS build * Fix ci * Delete macOS build * Update routing, onboarding and up to Swift6 * Fix #major * Fix ci * Update CI * Fix EventKit and Contacts * Format code * Fix example * Fix macOS * Update CI --- .github/workflows/ci-pull-request.yml | 36 - .github/workflows/ci-push.yml | 46 - .github/workflows/ci.yml | 100 +++ .github/workflows/release.yml | 2 +- .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 13 + .../Assets.xcassets/Contents.json | 6 + .../ContentView.swift | 30 + .../Example__watchOS_App.swift | 21 + .../Preview Assets.xcassets/Contents.json | 6 + AppExample/Example.xcodeproj/project.pbxproj | 433 +++++++++- .../UserInterfaceState.xcuserstate | Bin 97667 -> 299511 bytes .../Example (watchOS) Watch App.xcscheme | 92 ++ .../xcschemes/xcschememanagement.plist | 15 + AppExample/Example/ExampleApp.swift | 2 + .../Screens/Onboarding/OnboardingView.swift | 12 +- AppExample/Example/Test/TestView.swift | 24 + AppExample/Package.swift | 4 + Package.swift | 27 +- .../CreateEventScreen/CreateEventView.swift | 804 +++++++++--------- .../CreateEventViewModel.swift | 305 +++---- .../CreateEventViewSheet.swift | 179 ++-- .../SaveForView/SaveForView.swift | 68 +- .../Pickers/AlertPicker.swift | 68 +- .../Pickers/CalendarPicker.swift | 88 +- .../Pickers/RepeatPicker.swift | 204 ++--- .../AttendeesList/AttendeesView.swift | 130 +-- .../AttendeesList/AttendeesViewModel.swift | 69 +- .../ContactsLists/ContactsListsView.swift | 128 +-- .../ContactsListsViewModel.swift | 59 +- .../ContactsPicker/EmailPickerView.swift | 345 ++++---- .../ContactsPicker/EmailPickerViewModel.swift | 70 +- Sources/OversizeKit/AdsKit/AdView.swift | 9 +- .../OversizeKit/LauncherKit/Launcher.swift | 63 +- .../LauncherKit/LauncherViewModel.swift | 6 +- .../LauncherKit/RateAppScreen.swift | 18 +- .../LockscreenKit/LockscreenView.swift | 19 +- .../SettingsRouter/ResolveRouter.swift | 65 ++ .../SettingsKit/SettingsRouter/Screens.swift | 115 +++ .../SettingsRouter/SettingsRouting.swift | 20 + .../Views/About/About/AboutView.swift | 763 ++++++++--------- .../Views/About/FeedbackView.swift | 25 +- .../Views/About/OurResorsesView.swift | 7 +- .../SettingsKit/Views/About/SupportView.swift | 36 +- .../Apperance/AppearanceSettingView.swift | 450 +++++----- ...tongView.swift => BorderSettingView.swift} | 10 +- .../Views/Apperance/FontSettingView.swift | 34 +- .../Views/Apperance/RadiusSettingView.swift | 12 +- .../NotificationsSettingsView.swift | 40 +- .../Security/PINCode/SetPINCodeView.swift | 31 +- .../PINCode/SetPINCodeViewModel.swift | 5 +- .../Views/Security/SecuritySettingsView.swift | 179 ++-- .../SettingsKit/Views/SettingsView.swift | 572 ++++++------- .../SoundsAndVibrationsSettingsView.swift | 110 ++- .../Views/iCloud/iCloudSettingsView.swift | 92 +- .../StateKit/LoadingViewState.swift | 49 ++ .../AppStoreProductViewController.swift | 2 +- .../StoreScreen/StoreInstuctinsView.swift | 22 +- .../StoreScreen/StoreSpecialOfferView.swift | 16 +- .../StoreKit/StoreScreen/StoreView.swift | 48 +- .../ViewModel/StoreViewModel.swift | 41 +- .../ViewModifier/PremiumBlockOverlay.swift | 4 +- .../StoreKit/Views/BuyButtonStyle.swift | 78 +- .../StoreKit/Views/FireworksBubbles.swift | 2 +- .../StoreKit/Views/PrmiumBannerRow.swift | 8 +- .../Views/StoreFeatureDetailView.swift | 17 +- .../Views/StoreFeaturesLargeView.swift | 8 +- .../StoreKit/Views/StoreFeaturesView.swift | 2 +- .../Views/StorePaymentButtonBar.swift | 2 +- .../StoreKit/Views/StoreProductView.swift | 30 +- .../Views/SubscriptionPrivacyView.swift | 6 +- .../SystemKit/SystemServices.swift | 92 +- .../AddressField/AddressField.swift | 20 +- .../AddressPicker/AddressPicker.swift | 362 ++++---- .../AddressPickerViewModel.swift | 112 +-- .../MapCoordinateView/MapCoordinateView.swift | 250 +++--- .../LocalNotificationSetScreenViewModel.swift | 119 +-- .../LocalNotificationView.swift | 170 ++-- .../Model/LocalNotificationAlertsTimes.swift | 5 +- .../OnboardingItem.swift | 20 - .../OnboardingItemPreferenceKey.swift | 14 - .../OnboardingItemViewModifier.swift | 21 - .../OnboardingView.swift | 196 +++-- .../OversizePhotoKit/PhotoOptionsView.swift | 27 +- .../OversizePhotoKit/PhotosGalleryView.swift | 2 +- 85 files changed, 4264 insertions(+), 3559 deletions(-) delete mode 100644 .github/workflows/ci-pull-request.yml delete mode 100644 .github/workflows/ci-push.yml create mode 100644 .github/workflows/ci.yml create mode 100644 AppExample/Example (watchOS) Watch App/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 AppExample/Example (watchOS) Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 AppExample/Example (watchOS) Watch App/Assets.xcassets/Contents.json create mode 100644 AppExample/Example (watchOS) Watch App/ContentView.swift create mode 100644 AppExample/Example (watchOS) Watch App/Example__watchOS_App.swift create mode 100644 AppExample/Example (watchOS) Watch App/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 AppExample/Example.xcodeproj/xcshareddata/xcschemes/Example (watchOS) Watch App.xcscheme create mode 100644 AppExample/Example/Test/TestView.swift create mode 100644 AppExample/Package.swift create mode 100644 Sources/OversizeKit/SettingsKit/SettingsRouter/ResolveRouter.swift create mode 100644 Sources/OversizeKit/SettingsKit/SettingsRouter/Screens.swift create mode 100644 Sources/OversizeKit/SettingsKit/SettingsRouter/SettingsRouting.swift rename Sources/OversizeKit/SettingsKit/Views/Apperance/{BorderSettongView.swift => BorderSettingView.swift} (94%) create mode 100644 Sources/OversizeKit/StateKit/LoadingViewState.swift delete mode 100644 Sources/OversizeOnboardingKit/OnboardingItem.swift delete mode 100644 Sources/OversizeOnboardingKit/OnboardingItemPreferenceKey.swift delete mode 100644 Sources/OversizeOnboardingKit/OnboardingItemViewModifier.swift diff --git a/.github/workflows/ci-pull-request.yml b/.github/workflows/ci-pull-request.yml deleted file mode 100644 index cdc2195..0000000 --- a/.github/workflows/ci-pull-request.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: CI - Pull Request -on: - pull_request: - branches: - - main - workflow_dispatch: - -jobs: - build-swiftpm: - name: Build SwiftPM - uses: oversizedev/GithubWorkflows/.github/workflows/build-swiftpm.yml@main - strategy: - matrix: - packages: [OversizeKit, OversizeCalendarKit, OversizeContactsKit, OversizeLocationKit, OversizeNoticeKit, OversizeNotificationKit, OversizeOnboardingKit, OversizePhotoKit] - with: - package: ${{ matrix.packages }} - secrets: inherit - - build-example: - name: Build Example - needs: build-swiftpm - uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main - strategy: - matrix: - destination: ['platform=iOS Simulator,name=iPhone 15 Pro,OS=17.2', 'platform=iOS Simulator,name=iPad Pro (12.9-inch) (6th generation),OS=17.2'] - with: - path: AppExample/Example - scheme: Example - destination: ${{ matrix.destination }} - secrets: inherit - -# tests: -# name: Test -# needs: build-example -# uses: oversizedev/GithubWorkflows/.github/workflows/test.yml@main -# secrets: inherit diff --git a/.github/workflows/ci-push.yml b/.github/workflows/ci-push.yml deleted file mode 100644 index 9b09524..0000000 --- a/.github/workflows/ci-push.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: CI - Push -on: - pull_request: - types: - - closed - branches: - - main - workflow_dispatch: - -jobs: - - build-swiftpm: - name: Build SwiftPM - uses: oversizedev/GithubWorkflows/.github/workflows/build-swiftpm.yml@main - strategy: - matrix: - packages: [OversizeKit, OversizeCalendarKit, OversizeContactsKit, OversizeLocationKit, OversizeNoticeKit, OversizeNotificationKit, OversizeOnboardingKit, OversizePhotoKit] - with: - package: ${{ matrix.packages }} - secrets: inherit - - build-example: - name: Build Example - needs: build-swiftpm - uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main - strategy: - matrix: - destination: ['platform=iOS Simulator,name=iPhone 15 Pro,OS=17.2', 'platform=iOS Simulator,name=iPad Pro (12.9-inch) (6th generation),OS=17.2'] - with: - path: AppExample/Example - scheme: Example - destination: ${{ matrix.destination }} - secrets: inherit - -# tests: -# name: Test -# needs: build-example -# uses: oversizedev/GithubWorkflows/.github/workflows/test.yml@main -# secrets: inherit - - bump: - name: Bump version - needs: build-example - uses: oversizedev/GithubWorkflows/.github/workflows/bump.yml@main - secrets: inherit - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b6221dc --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,100 @@ +name: CI + +on: + push: + branches: + - '**' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-oversize-kit: + name: Build OversizeKit + uses: oversizedev/GithubWorkflows/.github/workflows/build-swiftpm-all-platforms.yml@main + with: + package: OversizeKit + secrets: inherit + + build-calendar-kit: + name: Build CalendarKit + uses: oversizedev/GithubWorkflows/.github/workflows/build-swiftpm-all-platforms.yml@main + with: + package: OversizeCalendarKit + secrets: inherit + + build-contacts-kit: + name: Build OversizeContactsKit + uses: oversizedev/GithubWorkflows/.github/workflows/build-swiftpm-all-platforms.yml@main + with: + package: OversizeContactsKit + secrets: inherit + + build-location-kit: + name: Build LocationKit + uses: oversizedev/GithubWorkflows/.github/workflows/build-swiftpm-all-platforms.yml@main + with: + package: OversizeLocationKit + secrets: inherit + + build-notice-kit: + name: Build NoticeKit + uses: oversizedev/GithubWorkflows/.github/workflows/build-swiftpm-all-platforms.yml@main + with: + package: OversizeNoticeKit + secrets: inherit + + build-notification-kit: + name: Build NotificationKit + uses: oversizedev/GithubWorkflows/.github/workflows/build-swiftpm-all-platforms.yml@main + with: + package: OversizeNotificationKit + secrets: inherit + + build-onboarding-kit: + name: Build OnboardingKit + uses: oversizedev/GithubWorkflows/.github/workflows/build-swiftpm-all-platforms.yml@main + with: + package: OversizeOnboardingKit + secrets: inherit + + build-photo-kit: + name: Build PhotoKit + uses: oversizedev/GithubWorkflows/.github/workflows/build-swiftpm-all-platforms.yml@main + with: + package: OversizePhotoKit + secrets: inherit + + build-example: + name: Build Example + needs: + - build-oversize-kit + - build-calendar-kit + - build-contacts-kit + - build-location-kit + - build-notice-kit + - build-notification-kit + - build-onboarding-kit + - build-photo-kit + uses: oversizedev/GithubWorkflows/.github/workflows/build-app.yml@main + with: + path: AppExample/Example + scheme: Example + destination: platform=iOS Simulator,name=iPhone 16,OS=18.1 + secrets: inherit + +# tests: +# name: Test +# needs: build-swiftpm +# uses: oversizedev/GithubWorkflows/.github/workflows/test.yml@main +# secrets: inherit + + bump: + name: Bump version + needs: + - build-example + if: github.ref == 'refs/heads/main' + uses: oversizedev/GithubWorkflows/.github/workflows/bump.yml@main + secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3065293..988e905 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: - "*.*.*" jobs: - build: + release: name: Create release runs-on: ubuntu-latest steps: diff --git a/AppExample/Example (watchOS) Watch App/Assets.xcassets/AccentColor.colorset/Contents.json b/AppExample/Example (watchOS) Watch App/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/AppExample/Example (watchOS) Watch App/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AppExample/Example (watchOS) Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json b/AppExample/Example (watchOS) Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..49c81cd --- /dev/null +++ b/AppExample/Example (watchOS) Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AppExample/Example (watchOS) Watch App/Assets.xcassets/Contents.json b/AppExample/Example (watchOS) Watch App/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/AppExample/Example (watchOS) Watch App/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AppExample/Example (watchOS) Watch App/ContentView.swift b/AppExample/Example (watchOS) Watch App/ContentView.swift new file mode 100644 index 0000000..b173e94 --- /dev/null +++ b/AppExample/Example (watchOS) Watch App/ContentView.swift @@ -0,0 +1,30 @@ +// +// Copyright © 2024 Alexander Romanov +// ContentView.swift, created on 19.05.2024 +// + +import OversizeCalendarKit +import OversizeContactsKit +import OversizeKit +import OversizeLocationKit +import OversizeNoticeKit +import OversizeNotificationKit +import OversizeOnboardingKit +import OversizePhotoKit +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/AppExample/Example (watchOS) Watch App/Example__watchOS_App.swift b/AppExample/Example (watchOS) Watch App/Example__watchOS_App.swift new file mode 100644 index 0000000..c00dd47 --- /dev/null +++ b/AppExample/Example (watchOS) Watch App/Example__watchOS_App.swift @@ -0,0 +1,21 @@ +// +// Copyright © 2024 Alexander Romanov +// Example__watchOS_App.swift, created on 19.05.2024 +// + +import Factory +import OversizeKit +import OversizeServices +import OversizeUI +import SwiftUI + +@main +struct Example__watchOS__Watch_AppApp: App { + @Injected(\.appStateService) var appStateService: AppStateService + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/AppExample/Example (watchOS) Watch App/Preview Content/Preview Assets.xcassets/Contents.json b/AppExample/Example (watchOS) Watch App/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/AppExample/Example (watchOS) Watch App/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AppExample/Example.xcodeproj/project.pbxproj b/AppExample/Example.xcodeproj/project.pbxproj index a8d543f..d0a2ce7 100644 --- a/AppExample/Example.xcodeproj/project.pbxproj +++ b/AppExample/Example.xcodeproj/project.pbxproj @@ -22,19 +22,57 @@ 840CD68E2AC0E39D00C6AAD0 /* ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840CD68D2AC0E39D00C6AAD0 /* ExampleApp.swift */; }; 840CD6902AC0E3A600C6AAD0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 840CD68F2AC0E3A600C6AAD0 /* Assets.xcassets */; }; 840CD6932AC0E3A600C6AAD0 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 840CD6922AC0E3A600C6AAD0 /* Preview Assets.xcassets */; }; - 840CD69E2AC0E43000C6AAD0 /* OversizeCalendarKit in Frameworks */ = {isa = PBXBuildFile; productRef = 840CD69D2AC0E43000C6AAD0 /* OversizeCalendarKit */; }; - 840CD6A02AC0E43000C6AAD0 /* OversizeContactsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 840CD69F2AC0E43000C6AAD0 /* OversizeContactsKit */; }; - 840CD6A22AC0E43000C6AAD0 /* OversizeKit in Frameworks */ = {isa = PBXBuildFile; productRef = 840CD6A12AC0E43000C6AAD0 /* OversizeKit */; }; - 840CD6A42AC0E43000C6AAD0 /* OversizeLocationKit in Frameworks */ = {isa = PBXBuildFile; productRef = 840CD6A32AC0E43000C6AAD0 /* OversizeLocationKit */; }; - 840CD6A62AC0E43000C6AAD0 /* OversizeNoticeKit in Frameworks */ = {isa = PBXBuildFile; productRef = 840CD6A52AC0E43000C6AAD0 /* OversizeNoticeKit */; }; - 840CD6A82AC0E43000C6AAD0 /* OversizeNotificationKit in Frameworks */ = {isa = PBXBuildFile; productRef = 840CD6A72AC0E43000C6AAD0 /* OversizeNotificationKit */; }; - 840CD6AA2AC0E43000C6AAD0 /* OversizeOnboardingKit in Frameworks */ = {isa = PBXBuildFile; productRef = 840CD6A92AC0E43000C6AAD0 /* OversizeOnboardingKit */; }; - 840CD6AC2AC0E43000C6AAD0 /* OversizePhotoKit in Frameworks */ = {isa = PBXBuildFile; productRef = 840CD6AB2AC0E43000C6AAD0 /* OversizePhotoKit */; }; 840CD6AF2AC0E44E00C6AAD0 /* Factory in Frameworks */ = {isa = PBXBuildFile; productRef = 840CD6AE2AC0E44E00C6AAD0 /* Factory */; }; 840CD6B12AC0E6E200C6AAD0 /* Products.storekit in Resources */ = {isa = PBXBuildFile; fileRef = 840CD6B02AC0E6E200C6AAD0 /* Products.storekit */; }; - 845A59332BA4FD2B00988D52 /* OversizeModels in Frameworks */ = {isa = PBXBuildFile; productRef = 845A59322BA4FD2B00988D52 /* OversizeModels */; }; + 84664C312BF9FD6400A24148 /* Example (watchOS) Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 84664C302BF9FD6400A24148 /* Example (watchOS) Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 84664C362BF9FD6400A24148 /* Example__watchOS_App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84664C352BF9FD6400A24148 /* Example__watchOS_App.swift */; }; + 84664C382BF9FD6400A24148 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84664C372BF9FD6400A24148 /* ContentView.swift */; }; + 84664C3A2BF9FD6500A24148 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84664C392BF9FD6500A24148 /* Assets.xcassets */; }; + 84664C3D2BF9FD6500A24148 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84664C3C2BF9FD6500A24148 /* Preview Assets.xcassets */; }; + 84664C472BF9FDC200A24148 /* Factory in Frameworks */ = {isa = PBXBuildFile; productRef = 84664C462BF9FDC200A24148 /* Factory */; }; + 848479442BF9FF94003FC3FC /* OversizeCalendarKit in Frameworks */ = {isa = PBXBuildFile; productRef = 848479432BF9FF94003FC3FC /* OversizeCalendarKit */; }; + 848479462BF9FF94003FC3FC /* OversizeContactsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 848479452BF9FF94003FC3FC /* OversizeContactsKit */; }; + 848479482BF9FF94003FC3FC /* OversizeKit in Frameworks */ = {isa = PBXBuildFile; productRef = 848479472BF9FF94003FC3FC /* OversizeKit */; }; + 8484794A2BF9FF94003FC3FC /* OversizeLocationKit in Frameworks */ = {isa = PBXBuildFile; productRef = 848479492BF9FF94003FC3FC /* OversizeLocationKit */; }; + 8484794C2BF9FF94003FC3FC /* OversizeNoticeKit in Frameworks */ = {isa = PBXBuildFile; productRef = 8484794B2BF9FF94003FC3FC /* OversizeNoticeKit */; }; + 8484794E2BF9FF94003FC3FC /* OversizeNotificationKit in Frameworks */ = {isa = PBXBuildFile; productRef = 8484794D2BF9FF94003FC3FC /* OversizeNotificationKit */; }; + 848479502BF9FF94003FC3FC /* OversizeOnboardingKit in Frameworks */ = {isa = PBXBuildFile; productRef = 8484794F2BF9FF94003FC3FC /* OversizeOnboardingKit */; }; + 848479522BF9FF94003FC3FC /* OversizePhotoKit in Frameworks */ = {isa = PBXBuildFile; productRef = 848479512BF9FF94003FC3FC /* OversizePhotoKit */; }; + 848479542BF9FFA4003FC3FC /* OversizeCalendarKit in Frameworks */ = {isa = PBXBuildFile; productRef = 848479532BF9FFA4003FC3FC /* OversizeCalendarKit */; }; + 848479562BF9FFA4003FC3FC /* OversizeContactsKit in Frameworks */ = {isa = PBXBuildFile; productRef = 848479552BF9FFA4003FC3FC /* OversizeContactsKit */; }; + 848479582BF9FFA4003FC3FC /* OversizeKit in Frameworks */ = {isa = PBXBuildFile; productRef = 848479572BF9FFA4003FC3FC /* OversizeKit */; }; + 8484795A2BF9FFA4003FC3FC /* OversizeLocationKit in Frameworks */ = {isa = PBXBuildFile; productRef = 848479592BF9FFA4003FC3FC /* OversizeLocationKit */; }; + 8484795C2BF9FFA4003FC3FC /* OversizeNoticeKit in Frameworks */ = {isa = PBXBuildFile; productRef = 8484795B2BF9FFA4003FC3FC /* OversizeNoticeKit */; }; + 8484795E2BF9FFA4003FC3FC /* OversizeNotificationKit in Frameworks */ = {isa = PBXBuildFile; productRef = 8484795D2BF9FFA4003FC3FC /* OversizeNotificationKit */; }; + 848479602BF9FFA4003FC3FC /* OversizeOnboardingKit in Frameworks */ = {isa = PBXBuildFile; productRef = 8484795F2BF9FFA4003FC3FC /* OversizeOnboardingKit */; }; + 848479622BF9FFA4003FC3FC /* OversizePhotoKit in Frameworks */ = {isa = PBXBuildFile; productRef = 848479612BF9FFA4003FC3FC /* OversizePhotoKit */; }; + 848479652BFA0E64003FC3FC /* TestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848479642BFA0E64003FC3FC /* TestView.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 84664C322BF9FD6400A24148 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 840CD6592AC0E39D00C6AAD0 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 84664C2F2BF9FD6400A24148; + remoteInfo = "Example (watchOS) Watch App"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 84664C412BF9FD6500A24148 /* Embed Watch Content */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; + dstSubfolderSpec = 16; + files = ( + 84664C312BF9FD6400A24148 /* Example (watchOS) Watch App.app in Embed Watch Content */, + ); + name = "Embed Watch Content"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 840CD6632AC0E39D00C6AAD0 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 840CD6682AC0E39D00C6AAD0 /* AppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsView.swift; sourceTree = ""; }; @@ -58,6 +96,15 @@ 840CD6922AC0E3A600C6AAD0 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 840CD6942AC0E3A600C6AAD0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 840CD6B02AC0E6E200C6AAD0 /* Products.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Products.storekit; sourceTree = ""; }; + 84664C2B2BF9FD6400A24148 /* Example (watchOS).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example (watchOS).app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 84664C302BF9FD6400A24148 /* Example (watchOS) Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example (watchOS) Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 84664C352BF9FD6400A24148 /* Example__watchOS_App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Example__watchOS_App.swift; sourceTree = ""; }; + 84664C372BF9FD6400A24148 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 84664C392BF9FD6500A24148 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 84664C3C2BF9FD6500A24148 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 848479642BFA0E64003FC3FC /* TestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestView.swift; sourceTree = ""; }; + 84DE88812BF9FEED00CCF37A /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Package.swift; path = ../Package.swift; sourceTree = ""; }; + 84DE88822BF9FF5100CCF37A /* OversizeKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = OversizeKit; path = ..; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -65,16 +112,31 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 840CD6AC2AC0E43000C6AAD0 /* OversizePhotoKit in Frameworks */, + 848479622BF9FFA4003FC3FC /* OversizePhotoKit in Frameworks */, + 848479542BF9FFA4003FC3FC /* OversizeCalendarKit in Frameworks */, + 8484795E2BF9FFA4003FC3FC /* OversizeNotificationKit in Frameworks */, 840CD6AF2AC0E44E00C6AAD0 /* Factory in Frameworks */, - 840CD69E2AC0E43000C6AAD0 /* OversizeCalendarKit in Frameworks */, - 840CD6A82AC0E43000C6AAD0 /* OversizeNotificationKit in Frameworks */, - 840CD6A22AC0E43000C6AAD0 /* OversizeKit in Frameworks */, - 840CD6A42AC0E43000C6AAD0 /* OversizeLocationKit in Frameworks */, - 840CD6A62AC0E43000C6AAD0 /* OversizeNoticeKit in Frameworks */, - 845A59332BA4FD2B00988D52 /* OversizeModels in Frameworks */, - 840CD6A02AC0E43000C6AAD0 /* OversizeContactsKit in Frameworks */, - 840CD6AA2AC0E43000C6AAD0 /* OversizeOnboardingKit in Frameworks */, + 848479582BF9FFA4003FC3FC /* OversizeKit in Frameworks */, + 8484795A2BF9FFA4003FC3FC /* OversizeLocationKit in Frameworks */, + 8484795C2BF9FFA4003FC3FC /* OversizeNoticeKit in Frameworks */, + 848479562BF9FFA4003FC3FC /* OversizeContactsKit in Frameworks */, + 848479602BF9FFA4003FC3FC /* OversizeOnboardingKit in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 84664C2D2BF9FD6400A24148 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 848479522BF9FF94003FC3FC /* OversizePhotoKit in Frameworks */, + 848479442BF9FF94003FC3FC /* OversizeCalendarKit in Frameworks */, + 8484794E2BF9FF94003FC3FC /* OversizeNotificationKit in Frameworks */, + 84664C472BF9FDC200A24148 /* Factory in Frameworks */, + 848479482BF9FF94003FC3FC /* OversizeKit in Frameworks */, + 8484794A2BF9FF94003FC3FC /* OversizeLocationKit in Frameworks */, + 8484794C2BF9FF94003FC3FC /* OversizeNoticeKit in Frameworks */, + 848479462BF9FF94003FC3FC /* OversizeContactsKit in Frameworks */, + 848479502BF9FF94003FC3FC /* OversizeOnboardingKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -84,8 +146,11 @@ 840CD6582AC0E39D00C6AAD0 = { isa = PBXGroup; children = ( + 84DE88822BF9FF5100CCF37A /* OversizeKit */, 840CD6652AC0E39D00C6AAD0 /* Example */, + 84664C342BF9FD6400A24148 /* Example (watchOS) Watch App */, 840CD6642AC0E39D00C6AAD0 /* Products */, + 84664C452BF9FDC200A24148 /* Frameworks */, ); sourceTree = ""; }; @@ -93,6 +158,8 @@ isa = PBXGroup; children = ( 840CD6632AC0E39D00C6AAD0 /* Example.app */, + 84664C2B2BF9FD6400A24148 /* Example (watchOS).app */, + 84664C302BF9FD6400A24148 /* Example (watchOS) Watch App.app */, ); name = Products; sourceTree = ""; @@ -101,6 +168,7 @@ isa = PBXGroup; children = ( 840CD68D2AC0E39D00C6AAD0 /* ExampleApp.swift */, + 848479632BFA0E55003FC3FC /* Test */, 840CD6662AC0E39D00C6AAD0 /* Screens */, 840CD6782AC0E39D00C6AAD0 /* Router */, 840CD67D2AC0E39D00C6AAD0 /* Resources */, @@ -189,6 +257,41 @@ path = "Preview Content"; sourceTree = ""; }; + 84664C342BF9FD6400A24148 /* Example (watchOS) Watch App */ = { + isa = PBXGroup; + children = ( + 84664C352BF9FD6400A24148 /* Example__watchOS_App.swift */, + 84664C372BF9FD6400A24148 /* ContentView.swift */, + 84664C392BF9FD6500A24148 /* Assets.xcassets */, + 84664C3B2BF9FD6500A24148 /* Preview Content */, + ); + path = "Example (watchOS) Watch App"; + sourceTree = ""; + }; + 84664C3B2BF9FD6500A24148 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 84664C3C2BF9FD6500A24148 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 84664C452BF9FDC200A24148 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 84DE88812BF9FEED00CCF37A /* Package.swift */, + ); + name = Frameworks; + sourceTree = ""; + }; + 848479632BFA0E55003FC3FC /* Test */ = { + isa = PBXGroup; + children = ( + 848479642BFA0E64003FC3FC /* TestView.swift */, + ); + path = Test; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -206,21 +309,65 @@ ); name = Example; packageProductDependencies = ( - 840CD69D2AC0E43000C6AAD0 /* OversizeCalendarKit */, - 840CD69F2AC0E43000C6AAD0 /* OversizeContactsKit */, - 840CD6A12AC0E43000C6AAD0 /* OversizeKit */, - 840CD6A32AC0E43000C6AAD0 /* OversizeLocationKit */, - 840CD6A52AC0E43000C6AAD0 /* OversizeNoticeKit */, - 840CD6A72AC0E43000C6AAD0 /* OversizeNotificationKit */, - 840CD6A92AC0E43000C6AAD0 /* OversizeOnboardingKit */, - 840CD6AB2AC0E43000C6AAD0 /* OversizePhotoKit */, 840CD6AE2AC0E44E00C6AAD0 /* Factory */, - 845A59322BA4FD2B00988D52 /* OversizeModels */, + 848479532BF9FFA4003FC3FC /* OversizeCalendarKit */, + 848479552BF9FFA4003FC3FC /* OversizeContactsKit */, + 848479572BF9FFA4003FC3FC /* OversizeKit */, + 848479592BF9FFA4003FC3FC /* OversizeLocationKit */, + 8484795B2BF9FFA4003FC3FC /* OversizeNoticeKit */, + 8484795D2BF9FFA4003FC3FC /* OversizeNotificationKit */, + 8484795F2BF9FFA4003FC3FC /* OversizeOnboardingKit */, + 848479612BF9FFA4003FC3FC /* OversizePhotoKit */, ); productName = Example; productReference = 840CD6632AC0E39D00C6AAD0 /* Example.app */; productType = "com.apple.product-type.application"; }; + 84664C2A2BF9FD6400A24148 /* Example (watchOS) */ = { + isa = PBXNativeTarget; + buildConfigurationList = 84664C422BF9FD6500A24148 /* Build configuration list for PBXNativeTarget "Example (watchOS)" */; + buildPhases = ( + 84664C292BF9FD6400A24148 /* Resources */, + 84664C412BF9FD6500A24148 /* Embed Watch Content */, + ); + buildRules = ( + ); + dependencies = ( + 84664C332BF9FD6400A24148 /* PBXTargetDependency */, + ); + name = "Example (watchOS)"; + productName = "Example (watchOS)"; + productReference = 84664C2B2BF9FD6400A24148 /* Example (watchOS).app */; + productType = "com.apple.product-type.application.watchapp2-container"; + }; + 84664C2F2BF9FD6400A24148 /* Example (watchOS) Watch App */ = { + isa = PBXNativeTarget; + buildConfigurationList = 84664C3E2BF9FD6500A24148 /* Build configuration list for PBXNativeTarget "Example (watchOS) Watch App" */; + buildPhases = ( + 84664C2C2BF9FD6400A24148 /* Sources */, + 84664C2D2BF9FD6400A24148 /* Frameworks */, + 84664C2E2BF9FD6400A24148 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Example (watchOS) Watch App"; + packageProductDependencies = ( + 84664C462BF9FDC200A24148 /* Factory */, + 848479432BF9FF94003FC3FC /* OversizeCalendarKit */, + 848479452BF9FF94003FC3FC /* OversizeContactsKit */, + 848479472BF9FF94003FC3FC /* OversizeKit */, + 848479492BF9FF94003FC3FC /* OversizeLocationKit */, + 8484794B2BF9FF94003FC3FC /* OversizeNoticeKit */, + 8484794D2BF9FF94003FC3FC /* OversizeNotificationKit */, + 8484794F2BF9FF94003FC3FC /* OversizeOnboardingKit */, + 848479512BF9FF94003FC3FC /* OversizePhotoKit */, + ); + productName = "Example (watchOS) Watch App"; + productReference = 84664C302BF9FD6400A24148 /* Example (watchOS) Watch App.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -228,12 +375,18 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1500; + LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1500; TargetAttributes = { 840CD6622AC0E39D00C6AAD0 = { CreatedOnToolsVersion = 15.0; }; + 84664C2A2BF9FD6400A24148 = { + CreatedOnToolsVersion = 15.4; + }; + 84664C2F2BF9FD6400A24148 = { + CreatedOnToolsVersion = 15.4; + }; }; }; buildConfigurationList = 840CD65C2AC0E39D00C6AAD0 /* Build configuration list for PBXProject "Example" */; @@ -255,6 +408,8 @@ projectRoot = ""; targets = ( 840CD6622AC0E39D00C6AAD0 /* Example */, + 84664C2A2BF9FD6400A24148 /* Example (watchOS) */, + 84664C2F2BF9FD6400A24148 /* Example (watchOS) Watch App */, ); }; /* End PBXProject section */ @@ -271,6 +426,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 84664C292BF9FD6400A24148 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 84664C2E2BF9FD6400A24148 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 84664C3D2BF9FD6500A24148 /* Preview Assets.xcassets in Resources */, + 84664C3A2BF9FD6500A24148 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -289,12 +460,30 @@ 840CD66D2AC0E39D00C6AAD0 /* AppSettingsPageView.swift in Sources */, 840CD67C2AC0E39D00C6AAD0 /* Screens.swift in Sources */, 840CD6722AC0E39D00C6AAD0 /* OnboardingView.swift in Sources */, + 848479652BFA0E64003FC3FC /* TestView.swift in Sources */, 840CD66B2AC0E39D00C6AAD0 /* AppSettingsViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + 84664C2C2BF9FD6400A24148 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 84664C382BF9FD6400A24148 /* ContentView.swift in Sources */, + 84664C362BF9FD6400A24148 /* Example__watchOS_App.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 84664C332BF9FD6400A24148 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 84664C2F2BF9FD6400A24148 /* Example (watchOS) Watch App */; + targetProxy = 84664C322BF9FD6400A24148 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ 840CD6952AC0E3A600C6AAD0 /* Debug */ = { isa = XCBuildConfiguration; @@ -432,17 +621,22 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = app.oversize.Example; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 18.0; }; name = Debug; }; @@ -463,17 +657,112 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 15.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = app.oversize.Example; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 18.0; + }; + name = Release; + }; + 84664C3F2BF9FD6500A24148 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Example (watchOS) Watch App/Preview Content\""; + DEVELOPMENT_TEAM = ER582ZK85C; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "Example (watchOS)"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_WKWatchOnly = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "app.oversize.Example--watchOS-.watchkitapp"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Debug; + }; + 84664C402BF9FD6500A24148 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Example (watchOS) Watch App/Preview Content\""; + DEVELOPMENT_TEAM = ER582ZK85C; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "Example (watchOS)"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_WKWatchOnly = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "app.oversize.Example--watchOS-.watchkitapp"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Release; + }; + 84664C432BF9FD6500A24148 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ER582ZK85C; + INFOPLIST_KEY_CFBundleDisplayName = "Example (watchOS)"; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "app.oversize.Example--watchOS-"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 84664C442BF9FD6500A24148 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ER582ZK85C; + INFOPLIST_KEY_CFBundleDisplayName = "Example (watchOS)"; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "app.oversize.Example--watchOS-"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -498,6 +787,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 84664C3E2BF9FD6500A24148 /* Build configuration list for PBXNativeTarget "Example (watchOS) Watch App" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 84664C3F2BF9FD6500A24148 /* Debug */, + 84664C402BF9FD6500A24148 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 84664C422BF9FD6500A24148 /* Build configuration list for PBXNativeTarget "Example (watchOS)" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 84664C432BF9FD6500A24148 /* Debug */, + 84664C442BF9FD6500A24148 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ @@ -513,7 +820,7 @@ repositoryURL = "https://github.com/hmlongco/Factory"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 2.2.0; + minimumVersion = 2.1.3; }; }; 845A59312BA4FD2B00988D52 /* XCRemoteSwiftPackageReference "OversizeModels" */ = { @@ -527,47 +834,79 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 840CD69D2AC0E43000C6AAD0 /* OversizeCalendarKit */ = { + 840CD6AE2AC0E44E00C6AAD0 /* Factory */ = { + isa = XCSwiftPackageProductDependency; + package = 840CD6AD2AC0E44E00C6AAD0 /* XCRemoteSwiftPackageReference "Factory" */; + productName = Factory; + }; + 84664C462BF9FDC200A24148 /* Factory */ = { + isa = XCSwiftPackageProductDependency; + package = 840CD6AD2AC0E44E00C6AAD0 /* XCRemoteSwiftPackageReference "Factory" */; + productName = Factory; + }; + 848479432BF9FF94003FC3FC /* OversizeCalendarKit */ = { isa = XCSwiftPackageProductDependency; productName = OversizeCalendarKit; }; - 840CD69F2AC0E43000C6AAD0 /* OversizeContactsKit */ = { + 848479452BF9FF94003FC3FC /* OversizeContactsKit */ = { isa = XCSwiftPackageProductDependency; productName = OversizeContactsKit; }; - 840CD6A12AC0E43000C6AAD0 /* OversizeKit */ = { + 848479472BF9FF94003FC3FC /* OversizeKit */ = { isa = XCSwiftPackageProductDependency; productName = OversizeKit; }; - 840CD6A32AC0E43000C6AAD0 /* OversizeLocationKit */ = { + 848479492BF9FF94003FC3FC /* OversizeLocationKit */ = { isa = XCSwiftPackageProductDependency; productName = OversizeLocationKit; }; - 840CD6A52AC0E43000C6AAD0 /* OversizeNoticeKit */ = { + 8484794B2BF9FF94003FC3FC /* OversizeNoticeKit */ = { isa = XCSwiftPackageProductDependency; productName = OversizeNoticeKit; }; - 840CD6A72AC0E43000C6AAD0 /* OversizeNotificationKit */ = { + 8484794D2BF9FF94003FC3FC /* OversizeNotificationKit */ = { isa = XCSwiftPackageProductDependency; productName = OversizeNotificationKit; }; - 840CD6A92AC0E43000C6AAD0 /* OversizeOnboardingKit */ = { + 8484794F2BF9FF94003FC3FC /* OversizeOnboardingKit */ = { isa = XCSwiftPackageProductDependency; productName = OversizeOnboardingKit; }; - 840CD6AB2AC0E43000C6AAD0 /* OversizePhotoKit */ = { + 848479512BF9FF94003FC3FC /* OversizePhotoKit */ = { isa = XCSwiftPackageProductDependency; productName = OversizePhotoKit; }; - 840CD6AE2AC0E44E00C6AAD0 /* Factory */ = { + 848479532BF9FFA4003FC3FC /* OversizeCalendarKit */ = { isa = XCSwiftPackageProductDependency; - package = 840CD6AD2AC0E44E00C6AAD0 /* XCRemoteSwiftPackageReference "Factory" */; - productName = Factory; + productName = OversizeCalendarKit; + }; + 848479552BF9FFA4003FC3FC /* OversizeContactsKit */ = { + isa = XCSwiftPackageProductDependency; + productName = OversizeContactsKit; + }; + 848479572BF9FFA4003FC3FC /* OversizeKit */ = { + isa = XCSwiftPackageProductDependency; + productName = OversizeKit; + }; + 848479592BF9FFA4003FC3FC /* OversizeLocationKit */ = { + isa = XCSwiftPackageProductDependency; + productName = OversizeLocationKit; + }; + 8484795B2BF9FFA4003FC3FC /* OversizeNoticeKit */ = { + isa = XCSwiftPackageProductDependency; + productName = OversizeNoticeKit; }; - 845A59322BA4FD2B00988D52 /* OversizeModels */ = { + 8484795D2BF9FFA4003FC3FC /* OversizeNotificationKit */ = { isa = XCSwiftPackageProductDependency; - package = 845A59312BA4FD2B00988D52 /* XCRemoteSwiftPackageReference "OversizeModels" */; - productName = OversizeModels; + productName = OversizeNotificationKit; + }; + 8484795F2BF9FFA4003FC3FC /* OversizeOnboardingKit */ = { + isa = XCSwiftPackageProductDependency; + productName = OversizeOnboardingKit; + }; + 848479612BF9FFA4003FC3FC /* OversizePhotoKit */ = { + isa = XCSwiftPackageProductDependency; + productName = OversizePhotoKit; }; /* End XCSwiftPackageProductDependency section */ }; diff --git a/AppExample/Example.xcodeproj/project.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate b/AppExample/Example.xcodeproj/project.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate index 1cd4fb570860f7d0a6a2aa142d68bb1952af2462..df3e6d45d31e88768314d9106ff7912dd5dd3de9 100644 GIT binary patch literal 299511 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 P#P9yG`~UwFN`Lu3A@Anv diff --git a/AppExample/Example.xcodeproj/xcshareddata/xcschemes/Example (watchOS) Watch App.xcscheme b/AppExample/Example.xcodeproj/xcshareddata/xcschemes/Example (watchOS) Watch App.xcscheme new file mode 100644 index 0000000..06a4734 --- /dev/null +++ b/AppExample/Example.xcodeproj/xcshareddata/xcschemes/Example (watchOS) Watch App.xcscheme @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AppExample/Example.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/xcschememanagement.plist b/AppExample/Example.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/xcschememanagement.plist index 268deb9..597af26 100644 --- a/AppExample/Example.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/AppExample/Example.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/xcschememanagement.plist @@ -4,6 +4,11 @@ SchemeUserState + Example (watchOS) Watch App.xcscheme_^#shared#^_ + + orderHint + 2 + Example.xcscheme_^#shared#^_ orderHint @@ -17,6 +22,16 @@ primary + 84664C2A2BF9FD6400A24148 + + primary + + + 84664C2F2BF9FD6400A24148 + + primary + + diff --git a/AppExample/Example/ExampleApp.swift b/AppExample/Example/ExampleApp.swift index 0b518ba..32eedf3 100644 --- a/AppExample/Example/ExampleApp.swift +++ b/AppExample/Example/ExampleApp.swift @@ -97,6 +97,7 @@ struct ExampleApp: App { .environmentObject(appSettingsViewModel) .systemServices() } + #if os(iOS) .fullScreenCover(item: $router.fullScreenCover) { fullScreenCover in router.resolve(pathItem: fullScreenCover) .hud(router.hudText, isPresented: $router.isShowHud) @@ -104,6 +105,7 @@ struct ExampleApp: App { .environmentObject(appSettingsViewModel) .systemServices() } + #endif .alert(item: $router.alert) { $0.alert } .onOpenURL { router.handle($0) } .environmentObject(router) diff --git a/AppExample/Example/Screens/Onboarding/OnboardingView.swift b/AppExample/Example/Screens/Onboarding/OnboardingView.swift index c3b89b6..19d572e 100644 --- a/AppExample/Example/Screens/Onboarding/OnboardingView.swift +++ b/AppExample/Example/Screens/Onboarding/OnboardingView.swift @@ -35,11 +35,11 @@ struct OnboardingView: View { VStack(spacing: .medium) { Text("Welcome to\nExample") .largeTitle() - .onBackgroundHighEmphasisForegroundColor() + .onBackgroundPrimaryForeground() Text("Welcome text") .title2(.semibold) - .onBackgroundMediumEmphasisForegroundColor() + .onBackgroundSecondaryForeground() } .paddingContent(.horizontal) .multilineTextAlignment(.center) @@ -81,11 +81,11 @@ struct OnboardingView: View { VStack(spacing: .medium) { Text("Welcome to\nExample") .largeTitle() - .onBackgroundHighEmphasisForegroundColor() + .onBackgroundPrimaryForeground() Text("Welcome text") .title2(.semibold) - .onBackgroundMediumEmphasisForegroundColor() + .onBackgroundSecondaryForeground() } .paddingContent(.horizontal) .multilineTextAlignment(.center) @@ -123,11 +123,11 @@ struct OnboardingView: View { VStack(spacing: .medium) { Text("Example") .largeTitle() - .onBackgroundHighEmphasisForegroundColor() + .onBackgroundPrimaryForeground() Text("Welcome text") .title2(.semibold) - .onBackgroundMediumEmphasisForegroundColor() + .onBackgroundSecondaryForeground() } .paddingContent(.horizontal) .multilineTextAlignment(.center) diff --git a/AppExample/Example/Test/TestView.swift b/AppExample/Example/Test/TestView.swift new file mode 100644 index 0000000..9787784 --- /dev/null +++ b/AppExample/Example/Test/TestView.swift @@ -0,0 +1,24 @@ +// +// Copyright © 2024 Alexander Romanov +// TestView.swift, created on 19.05.2024 +// + +import OversizeCalendarKit +import OversizeContactsKit +import OversizeKit +import OversizeLocationKit +import OversizeNoticeKit +import OversizeNotificationKit +import OversizeOnboardingKit +import OversizePhotoKit +import SwiftUI + +struct TestView: View { + var body: some View { + Text("Hello, World!") + } +} + +#Preview { + TestView() +} diff --git a/AppExample/Package.swift b/AppExample/Package.swift new file mode 100644 index 0000000..9fdca6f --- /dev/null +++ b/AppExample/Package.swift @@ -0,0 +1,4 @@ +// swift-tools-version: 5.6 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription diff --git a/Package.swift b/Package.swift index 5cdcfc2..1795262 100644 --- a/Package.swift +++ b/Package.swift @@ -1,18 +1,19 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import Foundation import PackageDescription -let productionDependencies: [PackageDescription.Package.Dependency] = [ +let remoteDependencies: [PackageDescription.Package.Dependency] = [ .package(url: "https://github.com/oversizedev/OversizeUI.git", .upToNextMajor(from: "3.0.2")), .package(url: "https://github.com/oversizedev/OversizeCore.git", .upToNextMajor(from: "1.3.0")), .package(url: "https://github.com/oversizedev/OversizeServices.git", .upToNextMajor(from: "1.4.0")), .package(url: "https://github.com/oversizedev/OversizeLocalizable.git", .upToNextMajor(from: "1.4.0")), - .package(url: "https://github.com/oversizedev/OversizeComponents.git", .upToNextMajor(from: "1.2.0")), + .package(url: "https://github.com/oversizedev/OversizeComponents.git", .upToNextMajor(from: "2.0.0")), .package(url: "https://github.com/oversizedev/OversizeResources.git", .upToNextMajor(from: "2.0.0")), .package(url: "https://github.com/oversizedev/OversizeNetwork.git", .upToNextMajor(from: "0.4.0")), .package(url: "https://github.com/oversizedev/OversizeModels.git", .upToNextMajor(from: "0.1.0")), + .package(url: "https://github.com/oversizedev/OversizeRouter.git", .upToNextMajor(from: "0.1.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")), ] @@ -26,19 +27,24 @@ let developmentDependencies: [PackageDescription.Package.Dependency] = [ .package(name: "OversizeResources", path: "../OversizeResources"), .package(name: "OversizeNetwork", path: "../OversizeNetwork"), .package(name: "OversizeModels", path: "../OversizeModels"), + .package(name: "OversizeRouter", path: "../OversizeRouter"), .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")), ] -let isProductionDependencies = ProcessInfo.processInfo.environment["RELEASE_DEPENDENCIES"] == "TRUE" +var dependencies: [PackageDescription.Package.Dependency] = remoteDependencies + +if ProcessInfo.processInfo.environment["BUILD_MODE"] == "DEV" { + dependencies = developmentDependencies +} let package = Package( name: "OversizeKit", platforms: [ - .iOS(.v15), - .macOS(.v13), - .tvOS(.v15), - .watchOS(.v9), + .iOS(.v17), + .macOS(.v14), + .tvOS(.v17), + .watchOS(.v10), ], products: [ .library(name: "OversizeKit", targets: ["OversizeKit"]), @@ -50,7 +56,7 @@ let package = Package( .library(name: "OversizeNotificationKit", targets: ["OversizeNotificationKit"]), .library(name: "OversizePhotoKit", targets: ["OversizePhotoKit"]), ], - dependencies: productionDependencies, + dependencies: dependencies, targets: [ .target( name: "OversizeKit", @@ -65,8 +71,9 @@ let package = Package( .product(name: "OversizeNotificationService", package: "OversizeServices"), .product(name: "OversizeModels", package: "OversizeModels"), .product(name: "OversizeNetwork", package: "OversizeNetwork"), + .product(name: "OversizeRouter", package: "OversizeRouter"), .product(name: "Factory", package: "Factory"), - .product(name: "CachedAsyncImage", package: "swiftui-cached-async-image") + .product(name: "CachedAsyncImage", package: "swiftui-cached-async-image"), ] ), .target( diff --git a/Sources/OversizeCalendarKit/CreateEventScreen/CreateEventView.swift b/Sources/OversizeCalendarKit/CreateEventScreen/CreateEventView.swift index b86aa0a..cfdb2ca 100644 --- a/Sources/OversizeCalendarKit/CreateEventScreen/CreateEventView.swift +++ b/Sources/OversizeCalendarKit/CreateEventScreen/CreateEventView.swift @@ -3,7 +3,9 @@ // CreateEventView.swift // -import EventKit +#if canImport(EventKit) + import EventKit +#endif import MapKit import OversizeCalendarService import OversizeComponents @@ -13,266 +15,317 @@ import OversizeModels import OversizeUI import SwiftUI -public struct CreateEventView: View { - @StateObject var viewModel: CreateEventViewModel - @Environment(\.dismiss) var dismiss - @FocusState private var focusedField: FocusField? +#if !os(tvOS) + public struct CreateEventView: View { + @StateObject var viewModel: CreateEventViewModel + @Environment(\.dismiss) var dismiss + @FocusState private var focusedField: FocusField? - public init(_ type: CreateEventType = .new(nil, calendar: nil)) { - _viewModel = StateObject(wrappedValue: CreateEventViewModel(type)) - } - - public var body: some View { - PageView { - content() - } - .leadingBar { - BarButton(.closeAction { - dismiss() - }) + public init(_ type: CreateEventType = .new(nil, calendar: nil)) { + _viewModel = StateObject(wrappedValue: CreateEventViewModel(type)) } - .trailingBar { - BarButton(.accent(L10n.Button.save, action: { - switch viewModel.type { - case .new: - Task { - _ = await viewModel.save() - dismiss() - } - case .update: - if viewModel.span == nil, viewModel.repitRule != .never { - viewModel.present(.span) - } else { + + public var body: some View { + PageView { + content() + } + .leadingBar { + BarButton(.closeAction { + dismiss() + }) + } + .trailingBar { + BarButton(.accent(L10n.Button.save, action: { + switch viewModel.type { + case .new: Task { _ = await viewModel.save() dismiss() } + case .update: + if viewModel.span == nil, viewModel.repitRule != .never { + viewModel.present(.span) + } else { + Task { + _ = await viewModel.save() + dismiss() + } + } + } + })) + .disabled(viewModel.title.isEmpty) + } + .titleLabel { + Button { viewModel.present(.calendar) } label: { + HStack(spacing: .xxxSmall) { + Circle() + .fill(Color(viewModel.calendar?.cgColor ?? CGColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1))) + .frame(width: 16, height: 16) + .padding(.xxxSmall) + + Text(viewModel.calendar?.title ?? "") + .padding(.trailing, .xxSmall) } } - })) - .disabled(viewModel.title.isEmpty) - } - .titleLabel { - Button { viewModel.present(.calendar) } label: { - HStack(spacing: .xxxSmall) { - Circle() - .fill(Color(viewModel.calendar?.cgColor ?? CGColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1))) - .frame(width: 16, height: 16) - .padding(.xxxSmall) - - Text(viewModel.calendar?.title ?? "") - .padding(.trailing, .xxSmall) - } + .buttonStyle(.tertiary) + .controlBorderShape(.capsule) + .controlSize(.mini) } - .buttonStyle(.tertiary) - .controlBorderShape(.capsule) - .controlSize(.mini) - } - .navigationBarDividerColor(Color.onSurfaceHighEmphasis.opacity(0.1)) - .safeAreaInset(edge: .bottom) { - bottomBar - } - .task { - await viewModel.fetchData() - } - .onAppear { - focusedField = .title - } - .sheet(item: $viewModel.sheet) { sheet in - resolveSheet(sheet: sheet) - } - .onChange(of: viewModel.span) { _ in - Task { - _ = await viewModel.save() - dismiss() + .navigationBarDividerColor(Color.onSurfacePrimary.opacity(0.1)) + .safeAreaInset(edge: .bottom) { + bottomBar + } + .task { + await viewModel.fetchData() + } + .onAppear { + focusedField = .title + } + .sheet(item: $viewModel.sheet) { sheet in + resolveSheet(sheet: sheet) + } + .onChange(of: viewModel.span) { _ in + Task { + _ = await viewModel.save() + dismiss() + } } } - } - @ViewBuilder - private func content() -> some View { - VStack(spacing: .small) { - TextField("Event name", text: $viewModel.title) - .title(.bold) - .focused($focusedField, equals: .title) - .onSurfaceHighEmphasisForegroundColor() - .padding(.bottom, .xxxSmall) - .padding(.horizontal, .small) + @ViewBuilder + private func content() -> some View { + VStack(spacing: .small) { + TextField("Event name", text: $viewModel.title) + .title(.bold) + .focused($focusedField, equals: .title) + .onSurfacePrimaryForeground() + .padding(.bottom, .xxxSmall) + .padding(.horizontal, .small) - textEditor + #if !os(watchOS) + textEditor + #endif - calendarButtons + calendarButtons - allDayEvent + allDayEvent - locationView + locationView - alarmView + alarmView - membersView + membersView - repitView + repitView + } + .padding(.horizontal, .small) + .padding(.vertical, .medium) } - .padding(.horizontal, .small) - .padding(.vertical, .medium) - } - var allDayEvent: some View { - Surface { - viewModel.isAllDay.toggle() - } label: { - HStack { - Text("All-day event") - .headline(.semibold) - .foregroundColor(.onSurfaceHighEmphasis) - .padding(.leading, .xxxSmall) + var allDayEvent: some View { + Surface { + viewModel.isAllDay.toggle() + } label: { + HStack { + Text("All-day event") + .headline(.semibold) + .foregroundColor(.onSurfacePrimary) + .padding(.leading, .xxxSmall) - Spacer() + Spacer() - Toggle(isOn: $viewModel.isAllDay) {} - .labelsHidden() + Toggle(isOn: $viewModel.isAllDay) {} + .labelsHidden() + } } + .surfaceBorderColor(Color.surfaceSecondary) + .surfaceBorderWidth(1) + .surfaceContentMargins(.init(horizontal: .xSmall, vertical: .xSmall)) + .controlRadius(.large) } - .surfaceBorderColor(Color.surfaceSecondary) - .surfaceBorderWidth(1) - .surfaceContentMargins(.init(horizontal: .xSmall, vertical: .xSmall)) - .controlRadius(.large) - } - var textEditor: some View { - VStack(spacing: 2) { - TextEditor(text: $viewModel.note) - .onSurfaceHighEmphasisForegroundColor() - .padding(.horizontal, .xSmall) - .padding(.vertical, .xxSmall) - .focused($focusedField, equals: .note) - .body(.medium) - .scrollContentBackground(.hidden) - .background { - #if os(iOS) - RoundedRectangleCorner(radius: 4, corners: [.bottomLeft, .bottomRight]) - .fillSurfaceSecondary() - .overlay(alignment: .topLeading) { - if viewModel.note.isEmpty { - Text("Note") - .body(.medium) - .onSurfaceDisabledForegroundColor() - .padding(.small) - } - } - #else - RoundedRectangle(cornerRadius: .small) - .fillSurfaceSecondary() - .overlay(alignment: .topLeading) { - if viewModel.note.isEmpty { - Text("Note") - .body(.medium) - .onSurfaceDisabledForegroundColor() - .padding(.small) - } - } - #endif + #if !os(watchOS) + var textEditor: some View { + VStack(spacing: 2) { + TextEditor(text: $viewModel.note) + .onSurfacePrimaryForeground() + .padding(.horizontal, .xSmall) + .padding(.vertical, .xxSmall) + .focused($focusedField, equals: .note) + .body(.medium) + .scrollContentBackground(.hidden) + .background { + #if os(iOS) + RoundedRectangleCorner(radius: 4, corners: [.bottomLeft, .bottomRight]) + .fillSurfaceSecondary() + .overlay(alignment: .topLeading) { + if viewModel.note.isEmpty { + Text("Note") + .body(.medium) + .onSurfaceTertiaryForeground() + .padding(.small) + } + } + #else + RoundedRectangle(cornerRadius: .small) + .fillSurfaceSecondary() + .overlay(alignment: .topLeading) { + if viewModel.note.isEmpty { + Text("Note") + .body(.medium) + .onSurfaceTertiaryForeground() + .padding(.small) + } + } + #endif + } + .frame(minHeight: 76) + + TextField("URL", text: $viewModel.url) + .focused($focusedField, equals: .url) + .onSurfacePrimaryForeground() + .body(.medium) + .padding(.horizontal, .small) + .padding(.vertical, 18) + .background { + #if os(iOS) + RoundedRectangleCorner(radius: 4, corners: [.topLeft, .topRight]) + .fillSurfaceSecondary() + #else + RoundedRectangle(cornerRadius: .small) + .fillSurfaceSecondary() + #endif + } } - .frame(minHeight: 76) - - TextField("URL", text: $viewModel.url) - .focused($focusedField, equals: .url) - .onSurfaceHighEmphasisForegroundColor() - .body(.medium) - .padding(.horizontal, .small) - .padding(.vertical, 18) - .background { - #if os(iOS) - RoundedRectangleCorner(radius: 4, corners: [.topLeft, .topRight]) - .fillSurfaceSecondary() - #else - RoundedRectangle(cornerRadius: .small) - .fillSurfaceSecondary() - #endif + .clipShape(RoundedRectangle(cornerRadius: .large, style: .continuous)) + } + #endif + + var repitView: some View { + Group { + if viewModel.repitRule != .never { + Surface { + Row(viewModel.repitRule.title, subtitle: repeatSubtitleText) { + viewModel.present(.repeat) + } leading: { + IconDeprecated(.refresh) + .iconColor(.onSurfacePrimary) + } + .rowClearButton(style: .onSurface) { + viewModel.repitRule = .never + viewModel.repitEndRule = .never + } + .surfaceContentMargins(.init(horizontal: .small, vertical: .medium)) + } + .surfaceBorderColor(Color.surfaceSecondary) + .surfaceBorderWidth(1) + .surfaceContentMargins(.zero) + .controlRadius(.large) } + } } - .clipShape(RoundedRectangle(cornerRadius: .large, style: .continuous)) - } - var repitView: some View { - Group { - if viewModel.repitRule != .never { - Surface { - Row(viewModel.repitRule.title, subtitle: repeatSubtitleText) { - viewModel.present(.repeat) - } leading: { - IconDeprecated(.refresh) - .iconColor(.onSurfaceHighEmphasis) - } - .rowClearButton(style: .onSurface) { - viewModel.repitRule = .never - viewModel.repitEndRule = .never + var membersView: some View { + Group { + if !viewModel.members.isEmpty { + Surface { + VStack(spacing: .zero) { + ForEach(viewModel.members, id: \.self) { email in + Row(email) { + viewModel.present(.invites) + } leading: { + IconDeprecated(.user) + .iconColor(.onSurfacePrimary) + } + .rowClearButton(style: .onSurface) { + viewModel.members.remove(email) + } + .rowContentMargins(.small) + .overlay(alignment: .bottomLeading) { + Rectangle() + .fillSurfaceSecondary() + .padding(.leading, 56) + .frame(height: 1) + } + } + } } - .surfaceContentMargins(.init(horizontal: .small, vertical: .medium)) + .surfaceBorderColor(Color.surfaceSecondary) + .surfaceBorderWidth(1) + .surfaceContentMargins(.zero) + .controlRadius(.large) } - .surfaceBorderColor(Color.surfaceSecondary) - .surfaceBorderWidth(1) - .surfaceContentMargins(.zero) - .controlRadius(.large) } } - } - var membersView: some View { - Group { - if !viewModel.members.isEmpty { - Surface { - VStack(spacing: .zero) { - ForEach(viewModel.members, id: \.self) { email in - Row(email) { - viewModel.present(.invites) - } leading: { - IconDeprecated(.user) - .iconColor(.onSurfaceHighEmphasis) - } - .rowClearButton(style: .onSurface) { - viewModel.members.remove(email) - } - .rowContentMargins(.small) - .overlay(alignment: .bottomLeading) { - Rectangle() - .fillSurfaceSecondary() - .padding(.leading, 56) - .frame(height: 1) + @ViewBuilder + var alarmView: some View { + Group { + if !viewModel.alarms.isEmpty { + Surface { + VStack(spacing: .zero) { + ForEach(viewModel.alarms) { alarm in + Row(alarm.title) { + viewModel.present(.alarm) + } leading: { + IconDeprecated(.bell) + .iconColor(.onSurfacePrimary) + } + .rowClearButton(style: .onSurface) { + viewModel.alarms.remove(alarm) + } + .surfaceContentMargins(.init(horizontal: .small, vertical: .medium)) + .overlay(alignment: .bottomLeading) { + Rectangle() + .fillSurfaceSecondary() + .padding(.leading, 56) + .frame(height: 1) + } } } } + .surfaceBorderColor(Color.surfaceSecondary) + .surfaceBorderWidth(1) + .surfaceContentMargins(.zero) + .controlRadius(.large) } - .surfaceBorderColor(Color.surfaceSecondary) - .surfaceBorderWidth(1) - .surfaceContentMargins(.zero) - .controlRadius(.large) } } - } - @ViewBuilder - var alarmView: some View { - Group { - if !viewModel.alarms.isEmpty { + @ViewBuilder + var locationView: some View { + if viewModel.locationName != nil || viewModel.location != nil { Surface { VStack(spacing: .zero) { - ForEach(viewModel.alarms) { alarm in - Row(alarm.title) { - viewModel.present(.alarm) - } leading: { - IconDeprecated(.bell) - .iconColor(.onSurfaceHighEmphasis) + if let locationName = viewModel.locationName { + VStack(spacing: .xxSmall) { + Row(locationName) { + viewModel.present(.location) + } leading: { + IconDeprecated(.mapPin) + .iconColor(.onSurfacePrimary) + } + .rowClearButton(style: .onSurface) { + viewModel.locationName = nil + viewModel.location = nil + } + .rowContentMargins(.init(horizontal: .small, vertical: .xSmall)) } - .rowClearButton(style: .onSurface) { - viewModel.alarms.remove(alarm) + } + + if let location = viewModel.location { + let region = MKCoordinateRegion(center: location, latitudinalMeters: 10000, longitudinalMeters: 10000) + let annotations = [MapPreviewPoint(name: "\(viewModel.locationName ?? "")", coordinate: location)] + Map(coordinateRegion: .constant(region), annotationItems: annotations) { + MapMarker(coordinate: $0.coordinate) } - .surfaceContentMargins(.init(horizontal: .small, vertical: .medium)) - .overlay(alignment: .bottomLeading) { - Rectangle() - .fillSurfaceSecondary() - .padding(.leading, 56) - .frame(height: 1) + .frame(height: 130) + .cornerRadius(.small) + .padding(.horizontal, .xxSmall) + .padding(.bottom, .xxSmall) + .onTapGesture { + focusedField = nil + viewModel.present(.location) } } } @@ -283,221 +336,174 @@ public struct CreateEventView: View { .controlRadius(.large) } } - } - @ViewBuilder - var locationView: some View { - if viewModel.locationName != nil || viewModel.location != nil { - Surface { - VStack(spacing: .zero) { - if let locationName = viewModel.locationName { - VStack(spacing: .xxSmall) { - Row(locationName) { - viewModel.present(.location) - } leading: { - IconDeprecated(.mapPin) - .iconColor(.onSurfaceHighEmphasis) - } - .rowClearButton(style: .onSurface) { - viewModel.locationName = nil - viewModel.location = nil - } - .rowContentMargins(.init(horizontal: .small, vertical: .xSmall)) - } - } - - if let location = viewModel.location { - let region = MKCoordinateRegion(center: location, latitudinalMeters: 10000, longitudinalMeters: 10000) - let annotations = [MapPreviewPoint(name: "\(viewModel.locationName ?? "")", coordinate: location)] - Map(coordinateRegion: .constant(region), annotationItems: annotations) { - MapMarker(coordinate: $0.coordinate) - } - .frame(height: 130) - .cornerRadius(.small) - .padding(.horizontal, .xxSmall) - .padding(.bottom, .xxSmall) - .onTapGesture { - focusedField = nil - viewModel.present(.location) - } - } - } + var repeatSubtitleText: String? { + switch viewModel.repitEndRule { + case .never: + return nil + case let .occurrenceCount(count): + return count > 1 ? "With \(count) repetitions" : "With 1 repetition" + case let .endDate(date): + return "Until \(date.formatted(date: .long, time: .omitted))" } - .surfaceBorderColor(Color.surfaceSecondary) - .surfaceBorderWidth(1) - .surfaceContentMargins(.zero) - .controlRadius(.large) - } - } - - var repeatSubtitleText: String? { - switch viewModel.repitEndRule { - case .never: - return nil - case let .occurrenceCount(count): - return count > 1 ? "With \(count) repetitions" : "With 1 repetition" - case let .endDate(date): - return "Until \(date.formatted(date: .long, time: .omitted))" } - } - - var calendarButtons: some View { - HStack(spacing: .small) { - Button { - focusedField = nil - viewModel.present(.startTime) - } label: { - VStack(alignment: .leading, spacing: .xxxSmall) { - Text("Starts") - .onSurfaceMediumEmphasisForegroundColor() - .subheadline(.semibold) - - Text(startDateText) - .onSurfaceHighEmphasisForegroundColor() - .headline(.semibold) - if !isCurrentYearEvent { - Text(viewModel.dateStart.formatted(.dateTime.year())) - .onSurfaceHighEmphasisForegroundColor() + var calendarButtons: some View { + HStack(spacing: .small) { + Button { + focusedField = nil + viewModel.present(.startTime) + } label: { + VStack(alignment: .leading, spacing: .xxxSmall) { + Text("Starts") + .onSurfaceSecondaryForeground() + .subheadline(.semibold) + + Text(startDateText) + .onSurfacePrimaryForeground() .headline(.semibold) + + if !isCurrentYearEvent { + Text(viewModel.dateStart.formatted(.dateTime.year())) + .onSurfacePrimaryForeground() + .headline(.semibold) + } + } + .padding(.small) + .hLeading() + .background { + RoundedRectangle(cornerRadius: .large, style: .continuous) + .fillSurfaceSecondary() } } - .padding(.small) - .hLeading() - .background { - RoundedRectangle(cornerRadius: .large, style: .continuous) - .fillSurfaceSecondary() - } - } - .buttonStyle(.scale) - - Button { - focusedField = nil - viewModel.present(.endTime) - } label: { - VStack(alignment: .leading, spacing: .xxxSmall) { - Text("Ended") - .onSurfaceMediumEmphasisForegroundColor() - .subheadline(.semibold) + .buttonStyle(.scale) - Text(endDateText) - .onSurfaceHighEmphasisForegroundColor() - .headline(.semibold) - - if !isCurrentYearEvent { - Text(viewModel.dateEnd.formatted(.dateTime.year())) - .onSurfaceHighEmphasisForegroundColor() + Button { + focusedField = nil + viewModel.present(.endTime) + } label: { + VStack(alignment: .leading, spacing: .xxxSmall) { + Text("Ended") + .onSurfaceSecondaryForeground() + .subheadline(.semibold) + + Text(endDateText) + .onSurfacePrimaryForeground() .headline(.semibold) + + if !isCurrentYearEvent { + Text(viewModel.dateEnd.formatted(.dateTime.year())) + .onSurfacePrimaryForeground() + .headline(.semibold) + } + } + .padding(.small) + .hLeading() + .background { + RoundedRectangle(cornerRadius: .large, style: .continuous) + .fillSurfaceSecondary() } } - .padding(.small) - .hLeading() - .background { - RoundedRectangle(cornerRadius: .large, style: .continuous) - .fillSurfaceSecondary() - } + .buttonStyle(.scale) } - .buttonStyle(.scale) } - } - var isCurrentYearEvent: Bool { - Calendar.current.component(.year, from: viewModel.dateStart) == Calendar.current.component(.year, from: Date()) && Calendar.current.component(.year, from: viewModel.dateEnd) == Calendar.current.component(.year, from: Date()) - } + var isCurrentYearEvent: Bool { + Calendar.current.component(.year, from: viewModel.dateStart) == Calendar.current.component(.year, from: Date()) && Calendar.current.component(.year, from: viewModel.dateEnd) == Calendar.current.component(.year, from: Date()) + } - var startDateText: String { - if Calendar.current.isDateInToday(viewModel.dateStart) { - return "Today \(viewModel.dateStart.formatted(date: .omitted, time: .shortened))" - } else if Calendar.current.isDateInTomorrow(viewModel.dateStart) { - return "Tomorrow \(viewModel.dateStart.formatted(date: .omitted, time: .shortened))" - } else if Calendar.current.isDateInYesterday(viewModel.dateStart) { - return "Yesterday \(viewModel.dateStart.formatted(date: .omitted, time: .shortened))" - } else { - return "\(viewModel.dateStart.formatted(.dateTime.day().month())) \(viewModel.dateStart.formatted(date: .omitted, time: .shortened))" + var startDateText: String { + if Calendar.current.isDateInToday(viewModel.dateStart) { + return "Today \(viewModel.dateStart.formatted(date: .omitted, time: .shortened))" + } else if Calendar.current.isDateInTomorrow(viewModel.dateStart) { + return "Tomorrow \(viewModel.dateStart.formatted(date: .omitted, time: .shortened))" + } else if Calendar.current.isDateInYesterday(viewModel.dateStart) { + return "Yesterday \(viewModel.dateStart.formatted(date: .omitted, time: .shortened))" + } else { + return "\(viewModel.dateStart.formatted(.dateTime.day().month())) \(viewModel.dateStart.formatted(date: .omitted, time: .shortened))" + } } - } - var endDateText: String { - if Calendar.current.isDateInToday(viewModel.dateEnd) { - return "Today \(viewModel.dateEnd.formatted(date: .omitted, time: .shortened))" - } else if Calendar.current.isDateInTomorrow(viewModel.dateEnd) { - return "Tomorrow \(viewModel.dateEnd.formatted(date: .omitted, time: .shortened))" - } else if Calendar.current.isDateInYesterday(viewModel.dateEnd) { - return "Yesterday \(viewModel.dateEnd.formatted(date: .omitted, time: .shortened))" - } else { - return "\(viewModel.dateEnd.formatted(.dateTime.day().month())) \(viewModel.dateEnd.formatted(date: .omitted, time: .shortened))" + var endDateText: String { + if Calendar.current.isDateInToday(viewModel.dateEnd) { + return "Today \(viewModel.dateEnd.formatted(date: .omitted, time: .shortened))" + } else if Calendar.current.isDateInTomorrow(viewModel.dateEnd) { + return "Tomorrow \(viewModel.dateEnd.formatted(date: .omitted, time: .shortened))" + } else if Calendar.current.isDateInYesterday(viewModel.dateEnd) { + return "Yesterday \(viewModel.dateEnd.formatted(date: .omitted, time: .shortened))" + } else { + return "\(viewModel.dateEnd.formatted(.dateTime.day().month())) \(viewModel.dateEnd.formatted(date: .omitted, time: .shortened))" + } } - } - var bottomBar: some View { - HStack(spacing: .medium) { - Button { - Task { - focusedField = nil - viewModel.present(.location) - } - } label: { - if viewModel.isFetchUpdatePositon { - ProgressView() - } else { - IconDeprecated(.mapPin) -// Icon.Solid.NavigationandTravel.location -// .renderingMode(.template) + var bottomBar: some View { + HStack(spacing: .medium) { + Button { + Task { + focusedField = nil + viewModel.present(.location) + } + } label: { + if viewModel.isFetchUpdatePositon { + ProgressView() + } else { + IconDeprecated(.mapPin) + } } - } - .disabled(viewModel.isFetchUpdatePositon) + .disabled(viewModel.isFetchUpdatePositon) - Button { viewModel.present(.alarm) } label: { - IconDeprecated(.bell) -// Icon.Solid.UserInterface.bell -// .renderingMode(.template) - } + Button { viewModel.present(.alarm) } label: { + IconDeprecated(.bell) + } - Button { viewModel.present(.repeat) } label: { - IconDeprecated(.refresh) - } + Button { viewModel.present(.repeat) } label: { + IconDeprecated(.refresh) + } - /* - Button { viewModel.present(.attachment) } label: { - IconDeprecated(.moreHorizontal) - } - */ + /* + Button { viewModel.present(.attachment) } label: { + IconDeprecated(.moreHorizontal) + } + */ - Spacer() + Spacer() - Button { viewModel.present(.invites) } label: { - IconDeprecated(.userPlus) - } + Button { viewModel.present(.invites) } label: { + IconDeprecated(.userPlus) + } // Icon.Solid.UserInterface.plusCrFr // .renderingMode(.template) + } + .buttonStyle(.scale) + .padding(.horizontal, .medium) + .padding(.vertical, 20) + .onSurfaceSecondaryForeground() + #if !os(watchOS) + .background(.ultraThinMaterial) + #endif + .overlay(alignment: .top) { + Rectangle() + .fill(Color.onSurfacePrimary.opacity(0.05)) + .frame(height: 1) + } } - .buttonStyle(.scale) - .padding(.horizontal, .medium) - .padding(.vertical, 20) - .onSurfaceMediumEmphasisForegroundColor() - .background(.ultraThinMaterial) - .overlay(alignment: .top) { - Rectangle() - .fill(Color.onSurfaceHighEmphasis.opacity(0.05)) - .frame(height: 1) - } - } - @ViewBuilder - private func placeholder() -> some View {} -} + @ViewBuilder + private func placeholder() -> some View {} + } -extension CreateEventView { - enum FocusField: Hashable { - case title - case note - case url + extension CreateEventView { + enum FocusField: Hashable { + case title + case note + case url + } } -} -struct CreateEventView_Previews: PreviewProvider { - static var previews: some View { - CreateEventView() + struct CreateEventView_Previews: PreviewProvider { + static var previews: some View { + CreateEventView() + } } -} +#endif diff --git a/Sources/OversizeCalendarKit/CreateEventScreen/CreateEventViewModel.swift b/Sources/OversizeCalendarKit/CreateEventScreen/CreateEventViewModel.swift index a7f3335..004698e 100644 --- a/Sources/OversizeCalendarKit/CreateEventScreen/CreateEventViewModel.swift +++ b/Sources/OversizeCalendarKit/CreateEventScreen/CreateEventViewModel.swift @@ -3,7 +3,9 @@ // CreateEventViewModel.swift // -import EventKit +#if canImport(EventKit) + @preconcurrency import EventKit +#endif import Factory import OversizeCalendarService import OversizeCore @@ -11,173 +13,174 @@ import OversizeLocationService import OversizeModels import SwiftUI -public enum CreateEventType: Equatable { - case new(Date?, calendar: EKCalendar?) - case update(EKEvent) -} - -@MainActor -public class CreateEventViewModel: ObservableObject { - @Injected(\.calendarService) private var calendarService: CalendarService - @Injected(\.locationService) private var locationService: LocationServiceProtocol - - @Published var state = CreateEventViewModelState.initial - @Published var sheet: CreateEventViewModel.Sheet? = nil - @Published var isFetchUpdatePositon: Bool = .init(false) - - @Published var title: String = .init() - @Published var note: String = .init() - @Published var url: String = .init() - @Published var dateStart: Date = .init() - @Published var dateEnd: Date = .init().halfHour - @Published var isAllDay: Bool = .init(false) - @Published var calendar: EKCalendar? - @Published var calendars: [EKCalendar] = .init() - @Published var sourses: [EKSource] = .init() - @Published var locationName: String? - @Published var location: CLLocationCoordinate2D? - @Published var repitRule: CalendarEventRecurrenceRules = .never - @Published var repitEndRule: CalendarEventEndRecurrenceRules = .never - @Published var alarms: [CalendarAlertsTimes] = .init() - @Published var members: [String] = .init() - @Published var span: EKSpan? - - let type: CreateEventType - - var isLocationSelected: Bool { - location != nil +#if !os(tvOS) + public enum CreateEventType: Equatable, @unchecked Sendable { + case new(Date?, calendar: EKCalendar?) + case update(EKEvent) } - public init(_ type: CreateEventType) { - self.type = type - setEvent(type: type) - } + public class CreateEventViewModel: ObservableObject, @unchecked Sendable { + @Injected(\.calendarService) private var calendarService: CalendarService + @Injected(\.locationService) private var locationService: LocationServiceProtocol + + @Published var state = CreateEventViewModelState.initial + @Published var sheet: CreateEventViewModel.Sheet? = nil + @Published var isFetchUpdatePositon: Bool = .init(false) + + @Published var title: String = .init() + @Published var note: String = .init() + @Published var url: String = .init() + @Published var dateStart: Date = .init() + @Published var dateEnd: Date = .init().halfHour + @Published var isAllDay: Bool = .init(false) + @Published var calendar: EKCalendar? + @Published var calendars: [EKCalendar] = .init() + @Published var sourses: [EKSource] = .init() + @Published var locationName: String? + @Published var location: CLLocationCoordinate2D? + @Published var repitRule: CalendarEventRecurrenceRules = .never + @Published var repitEndRule: CalendarEventEndRecurrenceRules = .never + @Published var alarms: [CalendarAlertsTimes] = .init() + @Published var members: [String] = .init() + @Published var span: EKSpan? + + let type: CreateEventType + + var isLocationSelected: Bool { + location != nil + } - func setEvent(type: CreateEventType) { - switch type { - case let .new(date, calendar): - if let date { - dateStart = date - dateEnd = date.halfHour - } - if let calendar { - self.calendar = calendar - } - case let .update(event): - title = event.title - note = event.notes ?? "" - url = event.url?.absoluteString ?? "" - dateStart = event.startDate - dateEnd = event.endDate - isAllDay = event.isAllDay - calendar = event.calendar - locationName = event.location - if let coordinate = event.structuredLocation?.geoLocation?.coordinate { - location = coordinate + public init(_ type: CreateEventType) { + self.type = type + setEvent(type: type) + } + + func setEvent(type: CreateEventType) { + switch type { + case let .new(date, calendar): + if let date { + dateStart = date + dateEnd = date.halfHour + } + if let calendar { + self.calendar = calendar + } + case let .update(event): + title = event.title + note = event.notes ?? "" + url = event.url?.absoluteString ?? "" + dateStart = event.startDate + dateEnd = event.endDate + isAllDay = event.isAllDay + calendar = event.calendar + locationName = event.location + if let coordinate = event.structuredLocation?.geoLocation?.coordinate { + location = coordinate + } + if let rule = event.recurrenceRules?.first { + repitRule = rule.calendarRecurrenceRule + repitEndRule = rule.recurrenceEnd?.calendarEndRecurrenceRule ?? .never + } + if let eventAlarms = event.alarms { + alarms = eventAlarms.compactMap { $0.calendarAlert } + } + if let attendees = event.attendees { + members = attendees.compactMap { $0.url.absoluteString } + } } - if let rule = event.recurrenceRules?.first { - repitRule = rule.calendarRecurrenceRule - repitEndRule = rule.recurrenceEnd?.calendarEndRecurrenceRule ?? .never + } + + func fetchData() async { + state = .loading + async let calendarsResult = await calendarService.fetchCalendars() + switch await calendarsResult { + case let .success(data): + log("✅ EKCalendars fetched") + calendars = data + case let .failure(error): + log("❌ EKCalendars not fetched (\(error.title))") + state = .error(error) } - if let eventAlarms = event.alarms { - alarms = eventAlarms.compactMap { $0.calendarAlert } + async let soursesResult = await calendarService.fetchSourses() + switch await soursesResult { + case let .success(data): + log("✅ EKSource fetched") + sourses = data + case let .failure(error): + log("❌ EKSource not fetched (\(error.title))") + state = .error(error) } - if let attendees = event.attendees { - members = attendees.compactMap { $0.url.absoluteString } + if case let .new(_, calendar) = type, calendar == nil { + let result = await calendarService.fetchDefaultCalendar() + switch result { + case let .success(calendar): + self.calendar = calendar + case let .failure(error): + log("❌ Default calendar not fetched (\(error.title))") + } } } - } - func fetchData() async { - state = .loading - async let calendarsResult = await calendarService.fetchCalendars() - switch await calendarsResult { - case let .success(data): - log("✅ EKCalendars fetched") - calendars = data - case let .failure(error): - log("❌ EKCalendars not fetched (\(error.title))") - state = .error(error) - } - async let soursesResult = await calendarService.fetchSourses() - switch await soursesResult { - case let .success(data): - log("✅ EKSource fetched") - sourses = data - case let .failure(error): - log("❌ EKSource not fetched (\(error.title))") - state = .error(error) - } - if case let .new(_, calendar) = type, calendar == nil { - let result = await calendarService.fetchDefaultCalendar() + func save() async -> Result { + var oldEvent: EKEvent? + + if case let .update(event) = type { + oldEvent = event + } + + let result = await calendarService.createEvent( + event: oldEvent, + title: title, + notes: note, + startDate: dateStart, + endDate: dateEnd, + calendar: calendar, + isAllDay: isAllDay, + location: locationName, + structuredLocation: getEKStructuredLocation(), + alarms: alarms, + url: URL(string: url), + memberEmails: members, + recurrenceRules: repitRule, + recurrenceEndRules: repitEndRule, + span: span ?? .thisEvent + ) switch result { - case let .success(calendar): - self.calendar = calendar + case let .success(data): + log("✅ EKEvent saved") + return .success(data) case let .failure(error): - log("❌ Default calendar not fetched (\(error.title))") + log("❌ EKEvent not saved (\(error.title))") + return .failure(error) } } - } - func save() async -> Result { - var oldEvent: EKEvent? - - if case let .update(event) = type { - oldEvent = event - } - - let result = await calendarService.createEvent( - event: oldEvent, - title: title, - notes: note, - startDate: dateStart, - endDate: dateEnd, - calendar: calendar, - isAllDay: isAllDay, - location: locationName, - structuredLocation: getEKStructuredLocation(), - alarms: alarms, - url: URL(string: url), - memberEmails: members, - recurrenceRules: repitRule, - recurrenceEndRules: repitEndRule, - span: span ?? .thisEvent - ) - switch result { - case let .success(data): - log("✅ EKEvent saved") - return .success(data) - case let .failure(error): - log("❌ EKEvent not saved (\(error.title))") - return .failure(error) + func getEKStructuredLocation() -> EKStructuredLocation? { + if let location { + let structuredLocation: EKStructuredLocation? + let location = CLLocation(latitude: location.latitude, longitude: location.longitude) + structuredLocation = EKStructuredLocation(title: locationName ?? "") // same title with ekEvent.location + structuredLocation?.geoLocation = location + return structuredLocation + } else { + return nil + } } - } - func getEKStructuredLocation() -> EKStructuredLocation? { - if let location { - let structuredLocation: EKStructuredLocation? - let location = CLLocation(latitude: location.latitude, longitude: location.longitude) - structuredLocation = EKStructuredLocation(title: locationName ?? "") // same title with ekEvent.location - structuredLocation?.geoLocation = location - return structuredLocation - } else { - return nil + func updateCurrentPosition() async throws { + isFetchUpdatePositon = true + let currentPosition = try await locationService.currentLocation() + guard let newLocation = currentPosition else { return } + location = newLocation + log("📍 Location: \(newLocation.latitude), \(newLocation.longitude)") + isFetchUpdatePositon = false } } - func updateCurrentPosition() async throws { - isFetchUpdatePositon = true - let currentPosition = try await locationService.currentLocation() - guard let newLocation = currentPosition else { return } - location = newLocation - log("📍 Location: \(newLocation.latitude), \(newLocation.longitude)") - isFetchUpdatePositon = false + public enum CreateEventViewModelState { + case initial + case loading + case result([EKEvent]) + case error(AppError) } -} - -public enum CreateEventViewModelState { - case initial - case loading - case result([EKEvent]) - case error(AppError) -} +#endif diff --git a/Sources/OversizeCalendarKit/CreateEventScreen/CreateEventViewSheet.swift b/Sources/OversizeCalendarKit/CreateEventScreen/CreateEventViewSheet.swift index 5136669..beb178c 100644 --- a/Sources/OversizeCalendarKit/CreateEventScreen/CreateEventViewSheet.swift +++ b/Sources/OversizeCalendarKit/CreateEventScreen/CreateEventViewSheet.swift @@ -3,112 +3,119 @@ // CreateEventViewSheet.swift // -import EventKit +#if canImport(EventKit) + import EventKit +#endif import OversizeComponents import OversizeContactsKit import OversizeLocationKit import OversizeUI import SwiftUI -public extension CreateEventViewModel { - func present(_ sheet: CreateEventViewModel.Sheet) { - self.sheet = sheet - } +#if !os(tvOS) + public extension CreateEventViewModel { + func present(_ sheet: CreateEventViewModel.Sheet) { + self.sheet = sheet + } - func close() { - sheet = nil + func close() { + sheet = nil + } } -} -public extension CreateEventViewModel { - enum Sheet { - case startTime - case endTime - case attachment - case calendar - case location - case `repeat` - case alarm - case invites - case span - } // attachment, alert, invitees -} - -extension CreateEventViewModel.Sheet: Identifiable { - public var id: String { - switch self { - case .startTime: - return "startTime" - case .endTime: - return "endTime" - case .attachment: - return "attachment" - case .calendar: - return "calendar" - case .location: - return "location" - case .repeat: - return "repeat" - case .alarm: - return "alarm" - case .invites: - return "alarm" - case .span: - return "span" - } + public extension CreateEventViewModel { + enum Sheet { + case startTime + case endTime + case attachment + case calendar + case location + case `repeat` + case alarm + case invites + case span + } // attachment, alert, invitees } -} -public extension CreateEventView { - func resolveSheet(sheet: CreateEventViewModel.Sheet) -> some View { - Group { - switch sheet { + extension CreateEventViewModel.Sheet: Identifiable { + public var id: String { + switch self { case .startTime: - #if os(iOS) - DatePickerSheet(title: "Starts time", selection: $viewModel.dateStart) - .onDisappear { - if viewModel.dateStart > viewModel.dateEnd { - viewModel.dateEnd = viewModel.dateStart.halfHour - } - } - .presentationDetents([.height(500)]) - #else - EmptyView() - #endif + return "startTime" case .endTime: - #if os(iOS) - DatePickerSheet(title: "Ends time", selection: $viewModel.dateEnd) - .datePickerMinimumDate(viewModel.dateStart.minute) - .presentationDetents([.height(500)]) - #else - EmptyView() - #endif + return "endTime" case .attachment: - AttachmentView() - .presentationDetents([.height(270)]) + return "attachment" case .calendar: - CalendarPicker(selection: $viewModel.calendar, calendars: viewModel.calendars, sourses: viewModel.sourses) - .presentationDetents([.large]) + return "calendar" case .location: - AddressPicker(address: $viewModel.locationName, location: $viewModel.location) - .interactiveDismissDisabled(true) - .presentationDetents([.large]) + return "location" case .repeat: - RepeatPicker(selectionRule: $viewModel.repitRule, selectionEndRule: $viewModel.repitEndRule) - + return "repeat" case .alarm: - AlarmPicker(selection: $viewModel.alarms) - .presentationDetents([.height(630), .large]) - .presentationDragIndicator(.hidden) + return "alarm" case .invites: - EmailPickerView(selection: $viewModel.members) - .presentationDetents([.large]) - .interactiveDismissDisabled(true) + return "alarm" case .span: - SaveForView(selection: $viewModel.span) - .presentationDetents([.height(270)]) + return "span" + } + } + } + + public extension CreateEventView { + func resolveSheet(sheet: CreateEventViewModel.Sheet) -> some View { + Group { + switch sheet { + case .startTime: + #if os(iOS) + DatePickerSheet(title: "Starts time", selection: $viewModel.dateStart) + .onDisappear { + if viewModel.dateStart > viewModel.dateEnd { + viewModel.dateEnd = viewModel.dateStart.halfHour + } + } + .presentationDetents([.height(500)]) + #else + EmptyView() + #endif + case .endTime: + #if os(iOS) + DatePickerSheet(title: "Ends time", selection: $viewModel.dateEnd) + .datePickerMinimumDate(viewModel.dateStart.minute) + .presentationDetents([.height(500)]) + #else + EmptyView() + #endif + case .attachment: + AttachmentView() + .presentationDetents([.height(270)]) + case .calendar: + CalendarPicker(selection: $viewModel.calendar, calendars: viewModel.calendars, sourses: viewModel.sourses) + .presentationDetents([.large]) + case .location: + #if !os(watchOS) + AddressPicker(address: $viewModel.locationName, location: $viewModel.location) + .interactiveDismissDisabled(true) + .presentationDetents([.large]) + #else + EmptyView() + #endif + case .repeat: + RepeatPicker(selectionRule: $viewModel.repitRule, selectionEndRule: $viewModel.repitEndRule) + case .alarm: + AlarmPicker(selection: $viewModel.alarms) + .presentationDetents([.height(630), .large]) + .presentationDragIndicator(.hidden) + case .invites: + EmailPickerView(selection: $viewModel.members) + .presentationDetents([.large]) + .interactiveDismissDisabled(true) + case .span: + SaveForView(selection: $viewModel.span) + .presentationDetents([.height(270)]) + } } + .systemServices() } - .systemServices() } -} +#endif diff --git a/Sources/OversizeCalendarKit/CreateEventScreen/SaveForView/SaveForView.swift b/Sources/OversizeCalendarKit/CreateEventScreen/SaveForView/SaveForView.swift index b8ee9d7..676b9a5 100644 --- a/Sources/OversizeCalendarKit/CreateEventScreen/SaveForView/SaveForView.swift +++ b/Sources/OversizeCalendarKit/CreateEventScreen/SaveForView/SaveForView.swift @@ -3,46 +3,50 @@ // SaveForView.swift // -import EventKit +#if canImport(EventKit) + import EventKit +#endif import OversizeUI import SwiftUI -public struct SaveForView: View { - @Environment(\.dismiss) var dismiss - @Binding private var span: EKSpan? +#if !os(tvOS) + public struct SaveForView: View { + @Environment(\.dismiss) var dismiss + @Binding private var span: EKSpan? - public init(selection: Binding) { - _span = selection - } + public init(selection: Binding) { + _span = selection + } - public var body: some View { - PageView("This is repeating event") { - SectionView { - VStack(spacing: .zero) { - Row("Save for this event only") { - span = .thisEvent - dismiss() - } leading: { - Image.Date.calendar - .renderingMode(.template) - .foregroundColor(.onSurfaceHighEmphasis) - } + public var body: some View { + PageView("This is repeating event") { + SectionView { + VStack(spacing: .zero) { + Row("Save for this event only") { + span = .thisEvent + dismiss() + } leading: { + Image.Date.calendar + .renderingMode(.template) + .foregroundColor(.onSurfacePrimary) + } - Row("Save for feature events") { - span = .futureEvents - dismiss() - } leading: { - Image.Base.calendar - .renderingMode(.template) - .foregroundColor(.onSurfaceHighEmphasis) + Row("Save for feature events") { + span = .futureEvents + dismiss() + } leading: { + Image.Base.calendar + .renderingMode(.template) + .foregroundColor(.onSurfacePrimary) + } } } + .surfaceContentRowMargins() + } + .backgroundSecondary() + .leadingBar { + BarButton(.close) } - .surfaceContentRowMargins() - } - .backgroundSecondary() - .leadingBar { - BarButton(.close) } } -} +#endif diff --git a/Sources/OversizeCalendarKit/Pickers/AlertPicker.swift b/Sources/OversizeCalendarKit/Pickers/AlertPicker.swift index 68fbfb8..f31672c 100644 --- a/Sources/OversizeCalendarKit/Pickers/AlertPicker.swift +++ b/Sources/OversizeCalendarKit/Pickers/AlertPicker.swift @@ -3,51 +3,55 @@ // AlertPicker.swift // -import EventKit +#if canImport(EventKit) + import EventKit +#endif import OversizeCalendarService import OversizeUI import SwiftUI -public struct AlarmPicker: View { - @Environment(\.dismiss) var dismiss - @Binding private var selection: [CalendarAlertsTimes] - @State private var selectedAlerts: [CalendarAlertsTimes] = [] +#if !os(tvOS) + public struct AlarmPicker: View { + @Environment(\.dismiss) var dismiss + @Binding private var selection: [CalendarAlertsTimes] + @State private var selectedAlerts: [CalendarAlertsTimes] = [] - public init(selection: Binding<[CalendarAlertsTimes]>) { - _selection = selection - _selectedAlerts = State(wrappedValue: selection.wrappedValue) - } + public init(selection: Binding<[CalendarAlertsTimes]>) { + _selection = selection + _selectedAlerts = State(wrappedValue: selection.wrappedValue) + } - public var body: some View { - PageView("Alarm") { - SectionView { - VStack(spacing: .zero) { - ForEach(CalendarAlertsTimes.allCases) { alert in - Checkbox(alert.title, isOn: .constant((selectedAlerts.first { $0.id == alert.id } != nil) ? true : false)) { - if !selectedAlerts.isEmpty, let _ = selectedAlerts.first(where: { $0.id == alert.id }) { - selectedAlerts.remove(alert) - } else { - selectedAlerts.append(alert) + public var body: some View { + PageView("Alarm") { + SectionView { + VStack(spacing: .zero) { + ForEach(CalendarAlertsTimes.allCases) { alert in + Checkbox(alert.title, isOn: .constant((selectedAlerts.first { $0.id == alert.id } != nil) ? true : false)) { + if !selectedAlerts.isEmpty, let _ = selectedAlerts.first(where: { $0.id == alert.id }) { + selectedAlerts.remove(alert) + } else { + selectedAlerts.append(alert) + } } } } } + .surfaceContentRowMargins() + } + .backgroundSecondary() + .leadingBar { + BarButton(.close) + } + .trailingBar { + BarButton(.accent("Done", action: { + selection = selectedAlerts + dismiss() + })) + .disabled(selectedAlerts.isEmpty) } - .surfaceContentRowMargins() - } - .backgroundSecondary() - .leadingBar { - BarButton(.close) - } - .trailingBar { - BarButton(.accent("Done", action: { - selection = selectedAlerts - dismiss() - })) - .disabled(selectedAlerts.isEmpty) } } -} +#endif // struct AlertPicker_Previews: PreviewProvider { // static var previews: some View { diff --git a/Sources/OversizeCalendarKit/Pickers/CalendarPicker.swift b/Sources/OversizeCalendarKit/Pickers/CalendarPicker.swift index 173685e..8244d84 100644 --- a/Sources/OversizeCalendarKit/Pickers/CalendarPicker.swift +++ b/Sources/OversizeCalendarKit/Pickers/CalendarPicker.swift @@ -3,66 +3,70 @@ // CalendarPicker.swift // -import EventKit +#if canImport(EventKit) + import EventKit +#endif import OversizeUI import SwiftUI -public struct CalendarPicker: View { - @Environment(\.dismiss) var dismiss +#if !os(tvOS) + public struct CalendarPicker: View { + @Environment(\.dismiss) var dismiss - @Binding private var selection: EKCalendar? + @Binding private var selection: EKCalendar? - private let calendars: [EKCalendar] + private let calendars: [EKCalendar] - private let sourses: [EKSource] + private let sourses: [EKSource] - private let closable: Bool + private let closable: Bool - public init(selection: Binding, calendars: [EKCalendar], sourses: [EKSource], closable: Bool = true) { - _selection = selection - self.calendars = calendars - self.sourses = sourses - self.closable = closable - } + public init(selection: Binding, calendars: [EKCalendar], sourses: [EKSource], closable: Bool = true) { + _selection = selection + self.calendars = calendars + self.sourses = sourses + self.closable = closable + } - public var body: some View { - PageView("Calendar") { - ForEach(sourses, id: \.sourceIdentifier) { source in - let filtredCalendar: [EKCalendar] = calendars.filter { $0.source.sourceIdentifier == source.sourceIdentifier && $0.allowsContentModifications } - if !filtredCalendar.isEmpty { - calendarSection(source: source, calendars: filtredCalendar) + public var body: some View { + PageView("Calendar") { + ForEach(sourses, id: \.sourceIdentifier) { source in + let filtredCalendar: [EKCalendar] = calendars.filter { $0.source.sourceIdentifier == source.sourceIdentifier && $0.allowsContentModifications } + if !filtredCalendar.isEmpty { + calendarSection(source: source, calendars: filtredCalendar) + } } } + .backgroundSecondary() + .leadingBar { + BarButton(closable ? .close : .back) + } } - .backgroundSecondary() - .leadingBar { - BarButton(closable ? .close : .back) - } - } - func calendarSection(source: EKSource, calendars: [EKCalendar]) -> some View { - SectionView(source.title) { - VStack(spacing: .zero) { - ForEach(calendars, id: \.calendarIdentifier) { calendar in - Radio(isOn: selection?.calendarIdentifier == calendar.calendarIdentifier) { - selection = calendar - dismiss() - } label: { - Row(calendar.title) { - Circle() - .fill(Color(calendar.cgColor)) - .frame(width: 16, height: 16) + func calendarSection(source: EKSource, calendars: [EKCalendar]) -> some View { + SectionView(source.title) { + VStack(spacing: .zero) { + ForEach(calendars, id: \.calendarIdentifier) { calendar in + Radio(isOn: selection?.calendarIdentifier == calendar.calendarIdentifier) { + selection = calendar + dismiss() + } label: { + Row(calendar.title) { + Circle() + .fill(Color(calendar.cgColor)) + .frame(width: 16, height: 16) + } } } } } + .surfaceContentRowMargins() } - .surfaceContentRowMargins() } -} -struct CalendarPicker_Previews: PreviewProvider { - static var previews: some View { - CalendarPicker(selection: .constant(nil), calendars: [], sourses: []) + struct CalendarPicker_Previews: PreviewProvider { + static var previews: some View { + CalendarPicker(selection: .constant(nil), calendars: [], sourses: []) + } } -} +#endif diff --git a/Sources/OversizeCalendarKit/Pickers/RepeatPicker.swift b/Sources/OversizeCalendarKit/Pickers/RepeatPicker.swift index ec7b5d2..2c32cb8 100644 --- a/Sources/OversizeCalendarKit/Pickers/RepeatPicker.swift +++ b/Sources/OversizeCalendarKit/Pickers/RepeatPicker.swift @@ -3,132 +3,134 @@ // RepeatPicker.swift // -import EventKit +#if canImport(EventKit) + import EventKit +#endif import OversizeCalendarService import OversizeUI import SwiftUI -public struct RepeatPicker: View { - @Environment(\.dismiss) private var dismiss +#if !os(tvOS) + public struct RepeatPicker: View { + @Environment(\.dismiss) private var dismiss - @Binding private var selectionRule: CalendarEventRecurrenceRules - @Binding private var selectionEndRule: CalendarEventEndRecurrenceRules + @Binding private var selectionRule: CalendarEventRecurrenceRules + @Binding private var selectionEndRule: CalendarEventEndRecurrenceRules - @State private var rule: CalendarEventRecurrenceRules - @State private var endRule: CalendarEventEndRecurrenceRules + @State private var rule: CalendarEventRecurrenceRules + @State private var endRule: CalendarEventEndRecurrenceRules - @State private var endDate: Date = .init() - @State private var repeatCount: String = "1" - @FocusState private var isFocusedRepitCount: Bool + @State private var endDate: Date = .init() + @State private var repeatCount: String = "1" + @FocusState private var isFocusedRepitCount: Bool - public init(selectionRule: Binding, selectionEndRule: Binding) { - _selectionRule = selectionRule - _selectionEndRule = selectionEndRule - _rule = State(wrappedValue: selectionRule.wrappedValue) - _endRule = State(wrappedValue: selectionEndRule.wrappedValue) - } + public init(selectionRule: Binding, selectionEndRule: Binding) { + _selectionRule = selectionRule + _selectionEndRule = selectionEndRule + _rule = State(wrappedValue: selectionRule.wrappedValue) + _endRule = State(wrappedValue: selectionEndRule.wrappedValue) + } - public var body: some View { - ScrollViewReader { scrollView in - PageView("Repeat") { - SectionView { - VStack(spacing: .zero) { - ForEach(CalendarEventRecurrenceRules.allCases) { rule in - Radio(isOn: self.rule.id == rule.id) { - withAnimation { - self.rule = rule + public var body: some View { + ScrollViewReader { scrollView in + PageView("Repeat") { + SectionView { + VStack(spacing: .zero) { + ForEach(CalendarEventRecurrenceRules.allCases) { rule in + Radio(isOn: self.rule.id == rule.id) { + withAnimation { + self.rule = rule + } + } label: { + Row(rule.title) } - } label: { - Row(rule.title) } } } - } - if rule != .never { - SectionView("End Repeat") { - VStack(spacing: .zero) { - ForEach(CalendarEventEndRecurrenceRules.allCases) { rule in - VStack(spacing: .xxSmall) { - Radio(isOn: endRule.id == rule.id) { - endRule = rule - if case .occurrenceCount = endRule { - isFocusedRepitCount = true - scrollView.scrollTo(rule.id) - } + if rule != .never { + SectionView("End Repeat") { + VStack(spacing: .zero) { + ForEach(CalendarEventEndRecurrenceRules.allCases) { rule in + VStack(spacing: .xxSmall) { + Radio(isOn: endRule.id == rule.id) { + endRule = rule + if case .occurrenceCount = endRule { + isFocusedRepitCount = true + scrollView.scrollTo(rule.id) + } - if case .endDate = endRule { - isFocusedRepitCount = true - scrollView.scrollTo(rule.id) + if case .endDate = endRule { + isFocusedRepitCount = true + scrollView.scrollTo(rule.id) + } + } label: { + Row(rule.title) } - } label: { - Row(rule.title) - } - if endRule.id == rule.id { - repartPicker(rules: rule) - .padding(.horizontal, .medium) - .padding(.bottom, .small) + if endRule.id == rule.id { + repartPicker(rules: rule) + .padding(.horizontal, .medium) + .padding(.bottom, .small) + } } } } } + .transition(.move(edge: .top)) } - .transition(.move(edge: .top)) } + .backgroundSecondary() + .leadingBar { + BarButton(.close) + } + .trailingBar { + BarButton(.accent("Done", action: { + selectionRule = rule + selectionEndRule = endRule + dismiss() + })) + .disabled(rule == .never) + } + .surfaceContentRowMargins() } - .backgroundSecondary() - .leadingBar { - BarButton(.close) - } - .trailingBar { - BarButton(.accent("Done", action: { - selectionRule = rule - selectionEndRule = endRule - dismiss() - })) - .disabled(rule == .never) - } - .surfaceContentRowMargins() + .presentationDetents(rule == .never ? [.height(630), .large] : [.large]) + .presentationDragIndicator(.hidden) } - .presentationDetents(rule == .never ? [.height(630), .large] : [.large]) - .presentationDragIndicator(.hidden) - } - @ViewBuilder - func repartPicker(rules: CalendarEventEndRecurrenceRules) -> some View { - switch rules { - case .never: - EmptyView() - case .occurrenceCount: - TextField("Number of repetitions", text: Binding(get: { - repeatCount - }, set: { newValue in - repeatCount = newValue - endRule = .occurrenceCount(Int(newValue) ?? 1) - })) - #if os(iOS) - .keyboardType(.numberPad) - #endif - .textFieldStyle(DefaultPlaceholderTextFieldStyle()) - .focused($isFocusedRepitCount) - case .endDate: - DatePicker("Date", selection: Binding(get: { - endDate - }, set: { newDate in - endDate = newDate - endRule = .endDate(newDate) - })) - #if os(iOS) - .datePickerStyle(.wheel) - #endif - .labelsHidden() + @ViewBuilder + func repartPicker(rules: CalendarEventEndRecurrenceRules) -> some View { + switch rules { + case .never: + EmptyView() + case .occurrenceCount: + TextField("Number of repetitions", text: Binding(get: { + repeatCount + }, set: { newValue in + repeatCount = newValue + endRule = .occurrenceCount(Int(newValue) ?? 1) + })) + #if os(iOS) + .keyboardType(.numberPad) + #endif + .textFieldStyle(.default) + .focused($isFocusedRepitCount) + case .endDate: + #if !os(watchOS) + DatePicker("Date", selection: Binding(get: { + endDate + }, set: { newDate in + endDate = newDate + endRule = .endDate(newDate) + })) + #if os(iOS) + .datePickerStyle(.wheel) + #endif + .labelsHidden() + #else + ProgressView() + #endif + } } } -} - -// struct RepeatPicker_Previews: PreviewProvider { -// static var previews: some View { -// RepeatPicker() -// } -// } +#endif diff --git a/Sources/OversizeContactsKit/AttendeesList/AttendeesView.swift b/Sources/OversizeContactsKit/AttendeesList/AttendeesView.swift index 3125879..3bca2d1 100644 --- a/Sources/OversizeContactsKit/AttendeesList/AttendeesView.swift +++ b/Sources/OversizeContactsKit/AttendeesList/AttendeesView.swift @@ -3,8 +3,10 @@ // AttendeesView.swift // -import Contacts -import EventKit +#if canImport(Contacts) && canImport(EventKit) + import Contacts + import EventKit +#endif import OversizeCalendarService import OversizeContactsService import OversizeCore @@ -13,81 +15,87 @@ import OversizeLocalizable import OversizeUI import SwiftUI -public struct AttendeesView: View { - @StateObject var viewModel: AttendeesViewModel - @Environment(\.dismiss) var dismiss +#if !os(tvOS) + public struct AttendeesView: View { + @StateObject var viewModel: AttendeesViewModel + @Environment(\.dismiss) var dismiss - public init(event: EKEvent) { - _viewModel = StateObject(wrappedValue: AttendeesViewModel(event: event)) - } + public init(event: EKEvent) { + _viewModel = StateObject(wrappedValue: AttendeesViewModel(event: event)) + } - public var body: some View { - PageView("Invitees") { - Group { - switch viewModel.state { - case .initial: - placeholder() - .onAppear { - Task { - await viewModel.fetchData() + public var body: some View { + PageView("Invitees") { + Group { + switch viewModel.state { + case .initial: + placeholder() + .onAppear { + Task { + await viewModel.fetchData() + } } - } - case .loading: - placeholder() - case let .result(data): - content(data) - case let .error(error): - ErrorView(error) + case .loading: + placeholder() + case let .result(data): + content(data) + case let .error(error): + ErrorView(error) + } } } + .leadingBar { + BarButton(.close) + } } - .leadingBar { - BarButton(.close) - } - } - @ViewBuilder - private func content(_: [CNContact]) -> some View { - if let attendees = viewModel.event.attendees { - VStack(spacing: .zero) { - if let organizer = viewModel.event.organizer { - Row(organizer.name ?? organizer.url.absoluteString, subtitle: "Organizer") { - userAvatarView(participant: organizer) + @ViewBuilder + private func content(_: [CNContact]) -> some View { + if let attendees = viewModel.event.attendees { + VStack(spacing: .zero) { + if let organizer = viewModel.event.organizer { + Row(organizer.name ?? organizer.url.absoluteString, subtitle: "Organizer") { + userAvatarView(participant: organizer) + } } - } - ForEach(attendees, id: \.self) { attender in - Row(attender.name ?? attender.url.absoluteString, subtitle: attender.participantRole.title) { - userAvatarView(participant: attender) + ForEach(attendees, id: \.self) { attender in + Row(attender.name ?? attender.url.absoluteString, subtitle: attender.participantRole.title) { + userAvatarView(participant: attender) + } } } } } - } - func userAvatarView(participant: EKParticipant) -> some View { - ZStack(alignment: .bottomTrailing) { - Avatar(firstName: participant.name ?? participant.url.absoluteString) - .controlSize(.regular) + func userAvatarView(participant: EKParticipant) -> some View { + ZStack(alignment: .bottomTrailing) { + Avatar(firstName: participant.name ?? participant.url.absoluteString) + .controlSize(.regular) - ZStack { - Circle() - .fill(participant.color) - .frame(width: 16, height: 16) - .background { - Circle() - .stroke(lineWidth: 4) - .fillBackgroundPrimary() - } - Image(systemName: participant.symbolName) - .onPrimaryHighEmphasisForegroundColor() - .font(.system(size: 9, weight: .black)) + ZStack { + Circle() + .fill(participant.color) + .frame(width: 16, height: 16) + .background { + Circle() + .stroke(lineWidth: 4) + .fillBackgroundPrimary() + } + Image(systemName: participant.symbolName) + .onPrimaryForeground() + .font(.system(size: 9, weight: .black)) + } } } - } - @ViewBuilder - private func placeholder() -> some View { - LoaderOverlayView() + @ViewBuilder + private func placeholder() -> some View { + #if os(watchOS) + ProgressView() + #else + LoaderOverlayView() + #endif + } } -} +#endif diff --git a/Sources/OversizeContactsKit/AttendeesList/AttendeesViewModel.swift b/Sources/OversizeContactsKit/AttendeesList/AttendeesViewModel.swift index 3b06806..d597c36 100644 --- a/Sources/OversizeContactsKit/AttendeesList/AttendeesViewModel.swift +++ b/Sources/OversizeContactsKit/AttendeesList/AttendeesViewModel.swift @@ -3,32 +3,35 @@ // AttendeesViewModel.swift // -import Contacts -import EventKit +#if canImport(Contacts) && canImport(EventKit) + @preconcurrency import Contacts + import EventKit +#endif import Factory import OversizeContactsService import OversizeCore import OversizeModels import SwiftUI -@MainActor -class AttendeesViewModel: ObservableObject { - @Injected(\.contactsService) private var contactsService: ContactsService - @Published var state = AttendeesViewModelState.initial - @Published var searchText: String = .init() +#if !os(tvOS) + @MainActor + class AttendeesViewModel: ObservableObject { + @Injected(\.contactsService) private var contactsService: ContactsService + @Published var state = AttendeesViewModelState.initial + @Published var searchText: String = .init() - let event: EKEvent + let event: EKEvent - init(event: EKEvent) { - self.event = event - } + init(event: EKEvent) { + self.event = event + } + + func fetchData() async { + state = .loading + let _ = await contactsService.requestAccess() - func fetchData() async { - state = .loading - let _ = await contactsService.requestAccess() - do { let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactThumbnailImageDataKey] - let result = try await contactsService.fetchContacts(keysToFetch: keys as [CNKeyDescriptor]) + let result = await contactsService.fetchContacts(keysToFetch: keys as [CNKeyDescriptor]) switch result { case let .success(data): log("✅ CNContact fetched") @@ -37,27 +40,25 @@ class AttendeesViewModel: ObservableObject { log("❌ CNContact not fetched (\(error.title))") state = .error(error) } - } catch { - state = .error(.custom(title: "Not contacts")) } - } - func getContactFromEmail(email: String, contacts: [CNContact]) -> CNContact? { - for contact in contacts where !contact.emailAddresses.isEmpty { - for emailAddress in contact.emailAddresses { - let emailAddressString = emailAddress.value as String - if emailAddressString == email { - return contact + func getContactFromEmail(email: String, contacts: [CNContact]) -> CNContact? { + for contact in contacts where !contact.emailAddresses.isEmpty { + for emailAddress in contact.emailAddresses { + let emailAddressString = emailAddress.value as String + if emailAddressString == email { + return contact + } } } + return nil } - return nil } -} - -enum AttendeesViewModelState { - case initial - case loading - case result([CNContact]) - case error(AppError) -} + + enum AttendeesViewModelState { + case initial + case loading + case result([CNContact]) + case error(AppError) + } +#endif diff --git a/Sources/OversizeContactsKit/ContactsLists/ContactsListsView.swift b/Sources/OversizeContactsKit/ContactsLists/ContactsListsView.swift index c1d8c82..11d868c 100644 --- a/Sources/OversizeContactsKit/ContactsLists/ContactsListsView.swift +++ b/Sources/OversizeContactsKit/ContactsLists/ContactsListsView.swift @@ -3,7 +3,9 @@ // ContactsListsView.swift // -import Contacts +#if canImport(Contacts) + import Contacts +#endif import OversizeComponents import OversizeCore import OversizeKit @@ -11,83 +13,85 @@ import OversizeLocalizable import OversizeUI import SwiftUI -public struct ContactsListsView: View { - @StateObject var viewModel: ContactsListsViewModel - @Environment(\.dismiss) var dismiss - @Binding private var emails: [String] +#if !os(tvOS) + public struct ContactsListsView: View { + @StateObject var viewModel: ContactsListsViewModel + @Environment(\.dismiss) var dismiss + @Binding private var emails: [String] - public init(emails: Binding<[String]>) { - _viewModel = StateObject(wrappedValue: ContactsListsViewModel()) - _emails = emails - } + public init(emails: Binding<[String]>) { + _viewModel = StateObject(wrappedValue: ContactsListsViewModel()) + _emails = emails + } - public var body: some View { - PageView("") { - Group { - switch viewModel.state { - case .initial: - placeholder() - case .loading: - placeholder() - case let .result(data): - content(data: data) - case let .error(error): - ErrorView(error) + public var body: some View { + PageView("") { + Group { + switch viewModel.state { + case .initial: + placeholder() + case .loading: + placeholder() + case let .result(data): + content(data: data) + case let .error(error): + ErrorView(error) + } } } + .leadingBar { + BarButton(.close) + } + .task { + await viewModel.fetchData() + } } - .leadingBar { - BarButton(.close) - } - .task { - await viewModel.fetchData() - } - } - @ViewBuilder - private func content(data: [CNContact]) -> some View { - ForEach(emails, id: \.self) { email in - if let contact = viewModel.getContactFromEmail(email: email, contacts: data) { - let emails = contact.emailAddresses - if !emails.isEmpty { - ForEach(emails, id: \.identifier) { email in - emailRow(email: email, contact: contact) + @ViewBuilder + private func content(data: [CNContact]) -> some View { + ForEach(emails, id: \.self) { email in + if let contact = viewModel.getContactFromEmail(email: email, contacts: data) { + let emails = contact.emailAddresses + if !emails.isEmpty { + ForEach(emails, id: \.identifier) { email in + emailRow(email: email, contact: contact) + } + } + } else { + Row(email) { + Avatar(firstName: email) } - } - } else { - Row(email) { - Avatar(firstName: email) } } } - } - @ViewBuilder - private func emailRow(email: CNLabeledValue, contact: CNContact) -> some View { - let email = email.value as String - #if os(iOS) - if let avatarThumbnailData = contact.thumbnailImageData, let avatarThumbnail = UIImage(data: avatarThumbnailData) { - Row(contact.givenName + " " + contact.familyName, subtitle: email) { - Avatar(firstName: contact.givenName, lastName: contact.familyName, avatar: Image(uiImage: avatarThumbnail)) + @ViewBuilder + private func emailRow(email: CNLabeledValue, contact: CNContact) -> some View { + let email = email.value as String + #if os(iOS) + if let avatarThumbnailData = contact.thumbnailImageData, let avatarThumbnail = UIImage(data: avatarThumbnailData) { + Row(contact.givenName + " " + contact.familyName, subtitle: email) { + Avatar(firstName: contact.givenName, lastName: contact.familyName, avatar: Image(uiImage: avatarThumbnail)) + } + } else { + Row(contact.givenName + " " + contact.familyName, subtitle: email) { + Avatar(firstName: contact.givenName, lastName: contact.familyName) + } } - } else { + #else Row(contact.givenName + " " + contact.familyName, subtitle: email) { Avatar(firstName: contact.givenName, lastName: contact.familyName) } - } - #else - Row(contact.givenName + " " + contact.familyName, subtitle: email) { - Avatar(firstName: contact.givenName, lastName: contact.familyName) - } - #endif - } + #endif + } - @ViewBuilder - private func placeholder() -> some View { - ForEach(emails, id: \.self) { email in - Row(email) { - Avatar(firstName: email) + @ViewBuilder + private func placeholder() -> some View { + ForEach(emails, id: \.self) { email in + Row(email) { + Avatar(firstName: email) + } } } } -} +#endif diff --git a/Sources/OversizeContactsKit/ContactsLists/ContactsListsViewModel.swift b/Sources/OversizeContactsKit/ContactsLists/ContactsListsViewModel.swift index bed0d93..a7ce31c 100644 --- a/Sources/OversizeContactsKit/ContactsLists/ContactsListsViewModel.swift +++ b/Sources/OversizeContactsKit/ContactsLists/ContactsListsViewModel.swift @@ -3,27 +3,30 @@ // ContactsListsViewModel.swift // -import Contacts +#if canImport(Contacts) + @preconcurrency import Contacts +#endif import Factory import OversizeContactsService import OversizeCore import OversizeModels import SwiftUI -@MainActor -public class ContactsListsViewModel: ObservableObject { - @Injected(\.contactsService) private var contactsService: ContactsService - @Published var state = ContactsPickerViewModelState.initial - @Published var searchText: String = .init() +#if !os(tvOS) + @MainActor + public class ContactsListsViewModel: ObservableObject { + @Injected(\.contactsService) private var contactsService: ContactsService + @Published var state = ContactsPickerViewModelState.initial + @Published var searchText: String = .init() - public init() {} + public init() {} + + func fetchData() async { + state = .loading + let _ = await contactsService.requestAccess() - func fetchData() async { - state = .loading - let _ = await contactsService.requestAccess() - do { let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactThumbnailImageDataKey] - let result = try await contactsService.fetchContacts(keysToFetch: keys as [CNKeyDescriptor]) + let result = await contactsService.fetchContacts(keysToFetch: keys as [CNKeyDescriptor]) switch result { case let .success(data): log("✅ CNContact fetched") @@ -32,27 +35,25 @@ public class ContactsListsViewModel: ObservableObject { log("❌ CNContact not fetched (\(error.title))") state = .error(error) } - } catch { - state = .error(.custom(title: "Not contacts")) } - } - func getContactFromEmail(email: String, contacts: [CNContact]) -> CNContact? { - for contact in contacts where !contact.emailAddresses.isEmpty { - for emailAddress in contact.emailAddresses { - let emailAddressString = emailAddress.value as String - if emailAddressString == email { - return contact + func getContactFromEmail(email: String, contacts: [CNContact]) -> CNContact? { + for contact in contacts where !contact.emailAddresses.isEmpty { + for emailAddress in contact.emailAddresses { + let emailAddressString = emailAddress.value as String + if emailAddressString == email { + return contact + } } } + return nil } - return nil } -} -enum ContactsListsViewModelState { - case initial - case loading - case result([CNContact]) - case error(AppError) -} + enum ContactsListsViewModelState { + case initial + case loading + case result([CNContact]) + case error(AppError) + } +#endif diff --git a/Sources/OversizeContactsKit/ContactsPicker/EmailPickerView.swift b/Sources/OversizeContactsKit/ContactsPicker/EmailPickerView.swift index 3301f8b..053b539 100644 --- a/Sources/OversizeContactsKit/ContactsPicker/EmailPickerView.swift +++ b/Sources/OversizeContactsKit/ContactsPicker/EmailPickerView.swift @@ -3,168 +3,182 @@ // EmailPickerView.swift // -import Contacts +#if canImport(Contacts) + import Contacts +#endif import OversizeKit import OversizeLocalizable import OversizeUI import SwiftUI -public struct EmailPickerView: View { - @Environment(\.dismiss) private var dismiss - @StateObject private var viewModel: EmailPickerViewModel +#if !os(tvOS) + public struct EmailPickerView: View { + @Environment(\.dismiss) private var dismiss + @StateObject private var viewModel: EmailPickerViewModel - @Binding private var selection: [String] - @State private var selectedEmails: [String] = .init() + @Binding private var selection: [String] + @State private var selectedEmails: [String] = .init() - @FocusState private var isFocusSearth + @FocusState private var isFocusSearth - public init(selection: Binding<[String]>) { - _viewModel = StateObject(wrappedValue: EmailPickerViewModel()) - _selection = selection - } + public init(selection: Binding<[String]>) { + _viewModel = StateObject(wrappedValue: EmailPickerViewModel()) + _selection = selection + } - public var body: some View { - PageView("Add Invitees") { - Group { - switch viewModel.state { - case .initial, .loading: - placeholder() - case let .result(data): - content(data: data) - case let .error(error): - ErrorView(error) + public var body: some View { + PageView("Add Invitees") { + Group { + switch viewModel.state { + case .initial, .loading: + placeholder() + case let .result(data): + content(data: data) + case let .error(error): + ErrorView(error) + } } } + .leadingBar { + BarButton(.close) + } + .trailingBar { + BarButton(.accent("Done", action: { + onDoneAction() + })) + .disabled(selectedEmails.isEmpty && !viewModel.searchText.isEmail) + } + .topToolbar { + TextField("Email or name", text: $viewModel.searchText) + .textFieldStyle(.default) + .focused($isFocusSearth) + #if os(iOS) + .keyboardType(.emailAddress) + #endif + } + .onAppear { + isFocusSearth = true + } + .task { + await viewModel.fetchData() + } } - .leadingBar { - BarButton(.close) - } - .trailingBar { - BarButton(.accent("Done", action: { - onDoneAction() - })) - .disabled(selectedEmails.isEmpty && !viewModel.searchText.isEmail) - } - .topToolbar { - TextField("Email or name", text: $viewModel.searchText) - .textFieldStyle(DefaultPlaceholderTextFieldStyle()) - .focused($isFocusSearth) - #if os(iOS) - .keyboardType(.emailAddress) - #endif - } - .onAppear { - isFocusSearth = true - } - .task { - await viewModel.fetchData() - } - } - @ViewBuilder - private func content(data: [CNContact]) -> some View { - LazyVStack(spacing: .zero) { - newEmailView() + @ViewBuilder + private func content(data: [CNContact]) -> some View { + LazyVStack(spacing: .zero) { + newEmailView() - newSelectedContactsRows(data: data) + newSelectedContactsRows(data: data) - contactsRows(data: data) + contactsRows(data: data) + } } - } - @ViewBuilder - private func newEmailView() -> some View { - if !viewModel.searchText.isEmpty { - Checkbox( - isOn: .constant(viewModel.searchText.isEmail), - label: { - Row(viewModel.searchText, subtitle: "New member") { - Avatar(firstName: viewModel.searchText) + @ViewBuilder + private func newEmailView() -> some View { + if !viewModel.searchText.isEmpty { + Checkbox( + isOn: .constant(viewModel.searchText.isEmail), + label: { + Row(viewModel.searchText, subtitle: "New member") { + Avatar(firstName: viewModel.searchText) + } } - } - ) - .padding(.bottom, .small) + ) + .padding(.bottom, .small) + } } - } - @ViewBuilder - private func newSelectedContactsRows(data: [CNContact]) -> some View { - if !viewModel.lastSelectedEmails.isEmpty { - HStack(spacing: .zero) { - Text("Latest") - Spacer() + @ViewBuilder + private func newSelectedContactsRows(data: [CNContact]) -> some View { + if !viewModel.lastSelectedEmails.isEmpty { + HStack(spacing: .zero) { + Text("Latest") + Spacer() + } + .title3() + .onSurfaceSecondaryForeground() + .padding(.vertical, .xxSmall) + .paddingContent(.horizontal) + + ForEach(viewModel.lastSelectedEmails, id: \.self) { email in + if let contact = viewModel.getContactFromEmail(email: email, contacts: data) { + let emails = contact.emailAddresses + if !emails.isEmpty { + ForEach(emails, id: \.identifier) { email in + emailRow(email: email, contact: contact) + } + } + } else { + let isSelected = selectedEmails.contains(email) + Checkbox( + isOn: Binding( + get: { isSelected }, + set: { _ in onContactClick(email: email) } + ), + label: { + Row(email) { + Avatar(firstName: email) + } + } + ) + } + } } - .title3() - .onSurfaceMediumEmphasisForegroundColor() - .padding(.vertical, .xxSmall) - .paddingContent(.horizontal) + } + + @ViewBuilder + private func contactsRows(data: [CNContact]) -> some View { + if !data.isEmpty { + HStack(spacing: .zero) { + Text("Contacts") + Spacer() + } + .title3() + .onSurfaceSecondaryForeground() + .padding(.vertical, .xxSmall) + .paddingContent(.horizontal) + .padding(.top, viewModel.lastSelectedEmails.isEmpty ? .zero : .small) - ForEach(viewModel.lastSelectedEmails, id: \.self) { email in - if let contact = viewModel.getContactFromEmail(email: email, contacts: data) { + ForEach(data, id: \.identifier) { contact in let emails = contact.emailAddresses if !emails.isEmpty { ForEach(emails, id: \.identifier) { email in emailRow(email: email, contact: contact) } } - } else { - let isSelected = selectedEmails.contains(email) - Checkbox( - isOn: Binding( - get: { isSelected }, - set: { _ in onContactClick(email: email) } - ), - label: { - Row(email) { - Avatar(firstName: email) - } - } - ) } } } - } - @ViewBuilder - private func contactsRows(data: [CNContact]) -> some View { - if !data.isEmpty { - HStack(spacing: .zero) { - Text("Contacts") - Spacer() - } - .title3() - .onSurfaceMediumEmphasisForegroundColor() - .padding(.vertical, .xxSmall) - .paddingContent(.horizontal) - .padding(.top, viewModel.lastSelectedEmails.isEmpty ? .zero : .small) - - ForEach(data, id: \.identifier) { contact in - let emails = contact.emailAddresses - if !emails.isEmpty { - ForEach(emails, id: \.identifier) { email in - emailRow(email: email, contact: contact) - } - } - } - } - } + @ViewBuilder + private func emailRow(email: CNLabeledValue, contact: CNContact) -> some View { + let email = email.value as String + let isSelected = selectedEmails.contains(email) + #if os(iOS) + if let avatarThumbnailData = contact.thumbnailImageData, let avatarThumbnail = UIImage(data: avatarThumbnailData) { + Checkbox(isOn: Binding( + get: { isSelected }, + set: { _ in onContactClick(email: email) } + ), label: { + Row(contact.givenName + " " + contact.familyName, subtitle: email) { + Avatar(firstName: contact.givenName, lastName: contact.familyName, avatar: Image(uiImage: avatarThumbnail)) + } - @ViewBuilder - private func emailRow(email: CNLabeledValue, contact: CNContact) -> some View { - let email = email.value as String - let isSelected = selectedEmails.contains(email) - #if os(iOS) - if let avatarThumbnailData = contact.thumbnailImageData, let avatarThumbnail = UIImage(data: avatarThumbnailData) { - Checkbox(isOn: Binding( - get: { isSelected }, - set: { _ in onContactClick(email: email) } - ), label: { - Row(contact.givenName + " " + contact.familyName, subtitle: email) { - Avatar(firstName: contact.givenName, lastName: contact.familyName, avatar: Image(uiImage: avatarThumbnail)) - } + }) + } else { + Checkbox(isOn: Binding( + get: { isSelected }, + set: { _ in onContactClick(email: email) } + ), label: { + Row(contact.givenName + " " + contact.familyName, subtitle: email) { + Avatar(firstName: contact.givenName, lastName: contact.familyName) + } - }) - } else { + }) + } + #else Checkbox(isOn: Binding( get: { isSelected }, set: { _ in onContactClick(email: email) } @@ -174,59 +188,52 @@ public struct EmailPickerView: View { } }) - } - #else - Checkbox(isOn: Binding( - get: { isSelected }, - set: { _ in onContactClick(email: email) } - ), label: { - Row(contact.givenName + " " + contact.familyName, subtitle: email) { - Avatar(firstName: contact.givenName, lastName: contact.familyName) - } + #endif + } - }) - #endif - } + private func onDoneAction() { + if viewModel.searchText.isEmail { + if !selection.contains(viewModel.searchText) { + selection.append(viewModel.searchText) + } - private func onDoneAction() { - if viewModel.searchText.isEmail { - if !selection.contains(viewModel.searchText) { - selection.append(viewModel.searchText) + if !viewModel.lastSelectedEmails.contains(viewModel.searchText) { + viewModel.lastSelectedEmails.append(viewModel.searchText) + } } - if !viewModel.lastSelectedEmails.contains(viewModel.searchText) { - viewModel.lastSelectedEmails.append(viewModel.searchText) - } - } + if !selectedEmails.isEmpty { + for email in selectedEmails where !selection.contains(email) { + selection.append(email) + } - if !selectedEmails.isEmpty { - for email in selectedEmails where !selection.contains(email) { - selection.append(email) + for email in selectedEmails where !viewModel.lastSelectedEmails.contains(email) { + viewModel.lastSelectedEmails.append(email) + } } - for email in selectedEmails where !viewModel.lastSelectedEmails.contains(email) { - viewModel.lastSelectedEmails.append(email) - } + dismiss() } - dismiss() - } - - private func onContactClick(email: String) { - let isSelected = selectedEmails.contains(email) - if isSelected { - selectedEmails.remove(email) - } else { - selectedEmails.append(email) + private func onContactClick(email: String) { + let isSelected = selectedEmails.contains(email) + if isSelected { + selectedEmails.remove(email) + } else { + selectedEmails.append(email) + } } - } - @ViewBuilder - private func placeholder() -> some View { - LoaderOverlayView() + @ViewBuilder + private func placeholder() -> some View { + #if os(watchOS) + ProgressView() + #else + LoaderOverlayView() + #endif + } } -} - +#endif // struct ContactsPickerView_Previews: PreviewProvider { // static var previews: some View { // EmailPickerView() diff --git a/Sources/OversizeContactsKit/ContactsPicker/EmailPickerViewModel.swift b/Sources/OversizeContactsKit/ContactsPicker/EmailPickerViewModel.swift index 8bbe16b..243dca3 100644 --- a/Sources/OversizeContactsKit/ContactsPicker/EmailPickerViewModel.swift +++ b/Sources/OversizeContactsKit/ContactsPicker/EmailPickerViewModel.swift @@ -3,29 +3,32 @@ // EmailPickerViewModel.swift // -import Contacts +#if canImport(Contacts) + @preconcurrency import Contacts +#endif import Factory import OversizeContactsService import OversizeCore import OversizeModels import SwiftUI -@MainActor -class EmailPickerViewModel: ObservableObject { - @Injected(\.contactsService) private var contactsService: ContactsService - @Published var state = ContactsPickerViewModelState.initial - @Published var searchText: String = .init() +#if !os(tvOS) + @MainActor + class EmailPickerViewModel: ObservableObject { + @Injected(\.contactsService) private var contactsService: ContactsService + @Published var state = ContactsPickerViewModelState.initial + @Published var searchText: String = .init() - @AppStorage("AppState.LastSelectedEmails") var lastSelectedEmails: [String] = .init() + @AppStorage("AppState.LastSelectedEmails") var lastSelectedEmails: [String] = .init() + + func fetchData() async { + state = .loading + let status = await contactsService.requestAccess() + switch status { + case .success: - func fetchData() async { - state = .loading - let status = await contactsService.requestAccess() - switch status { - case .success: - do { let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactThumbnailImageDataKey] - let result = try await contactsService.fetchContacts(keysToFetch: keys as [CNKeyDescriptor]) + let result = await contactsService.fetchContacts(keysToFetch: keys as [CNKeyDescriptor]) switch result { case let .success(data): log("✅ CNContact fetched") @@ -34,30 +37,29 @@ class EmailPickerViewModel: ObservableObject { log("❌ CNContact not fetched (\(error.title))") state = .error(error) } - } catch { - state = .error(.contacts(type: .unknown)) + + case let .failure(error): + state = .error(error) } - case let .failure(error): - state = .error(error) } - } - func getContactFromEmail(email: String, contacts: [CNContact]) -> CNContact? { - for contact in contacts where !contact.emailAddresses.isEmpty { - for emailAddress in contact.emailAddresses { - let emailAddressString = emailAddress.value as String - if emailAddressString == email { - return contact + func getContactFromEmail(email: String, contacts: [CNContact]) -> CNContact? { + for contact in contacts where !contact.emailAddresses.isEmpty { + for emailAddress in contact.emailAddresses { + let emailAddressString = emailAddress.value as String + if emailAddressString == email { + return contact + } } } + return nil } - return nil } -} - -enum ContactsPickerViewModelState { - case initial - case loading - case result([CNContact]) - case error(AppError) -} + + enum ContactsPickerViewModelState { + case initial + case loading + case result([CNContact]) + case error(AppError) + } +#endif diff --git a/Sources/OversizeKit/AdsKit/AdView.swift b/Sources/OversizeKit/AdsKit/AdView.swift index aeb1c44..b2e4836 100644 --- a/Sources/OversizeKit/AdsKit/AdView.swift +++ b/Sources/OversizeKit/AdsKit/AdView.swift @@ -30,6 +30,7 @@ public struct AdView: View { await viewModel.fetchAd() } } + case let .result(appAd): #if os(iOS) Surface { @@ -79,7 +80,7 @@ public struct AdView: View { HStack { Text(appAd.title) .subheadline(.bold) - .onSurfaceHighEmphasisForegroundColor() + .onSurfacePrimaryForeground() Bage(color: .warning) { Text("Our app") @@ -89,7 +90,7 @@ public struct AdView: View { Text(appAd.description) .subheadline() - .onSurfaceMediumEmphasisForegroundColor() + .onSurfaceSecondaryForeground() } .padding(.leading, .xSmall) @@ -100,9 +101,11 @@ public struct AdView: View { } .buttonStyle(.tertiary) .controlBorderShape(.capsule) - .controlSize(.small) .padding(.trailing, .xxxSmall) .loading(isShowProduct) + #if !os(tvOS) + .controlSize(.small) + #endif } } } diff --git a/Sources/OversizeKit/LauncherKit/Launcher.swift b/Sources/OversizeKit/LauncherKit/Launcher.swift index 3b87866..51a704f 100644 --- a/Sources/OversizeKit/LauncherKit/Launcher.swift +++ b/Sources/OversizeKit/LauncherKit/Launcher.swift @@ -14,10 +14,8 @@ public struct Launcher: View { private var onboarding: Onboarding? private let content: Content - private var transaction = Transaction() @StateObject private var viewModel = LauncherViewModel() - @State private var blurRadius: CGFloat = 0 public init(@ViewBuilder content: () -> Content) { self.content = content() @@ -39,40 +37,43 @@ public struct Launcher: View { .appLaunchCover(item: $viewModel.activeFullScreenSheet) { fullScreenCover(sheet: $0) .systemServices() + #if os(macOS) + .frame(width: 840, height: 672) + // .interactiveDismissDisabled(!viewModel.appStateService.isCompletedOnbarding) + #endif } - .onChange(of: viewModel.appStateService.isCompletedOnbarding) { isCompletedOnbarding in + .onChange(of: viewModel.appStateService.isCompletedOnbarding) { _, isCompletedOnbarding in if isCompletedOnbarding, !viewModel.isPremium { viewModel.setPayWall() } else { viewModel.activeFullScreenSheet = nil } } - .onChange(of: scenePhase, perform: { value in + .onChange(of: scenePhase) { _, value in switch value { - case .active, .inactive: - break case .background: viewModel.authState = .locked viewModel.pinCodeField = "" - @unknown default: - log("unknown") + default: + break } - }) + } } + @ViewBuilder var contentView: some View { - Group { - if viewModel.isShowSplashScreen { - SplashScreen() - } else if viewModel.isShowLockscreen { - lockscreenView - } else { - content - .onAppear { - viewModel.reviewService.launchEvent() - viewModel.launcherSheetsChek() + if viewModel.isShowSplashScreen { + SplashScreen() + } else if viewModel.isShowLockscreen { + lockscreenView + } else { + content + .onAppear { + Task { @MainActor in + await viewModel.reviewService.launchEvent() } - } + viewModel.launcherSheetsChek() + } } } @@ -87,14 +88,15 @@ public struct Launcher: View { } private var lockscreenView: some View { - LockscreenView(pinCode: $viewModel.pinCodeField, - state: $viewModel.authState, - title: L10n.Security.enterPINCode, - errorText: L10n.Security.invalidPIN, - pinCodeEnabled: viewModel.settingsService.pinCodeEnabend, - biometricEnabled: viewModel.settingsService.biometricEnabled, - biometricType: viewModel.biometricService.biometricType) - { + LockscreenView( + pinCode: $viewModel.pinCodeField, + state: $viewModel.authState, + title: L10n.Security.enterPINCode, + errorText: L10n.Security.invalidPIN, + pinCodeEnabled: viewModel.settingsService.pinCodeEnabend, + biometricEnabled: viewModel.settingsService.biometricEnabled, + biometricType: viewModel.biometricService.biometricType + ) { viewModel.checkPassword() } biometricAction: { viewModel.appLockValidation() @@ -125,7 +127,6 @@ public extension View { Launcher { self } - .systemServices() } func appLaunch(@ViewBuilder onboarding: @escaping () -> some View) -> some View { @@ -133,7 +134,6 @@ public extension View { self } .onboarding(onboarding: onboarding) - .systemServices() } } @@ -142,8 +142,7 @@ private extension View { item: Binding, onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping (Item) -> some View ) -> some View where Item: Identifiable { #if os(macOS) - interactiveDismissDisabled() - .sheet(item: item, onDismiss: onDismiss, content: content) + sheet(item: item, onDismiss: onDismiss, content: content) #else fullScreenCover(item: item, onDismiss: onDismiss, content: content) #endif diff --git a/Sources/OversizeKit/LauncherKit/LauncherViewModel.swift b/Sources/OversizeKit/LauncherKit/LauncherViewModel.swift index a8e4e0c..8899600 100644 --- a/Sources/OversizeKit/LauncherKit/LauncherViewModel.swift +++ b/Sources/OversizeKit/LauncherKit/LauncherViewModel.swift @@ -49,7 +49,7 @@ public final class LauncherViewModel: ObservableObject { } extension LauncherViewModel { - enum FullScreenSheet: Identifiable, Equatable { + enum FullScreenSheet: Identifiable, Equatable, Sendable { case onboarding case payWall case rate @@ -124,7 +124,9 @@ public extension LauncherViewModel { func setPayWall() { activeFullScreenSheet = nil delay(time: 0.2) { - self.activeFullScreenSheet = .payWall + Task { @MainActor in + self.activeFullScreenSheet = .payWall + } } } diff --git a/Sources/OversizeKit/LauncherKit/RateAppScreen.swift b/Sources/OversizeKit/LauncherKit/RateAppScreen.swift index a762121..b859b0d 100644 --- a/Sources/OversizeKit/LauncherKit/RateAppScreen.swift +++ b/Sources/OversizeKit/LauncherKit/RateAppScreen.swift @@ -17,7 +17,7 @@ struct RateAppScreen: View { VStack { Text("If you love, evaluate)") .largeTitle(.bold) - .onSurfaceHighEmphasisForegroundColor() + .onSurfacePrimaryForeground() Spacer() @@ -29,14 +29,14 @@ struct RateAppScreen: View { Text((Info.app.name ?? "App") + " is developed only one person, and your assessment would very much drop in") .title3() - .onSurfaceHighEmphasisForegroundColor() + .onSurfacePrimaryForeground() Spacer() if let reviewUrl = Info.url.appStoreReview { HStack(spacing: .large) { Link(destination: reviewUrl) { - IconDeprecated(.thumbsUp, color: .onPrimaryHighEmphasis) + IconDeprecated(.thumbsUp, color: .onPrimary) } .buttonStyle(.primary(infinityWidth: false)) .accent() @@ -49,13 +49,15 @@ struct RateAppScreen: View { reviewService.estimate(goodRating: false) dismiss() } label: { - IconDeprecated(.thumbsDown, color: .onSurfaceHighEmphasis) + IconDeprecated(.thumbsDown, color: .onSurfacePrimary) } .buttonStyle(.secondary(infinityWidth: false)) } .controlBorderShape(.capsule) .elevation(.z3) - .controlSize(.large) + #if !os(tvOS) + .controlSize(.large) + #endif } } .multilineTextAlignment(.center) @@ -65,12 +67,14 @@ struct RateAppScreen: View { reviewService.rewiewBunnerClosed() dismiss() } label: { - IconDeprecated(.xMini, color: .onSurfaceHighEmphasis) + IconDeprecated(.xMini, color: .onSurfacePrimary) } .buttonStyle(.tertiary(infinityWidth: false)) - .controlSize(.mini) .controlBorderShape(.capsule) .padding(.medium) + #if !os(tvOS) + .controlSize(.mini) + #endif } // reviewService.rewiewBunnerClosed() } diff --git a/Sources/OversizeKit/LockscreenKit/LockscreenView.swift b/Sources/OversizeKit/LockscreenKit/LockscreenView.swift index 0833b73..cfb01ef 100644 --- a/Sources/OversizeKit/LockscreenKit/LockscreenView.swift +++ b/Sources/OversizeKit/LockscreenKit/LockscreenView.swift @@ -87,7 +87,7 @@ public struct LockscreenView: View { public var body: some View { content() .background(Color.surfacePrimary.ignoresSafeArea(.all)) - .onChange(of: scenePhase) { phase in + .onChange(of: scenePhase) { _, phase in switch phase { case .active: if state == .locked, biometricEnabled { @@ -125,14 +125,14 @@ public struct LockscreenView: View { #else Text(biometricType.rawValue) .title2(.bold) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) #endif } else { Text(biometricType.rawValue) .title2(.semibold) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) } Spacer() @@ -165,7 +165,7 @@ public struct LockscreenView: View { Text(title ?? "") .title2(.bold) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) .opacity(title != nil ? 1 : 0) Spacer() @@ -180,7 +180,7 @@ public struct LockscreenView: View { Text(errorText ?? "") .subheadline() - .errorForegroundColor() + .errorForeground() .opacity(state == .error ? 1 : 0) if isShowTitle { @@ -247,18 +247,18 @@ public struct LockscreenView: View { EmptyView() case .touchID: Image(systemName: "touchid") - .foregroundColor(Color.onBackgroundHighEmphasis) + .foregroundColor(Color.onBackgroundPrimary) .font(.system(size: 26)) .frame(width: 24, height: 24, alignment: .center) case .faceID: Image(systemName: "faceid") .font(.system(size: 26)) - .foregroundColor(Color.onBackgroundHighEmphasis) + .foregroundColor(Color.onBackgroundPrimary) .frame(width: 24, height: 24, alignment: .center) case .opticID: Image(systemName: "opticid") .font(.system(size: 26)) - .foregroundColor(Color.onBackgroundHighEmphasis) + .foregroundColor(Color.onBackgroundPrimary) .frame(width: 24, height: 24, alignment: .center) } } @@ -284,7 +284,6 @@ public struct LockscreenView: View { .frame(width: 12, height: 12) .offset(x: leftOffset) // .animation(Animation.easeInOut(duration: 1).delay(0.2 * Double(number))) - .scaleEffect(shouldAnimate ? 0.5 : 1) .animation(Animation.easeInOut(duration: 0.5) .repeatForever() @@ -332,7 +331,7 @@ public struct NumpadButtonStyle: ButtonStyle { public func makeBody(configuration: Self.Configuration) -> some View { configuration.label .title2() - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) .frame(width: 72, height: 72, alignment: .center) .background( Circle() diff --git a/Sources/OversizeKit/SettingsKit/SettingsRouter/ResolveRouter.swift b/Sources/OversizeKit/SettingsKit/SettingsRouter/ResolveRouter.swift new file mode 100644 index 0000000..0da5cbe --- /dev/null +++ b/Sources/OversizeKit/SettingsKit/SettingsRouter/ResolveRouter.swift @@ -0,0 +1,65 @@ +// +// Copyright © 2024 Alexander Romanov +// ResolveRouter.swift, created on 16.05.2024 +// + +import Foundation +import OversizeComponents +import OversizeLocalizable +import OversizeNetwork +import OversizeRouter +import SwiftUI + +extension SettingsScreen: @preconcurrency RoutableView { + @MainActor @ViewBuilder + public func view() -> some View { + switch self { + case .premium: + StoreView() + case .soundAndVibration: + SoundsAndVibrationsSettingsView() + case .appearance: + AppearanceSettingView() + case .sync: + iCloudSettingsView() + case let .premiumFeature(feature: feature): + StoreFeatureDetailView(selection: feature) + case .about: + AboutView() + case .feedback: + FeedbackView() + case .ourResorses: + OurResorsesView() + case .support: + SupportView() + case .border: + BorderSettingView() + case .font: + FontSettingView() + case .radius: + RadiusSettingView() + case .notifications: + NotificationsSettingsView() + case .setPINCode: + SetPINCodeView(action: .set) + case .updatePINCode: + SetPINCodeView(action: .update) + case .security: + SecuritySettingsView() + case let .offer(event: event): + StoreSpecialOfferView(event: event) + case let .webView(url: url): + WebView(url: url) + case let .sendMail(to: to, subject: subject, content: content): + #if os(iOS) + MailView( + to: to, + subject: subject, + content: content + ) + #else + EmptyView() + #endif + } + } +} diff --git a/Sources/OversizeKit/SettingsKit/SettingsRouter/Screens.swift b/Sources/OversizeKit/SettingsKit/SettingsRouter/Screens.swift new file mode 100644 index 0000000..d7d20db --- /dev/null +++ b/Sources/OversizeKit/SettingsKit/SettingsRouter/Screens.swift @@ -0,0 +1,115 @@ +// +// Copyright © 2024 Alexander Romanov +// SettingsScreen.swift, created on 15.04.2024 +// + +import OversizeComponents +import OversizeModels +import OversizeNetwork +import OversizeRouter +import SwiftUI + +public enum SettingsScreen: Routable { + case premium + case premiumFeature(feature: PlistConfiguration.Store.StoreFeature) + case soundAndVibration + case appearance + case sync + case about + case feedback + case ourResorses + case support + case border + case font + case radius + case notifications + case setPINCode + case updatePINCode + case security + case offer(event: Components.Schemas.SpecialOffer) + case webView(url: URL) + case sendMail(to: String, subject: String, content: String) +} + +public extension SettingsScreen { + var id: String { + switch self { + case .premium: + "premium" + case .premiumFeature: + "premiumFeature" + case .soundAndVibration: + "soundAndVibration" + case .appearance: + "appearance" + case .sync: + "sync" + case .about: + "about" + case .feedback: + "feedback" + case .webView: + "webView" + case .ourResorses: + "ourResorses" + case .support: + "support" + case .border: + "border" + case .font: + "font" + case .radius: + "radius" + case .setPINCode: + "setPINCode" + case .updatePINCode: + "updatePINCode" + case .security: + "security" + case .offer: + "offer" + case .sendMail: + "sendMail" + case .notifications: + "notifications" + } + } +} + +// public struct SettingsNavigateAction { +// public typealias Action = (SettingsNavigationType) -> Void +// public let action: Action +// public func callAsFunction(_ navigationType: SettingsNavigationType) { +// action(navigationType) +// } +// } + +// public enum SettingsNavigationType { +// case move(SettingsScreen) +// case backToRoot +// case back(Int = 1) +// case present(_ sheet: SettingsScreen, detents: Set = [.large], indicator: Visibility = .hidden, dismissDisabled: Bool = false) +// case dismiss +// case dismissSheet +// case dismissFullScreenCover +// case dismissDisabled(_ isDismissDisabled: Bool = true) +// case presentHUD(_ text: String, type: HUDMessageType) +// } + +// +// public struct SettingsNavigateEnvironmentKey: EnvironmentKey { +// public static var defaultValue: SettingsNavigateAction = .init(action: { _ in }) +// } +// +// public extension EnvironmentValues { +// var settingsNavigate: SettingsNavigateAction { +// get { self[SettingsNavigateEnvironmentKey.self] } +// set { self[SettingsNavigateEnvironmentKey.self] = newValue } +// } +// } +// +// public extension View { +// func onSettingsNavigate(_ action: @escaping SettingsNavigateAction.Action) -> some View { +// environment(\.settingsNavigate, SettingsNavigateAction(action: action)) +// } +// } diff --git a/Sources/OversizeKit/SettingsKit/SettingsRouter/SettingsRouting.swift b/Sources/OversizeKit/SettingsKit/SettingsRouter/SettingsRouting.swift new file mode 100644 index 0000000..11aca22 --- /dev/null +++ b/Sources/OversizeKit/SettingsKit/SettingsRouter/SettingsRouting.swift @@ -0,0 +1,20 @@ +// +// Copyright © 2024 Alexander Romanov +// SettingsRouting.swift, created on 10.05.2024 +// + +import OversizeRouter +import SwiftUI + +public struct SettingsRoutingView: View { + public init() {} + + public var body: some View { + RoutingView { + SettingsView { + EmptyView() + } + } + .systemServices() + } +} diff --git a/Sources/OversizeKit/SettingsKit/Views/About/About/AboutView.swift b/Sources/OversizeKit/SettingsKit/Views/About/About/AboutView.swift index eaf732b..d852126 100644 --- a/Sources/OversizeKit/SettingsKit/Views/About/About/AboutView.swift +++ b/Sources/OversizeKit/SettingsKit/Views/About/About/AboutView.swift @@ -8,223 +8,213 @@ import OversizeComponents import OversizeCore import OversizeLocalizable import OversizeResources +import OversizeRouter import OversizeServices import OversizeUI import SwiftUI // swiftlint:disable all -#if os(iOS) +#if canImport(MessageUI) import MessageUI +#endif - public struct AboutView: View { - @Environment(\.verticalSizeClass) private var verticalSizeClass - @Environment(\.isPortrait) var isPortrait - @Environment(\.presentationMode) var presentationMode - @Environment(\.screenSize) var screenSize - @Environment(\.iconStyle) var iconStyle: IconStyle +public struct AboutView: View { + @Environment(Router.self) var router + @Environment(\.screenSize) var screenSize + @Environment(\.iconStyle) var iconStyle: IconStyle - @StateObject var viewModel: AboutViewModel + @StateObject var viewModel: AboutViewModel - public init() { - _viewModel = StateObject(wrappedValue: AboutViewModel()) - } + public init() { + _viewModel = StateObject(wrappedValue: AboutViewModel()) + } - @State var offset: CGFloat = 0 + @State var offset: CGFloat = 0 - @State var isSharePresented = false - @State private var isShowMail = false + @State var isSharePresented = false + @State private var isShowMail = false - @State var isShowPrivacy = false - @State var isShowTerms = false + @State private var isPresentStoreProduct: Bool = false - @State private var isPresentStoreProduct: Bool = false + var isLargeScreen: Bool { + screenSize.width < 500 ? false : true + } - var isLargeScreen: Bool { - screenSize.width < 500 ? false : true + var oppacity: CGFloat { + if offset < 0 { + return 1 + } else if offset > 500 { + return 0 + } else { + return Double(1 / (offset * 0.01)) } + } - var oppacity: CGFloat { - if offset < 0 { - return 1 - } else if offset > 500 { - return 0 - } else { - return Double(1 / (offset * 0.01)) - } + var blur: CGFloat { + if offset < 0 { + return 0 + } else { + return Double(offset * 0.05) } + } - var blur: CGFloat { - if offset < 0 { - return 0 - } else { - return Double(offset * 0.05) - } - } + #if os(iOS) + let scale = UIScreen.main.scale + #else + let scale: CGFloat = 2 + #endif + public var body: some View { #if os(iOS) - let scale = UIScreen.main.scale + Page(L10n.Settings.about) { + list + .surfaceContentRowMargins() + .task { + await viewModel.fetchApps() + } + } + .backgroundSecondary() + #else - let scale: CGFloat = 2 + list + .navigationTitle(L10n.Settings.about) #endif + } - public var body: some View { - #if os(iOS) - PageView(L10n.Settings.about, onOffsetChanged: { offset = $0 }) { - list - .surfaceContentRowMargins() - .task { - await viewModel.fetchApps() - } - } - .leadingBar { - /* - if !isPortrait, verticalSizeClass == .regular { - EmptyView() - } else { - BarButton(.back) - } - */ - BarButton(.back) - } - .backgroundSecondary() - - #else - list - .navigationTitle(L10n.Settings.about) - #endif - } + private func appLinks() -> some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(alignment: .top, spacing: Space.small) { + switch viewModel.state { + case .initial, .loading: + ForEach(0 ... 6, id: \.self) { _ in + RoundedRectangle(cornerRadius: .large, style: .continuous) + .fillSurfaceSecondary() + .frame(width: 74, height: 74) + } + case let .result(apps, _): + ForEach(apps, id: \.appStoreId) { app in + Button { + isPresentStoreProduct = true + } label: { + VStack(spacing: .xSmall) { + CachedAsyncImage(url: URL(string: "https://cdn.oversize.design/assets/apps/" + app.address + "/icon.png"), urlCache: .imageCache, content: { + $0 + .resizable() + .frame(width: 74, height: 74) + .mask(RoundedRectangle(cornerRadius: .large, + style: .continuous)) + .overlay( + RoundedRectangle(cornerRadius: 16, + style: .continuous) + .stroke(lineWidth: 1) + .opacity(0.15) + ) + + }, placeholder: { + RoundedRectangle(cornerRadius: .large, style: .continuous) + .fillSurfaceSecondary() + .frame(width: 74, height: 74) + }) - private func appLinks() -> some View { - ScrollView(.horizontal, showsIndicators: false) { - HStack(alignment: .top, spacing: Space.small) { - switch viewModel.state { - case .initial, .loading: - ForEach(0 ... 6, id: \.self) { _ in - RoundedRectangle(cornerRadius: .large, style: .continuous) - .fillSurfaceSecondary() - .frame(width: 74, height: 74) - } - case let .result(apps, _): - ForEach(apps, id: \.appStoreId) { app in - Button { - isPresentStoreProduct = true - } label: { - VStack(spacing: .xSmall) { - CachedAsyncImage(url: URL(string: "https://cdn.oversize.design/assets/apps/" + app.address + "/icon.png"), urlCache: .imageCache, content: { - $0 - .resizable() - .frame(width: 74, height: 74) - .mask(RoundedRectangle(cornerRadius: .large, - style: .continuous)) - .overlay( - RoundedRectangle(cornerRadius: 16, - style: .continuous) - .stroke(lineWidth: 1) - .opacity(0.15) - ) - - }, placeholder: { - RoundedRectangle(cornerRadius: .large, style: .continuous) - .fillSurfaceSecondary() - .frame(width: 74, height: 74) - }) - - Text(app.name) - .caption(.medium) - .multilineTextAlignment(.center) - .foregroundColor(.onSurfaceMediumEmphasis) - .frame(width: 74) - } + Text(app.name) + .caption(.medium) + .multilineTextAlignment(.center) + .foregroundColor(.onSurfaceSecondary) + .frame(width: 74) } - .buttonStyle(.scale) - .appStoreOverlay(isPresent: $isPresentStoreProduct, appId: app.appStoreId) } - case .error: - EmptyView() + .buttonStyle(.scale) + #if os(iOS) + .appStoreOverlay(isPresent: $isPresentStoreProduct, appId: app.appStoreId) + #endif } + case .error: + EmptyView() + } - if let authorAllApps = Info.url.developerAllApps { - VStack(spacing: .xSmall) { - Link(destination: authorAllApps) { - ZStack { - RoundedRectangle(cornerRadius: 16, style: .continuous) - .foregroundColor(.surfaceSecondary) - .frame(width: 74, height: 74) + if let authorAllApps = Info.url.developerAllApps { + VStack(spacing: .xSmall) { + Link(destination: authorAllApps) { + ZStack { + RoundedRectangle(cornerRadius: 16, style: .continuous) + .foregroundColor(.surfaceSecondary) + .frame(width: 74, height: 74) - IconDeprecated(.externalLink) - } + IconDeprecated(.externalLink) } - - Text("All apps") - .caption(.medium) - .multilineTextAlignment(.center) - .foregroundColor(.onSurfaceMediumEmphasis) - .frame(width: 74) } + + Text("All apps") + .caption(.medium) + .multilineTextAlignment(.center) + .foregroundColor(.onSurfaceSecondary) + .frame(width: 74) } + } - }.padding(.horizontal, .medium) - } - .padding(.bottom, 16) + }.padding(.horizontal, .medium) } + .padding(.bottom, 16) + } - var list: some View { - VStack(spacing: .zero) { - image - .padding(.top, isLargeScreen ? -70 : 0) - - if isLargeScreen { - HStack(spacing: .xxSmall) { - Image.Brands.Oversize.fill - .resizable() - .renderingMode(.template) - .foregroundColor(Color.onSurfaceHighEmphasis) - .frame(width: 32, height: 32) - - Resource.overszieTextLogo - .renderingMode(.template) - .foregroundColor(Color.onSurfaceHighEmphasis) - } - .padding(.top, -40) - .padding(.bottom, .xSmall) - - } else { - VStack(spacing: .xxSmall) { - Image.Brands.Oversize.fill - .resizable() - .renderingMode(.template) - .foregroundColor(Color.onSurfaceHighEmphasis) - .frame(width: 48, height: 48) + var list: some View { + VStack(spacing: .zero) { + image + .padding(.top, isLargeScreen ? -70 : 0) + + if isLargeScreen { + HStack(spacing: .xxSmall) { + Image.Brands.Oversize.fill + .resizable() + .renderingMode(.template) + .foregroundColor(Color.onSurfacePrimary) + .frame(width: 32, height: 32) + + Resource.overszieTextLogo + .renderingMode(.template) + .foregroundColor(Color.onSurfacePrimary) + } + .padding(.top, -40) + .padding(.bottom, .xSmall) - Resource.overszieTextLogo - .renderingMode(.template) - .foregroundColor(Color.onSurfaceHighEmphasis) - } - .padding(.top, 42) - .padding(.bottom, .medium) + } else { + VStack(spacing: .xxSmall) { + Image.Brands.Oversize.fill + .resizable() + .renderingMode(.template) + .foregroundColor(Color.onSurfacePrimary) + .frame(width: 48, height: 48) + + Resource.overszieTextLogo + .renderingMode(.template) + .foregroundColor(Color.onSurfacePrimary) } + .padding(.top, 42) + .padding(.bottom, .medium) + } - Text("The Oversize project is made with love and attention to the design of the forces of only one person") - .title3(.semibold) - .foregroundColor(.onBackgroundHighEmphasis) - .padding(.horizontal, isLargeScreen ? 72 : 52) - .padding(.bottom, .large) - .multilineTextAlignment(.center) - - soclal - .padding(.bottom, .small) - - SectionView { - VStack(spacing: .zero) { - if let reviewUrl = Info.url.appStoreReview, let id = Info.app.appStoreID, !id.isEmpty, let appName = Info.app.name { - Link(destination: reviewUrl) { - Row("Rate \(appName) on App Store") { - rateSettingsIcon.icon() - } + Text("The Oversize project is made with love and attention to the design of the forces of only one person") + .title3(.semibold) + .foregroundColor(.onBackgroundPrimary) + .padding(.horizontal, isLargeScreen ? 72 : 52) + .padding(.bottom, .large) + .multilineTextAlignment(.center) + + soclal + .padding(.bottom, .small) + + SectionView { + VStack(spacing: .zero) { + if let reviewUrl = Info.url.appStoreReview, let id = Info.app.appStoreID, !id.isEmpty, let appName = Info.app.name { + Link(destination: reviewUrl) { + Row("Rate \(appName) on App Store") { + rateSettingsIcon.icon() } - .buttonStyle(.row) } + .buttonStyle(.row) + } + #if os(iOS) if MFMailComposeViewController.canSendMail(), let mail = Info.links?.company.email, let appVersion = Info.app.verstion, @@ -247,73 +237,67 @@ import SwiftUI MailView(to: mail, subject: subject, content: contentPreText) } } + #endif - #if os(iOS) - if let shareUrl = Info.url.appInstallShare, let id = Info.app.appStoreID, !id.isEmpty { - Row(L10n.Settings.shareApplication) { - isSharePresented.toggle() - } leading: { - shareSettingsIcon.icon() - } - .sheet(isPresented: $isSharePresented) { - ActivityViewController(activityItems: [shareUrl]) - .presentationDetents([.medium, .large]) - } + #if os(iOS) + if let shareUrl = Info.url.appInstallShare, let id = Info.app.appStoreID, !id.isEmpty { + Row(L10n.Settings.shareApplication) { + isSharePresented.toggle() + } leading: { + shareSettingsIcon.icon() + } + .sheet(isPresented: $isSharePresented) { + ActivityViewController(activityItems: [shareUrl]) + .presentationDetents([.medium, .large]) } - #endif - } - } - - SectionView { - VStack(spacing: .zero) { - HStack { - Text(L10n.About.otherApplications.uppercased()) - .caption(true) - .foregroundColor(.onSurfaceMediumEmphasis) - .padding(.top, 12) - .padding(.leading, 26) - .padding(.bottom, 18) - Spacer() } + #endif + } + } - appLinks() + SectionView { + VStack(spacing: .zero) { + HStack { + Text(L10n.About.otherApplications.uppercased()) + .caption(true) + .foregroundColor(.onSurfaceSecondary) + .padding(.top, 12) + .padding(.leading, 26) + .padding(.bottom, 18) + Spacer() } + + appLinks() } + } - SectionView { - VStack(spacing: .zero) { - NavigationLink(destination: OurResorsesView()) { - Row("Our open resources") - .rowArrow() - } - .buttonStyle(.row) + SectionView { + VStack(spacing: .zero) { + Row("Our open resources") { + router.move(.ourResorses) + } + .rowArrow() - if let privacyUrl = Info.url.appPrivacyPolicyUrl { - Row(L10n.Store.privacyPolicy) { - isShowPrivacy.toggle() - } - .sheet(isPresented: $isShowPrivacy) { - WebView(url: privacyUrl) - } + if let privacyUrl = Info.url.appPrivacyPolicyUrl { + Row(L10n.Store.privacyPolicy) { + router.present(.webView(url: privacyUrl)) } + } - if let termsOfUde = Info.url.appTermsOfUseUrl { - Row(L10n.Store.termsOfUse) { - isShowTerms.toggle() - } - .sheet(isPresented: $isShowTerms) { - WebView(url: termsOfUde) - } + if let termsOfUde = Info.url.appTermsOfUseUrl { + Row(L10n.Store.termsOfUse) { + router.present(.webView(url: termsOfUde)) } } } - - footer } + + footer } + } - private var soclal: some View { - HStack(spacing: .small) { + private var soclal: some View { + HStack(spacing: .small) { // switch viewModel.state { // case .initial, .loading: // ForEach(0...6, id: \.self) { _ in @@ -349,203 +333,202 @@ import SwiftUI // EmptyView() // } - if let facebook = Info.url.companyFacebook { - Link(destination: facebook) { - // Surface { - HStack { - Spacer() - Image.Brands.Facebook.Circle.fill - .renderingMode(.template) - .foregroundColor(Color.onSurfaceMediumEmphasis) - Spacer() - } - // } + if let facebook = Info.url.companyFacebook { + Link(destination: facebook) { + // Surface { + HStack { + Spacer() + Image.Brands.Facebook.Circle.fill + .renderingMode(.template) + .foregroundColor(Color.onSurfaceSecondary) + Spacer() } + // } } + } - if let instagram = Info.url.companyInstagram { - Link(destination: instagram) { - // Surface { - HStack { - Spacer() - Image.Brands.Instagram.fill - .renderingMode(.template) - .foregroundColor(Color.onSurfaceMediumEmphasis) - Spacer() - } - // } + if let instagram = Info.url.companyInstagram { + Link(destination: instagram) { + // Surface { + HStack { + Spacer() + Image.Brands.Instagram.fill + .renderingMode(.template) + .foregroundColor(Color.onSurfaceSecondary) + Spacer() } + // } } + } - if let twitter = Info.url.companyTwitter { - Link(destination: twitter) { - // Surface { - HStack { - Spacer() - Image.Brands.xCom - .renderingMode(.template) - .foregroundColor(Color.onSurfaceMediumEmphasis) - Spacer() - } - // } + if let twitter = Info.url.companyTwitter { + Link(destination: twitter) { + // Surface { + HStack { + Spacer() + Image.Brands.xCom + .renderingMode(.template) + .foregroundColor(Color.onSurfaceSecondary) + Spacer() } + // } } + } - if let telegramUrl = Info.url.companyTelegram { - Link(destination: telegramUrl) { - // Surface { - HStack { - Spacer() - Image.Brands.Telegram.fill - .renderingMode(.template) - .foregroundColor(Color.onSurfaceMediumEmphasis) - Spacer() - } - // } + if let telegramUrl = Info.url.companyTelegram { + Link(destination: telegramUrl) { + // Surface { + HStack { + Spacer() + Image.Brands.Telegram.fill + .renderingMode(.template) + .foregroundColor(Color.onSurfaceSecondary) + Spacer() } + // } } + } - if let dribbble = Info.url.companyDribbble { - Link(destination: dribbble) { - // Surface { - HStack { - Spacer() - Image.Brands.Dribbble.fill - .renderingMode(.template) - .foregroundColor(Color.onSurfaceMediumEmphasis) - Spacer() - } - // } + if let dribbble = Info.url.companyDribbble { + Link(destination: dribbble) { + // Surface { + HStack { + Spacer() + Image.Brands.Dribbble.fill + .renderingMode(.template) + .foregroundColor(Color.onSurfaceSecondary) + Spacer() } + // } } } - .buttonStyle(.scale) - .frame(maxWidth: 300) - .controlMargin(.xSmall) - .paddingContent(.horizontal) } + .buttonStyle(.scale) + .frame(maxWidth: 300) + .controlMargin(.xSmall) + .paddingContent(.horizontal) + } - private var image: some View { - HStack { - VStack(alignment: .center) { - ZStack(alignment: .top) { - CachedAsyncImage(url: URL(string: "https://cdn.oversize.design/assets/illustrations/scenes/about-layer3.png"), urlCache: .imageCache, scale: scale) { - $0 - .resizable() - .scaledToFit() - .blur(radius: blur) - } placeholder: { - Rectangle() - .fillSurfaceTertiary() - .rotationEffect(.degrees(45)) - .opacity(0) - .frame(height: screenSize.width / 1.16) - } - .offset(y: -offset * 0.1) - - CachedAsyncImage(url: URL(string: "https://cdn.oversize.design/assets/illustrations/scenes/about-layer2.png"), urlCache: .imageCache, scale: scale) { - $0 - .resizable() - .scaledToFit() - .blur(radius: blur) - } placeholder: { - Rectangle() - .fillSurfaceTertiary() - .rotationEffect(.degrees(45)) - .padding(200) - .frame(height: screenSize.width / 1.16) - .overlay { - ProgressView() - } - } + private var image: some View { + HStack { + VStack(alignment: .center) { + ZStack(alignment: .top) { + CachedAsyncImage(url: URL(string: "https://cdn.oversize.design/assets/illustrations/scenes/about-layer3.png"), urlCache: .imageCache, scale: scale) { + $0 + .resizable() + .scaledToFit() + .blur(radius: blur) + } placeholder: { + Rectangle() + .fillSurfaceTertiary() + .rotationEffect(.degrees(45)) + .opacity(0) + .frame(height: screenSize.width / 1.16) + } + .offset(y: -offset * 0.1) - CachedAsyncImage(url: URL(string: "https://cdn.oversize.design/assets/illustrations/scenes/about-layer1.png"), urlCache: .imageCache, scale: scale) { - $0 - .resizable() - .scaledToFit() - .blur(radius: blur) - } placeholder: { - Rectangle() - .fillSurfaceTertiary() - .rotationEffect(.degrees(45)) - .opacity(0) - .frame(height: screenSize.width / 1.16) - } - .offset(y: -(offset * -0.04)) + CachedAsyncImage(url: URL(string: "https://cdn.oversize.design/assets/illustrations/scenes/about-layer2.png"), urlCache: .imageCache, scale: scale) { + $0 + .resizable() + .scaledToFit() + .blur(radius: blur) + } placeholder: { + Rectangle() + .fillSurfaceTertiary() + .rotationEffect(.degrees(45)) + .padding(200) + .frame(height: screenSize.width / 1.16) + .overlay { + ProgressView() + } } - .scaleEffect(screenSize.width < 500 ? 1.4 : 0.9) - .opacity(screenSize.width < 500 ? oppacity : 1) + + CachedAsyncImage(url: URL(string: "https://cdn.oversize.design/assets/illustrations/scenes/about-layer1.png"), urlCache: .imageCache, scale: scale) { + $0 + .resizable() + .scaledToFit() + .blur(radius: blur) + } placeholder: { + Rectangle() + .fillSurfaceTertiary() + .rotationEffect(.degrees(45)) + .opacity(0) + .frame(height: screenSize.width / 1.16) + } + .offset(y: -(offset * -0.04)) } + .scaleEffect(screenSize.width < 500 ? 1.4 : 0.9) + .opacity(screenSize.width < 500 ? oppacity : 1) } } + } - private var footer: some View { - HStack { - Spacer() - - VStack(alignment: .center) { - if let authorLink = Info.links?.company.url { - Link(destination: authorLink) { - if let developerName = Info.developer.name, - let appVersion = Info.app.verstion, - let appName = Info.app.name, - let appBuild = Info.app.build - { - Text("© 2023 \(developerName). \(appName) \(appVersion) (\(appBuild))") - .footnote() - .foregroundColor(.onBackgroundDisabled) - } else { - Text("Developer") - .footnote() - .foregroundColor(.onBackgroundDisabled) - } + private var footer: some View { + HStack { + Spacer() + + VStack(alignment: .center) { + if let authorLink = Info.links?.company.url { + Link(destination: authorLink) { + if let developerName = Info.developer.name, + let appVersion = Info.app.verstion, + let appName = Info.app.name, + let appBuild = Info.app.build + { + Text("© 2024 \(developerName). \(appName) \(appVersion) (\(appBuild))") + .footnote() + .foregroundColor(.onBackgroundTertiary) + } else { + Text("Developer") + .footnote() + .foregroundColor(.onBackgroundTertiary) } } } - - Spacer() } - .padding(.top, Space.small) - .padding(.bottom, 40) - } - var rateSettingsIcon: Image { - switch iconStyle { - case .line: - return Image.Base.heart - case .fill: - return Image.Base.Heart.fill - case .twoTone: - return Image.Base.Heart.TwoTone.fill - } + Spacer() } + .padding(.top, Space.small) + .padding(.bottom, 40) + } - var ideaSettingsIcon: Image { - switch iconStyle { - case .line: - return Image.Electricity.lamp - case .fill: - return Image.Electricity.Lamp.fill - case .twoTone: - return Image.Electricity.Lamp.TwoTone.fill - } + var rateSettingsIcon: Image { + switch iconStyle { + case .line: + return Image.Base.heart + case .fill: + return Image.Base.Heart.fill + case .twoTone: + return Image.Base.Heart.TwoTone.fill } + } - var shareSettingsIcon: Image { - switch iconStyle { - case .line: - return Image.Base.send - case .fill: - return Image.Base.Send.fill - case .twoTone: - return Image.Base.Send.TwoTone.fill - } + var ideaSettingsIcon: Image { + switch iconStyle { + case .line: + return Image.Electricity.lamp + case .fill: + return Image.Electricity.Lamp.fill + case .twoTone: + return Image.Electricity.Lamp.TwoTone.fill } } - struct AboutView_Previews: PreviewProvider { - static var previews: some View { - AboutView() + var shareSettingsIcon: Image { + switch iconStyle { + case .line: + return Image.Base.send + case .fill: + return Image.Base.Send.fill + case .twoTone: + return Image.Base.Send.TwoTone.fill } } -#endif +} + +struct AboutView_Previews: PreviewProvider { + static var previews: some View { + AboutView() + } +} diff --git a/Sources/OversizeKit/SettingsKit/Views/About/FeedbackView.swift b/Sources/OversizeKit/SettingsKit/Views/About/FeedbackView.swift index 976faa1..bd8491f 100644 --- a/Sources/OversizeKit/SettingsKit/Views/About/FeedbackView.swift +++ b/Sources/OversizeKit/SettingsKit/Views/About/FeedbackView.swift @@ -9,17 +9,18 @@ import OversizeComponents import OversizeLocalizable import OversizeResources +import OversizeRouter import OversizeServices import OversizeUI import SwiftUI public struct FeedbackView: View { + @Environment(Router.self) var router @Environment(\.iconStyle) var iconStyle: IconStyle - @State private var isShowMail = false public init() {} public var body: some View { - PageView("Feedback") { + Page("Feedback") { VStack(spacing: .large) { help @@ -27,10 +28,16 @@ public struct FeedbackView: View { .padding(.bottom, .medium) } } - .trailingBar { - BarButton(.close) - } .backgroundSecondary() + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button { + router.dismiss() + } label: { + Image.Base.close.icon() + } + } + } } private var hero: some View { @@ -63,15 +70,10 @@ public struct FeedbackView: View { let subject = "Feedback" Row(L10n.Settings.feedbakAuthor) { - isShowMail.toggle() + router.present(.sendMail(to: mail, subject: subject, content: contentPreText)) } leading: { mailIcon.icon() } - - .buttonStyle(.row) - .sheet(isPresented: $isShowMail) { - MailView(to: mail, subject: subject, content: contentPreText) - } } else { // Send author if let sendMailUrl = Info.url.developerSendMail { @@ -96,6 +98,7 @@ public struct FeedbackView: View { } } } + .sectionContentCompactRowMargins() } var heartIcon: Image { diff --git a/Sources/OversizeKit/SettingsKit/Views/About/OurResorsesView.swift b/Sources/OversizeKit/SettingsKit/Views/About/OurResorsesView.swift index 745cb19..ee5cae7 100644 --- a/Sources/OversizeKit/SettingsKit/Views/About/OurResorsesView.swift +++ b/Sources/OversizeKit/SettingsKit/Views/About/OurResorsesView.swift @@ -15,17 +15,14 @@ import SwiftUI public struct OurResorsesView: View { @Environment(\.iconStyle) var iconStyle: IconStyle - @State private var isShowMail = false public init() {} public var body: some View { - PageView("Our open resources") { + Page("Our open resources") { links .surfaceContentRowMargins() } - .leadingBar { - BarButton(.back) - } + .backgroundSecondary() } diff --git a/Sources/OversizeKit/SettingsKit/Views/About/SupportView.swift b/Sources/OversizeKit/SettingsKit/Views/About/SupportView.swift index cc4c5c4..d0aa8cc 100644 --- a/Sources/OversizeKit/SettingsKit/Views/About/SupportView.swift +++ b/Sources/OversizeKit/SettingsKit/Views/About/SupportView.swift @@ -9,17 +9,18 @@ import OversizeComponents import OversizeLocalizable import OversizeResources +import OversizeRouter import OversizeServices import OversizeUI import SwiftUI public struct SupportView: View { + @Environment(Router.self) var router @Environment(\.iconStyle) var iconStyle: IconStyle - @State private var isShowMail = false public init() {} public var body: some View { - PageView(L10n.Settings.supportSection) { + Page(L10n.Settings.supportSection) { VStack(spacing: .large) { help @@ -27,10 +28,16 @@ public struct SupportView: View { .padding(.bottom, .medium) } } - .trailingBar { - BarButton(.close) - } .backgroundSecondary() + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button { + router.dismissSheet() + } label: { + Image.Base.close.icon() + } + } + } } private var hero: some View { @@ -54,15 +61,10 @@ public struct SupportView: View { let subject = "Support" Row("Contact Us") { - isShowMail.toggle() + router.present(.sendMail(to: mail, subject: subject, content: contentPreText)) } leading: { mailIcon.icon() } - - .buttonStyle(.row) - .sheet(isPresented: $isShowMail) { - MailView(to: mail, subject: subject, content: contentPreText) - } } else { // Send author if let sendMailUrl = Info.url.developerSendMail { @@ -87,6 +89,7 @@ public struct SupportView: View { } } } + .sectionContentCompactRowMargins() } var heartIcon: Image { @@ -121,15 +124,4 @@ public struct SupportView: View { return Image.Brands.Telegram.twoTone } } - -// var chatIcon: Image { -// switch iconStyle { -// case .line: -// return Icon.Line.Communication.chatDots -// case .solid: -// return Icon.Solid.Communication.chatDots -// case .duotone: -// return Icon.Duotone.Communication.chatDots -// } -// } } diff --git a/Sources/OversizeKit/SettingsKit/Views/Apperance/AppearanceSettingView.swift b/Sources/OversizeKit/SettingsKit/Views/Apperance/AppearanceSettingView.swift index 2423055..89946c5 100644 --- a/Sources/OversizeKit/SettingsKit/Views/Apperance/AppearanceSettingView.swift +++ b/Sources/OversizeKit/SettingsKit/Views/Apperance/AppearanceSettingView.swift @@ -5,304 +5,256 @@ import OversizeCore import OversizeLocalizable +import OversizeRouter import OversizeServices import OversizeUI import SwiftUI -#if os(iOS) - public class AppIconSettings: ObservableObject { - public var iconNames: [String?] = [nil] - @Published public var currentIndex = 0 - - public init() { - getAlternateIconNames() +public struct AppearanceSettingView: View { + @Environment(Router.self) var router + @Environment(\.theme) private var theme: ThemeSettings + @Environment(\.iconStyle) var iconStyle: IconStyle + @Environment(\.isPremium) var isPremium: Bool - if let currentIcon = UIApplication.shared.alternateIconName { - currentIndex = iconNames.firstIndex(of: currentIcon) ?? 0 - } - } + #if os(iOS) + @StateObject var iconSettings = AppIconSettings() + #endif - private func getAlternateIconNames() { - if let iconCount = FeatureFlags.app.alternateAppIcons, iconCount != 0 { - for index in 1 ... iconCount { - iconNames.append("AlternateAppIcon\(index)") - } - } - } - } -#endif + private let columns = [ + GridItem(.adaptive(minimum: 78)), + ] -#if os(iOS) - public struct AppearanceSettingView: View { - @Environment(\.verticalSizeClass) private var verticalSizeClass - @Environment(\.presentationMode) var presentationMode - @Environment(\.theme) private var theme: ThemeSettings - @Environment(\.isPortrait) var isPortrait - @Environment(\.iconStyle) var iconStyle: IconStyle - @Environment(\.isPremium) var isPremium: Bool + public init() {} + public var body: some View { #if os(iOS) - @StateObject var iconSettings = AppIconSettings() - #endif - - // swiftlint:disable trailing_comma - - @State var offset = CGPoint(x: 0, y: 0) - @State var pageDestenation: Destenation? - @State var isShowPremium: Bool = .init(false) - private let columns = [ - GridItem(.adaptive(minimum: 78)), - ] - enum Destenation { - case font - case border - case radius - } - - public init() {} - public var body: some View { - #if os(iOS) - PageView(L10n.Settings.apperance) { - iOSSettings - .surfaceContentRowMargins() - } - .leadingBar { - /* - if !isPortrait, verticalSizeClass == .regular { - EmptyView() - } else { - BarButton(.back) - } - */ - BarButton(.back) - } - .backgroundSecondary() + Page(L10n.Settings.apperance) { + iOSSettings + .surfaceContentRowMargins() + } + .backgroundSecondary() - #else - macSettings - #endif - } + #else + macSettings + #endif + } - #if os(iOS) - private var iOSSettings: some View { - VStack(alignment: .center, spacing: 0) { - apperance + #if os(iOS) + private var iOSSettings: some View { + LazyVStack(alignment: .leading, spacing: 0) { + apperance - accentColor + accentColor - advanded + advanded - if iconSettings.iconNames.count > 1 { - appIcon - } + if iconSettings.iconNames.count > 1 { + appIcon } - .preferredColorScheme(theme.appearance.colorScheme) - .accentColor(theme.accentColor) } - #endif - - private var macSettings: some View { - VStack(alignment: .center, spacing: 0) { - advanded - } - .frame(width: 400, height: 300) - // swiftlint:disable multiple_closures_with_trailing_closure superfluous_disable_command - - .navigationTitle("Appearance") .preferredColorScheme(theme.appearance.colorScheme) + .accentColor(theme.accentColor) } + #endif - #if os(iOS) - private var apperance: some View { - SectionView { - HStack { - ForEach(Appearance.allCases, id: \.self) { appearance in + private var macSettings: some View { + VStack(alignment: .center, spacing: 0) { + advanded + } + .frame(width: 400, height: 300) + // swiftlint:disable multiple_closures_with_trailing_closure superfluous_disable_command + .navigationTitle("Appearance") + .preferredColorScheme(theme.appearance.colorScheme) + } - HStack { - Spacer() + private var apperance: some View { + SectionView { + HStack { + ForEach(Appearance.allCases, id: \.self) { appearance in - VStack(spacing: .zero) { - Text(appearance.name) - .foregroundColor(.onSurfaceHighEmphasis) - .font(.subheadline) - .bold() + HStack { + Spacer() - appearance.image - .padding(.vertical, .medium) + VStack(spacing: .zero) { + Text(appearance.name) + .foregroundColor(.onSurfacePrimary) + .font(.subheadline) + .bold() - if appearance == theme.appearance { - IconDeprecated(.checkCircle, color: Color.accent) - } else { - IconDeprecated(.circle, color: .onSurfaceMediumEmphasis) - } - } - Spacer() - } - .onTapGesture { - theme.appearance = appearance + appearance.image + .padding(.vertical, .medium) + + if appearance == theme.appearance { + IconDeprecated(.checkCircle, color: Color.accent) + } else { + IconDeprecated(.circle, color: .onSurfaceSecondary) } } - }.padding(.vertical, .xSmall) + Spacer() + } + .onTapGesture { + theme.appearance = appearance + } } - } - #endif + }.padding(.vertical, .xSmall) + } + } - #if os(iOS) - private var accentColor: some View { - SectionView("Accent color") { - ColorSelector(selection: theme.$accentColor) - } + #if os(iOS) + private var accentColor: some View { + SectionView("Accent color") { + ColorSelector(selection: theme.$accentColor) } + } - #endif - - #if os(iOS) - private var appIcon: some View { - SectionView("App icon") { - LazyVGrid(columns: columns, spacing: 24) { - ForEach(0 ..< iconSettings.iconNames.count, id: \.self) { index in - HStack { - Image(uiImage: UIImage(named: iconSettings.iconNames[index] - ?? "AppIcon") ?? UIImage()) - .renderingMode(.original) - .resizable() - .scaledToFit() - .frame(width: 78, height: 78) - .cornerRadius(18) - .overlay( - RoundedRectangle(cornerRadius: 20) - .stroke(index == iconSettings.currentIndex ? Color.accent : Color.border, - lineWidth: index == iconSettings.currentIndex ? 3 : 1) - ) - .onTapGesture { - if index != 0, isPremium == false { - isShowPremium.toggle() - } else { - let defaultIconIndex = iconSettings.iconNames - .firstIndex(of: UIApplication.shared.alternateIconName) ?? 0 - if defaultIconIndex != index { - // swiftlint:disable line_length - UIApplication.shared.setAlternateIconName(iconSettings.iconNames[index]) { error in - if let error { - log(error.localizedDescription) - } else { - log("Success! You have changed the app icon.") - } + #endif + + #if os(iOS) + private var appIcon: some View { + SectionView("App icon") { + LazyVGrid(columns: columns, spacing: 24) { + ForEach(0 ..< iconSettings.iconNames.count, id: \.self) { index in + HStack { + Image(uiImage: UIImage(named: iconSettings.iconNames[index] + ?? "AppIcon") ?? UIImage()) + .renderingMode(.original) + .resizable() + .scaledToFit() + .frame(width: 78, height: 78) + .cornerRadius(18) + .overlay( + RoundedRectangle(cornerRadius: 20) + .stroke(index == iconSettings.currentIndex ? Color.accent : Color.border, + lineWidth: index == iconSettings.currentIndex ? 3 : 1) + ) + .onTapGesture { + if index != 0, isPremium == false { + router.present(.premium) + } else { + let defaultIconIndex = iconSettings.iconNames + .firstIndex(of: UIApplication.shared.alternateIconName) ?? 0 + if defaultIconIndex != index { + // swiftlint:disable line_length + UIApplication.shared.setAlternateIconName(iconSettings.iconNames[index]) { error in + if let error { + log(error.localizedDescription) + } else { + log("Success! You have changed the app icon.") } } } } - } - .padding(3) + } } + .padding(3) } - .padding() } - .sheet(isPresented: $isShowPremium, content: { - StoreView() - .closable() - }) + .padding() } - #endif - - private var advanded: some View { - SectionView("Advanced settings") { - ZStack { - NavigationLink(destination: FontSettingView(), - tag: .font, - selection: $pageDestenation) { EmptyView() } - - NavigationLink(destination: BorderSettingView(), - tag: .border, - selection: $pageDestenation) { EmptyView() } - - NavigationLink(destination: RadiusSettingView(), - tag: .radius, - selection: $pageDestenation) { EmptyView() } - - VStack(spacing: .zero) { - Row("Fonts") { - pageDestenation = .font - } leading: { - textIcon.icon() - } - .rowArrow() - .premium() - .onPremiumTap() - - Switch(isOn: theme.$borderApp) { - Row("Borders") { - pageDestenation = .border - } leading: { - borderIcon.icon() - } - .premium() - } - .onPremiumTap() -// Row("Borders", leadingType: .image(borderIcon), trallingType: .toggleWithArrowButton(isOn: theme.$borderApp, action: { -// pageDestenation = .border -// })) { -// pageDestenation = .border -// } - - .onChange(of: theme.borderApp) { value in - theme.borderSurface = value - theme.borderButtons = value - theme.borderControls = value - theme.borderTextFields = value - } - - Row("Radius") { - pageDestenation = .radius - } leading: { - radiusIcon.icon() - } - .rowArrow() - .premium() - .onPremiumTap() + } + #endif + + private var advanded: some View { + SectionView("Advanced settings") { + VStack(spacing: .zero) { + Row("Fonts") { + router.move(.font) + } leading: { + textIcon.icon() + } + .rowArrow() + .premium() + .onPremiumTap() + + Switch(isOn: theme.$borderApp) { + Row("Borders") { + router.move(.border) + } leading: { + borderIcon.icon() } + .premium() + } + .onPremiumTap() + .onChange(of: theme.borderApp) { _, value in + theme.borderSurface = value + theme.borderButtons = value + theme.borderControls = value + theme.borderTextFields = value + } + + Row("Radius") { + router.move(.radius) + } leading: { + radiusIcon.icon() } + .rowArrow() + .premium() + .onPremiumTap() } } + } - var textIcon: Image { - switch iconStyle { - case .line: - return Image.Editor.Font.square - case .fill: - return Image.Editor.Font.Square.fill - case .twoTone: - return Image.Editor.Font.Square.TwoTone.fill - } + var textIcon: Image { + switch iconStyle { + case .line: + return Image.Editor.Font.square + case .fill: + return Image.Editor.Font.Square.fill + case .twoTone: + return Image.Editor.Font.Square.TwoTone.fill } + } - var borderIcon: Image { - switch iconStyle { - case .line: - return Image.Design.verticalMirror - case .fill: - return Image.Editor.Font.Square.fill - case .twoTone: - return Image.Editor.Font.Square.TwoTone.fill - } + var borderIcon: Image { + switch iconStyle { + case .line: + return Image.Design.verticalMirror + case .fill: + return Image.Editor.Font.Square.fill + case .twoTone: + return Image.Editor.Font.Square.TwoTone.fill } + } - var radiusIcon: Image { - switch iconStyle { - case .line: - return Image.Design.path - case .fill: - return Image.Design.Path.fill - case .twoTone: - return Image.Design.Path.twoTone - } + var radiusIcon: Image { + switch iconStyle { + case .line: + return Image.Design.path + case .fill: + return Image.Design.Path.fill + case .twoTone: + return Image.Design.Path.twoTone } } +} + +#if os(iOS) + @MainActor + public class AppIconSettings: ObservableObject { + public var iconNames: [String?] = [nil] + @Published public var currentIndex = 0 + + public init() { + getAlternateIconNames() - struct SettingsThemeView_Previews: PreviewProvider { - static var previews: some View { - AppearanceSettingView() - .previewPhones() + if let currentIcon = UIApplication.shared.alternateIconName { + currentIndex = iconNames.firstIndex(of: currentIcon) ?? 0 + } + } + + private func getAlternateIconNames() { + if let iconCount = FeatureFlags.app.alternateAppIcons, iconCount != 0 { + for index in 1 ... iconCount { + iconNames.append("AlternateAppIcon\(index)") + } + } } } #endif + +struct SettingsThemeView_Previews: PreviewProvider { + static var previews: some View { + AppearanceSettingView() + .previewPhones() + } +} diff --git a/Sources/OversizeKit/SettingsKit/Views/Apperance/BorderSettongView.swift b/Sources/OversizeKit/SettingsKit/Views/Apperance/BorderSettingView.swift similarity index 94% rename from Sources/OversizeKit/SettingsKit/Views/Apperance/BorderSettongView.swift rename to Sources/OversizeKit/SettingsKit/Views/Apperance/BorderSettingView.swift index 5092940..6a41ab4 100644 --- a/Sources/OversizeKit/SettingsKit/Views/Apperance/BorderSettongView.swift +++ b/Sources/OversizeKit/SettingsKit/Views/Apperance/BorderSettingView.swift @@ -1,6 +1,6 @@ // // Copyright © 2022 Alexander Romanov -// BorderSettongView.swift +// BorderSettingView.swift // import OversizeUI @@ -31,14 +31,14 @@ public struct BorderSettingView: View { SectionView { VStack(spacing: .zero) { Toggle("Borders in app", isOn: theme.$borderApp) - .onChange(of: theme.borderApp) { value in + .onChange(of: theme.borderApp) { _, value in theme.borderSurface = value theme.borderButtons = value theme.borderControls = value theme.borderTextFields = value } .headline() - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) .padding(.horizontal, .medium) .padding(.vertical, .small) @@ -50,13 +50,13 @@ public struct BorderSettingView: View { HStack { Text("Size") .subheadline() - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) Spacer() Text(String(format: "%.1f", theme.borderSize) + " px") .subheadline() - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) } Slider(value: theme.$borderSize, in: 0.5 ... 2, step: 0.5) diff --git a/Sources/OversizeKit/SettingsKit/Views/Apperance/FontSettingView.swift b/Sources/OversizeKit/SettingsKit/Views/Apperance/FontSettingView.swift index ed4fba7..ad9c8cf 100644 --- a/Sources/OversizeKit/SettingsKit/Views/Apperance/FontSettingView.swift +++ b/Sources/OversizeKit/SettingsKit/Views/Apperance/FontSettingView.swift @@ -56,11 +56,11 @@ public struct FontSettingView: View { VStack(alignment: .leading, spacing: 8) { Text("Aa") .font(.system(size: 34, weight: .heavy, design: fontStyle.system)) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) Text(fontStyle.rawValue.capitalizingFirstLetter()) .font(.system(size: 16, weight: .medium, design: fontStyle.system)) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) } Spacer() }.padding() @@ -74,11 +74,11 @@ public struct FontSettingView: View { VStack(alignment: .leading, spacing: 8) { Text("Aa") .font(.system(size: 34, weight: .heavy, design: fontStyle.system)) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) Text(fontStyle.rawValue.capitalizingFirstLetter()) .font(.system(size: 16, weight: .medium, design: fontStyle.system)) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) } Spacer() }.padding() @@ -91,16 +91,16 @@ public struct FontSettingView: View { Text("Button".uppercased()) .bold() .caption() - .onBackgroundMediumEmphasisForegroundColor() + .onBackgroundSecondaryForeground() SegmentedPickerSelector(FontDesignType.allCases, selection: theme.$fontButton) { fontStyle, _ in VStack(alignment: .center, spacing: 8) { Text("Aa") .font(.system(size: 28, weight: .heavy, design: fontStyle.system)) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) Text(fontStyle.rawValue.capitalizingFirstLetter()) .font(.system(size: 12, weight: .medium, design: fontStyle.system)) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) } } .segmentedControlStyle(.island(selected: .graySurface)) @@ -110,16 +110,16 @@ public struct FontSettingView: View { Text("Overline & caption".uppercased()) .bold() .caption() - .onBackgroundMediumEmphasisForegroundColor() + .onBackgroundSecondaryForeground() SegmentedPickerSelector(FontDesignType.allCases, selection: theme.$fontOverline) { fontStyle, _ in VStack(alignment: .center, spacing: 8) { Text("Aa") .font(.system(size: 28, weight: .heavy, design: fontStyle.system)) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) Text(fontStyle.rawValue.capitalizingFirstLetter()) .font(.system(size: 12, weight: .medium, design: fontStyle.system)) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) } } .segmentedControlStyle(.island(selected: .graySurface)) @@ -138,34 +138,34 @@ extension FontSettingView { Text("Overline".uppercased()) .bold() .caption() - .onBackgroundMediumEmphasisForegroundColor() + .onBackgroundSecondaryForeground() Text("Large title") .largeTitle() - .onBackgroundHighEmphasisForegroundColor() + .onBackgroundPrimaryForeground() Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.") .body() - .onBackgroundMediumEmphasisForegroundColor() + .onBackgroundSecondaryForeground() } VStack(alignment: .leading, spacing: Space.xxSmall.rawValue) { Text("Title") .title3() - .onBackgroundHighEmphasisForegroundColor() + .onBackgroundPrimaryForeground() Text("Subtitle") .headline() - .onBackgroundHighEmphasisForegroundColor() + .onBackgroundPrimaryForeground() Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.") .bold() .subheadline() - .onBackgroundHighEmphasisForegroundColor() + .onBackgroundPrimaryForeground() Text("Button") .body() - .onBackgroundHighEmphasisForegroundColor() + .onBackgroundPrimaryForeground() .padding(.top, .xxxSmall) } } diff --git a/Sources/OversizeKit/SettingsKit/Views/Apperance/RadiusSettingView.swift b/Sources/OversizeKit/SettingsKit/Views/Apperance/RadiusSettingView.swift index c42d83a..1d63b74 100644 --- a/Sources/OversizeKit/SettingsKit/Views/Apperance/RadiusSettingView.swift +++ b/Sources/OversizeKit/SettingsKit/Views/Apperance/RadiusSettingView.swift @@ -6,7 +6,7 @@ import OversizeUI import SwiftUI -struct RadiusSettingView: View { +public struct RadiusSettingView: View { @Environment(\.theme) private var theme: ThemeSettings public init() {} @@ -32,13 +32,13 @@ struct RadiusSettingView: View { HStack { Text("Size") .subheadline() - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) Spacer() Text(String(format: "%.0f", theme.radius) + " px") .subheadline() - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) } Slider(value: theme.$radius, in: 0 ... 12, step: 4) @@ -53,12 +53,6 @@ struct RadiusSettingView: View { Spacer() } -// .navigationBar("Radius", style: .fixed($offset)) { -// BarButton(.back) -// } trailingBar: {} bottomBar: {} -// .background(Color.backgroundSecondary.ignoresSafeArea(.all)) -// .preferredColorScheme(theme.appearance.colorScheme) -// .animation(.easeIn(duration: 0.2)) } } diff --git a/Sources/OversizeKit/SettingsKit/Views/Notifications/NotificationsSettingsView.swift b/Sources/OversizeKit/SettingsKit/Views/Notifications/NotificationsSettingsView.swift index 6129783..66cc4c8 100644 --- a/Sources/OversizeKit/SettingsKit/Views/Notifications/NotificationsSettingsView.swift +++ b/Sources/OversizeKit/SettingsKit/Views/Notifications/NotificationsSettingsView.swift @@ -9,33 +9,27 @@ import OversizeUI import SwiftUI // swiftlint:disable line_length -#if os(iOS) - public struct NotificationsSettingsView: View { - @Environment(\.presentationMode) var presentationMode - @Environment(\.verticalSizeClass) private var verticalSizeClass - @Environment(\.isPortrait) var isPortrait - @StateObject var settingsService = SettingsService() - @State var offset = CGPoint(x: 0, y: 0) - public var body: some View { - PageView(L10n.Settings.notifications) { - soundsAndVibrations - .surfaceContentRowMargins() - } - .leadingBar { - BarButton(.back) - } - .backgroundSecondary() +public struct NotificationsSettingsView: View { + @StateObject var settingsService = SettingsService() + + public init() {} + + public var body: some View { + Page(L10n.Settings.notifications) { + soundsAndVibrations + .surfaceContentRowMargins() } + .backgroundSecondary() } +} - extension NotificationsSettingsView { - private var soundsAndVibrations: some View { - SectionView { - VStack(spacing: .zero) { - Switch(L10n.Settings.notifications, isOn: $settingsService.notificationEnabled) - } +extension NotificationsSettingsView { + private var soundsAndVibrations: some View { + SectionView { + VStack(spacing: .zero) { + Switch(L10n.Settings.notifications, isOn: $settingsService.notificationEnabled) } } } -#endif +} diff --git a/Sources/OversizeKit/SettingsKit/Views/Security/PINCode/SetPINCodeView.swift b/Sources/OversizeKit/SettingsKit/Views/Security/PINCode/SetPINCodeView.swift index 8bfb6a8..c0484ee 100644 --- a/Sources/OversizeKit/SettingsKit/Views/Security/PINCode/SetPINCodeView.swift +++ b/Sources/OversizeKit/SettingsKit/Views/Security/PINCode/SetPINCodeView.swift @@ -4,12 +4,14 @@ // import OversizeLocalizable +import OversizeRouter import OversizeUI import SwiftUI public struct SetPINCodeView: View { + @Environment(Router.self) var router + @Environment(HUDRouter.self) private var hudRouter: HUDRouter @ObservedObject var viewModel: SetPINCodeViewModel - @EnvironmentObject private var hud: HUDDeprecated @Environment(\.dismiss) var dismiss public init(action: PINCodeAction) { @@ -17,20 +19,16 @@ public struct SetPINCodeView: View { } public var body: some View { - ZStack(alignment: .leading) { - stateView(state: viewModel.state) - - VStack(alignment: .leading) { - Button { - dismiss() - } label: { - IconDeprecated(.xMini, color: .onSurfaceHighEmphasis) + stateView(state: viewModel.state) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button { + router.dismiss() + } label: { + Image.Base.close.icon() + } } - .buttonStyle(.secondary) - - Spacer() - }.padding(20) - } + } } @ViewBuilder @@ -55,6 +53,7 @@ public struct SetPINCodeView: View { { viewModel.checkNewPINCode() } biometricAction: {} + case .confirmNewPINField: LockscreenView(pinCode: $viewModel.confirmNewCodeField, state: $viewModel.authState, @@ -69,9 +68,9 @@ public struct SetPINCodeView: View { dismiss() switch viewModel.action { case .set: - hud.show(title: L10n.Security.createPINCode, icon: .check) + hudRouter.present(L10n.Security.createPINCode) case .update: - hud.show(title: L10n.Security.pinChanged, icon: .check) + hudRouter.present(L10n.Security.pinChanged) } case false: diff --git a/Sources/OversizeKit/SettingsKit/Views/Security/PINCode/SetPINCodeViewModel.swift b/Sources/OversizeKit/SettingsKit/Views/Security/PINCode/SetPINCodeViewModel.swift index a75ebd3..3ef7f19 100644 --- a/Sources/OversizeKit/SettingsKit/Views/Security/PINCode/SetPINCodeViewModel.swift +++ b/Sources/OversizeKit/SettingsKit/Views/Security/PINCode/SetPINCodeViewModel.swift @@ -9,17 +9,18 @@ import OversizeServices import OversizeUI import SwiftUI -public enum PINCodeAction: Identifiable { +public enum PINCodeAction: Identifiable, Sendable { case set, update public var id: Int { hashValue } } -public enum SetPINCodeViewState { +public enum SetPINCodeViewState: Sendable { case oldPINField, newPINField, confirmNewPINField } +@MainActor public final class SetPINCodeViewModel: ObservableObject { @Published public var settingsStore: SettingsService diff --git a/Sources/OversizeKit/SettingsKit/Views/Security/SecuritySettingsView.swift b/Sources/OversizeKit/SettingsKit/Views/Security/SecuritySettingsView.swift index bc89bab..dc09593 100644 --- a/Sources/OversizeKit/SettingsKit/Views/Security/SecuritySettingsView.swift +++ b/Sources/OversizeKit/SettingsKit/Views/Security/SecuritySettingsView.swift @@ -5,107 +5,97 @@ import Factory import OversizeLocalizable +import OversizeRouter import OversizeServices import OversizeUI import SwiftUI // swiftlint:disable line_length -#if os(iOS) - public struct SecuritySettingsView: View { - @Injected(\.biometricService) var biometricService - @Environment(\.verticalSizeClass) private var verticalSizeClass - @Environment(\.isPortrait) var isPortrait - @Environment(\.presentationMode) var presentationMode - @StateObject var settingsService = SettingsService() - - @State var offset = CGPoint(x: 0, y: 0) - @State var isSetPINCodeSheet: PINCodeAction? - - public init() {} - - public var body: some View { - PageView(L10n.Security.title) { - iOSSettings - .surfaceContentRowMargins() - } - .leadingBar { - BarButton(.back) - } - .backgroundSecondary() + +public struct SecuritySettingsView: View { + @Injected(\.biometricService) var biometricService + @Environment(Router.self) var router + @StateObject var settingsService = SettingsService() + + public init() {} + + public var body: some View { + Page(L10n.Security.title) { + iOSSettings + .surfaceContentRowMargins() } + .backgroundSecondary() } +} - extension SecuritySettingsView { - private var iOSSettings: some View { - VStack(alignment: .center, spacing: 0) { - faceID +extension SecuritySettingsView { + private var iOSSettings: some View { + VStack(alignment: .center, spacing: 0) { + faceID - additionally - } + // additionally } } - - extension SecuritySettingsView { - private var faceID: some View { - SectionView(L10n.Settings.entrance) { - VStack(spacing: .zero) { - if FeatureFlags.secure.faceID.valueOrFalse, biometricService.checkIfBioMetricAvailable() { - Switch(isOn: - Binding(get: { - settingsService.biometricEnabled - }, set: { - biometricChange(state: $0) - }) - ) { - Row(biometricService.biometricType.rawValue) { - Image(systemName: biometricImageName) - .foregroundColor(Color.onBackgroundHighEmphasis) - .font(.system(size: 20, weight: .semibold)) - .frame(width: 24, height: 24, alignment: .center) - } +} + +extension SecuritySettingsView { + private var faceID: some View { + SectionView(L10n.Settings.entrance) { + VStack(spacing: .zero) { + if FeatureFlags.secure.faceID.valueOrFalse, biometricService.checkIfBioMetricAvailable() { + Switch(isOn: + Binding(get: { + settingsService.biometricEnabled + }, set: { + biometricChange(state: $0) + }) + ) { + Row(biometricService.biometricType.rawValue) { + Image(systemName: biometricImageName) + .foregroundColor(Color.onBackgroundPrimary) + .font(.system(size: 20, weight: .semibold)) + .frame(width: 24, height: 24, alignment: .center) } } + } - if FeatureFlags.secure.lookscreen.valueOrFalse { - Switch(isOn: - Binding(get: { - settingsService.pinCodeEnabend - }, set: { - if settingsService.isSetedPinCode() { - settingsService.pinCodeEnabend = $0 - } else { - isSetPINCodeSheet = .set - } - }) - ) { - Row(L10n.Security.pinCode) { - Image.Security.lock.icon() + if FeatureFlags.secure.lookscreen.valueOrFalse { + Switch(isOn: + Binding(get: { + settingsService.pinCodeEnabend + }, set: { + if settingsService.isSetedPinCode() { + settingsService.pinCodeEnabend = $0 + } else { + router.present(.setPINCode) } - }.sheet(item: $isSetPINCodeSheet) { sheet in - SetPINCodeView(action: sheet) - .systemServices() + }) + ) { + Row(L10n.Security.pinCode) { + Image.Security.lock.icon() } + } - if settingsService.isSetedPinCode() { - Row(L10n.Security.changePINCode) { - isSetPINCodeSheet = .update - } - .rowArrow() + if settingsService.isSetedPinCode() { + Row(L10n.Security.changePINCode) { + router.present(.updatePINCode) } + .rowArrow() } } } } + } - private func biometricChange(state: Bool) { - Task { - await settingsService.biometricChange(state) - } + private func biometricChange(state: Bool) { + Task { + await settingsService.biometricChange(state) } + } - private var additionally: some View { - SectionView(L10n.Settings.additionally) { - VStack(spacing: .zero) { + private var additionally: some View { + SectionView(L10n.Settings.additionally) { + VStack(spacing: .zero) { // if FeatureFlags.secure.lookscreen.valueOrFalse { // Row(L10n.Security.inactiveAskPassword, trallingType: .toggle(isOn: $settingsStore.askPasswordWhenInactiveEnabend)) // } @@ -136,34 +126,33 @@ import SwiftUI // Row(L10n.Security.facedownLock, trallingType: .toggle(isOn: $settingsStore.lookScreenDownEnabend)) // } // - if FeatureFlags.secure.blurMinimize.valueOrFalse { - Switch(isOn: $settingsService.blurMinimizeEnabend) { - Row(L10n.Security.blurMinimize) - .premium() - } - .onPremiumTap() + if FeatureFlags.secure.blurMinimize.valueOrFalse { + Switch(isOn: $settingsService.blurMinimizeEnabend) { + Row(L10n.Security.blurMinimize) + .premium() } + .onPremiumTap() + } // if FeatureFlags.secure.lookscreen.valueOrFalse { // Row(L10n.Security.authHistory, trallingType: .toggle(isOn: $settingsService.authHistoryEnabend)) // .premium() // .onPremiumTap() // } - } } } + } - private var biometricImageName: String { - switch biometricService.biometricType { - case .none: - return "" - case .touchID: - return "touchid" - case .faceID: - return "faceid" - case .opticID: - return "opticid" - } + private var biometricImageName: String { + switch biometricService.biometricType { + case .none: + return "" + case .touchID: + return "touchid" + case .faceID: + return "faceid" + case .opticID: + return "opticid" } } -#endif +} diff --git a/Sources/OversizeKit/SettingsKit/Views/SettingsView.swift b/Sources/OversizeKit/SettingsKit/Views/SettingsView.swift index 0a8d144..22fd90d 100644 --- a/Sources/OversizeKit/SettingsKit/Views/SettingsView.swift +++ b/Sources/OversizeKit/SettingsKit/Views/SettingsView.swift @@ -5,92 +5,42 @@ import OversizeLocalizable import OversizeResources +import OversizeRouter import OversizeServices import OversizeUI import SwiftUI // swiftlint:disable line_length -#if os(iOS) - public struct SettingsView: View { - @Environment(\.horizontalSizeClass) private var horizontalSizeClass - @Environment(\.verticalSizeClass) private var verticalSizeClass - @Environment(\.presentationMode) var presentationMode - @Environment(\.iconStyle) var iconStyle: IconStyle - @Environment(\.theme) var theme: ThemeSettings - @StateObject var settingsService = SettingsService() - @EnvironmentObject var hudState: HUDDeprecated - - let appSection: AppSection - let headSection: HeadSection - - @State private var isPortrait = false - @State private var isShowSupport = false - @State private var isShowFeedback = false - - var isShowArrow: Bool { - #if os(iOS) - if !isPortrait, verticalSizeClass == .regular { - return false - } else { - return true - } - #else - return true +public struct SettingsView: View { + @Environment(Router.self) var router + @Environment(\.iconStyle) var iconStyle: IconStyle + @Environment(\.theme) var theme: ThemeSettings + @StateObject var settingsService = SettingsService() + + let appSection: AppSection + let headSection: HeadSection + + public init( + @ViewBuilder appSection: () -> AppSection, + @ViewBuilder headSection: () -> HeadSection + ) { + self.appSection = appSection() + self.headSection = headSection() + } - #endif - } + public var body: some View { + #if os(iOS) - public init(@ViewBuilder appSection: () -> AppSection, - @ViewBuilder headSection: () -> HeadSection) - { - self.appSection = appSection() - self.headSection = headSection() - } + Page(L10n.Settings.title) { + iOSSettings + }.backgroundSecondary() - public var body: some View { - #if os(iOS) - Group { - if !isPortrait, verticalSizeClass == .regular { - Group { - PageView(L10n.Settings.title) { - iOSSettings - }.backgroundSecondary() + #else + macSettings - AppearanceSettingView() - } - .navigationable() - .navigationViewStyle(DoubleColumnNavigationViewStyle()) - } else { - Group { - PageView(L10n.Settings.title) { - iOSSettings - } - .backgroundSecondary() - } - .navigationable() - .navigationViewStyle(StackNavigationViewStyle()) - } - } - .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in - setOrientation() - } - .onAppear { - setOrientation() - } - .portraitMode(isPortrait) - - #else - macSettings - - #endif - } - - func setOrientation() { - guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return } - isPortrait = scene.interfaceOrientation.isPortrait - } + #endif } -#endif +} // iOS Settings #if os(iOS) @@ -116,312 +66,272 @@ import SwiftUI } #endif -#if os(iOS) - // Sections - extension SettingsView { - private var head: some View { - SectionView { - headSection - } +extension SettingsView { + private var head: some View { + SectionView { + headSection } + } - private var app: some View { - SectionView("General") { - VStack(spacing: .zero) { - if FeatureFlags.app.apperance.valueOrFalse { - NavigationLink(destination: AppearanceSettingView() - ) { - Row(L10n.Settings.apperance) { - apperanceSettingsIcon.icon() - } - .rowArrow(isShowArrow) - } - .buttonStyle(.row) + private var app: some View { + SectionView("General") { + VStack(spacing: .zero) { + if FeatureFlags.app.apperance.valueOrFalse { + Row(L10n.Settings.apperance) { + router.move(.appearance) + } leading: { + apperanceSettingsIcon.icon() } + .rowArrow() + } - if FeatureFlags.app.сloudKit.valueOrFalse || FeatureFlags.app.healthKit.valueOrFalse { - NavigationLink(destination: iCloudSettingsView() - ) { - Row(L10n.Title.synchronization) { - cloudKitIcon.icon() - } - .rowArrow(isShowArrow) - } - .buttonStyle(.row) + if FeatureFlags.app.сloudKit.valueOrFalse || FeatureFlags.app.healthKit.valueOrFalse { + Row(L10n.Title.synchronization) { + router.move(.sync) + } leading: { + cloudKitIcon.icon() } + .rowArrow() + } - if FeatureFlags.secure.faceID.valueOrFalse - || FeatureFlags.secure.lookscreen.valueOrFalse - || FeatureFlags.secure.CVVCodes.valueOrFalse - || FeatureFlags.secure.alertSecureCodes.valueOrFalse - || FeatureFlags.secure.blurMinimize.valueOrFalse - || FeatureFlags.secure.bruteForceSecure.valueOrFalse - || FeatureFlags.secure.photoBreaker.valueOrFalse - { - NavigationLink(destination: SecuritySettingsView() - ) { - Row(L10n.Security.title) { - securityIcon.icon() - } - .rowArrow(isShowArrow) - } - .buttonStyle(.row) + if FeatureFlags.secure.faceID.valueOrFalse + || FeatureFlags.secure.lookscreen.valueOrFalse + || FeatureFlags.secure.CVVCodes.valueOrFalse + || FeatureFlags.secure.alertSecureCodes.valueOrFalse + || FeatureFlags.secure.blurMinimize.valueOrFalse + || FeatureFlags.secure.bruteForceSecure.valueOrFalse + || FeatureFlags.secure.photoBreaker.valueOrFalse + { + Row(L10n.Security.title) { + router.move(.security) + } leading: { + securityIcon.icon() } + .rowArrow() + } - if FeatureFlags.app.sounds.valueOrFalse || FeatureFlags.app.vibration.valueOrFalse { - NavigationLink(destination: SoundsAndVibrationsSettingsView() - ) { - Row(soundsAndVibrationTitle) { - FeatureFlags.app.sounds.valueOrFalse ? soundIcon.icon() : vibrationIcon.icon() - } - .rowArrow(isShowArrow) - } - .buttonStyle(.row) + if FeatureFlags.app.sounds.valueOrFalse || FeatureFlags.app.vibration.valueOrFalse { + Row(soundsAndVibrationTitle) { + router.move(.soundAndVibration) + } leading: { + FeatureFlags.app.sounds.valueOrFalse ? soundIcon.icon() : vibrationIcon.icon() } + .rowArrow() + } - if FeatureFlags.app.notifications.valueOrFalse { - NavigationLink(destination: NotificationsSettingsView() - ) { - Row(L10n.Settings.notifications) { - notificationsIcon.icon() - } - .rowArrow(isShowArrow) - } - .buttonStyle(.row) + if FeatureFlags.app.notifications.valueOrFalse { + Row(L10n.Settings.notifications) { + router.move(.notifications) + } leading: { + notificationsIcon.icon() } - - appSection + .rowArrow() } - } - } - var apperanceSettingsIcon: Image { - switch iconStyle { - case .line: - return Image.Design.paintingPalette - case .fill: - return Image.Design.PaintingPalette.fill - case .twoTone: - return Image.Design.PaintingPalette.twoTone + appSection } } + } - var cloudKitIcon: Image { - switch iconStyle { - case .line: - return Image.Weather.cloud2 - case .fill: - return Image.Weather.Cloud.Square.fill - case .twoTone: - return Image.Weather.Cloud.Square.twoTone - } + var apperanceSettingsIcon: Image { + switch iconStyle { + case .line: + return Image.Design.paintingPalette + case .fill: + return Image.Design.PaintingPalette.fill + case .twoTone: + return Image.Design.PaintingPalette.twoTone } + } - var securityIcon: Image { - switch iconStyle { - case .line: - return Image.Base.lock - case .fill: - return Image.Base.Lock.fill - case .twoTone: - return Image.Base.Lock.TwoTone.fill - } + var cloudKitIcon: Image { + switch iconStyle { + case .line: + return Image.Weather.cloud2 + case .fill: + return Image.Weather.Cloud.Square.fill + case .twoTone: + return Image.Weather.Cloud.Square.twoTone } + } - var soundIcon: Image { - switch iconStyle { - case .line: - return Image.Base.volumeUp - case .fill: - return Image.Base.VolumeUp.fill - case .twoTone: - return Image.Base.VolumeUp.TwoTone.fill - } + var securityIcon: Image { + switch iconStyle { + case .line: + return Image.Base.lock + case .fill: + return Image.Base.Lock.fill + case .twoTone: + return Image.Base.Lock.TwoTone.fill } + } - var vibrationIcon: Image { - switch iconStyle { - case .line: - return Image.Mobile.vibration - case .fill: - return Image.Mobile.Vibration.fill - case .twoTone: - return Image.Mobile.Vibration.twoTone - } + var soundIcon: Image { + switch iconStyle { + case .line: + return Image.Base.volumeUp + case .fill: + return Image.Base.VolumeUp.fill + case .twoTone: + return Image.Base.VolumeUp.TwoTone.fill } + } - var notificationsIcon: Image { - switch iconStyle { - case .line: - return Image.Base.notification - case .fill: - return Image.Base.Notification.fill - case .twoTone: - return Image.Base.Notification.TwoTone.fill - } + var vibrationIcon: Image { + switch iconStyle { + case .line: + return Image.Mobile.vibration + case .fill: + return Image.Mobile.Vibration.fill + case .twoTone: + return Image.Mobile.Vibration.twoTone } + } - // App Store Review - private var help: some View { - SectionView(L10n.Settings.supportSection) { - VStack(alignment: .leading) { - Row("Get help") { - isShowSupport.toggle() - } leading: { - helpIcon.icon() - } - .rowArrow(isShowArrow) - .buttonStyle(.row) - .sheet(isPresented: $isShowSupport) { - SupportView() - .presentationDetents([.medium]) - .presentationContentInteraction(.resizes) - .presentationCompactAdaptation(.sheet) - .scrollDisabled(true) - } + var notificationsIcon: Image { + switch iconStyle { + case .line: + return Image.Base.notification + case .fill: + return Image.Base.Notification.fill + case .twoTone: + return Image.Base.Notification.TwoTone.fill + } + } - Row("Send feedback") { - isShowFeedback.toggle() - } leading: { - chatIcon.icon() - } - .rowArrow(isShowArrow) - .buttonStyle(.row) - .sheet(isPresented: $isShowFeedback) { - FeedbackView() - .presentationDetents([.height(560)]) - .presentationContentInteraction(.resizes) - .presentationCompactAdaptation(.sheet) - .scrollDisabled(true) - } + // App Store Review + private var help: some View { + SectionView(L10n.Settings.supportSection) { + VStack(alignment: .leading) { + Row("Get help") { + #if os(iOS) + router.present(.support, detents: [.medium]) + #endif + } leading: { + helpIcon.icon() } + .rowArrow() + .buttonStyle(.row) + + Row("Send feedback") { + #if os(iOS) + router.present(.feedback, detents: [.medium]) + #endif + } leading: { + chatIcon.icon() + } + .rowArrow() + .buttonStyle(.row) } } + } - var heartIcon: Image { - switch iconStyle { - case .line: - return Image.Base.heart - case .fill: - return Image.Base.Heart.fill - case .twoTone: - return Image.Base.Heart.TwoTone.fill - } + var heartIcon: Image { + switch iconStyle { + case .line: + return Image.Base.heart + case .fill: + return Image.Base.Heart.fill + case .twoTone: + return Image.Base.Heart.TwoTone.fill } + } - var mailIcon: Image { - switch iconStyle { - case .line: - return Image.Base.message - case .fill: - return Image.Base.Message.fill - case .twoTone: - return Image.Base.Message.TwoTone.fill - } + var mailIcon: Image { + switch iconStyle { + case .line: + return Image.Base.message + case .fill: + return Image.Base.Message.fill + case .twoTone: + return Image.Base.Message.TwoTone.fill } + } - var chatIcon: Image { - switch iconStyle { - case .line: - return Image.Base.chat - case .fill: - return Image.Base.Chat.fill - case .twoTone: - return Image.Base.Chat.twoTone - } + var chatIcon: Image { + switch iconStyle { + case .line: + return Image.Base.chat + case .fill: + return Image.Base.Chat.fill + case .twoTone: + return Image.Base.Chat.twoTone } + } - var infoIcon: Image { - switch iconStyle { - case .line: - return Image.Base.Info.circle - case .fill: - return Image.Base.Info.Circle.fill - case .twoTone: - return Image.Base.Info.Circle.twoTone - } + var infoIcon: Image { + switch iconStyle { + case .line: + return Image.Base.Info.circle + case .fill: + return Image.Base.Info.Circle.fill + case .twoTone: + return Image.Base.Info.Circle.twoTone } + } - var oversizeIcon: Image { - switch iconStyle { - case .line: - return Image.Brands.oversize - case .fill: - return Image.Brands.Oversize.fill - case .twoTone: - return Image.Brands.Oversize.TwoTone.fill - } + var oversizeIcon: Image { + switch iconStyle { + case .line: + return Image.Brands.oversize + case .fill: + return Image.Brands.Oversize.fill + case .twoTone: + return Image.Brands.Oversize.TwoTone.fill } + } - var helpIcon: Image { - switch iconStyle { - case .line: - return Image.Alert.Help.circle - case .fill: - return Image.Alert.Help.Circle.fill - case .twoTone: - return Image.Alert.Help.Circle.twoTone - } + var helpIcon: Image { + switch iconStyle { + case .line: + return Image.Alert.Help.circle + case .fill: + return Image.Alert.Help.Circle.fill + case .twoTone: + return Image.Alert.Help.Circle.twoTone } + } - private var about: some View { - SectionView { - VStack(spacing: .zero) { - NavigationLink(destination: AboutView()) { - Row(L10n.Settings.about) { - infoIcon.icon() - } - .rowArrow(isShowArrow) - } - .buttonStyle(.row) + private var about: some View { + SectionView { + VStack(spacing: .zero) { + Row(L10n.Settings.about) { + router.move(.about) + } leading: { + infoIcon.icon() } + .rowArrow() } - } - - var soundsAndVibrationTitle: String { - if FeatureFlags.app.sounds.valueOrFalse, FeatureFlags.app.vibration.valueOrFalse { - return L10n.Settings.soundsAndVibration - } else if FeatureFlags.app.sounds.valueOrFalse, !FeatureFlags.app.vibration.valueOrFalse { - return L10n.Settings.sounds - } else if !FeatureFlags.app.sounds.valueOrFalse, FeatureFlags.app.vibration.valueOrFalse { - return L10n.Settings.vibration - } else { - return "" - } - } - } -#endif -// Mac OS Settings -#if os(iOS) - extension SettingsView { - private var macSettings: some View { - VStack(alignment: .center, spacing: 0) { - Text("Mac") - } - .frame(width: 400, height: 300) - .navigationTitle(L10n.Settings.apperance) - .preferredColorScheme(theme.appearance.colorScheme) + .buttonStyle(.row) } } -#endif -#if os(iOS) - public extension SettingsView where HeadSection == EmptyView { - init(@ViewBuilder appSection: () -> AppSection) { - self.init(appSection: appSection, headSection: { EmptyView() }) + var soundsAndVibrationTitle: String { + if FeatureFlags.app.sounds.valueOrFalse, FeatureFlags.app.vibration.valueOrFalse { + return L10n.Settings.soundsAndVibration + } else if FeatureFlags.app.sounds.valueOrFalse, !FeatureFlags.app.vibration.valueOrFalse { + return L10n.Settings.sounds + } else if !FeatureFlags.app.sounds.valueOrFalse, FeatureFlags.app.vibration.valueOrFalse { + return L10n.Settings.vibration + } else { + return "" } } +} -#endif -#if os(iOS) - extension UINavigationController: UIGestureRecognizerDelegate { - override open func viewDidLoad() { - super.viewDidLoad() - interactivePopGestureRecognizer?.delegate = self +extension SettingsView { + private var macSettings: some View { + VStack(alignment: .center, spacing: 0) { + Text("Mac") } + .frame(width: 400, height: 300) + .navigationTitle(L10n.Settings.apperance) + .preferredColorScheme(theme.appearance.colorScheme) + } +} - public func gestureRecognizerShouldBegin(_: UIGestureRecognizer) -> Bool { - viewControllers.count > 1 - } +public extension SettingsView where HeadSection == EmptyView { + init(@ViewBuilder appSection: () -> AppSection) { + self.init(appSection: appSection, headSection: { EmptyView() }) } -#endif +} diff --git a/Sources/OversizeKit/SettingsKit/Views/SoundAndVibration/SoundsAndVibrationsSettingsView.swift b/Sources/OversizeKit/SettingsKit/Views/SoundAndVibration/SoundsAndVibrationsSettingsView.swift index ed0f81c..6465b3a 100644 --- a/Sources/OversizeKit/SettingsKit/Views/SoundAndVibration/SoundsAndVibrationsSettingsView.swift +++ b/Sources/OversizeKit/SettingsKit/Views/SoundAndVibration/SoundsAndVibrationsSettingsView.swift @@ -9,84 +9,72 @@ import OversizeServices import OversizeUI import SwiftUI -// swiftlint:disable line_length -#if os(iOS) - public struct SoundsAndVibrationsSettingsView: View { - @Environment(\.verticalSizeClass) private var verticalSizeClass - @Environment(\.iconStyle) var iconStyle: IconStyle - @Environment(\.isPortrait) var isPortrait - @Environment(\.presentationMode) var presentationMode - @State var offset = CGPoint(x: 0, y: 0) - @StateObject var settingsService = SettingsService() +public struct SoundsAndVibrationsSettingsView: View { + @Environment(\.iconStyle) var iconStyle: IconStyle + @StateObject var settingsService = SettingsService() - public var body: some View { - PageView(title) { - iOSSettings - .surfaceContentRowMargins() - } - .leadingBar { - if !isPortrait, verticalSizeClass == .regular { - EmptyView() - } else { - BarButton(.back) - } - } - .backgroundSecondary() + public init() {} + + public var body: some View { + Page(title) { + iOSSettings + .surfaceContentRowMargins() } + .backgroundSecondary() + } - private var title: String { - if FeatureFlags.app.sounds.valueOrFalse, FeatureFlags.app.vibration.valueOrFalse { - return L10n.Settings.soundsAndVibration - } else if FeatureFlags.app.sounds.valueOrFalse, !FeatureFlags.app.vibration.valueOrFalse { - return L10n.Settings.sounds - } else if !FeatureFlags.app.sounds.valueOrFalse, FeatureFlags.app.vibration.valueOrFalse { - return L10n.Settings.vibration - } else { - return "" - } + private var title: String { + if FeatureFlags.app.sounds.valueOrFalse, FeatureFlags.app.vibration.valueOrFalse { + return L10n.Settings.soundsAndVibration + } else if FeatureFlags.app.sounds.valueOrFalse, !FeatureFlags.app.vibration.valueOrFalse { + return L10n.Settings.sounds + } else if !FeatureFlags.app.sounds.valueOrFalse, FeatureFlags.app.vibration.valueOrFalse { + return L10n.Settings.vibration + } else { + return "" } } +} - extension SoundsAndVibrationsSettingsView { - private var iOSSettings: some View { - VStack(alignment: .center, spacing: 0) { - soundsAndVibrations - } +extension SoundsAndVibrationsSettingsView { + private var iOSSettings: some View { + VStack(alignment: .center, spacing: 0) { + soundsAndVibrations } } +} - extension SoundsAndVibrationsSettingsView { - private var soundsAndVibrations: some View { - SectionView { - VStack(spacing: .zero) { - if FeatureFlags.app.sounds.valueOrFalse { - Switch(isOn: $settingsService.soundsEnabled) { - Row(L10n.Settings.sounds) { - IconDeprecated(.music) - } +extension SoundsAndVibrationsSettingsView { + private var soundsAndVibrations: some View { + SectionView { + VStack(spacing: .zero) { + if FeatureFlags.app.sounds.valueOrFalse { + Switch(isOn: $settingsService.soundsEnabled) { + Row(L10n.Settings.sounds) { + IconDeprecated(.music) } } + } - if FeatureFlags.app.vibration.valueOrFalse { - Switch(isOn: $settingsService.vibrationEnabled) { - Row(L10n.Settings.vibration) { - vibrationIcon.icon() - } + if FeatureFlags.app.vibration.valueOrFalse { + Switch(isOn: $settingsService.vibrationEnabled) { + Row(L10n.Settings.vibration) { + vibrationIcon.icon() } } } } } + } - var vibrationIcon: Image { - switch iconStyle { - case .line: - return Image.Mobile.vibration - case .fill: - return Image.Mobile.Vibration.fill - case .twoTone: - return Image.Mobile.Vibration.twoTone - } + var vibrationIcon: Image { + switch iconStyle { + case .line: + return Image.Mobile.vibration + case .fill: + return Image.Mobile.Vibration.fill + case .twoTone: + return Image.Mobile.Vibration.twoTone } } -#endif +} diff --git a/Sources/OversizeKit/SettingsKit/Views/iCloud/iCloudSettingsView.swift b/Sources/OversizeKit/SettingsKit/Views/iCloud/iCloudSettingsView.swift index 59eab6b..6a42b66 100644 --- a/Sources/OversizeKit/SettingsKit/Views/iCloud/iCloudSettingsView.swift +++ b/Sources/OversizeKit/SettingsKit/Views/iCloud/iCloudSettingsView.swift @@ -9,71 +9,65 @@ import OversizeUI import SwiftUI // swiftlint:disable line_length type_name -#if os(iOS) - public struct iCloudSettingsView: View { // Synchronization - @Environment(\.presentationMode) var presentationMode - @Environment(\.verticalSizeClass) private var verticalSizeClass - @Environment(\.isPortrait) var isPortrait - @StateObject var settingsService = SettingsService() - @State var offset = CGPoint(x: 0, y: 0) - public var body: some View { - PageView(L10n.Title.synchronization) { - iOSSettings - .surfaceContentRowMargins() - } - .leadingBar { - BarButton(.back) - } - .backgroundSecondary() +public struct iCloudSettingsView: View { // Synchronization + @StateObject var settingsService = SettingsService() + + public init() {} + + public var body: some View { + Page(L10n.Title.synchronization) { + iOSSettings + .surfaceContentRowMargins() } + .backgroundSecondary() } +} - extension iCloudSettingsView { - private var iOSSettings: some View { - VStack(alignment: .center, spacing: 0) { - soundsAndVibrations - } +extension iCloudSettingsView { + private var iOSSettings: some View { + VStack(alignment: .center, spacing: 0) { + soundsAndVibrations } } +} - extension iCloudSettingsView { - private var soundsAndVibrations: some View { - SectionView { - VStack(spacing: .zero) { - if FeatureFlags.app.сloudKit.valueOrFalse { - Switch(isOn: $settingsService.cloudKitEnabled) { - Row(L10n.Settings.iCloudSync) { - Image.Weather.Cloud.square.icon() - } - .premium() +extension iCloudSettingsView { + private var soundsAndVibrations: some View { + SectionView { + VStack(spacing: .zero) { + if FeatureFlags.app.сloudKit.valueOrFalse { + Switch(isOn: $settingsService.cloudKitEnabled) { + Row(L10n.Settings.iCloudSync) { + Image.Weather.Cloud.square.icon() } - .onPremiumTap() + .premium() } + .onPremiumTap() + } - if FeatureFlags.secure.CVVCodes.valueOrFalse { - Switch(isOn: $settingsService.cloudKitCVVEnabled) { - Row(L10n.Security.iCloudSyncCVVDescriptionCloudKit, - subtitle: settingsService.cloudKitCVVEnabled ? L10n.Security.iCloudSyncCVVDescriptionCloudKit : L10n.Security.iCloudSyncCVVDescriptionLocal) - { - Image.Security.cloudLock - .icon() - .frame(width: 24, height: 24) - } - .premium() - .onPremiumTap() + if FeatureFlags.secure.CVVCodes.valueOrFalse { + Switch(isOn: $settingsService.cloudKitCVVEnabled) { + Row(L10n.Security.iCloudSyncCVVDescriptionCloudKit, + subtitle: settingsService.cloudKitCVVEnabled ? L10n.Security.iCloudSyncCVVDescriptionCloudKit : L10n.Security.iCloudSyncCVVDescriptionLocal) + { + Image.Security.cloudLock + .icon() + .frame(width: 24, height: 24) } + .premium() + .onPremiumTap() } + } - if FeatureFlags.app.healthKit.valueOrFalse { - Switch(isOn: $settingsService.healthKitEnabled) { - Row("HealthKit synchronization", subtitle: "After switching on, data from the Health app will be downloaded") { - Image.Romantic.heart.icon() - } + if FeatureFlags.app.healthKit.valueOrFalse { + Switch(isOn: $settingsService.healthKitEnabled) { + Row("HealthKit synchronization", subtitle: "After switching on, data from the Health app will be downloaded") { + Image.Romantic.heart.icon() } } } } } } -#endif +} diff --git a/Sources/OversizeKit/StateKit/LoadingViewState.swift b/Sources/OversizeKit/StateKit/LoadingViewState.swift new file mode 100644 index 0000000..05176b8 --- /dev/null +++ b/Sources/OversizeKit/StateKit/LoadingViewState.swift @@ -0,0 +1,49 @@ +// +// Copyright © 2024 Alexander Romanov +// LoadingViewState.swift, created on 25.04.2024 +// + +import Foundation +import OversizeModels + +public enum LoadingViewState: Equatable { + case idle + case loading + case result(Result) + case error(AppError) +} + +public extension LoadingViewState { + var isLoading: Bool { + switch self { + case .loading, .idle: + return true + default: + return false + } + } + + var result: Result? { + switch self { + case let .result(result): + return result + default: + return nil + } + } + + static func == (lhs: LoadingViewState, rhs: LoadingViewState) -> Bool { + switch (lhs, rhs) { + case (.idle, .idle): + return true + case (.loading, .loading): + return true + case (.result, .result): + return true + case (.error, .error): + return true + default: + return false + } + } +} diff --git a/Sources/OversizeKit/StoreKit/StoreScreen/AppStoreProduct/AppStoreProductViewController.swift b/Sources/OversizeKit/StoreKit/StoreScreen/AppStoreProduct/AppStoreProductViewController.swift index 3b18d10..1928817 100644 --- a/Sources/OversizeKit/StoreKit/StoreScreen/AppStoreProduct/AppStoreProductViewController.swift +++ b/Sources/OversizeKit/StoreKit/StoreScreen/AppStoreProduct/AppStoreProductViewController.swift @@ -57,7 +57,7 @@ } } - extension AppStoreProductViewController: SKStoreProductViewControllerDelegate { + extension AppStoreProductViewController: @preconcurrency SKStoreProductViewControllerDelegate { public func productViewControllerDidFinish(_ viewController: SKStoreProductViewController) { viewController.dismiss(animated: true) } diff --git a/Sources/OversizeKit/StoreKit/StoreScreen/StoreInstuctinsView.swift b/Sources/OversizeKit/StoreKit/StoreScreen/StoreInstuctinsView.swift index a736466..b579acd 100644 --- a/Sources/OversizeKit/StoreKit/StoreScreen/StoreInstuctinsView.swift +++ b/Sources/OversizeKit/StoreKit/StoreScreen/StoreInstuctinsView.swift @@ -59,7 +59,7 @@ public struct StoreInstuctinsView: View { .environmentObject(viewModel) } } - .onChange(of: isPremium) { status in + .onChange(of: isPremium) { _, status in if status { dismiss() } @@ -80,12 +80,12 @@ public struct StoreInstuctinsView: View { VStack(spacing: .xSmall) { Text("How your free trial works") .largeTitle() - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) if viewModel.isHaveSale { Group { Text("Begin your path towards feeling better with a ") - .foregroundColor(.onSurfaceMediumEmphasis) + .foregroundColor(.onSurfaceSecondary) + Text("--% discount") .foregroundColor(.accent) @@ -107,7 +107,7 @@ public struct StoreInstuctinsView: View { .overlay { ScrollArrow(width: 30, offset: -5 + (offset * 0.05)) .stroke(style: StrokeStyle(lineWidth: 5, lineCap: .round)) - .foregroundColor(.onSurfaceHighEmphasis.opacity(0.3)) + .foregroundColor(.onSurfacePrimary.opacity(0.3)) .frame(width: 30) .offset(y: screenSize.safeAreaHeight - 300) // .opacity(1 - (offset * 0.01)) @@ -128,12 +128,12 @@ public struct StoreInstuctinsView: View { VStack(spacing: .xSmall) { Text("How your free trial works") .largeTitle() - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) if viewModel.isHaveSale { Group { Text("Begin your path towards feeling better with a ") - .foregroundColor(.onSurfaceMediumEmphasis) + .foregroundColor(.onSurfaceSecondary) + Text("\(viewModel.saleProcent)% discount") .foregroundColor(.accent) @@ -155,7 +155,7 @@ public struct StoreInstuctinsView: View { .overlay { ScrollArrow(width: 30, offset: -5 + (offset * 0.05)) .stroke(style: StrokeStyle(lineWidth: 5, lineCap: .round)) - .foregroundColor(.onSurfaceHighEmphasis.opacity(0.3)) + .foregroundColor(.onSurfacePrimary.opacity(0.3)) .frame(width: 30) .offset(y: screenSize.safeAreaHeight - 300) // .opacity(1 - (offset * 0.01)) @@ -179,7 +179,7 @@ public struct StoreInstuctinsView: View { await viewModel.updateSubscriptionStatus(products: data) } } - .onChange(of: data.purchasedAutoRenewable) { _ in + .onChange(of: data.purchasedAutoRenewable) { _, _ in Task { await viewModel.updateSubscriptionStatus(products: data) } @@ -192,7 +192,7 @@ public struct StoreInstuctinsView: View { HStack(alignment: .top, spacing: .small) { Resource.Store.zap .renderingMode(.template) - .foregroundColor(.onPrimaryHighEmphasis) + .foregroundColor(.onPrimary) .padding(.small) .background { Circle() @@ -226,7 +226,7 @@ public struct StoreInstuctinsView: View { HStack(alignment: .top, spacing: .small) { Image.Base.Notification.fill .renderingMode(.template) - .foregroundColor(Color.onSurfaceDisabled) + .foregroundColor(Color.onSurfaceTertiary) .padding(14) .background { Circle() @@ -252,7 +252,7 @@ public struct StoreInstuctinsView: View { HStack(alignment: .top, spacing: .small) { Image.Base.Star.fill .renderingMode(.template) - .foregroundColor(Color.onSurfaceDisabled) + .foregroundColor(Color.onSurfaceTertiary) .padding(14) .background { Circle() diff --git a/Sources/OversizeKit/StoreKit/StoreScreen/StoreSpecialOfferView.swift b/Sources/OversizeKit/StoreKit/StoreScreen/StoreSpecialOfferView.swift index 2f522b6..e2c0f39 100644 --- a/Sources/OversizeKit/StoreKit/StoreScreen/StoreSpecialOfferView.swift +++ b/Sources/OversizeKit/StoreKit/StoreScreen/StoreSpecialOfferView.swift @@ -43,7 +43,7 @@ public struct StoreSpecialOfferView: View { } } - .onChange(of: isPremium) { status in + .onChange(of: isPremium) { _, status in if status { dismiss() } @@ -223,7 +223,7 @@ public struct StoreSpecialOfferView: View { .overlay { ScrollArrow(width: 30, offset: -5 + (offset * 0.05)) .stroke(style: StrokeStyle(lineWidth: 5, lineCap: .round)) - .foregroundColor(.onSurfaceHighEmphasis.opacity(0.3)) + .foregroundColor(.onSurfacePrimary.opacity(0.3)) .frame(width: 30) .offset(y: screenSize.safeAreaHeight - 280) .opacity(1 - (offset * 0.01)) @@ -232,7 +232,7 @@ public struct StoreSpecialOfferView: View { VStack(spacing: .zero) { Text("Additional features in\nthe subscription") .title() - .onBackgroundHighEmphasisForegroundColor() + .onBackgroundPrimaryForeground() .multilineTextAlignment(.center) .fixedSize() .padding(.top, .large) @@ -252,7 +252,7 @@ public struct StoreSpecialOfferView: View { .task { await viewModel.updateSubscriptionStatus(products: data) } - .onChange(of: data.purchasedAutoRenewable) { _ in + .onChange(of: data.purchasedAutoRenewable) { _, _ in Task { await viewModel.updateSubscriptionStatus(products: data) } @@ -264,12 +264,12 @@ public struct StoreSpecialOfferView: View { VStack(spacing: .zero) { Text(badgeText.uppercased()) .footnote(.semibold) - .onBackgroundMediumEmphasisForegroundColor() + .onBackgroundSecondaryForeground() .padding(.bottom, .xxxSmall) Text(headline) .title(.bold) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) .frame(maxWidth: .infinity, alignment: .center) Text(event.title) @@ -277,7 +277,7 @@ public struct StoreSpecialOfferView: View { .foregroundColor(titleColor) Text(description) - .foregroundColor(.onSurfaceMediumEmphasis) + .foregroundColor(.onSurfaceSecondary) .headline(.regular) .padding(.top, .xSmall) } @@ -332,7 +332,7 @@ public struct StoreSpecialOfferView: View { if let accentColor = event.accentColor { return Color(hex: accentColor) } else { - return Color.onBackgroundHighEmphasis + return Color.onBackgroundPrimary } } diff --git a/Sources/OversizeKit/StoreKit/StoreScreen/StoreView.swift b/Sources/OversizeKit/StoreKit/StoreScreen/StoreView.swift index b7a9d9f..861a886 100644 --- a/Sources/OversizeKit/StoreKit/StoreScreen/StoreView.swift +++ b/Sources/OversizeKit/StoreKit/StoreScreen/StoreView.swift @@ -26,7 +26,7 @@ import SwiftUI } public var body: some View { - PageView { + Page { Group { switch viewModel.state { case .initial, .loading: @@ -39,22 +39,22 @@ import SwiftUI } .paddingContent(.horizontal) } - .backgroundLinerGradient(LinearGradient(colors: [.backgroundPrimary, .backgroundSecondary], startPoint: .top, endPoint: .center)) - .titleLabel { - PremiumLabel(image: Resource.Store.zap, text: Info.store.subscriptionsName, size: .medium) - } - .leadingBar { - if !isPortrait, verticalSizeClass == .regular, isClosable { - EmptyView() - } else { - BarButton(.back) - } - } - .trailingBar { - if isClosable { - BarButton(.close) - } - } +// .backgroundLinerGradient(LinearGradient(colors: [.backgroundPrimary, .backgroundSecondary], startPoint: .top, endPoint: .center)) +// .titleLabel { +// PremiumLabel(image: Resource.Store.zap, text: Info.store.subscriptionsName, size: .medium) +// } +// .leadingBar { +// if !isPortrait, verticalSizeClass == .regular, isClosable { +// EmptyView() +// } else { +// BarButton(.back) +// } +// } +// .trailingBar { +// if isClosable { +// BarButton(.close) +// } +// } .bottomToolbar(style: .none) { if !viewModel.isPremium { StorePaymentButtonBar() @@ -93,11 +93,11 @@ import SwiftUI VStack(spacing: .xxSmall) { Text(titleText) .title() - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) Text(subtitleText) .headline() - .foregroundColor(.onSurfaceMediumEmphasis) + .foregroundColor(.onSurfaceSecondary) } .multilineTextAlignment(.center) @@ -120,11 +120,11 @@ import SwiftUI VStack(spacing: .xxSmall) { Text(titleText) .title() - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) Text(subtitleText) .headline() - .foregroundColor(.onSurfaceMediumEmphasis) + .foregroundColor(.onSurfaceSecondary) } .multilineTextAlignment(.center) @@ -163,14 +163,14 @@ import SwiftUI await viewModel.updateSubscriptionStatus(products: data) } } - .onChange(of: data.purchasedAutoRenewable) { _ in + .onChange(of: data.purchasedAutoRenewable) { _, _ in Task { // When `purchasedSubscriptions` changes, get the latest subscription status. await viewModel.updateSubscriptionStatus(products: data) } } - .onChange(of: viewModel.isPremium) { newValue in - isShowFireworks = newValue + .onChange(of: viewModel.isPremium) { _, status in + isShowFireworks = status DispatchQueue.main.asyncAfter(deadline: .now() + 30) { isShowFireworks = false } diff --git a/Sources/OversizeKit/StoreKit/StoreScreen/ViewModel/StoreViewModel.swift b/Sources/OversizeKit/StoreKit/StoreScreen/ViewModel/StoreViewModel.swift index 708b461..346fe95 100644 --- a/Sources/OversizeKit/StoreKit/StoreScreen/ViewModel/StoreViewModel.swift +++ b/Sources/OversizeKit/StoreKit/StoreScreen/ViewModel/StoreViewModel.swift @@ -23,7 +23,9 @@ public class StoreViewModel: ObservableObject { } @Injected(\.storeKitService) var storeKitService: StoreKitService - @Injected(\.localNotificationService) var localNotificationService: LocalNotificationServiceProtocol + #if !os(tvOS) + @Injected(\.localNotificationService) var localNotificationService: LocalNotificationServiceProtocol + #endif @Published var state = State.initial @@ -136,7 +138,6 @@ extension StoreViewModel { } } - // Percentage of decrease = |239.88 - 59.99|/239.88 = 179.89/239.88 = 0.74991662497916 = 74.991662497916% var saleProcent: String { guard let yearSubscriptionProduct else { return "" } if let monthSubscriptionProduct { @@ -303,23 +304,25 @@ extension StoreViewModel { } func addTrialNotification(product: Product) async { - if product.type == .autoRenewable, product.subscription?.introductoryOffer != nil { - do { - try await localNotificationService.requestAuthorization() - if let trialDaysCount = product.trialDaysCount { - let timeInterval = TimeInterval((trialDaysCount - 2) * 24 * 60 * 60) - let notificationTime = Date().addingTimeInterval(timeInterval) - let dateComponents = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: notificationTime) - await localNotificationService.schedule(localNotification: .init( - id: UUID(), - title: "Trial ends soon", - body: "Subscription ends in 2 days", - dateComponents: dateComponents, - repeats: false - )) - } - } catch {} - } + #if !os(tvOS) + if product.type == .autoRenewable, product.subscription?.introductoryOffer != nil { + do { + try await localNotificationService.requestAuthorization() + if let trialDaysCount = product.trialDaysCount { + let timeInterval = TimeInterval((trialDaysCount - 2) * 24 * 60 * 60) + let notificationTime = Date().addingTimeInterval(timeInterval) + let dateComponents = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: notificationTime) + await localNotificationService.schedule(localNotification: .init( + id: UUID(), + title: "Trial ends soon", + body: "Subscription ends in 2 days", + dateComponents: dateComponents, + repeats: false + )) + } + } catch {} + } + #endif } } diff --git a/Sources/OversizeKit/StoreKit/ViewModifier/PremiumBlockOverlay.swift b/Sources/OversizeKit/StoreKit/ViewModifier/PremiumBlockOverlay.swift index 0594af7..7fa7602 100644 --- a/Sources/OversizeKit/StoreKit/ViewModifier/PremiumBlockOverlay.swift +++ b/Sources/OversizeKit/StoreKit/ViewModifier/PremiumBlockOverlay.swift @@ -45,12 +45,12 @@ public struct PremiumBlockOverlay: ViewModifier { VStack(spacing: .small) { Text(title) .title() - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) if let subtitle { Text(subtitle) .headline(.medium) - .foregroundColor(.onSurfaceMediumEmphasis) + .foregroundColor(.onSurfaceSecondary) } } .multilineTextAlignment(.center) diff --git a/Sources/OversizeKit/StoreKit/Views/BuyButtonStyle.swift b/Sources/OversizeKit/StoreKit/Views/BuyButtonStyle.swift index 3922ccd..9e71cad 100644 --- a/Sources/OversizeKit/StoreKit/Views/BuyButtonStyle.swift +++ b/Sources/OversizeKit/StoreKit/Views/BuyButtonStyle.swift @@ -33,9 +33,11 @@ public struct PaymentButtonStyle: ButtonStyle { @Environment(\.isLoading) private var isLoading: Bool @Environment(\.isAccent) private var isAccent: Bool @Environment(\.elevation) private var elevation: Elevation - @Environment(\.controlSize) var controlSize: ControlSize @Environment(\.controlBorderShape) var controlBorderShape: ControlBorderShape @Environment(\.isBordered) var isBordered: Bool + #if !os(tvOS) + @Environment(\.controlSize) var controlSize: ControlSize + #endif private let isInfinityWidth: Bool? @@ -47,7 +49,7 @@ public struct PaymentButtonStyle: ButtonStyle { configuration.label .body(true) .opacity(isLoading ? 0 : 1) - .foregroundColor(.onPrimaryHighEmphasis) + .foregroundColor(.onPrimary) .padding(.horizontal, horizontalPadding) .padding(.vertical, verticalPadding) .frame(maxWidth: maxWidth) @@ -65,7 +67,7 @@ public struct PaymentButtonStyle: ButtonStyle { ) .overlay { RoundedRectangle(cornerRadius: 10, style: .continuous) - .strokeBorder(Color.onSurfaceHighEmphasis.opacity(0.15), lineWidth: 2) + .strokeBorder(Color.onSurfacePrimary.opacity(0.15), lineWidth: 2) .opacity(isBordered || theme.borderButtons ? 1 : 0) } } @@ -74,40 +76,48 @@ public struct PaymentButtonStyle: ButtonStyle { private func loadingView(for _: ButtonRole?) -> some View { if isLoading { ProgressView() - .progressViewStyle(CircularProgressViewStyle(tint: Color.onPrimaryHighEmphasis)) + .progressViewStyle(CircularProgressViewStyle(tint: Color.onPrimary)) } else { EmptyView() } } private var horizontalPadding: Space { - switch controlSize { - case .mini: - return .xxSmall - case .small: - return .small - case .regular: - return .small - case .large, .extraLarge: + #if os(tvOS) return .medium - @unknown default: - return .zero - } + #else + switch controlSize { + case .mini: + return .xxSmall + case .small: + return .small + case .regular: + return .small + case .large, .extraLarge: + return .medium + @unknown default: + return .zero + } + #endif } private var verticalPadding: Space { - switch controlSize { - case .mini: - return .xxSmall - case .small: - return .xxSmall - case .regular: - return .small - case .large, .extraLarge: + #if os(tvOS) return .medium - @unknown default: - return .zero - } + #else + switch controlSize { + case .mini: + return .xxSmall + case .small: + return .xxSmall + case .regular: + return .small + case .large, .extraLarge: + return .medium + @unknown default: + return .zero + } + #endif } private var backgroundOpacity: CGFloat { @@ -119,13 +129,17 @@ public struct PaymentButtonStyle: ButtonStyle { } private var maxWidth: CGFloat? { - if isInfinityWidth == nil, controlSize == .regular { - return .infinity - } else if let infinity = isInfinityWidth, infinity == true { - return .infinity - } else { + #if os(tvOS) return nil - } + #else + if isInfinityWidth == nil, controlSize == .regular { + return .infinity + } else if let infinity = isInfinityWidth, infinity == true { + return .infinity + } else { + return nil + } + #endif } } diff --git a/Sources/OversizeKit/StoreKit/Views/FireworksBubbles.swift b/Sources/OversizeKit/StoreKit/Views/FireworksBubbles.swift index c2e78af..6db793a 100644 --- a/Sources/OversizeKit/StoreKit/Views/FireworksBubbles.swift +++ b/Sources/OversizeKit/StoreKit/Views/FireworksBubbles.swift @@ -54,7 +54,7 @@ struct FireworksBubbles: View { ZStack { ForEach(0 ..< Int.random(in: 2 ... 5), id: \.self) { _ in Circle() - .fill(Color.onPrimaryDisabled) + .fill(Color.onPrimaryTertiary) .frame(width: 30, height: 30) .modifier(ParticlesBubblesModifier()) .offset(x: CGFloat.random(in: -200 ... 200), y: CGFloat.random(in: -200 ... 200)) diff --git a/Sources/OversizeKit/StoreKit/Views/PrmiumBannerRow.swift b/Sources/OversizeKit/StoreKit/Views/PrmiumBannerRow.swift index d479082..7818f76 100644 --- a/Sources/OversizeKit/StoreKit/Views/PrmiumBannerRow.swift +++ b/Sources/OversizeKit/StoreKit/Views/PrmiumBannerRow.swift @@ -67,14 +67,14 @@ public struct PrmiumBannerRow: View { Text(Info.store.subscriptionsName) .headline(.semibold) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) Spacer() HStack(spacing: .small) { Text(viewModel.subsribtionStatusText) .headline(.medium) - .foregroundColor(.onSurfaceMediumEmphasis) + .foregroundColor(.onSurfaceSecondary) Circle() .foregroundColor(viewModel.subsribtionStatusColor) @@ -118,13 +118,13 @@ public extension PrmiumBannerRow { } .background( RoundedRectangle(cornerRadius: Radius.small.rawValue, style: .continuous) - .fill(Color.onPrimaryHighEmphasis + .fill(Color.onPrimary )) Text(Info.store.subscriptionsDescription) .headline(.semibold) - .onPrimaryHighEmphasisForegroundColor() + .onPrimaryForeground() .multilineTextAlignment(.center) .padding(.top, Space.xSmall) .frame(maxWidth: 260) diff --git a/Sources/OversizeKit/StoreKit/Views/StoreFeatureDetailView.swift b/Sources/OversizeKit/StoreKit/Views/StoreFeatureDetailView.swift index d0ff026..d192629 100644 --- a/Sources/OversizeKit/StoreKit/Views/StoreFeatureDetailView.swift +++ b/Sources/OversizeKit/StoreKit/Views/StoreFeatureDetailView.swift @@ -45,13 +45,16 @@ public struct StoreFeatureDetailView: View { Button { dismiss() } label: { - IconDeprecated(.xMini, color: selection.screenURL != nil ? .onPrimaryHighEmphasis : .onSurfaceDisabled) - .padding(.xxSmall) - .background { - Circle() - .fill(.ultraThinMaterial) - } - .padding(.small) + IconDeprecated( + .xMini, + color: selection.screenURL != nil ? .onPrimary : .onSurfaceTertiary + ) + .padding(.xxSmall) + .background { + Circle() + .fill(.ultraThinMaterial) + } + .padding(.small) } } #endif diff --git a/Sources/OversizeKit/StoreKit/Views/StoreFeaturesLargeView.swift b/Sources/OversizeKit/StoreKit/Views/StoreFeaturesLargeView.swift index 0668344..3b20f80 100644 --- a/Sources/OversizeKit/StoreKit/Views/StoreFeaturesLargeView.swift +++ b/Sources/OversizeKit/StoreKit/Views/StoreFeaturesLargeView.swift @@ -54,11 +54,11 @@ struct StoreFeaturesLargeView: View { VStack(spacing: .xxSmall) { Text(feature.title.valueOrEmpty) .title2(.bold) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) Text(feature.subtitle.valueOrEmpty) .body(.medium) - .foregroundColor(.onSurfaceMediumEmphasis) + .foregroundColor(.onSurfaceSecondary) } .padding(.vertical, .medium) .padding(.horizontal, .xSmall) @@ -119,11 +119,11 @@ struct StoreFeaturesLargeView: View { VStack(spacing: .xSmall) { Text(feature.title.valueOrEmpty) .title2(.bold) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) Text(feature.subtitle.valueOrEmpty) .body(.medium) - .foregroundColor(.onSurfaceMediumEmphasis) + .foregroundColor(.onSurfaceSecondary) } } .padding(.vertical, .large) diff --git a/Sources/OversizeKit/StoreKit/Views/StoreFeaturesView.swift b/Sources/OversizeKit/StoreKit/Views/StoreFeaturesView.swift index a0c895b..c20fc1a 100644 --- a/Sources/OversizeKit/StoreKit/Views/StoreFeaturesView.swift +++ b/Sources/OversizeKit/StoreKit/Views/StoreFeaturesView.swift @@ -31,7 +31,7 @@ struct StoreFeaturesView: View { .renderingMode(.template) } } - .onPrimaryHighEmphasisForegroundColor() + .onPrimaryForeground() .iconOnSurface(surfaceSolor: backgroundColor(feature: feature)) } .rowArrow() diff --git a/Sources/OversizeKit/StoreKit/Views/StorePaymentButtonBar.swift b/Sources/OversizeKit/StoreKit/Views/StorePaymentButtonBar.swift index 0399d3b..42ad0df 100644 --- a/Sources/OversizeKit/StoreKit/Views/StorePaymentButtonBar.swift +++ b/Sources/OversizeKit/StoreKit/Views/StorePaymentButtonBar.swift @@ -23,7 +23,7 @@ struct StorePaymentButtonBar: View { if showDescription { Text(viewModel.selectedProductButtonDescription) .subheadline(.semibold) - .foregroundColor(.onSurfaceMediumEmphasis) + .foregroundColor(.onSurfaceSecondary) .padding(.vertical, 20) } diff --git a/Sources/OversizeKit/StoreKit/Views/StoreProductView.swift b/Sources/OversizeKit/StoreKit/Views/StoreProductView.swift index 2fa0d08..a10a81c 100644 --- a/Sources/OversizeKit/StoreKit/Views/StoreProductView.swift +++ b/Sources/OversizeKit/StoreKit/Views/StoreProductView.swift @@ -148,7 +148,7 @@ public struct StoreProductView: View { if isSelected { Circle() - .fill(Color.onPrimaryHighEmphasis) + .fill(Color.onPrimary) .frame(width: 20, height: 20) .overlay { IconDeprecated(.checkMini, color: topLabelbackgroundColor) @@ -174,21 +174,21 @@ public struct StoreProductView: View { VStack(spacing: .zero) { Text(product.displayMonthsCount) .title2() - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) Text(product.displayMonthsCountDescription) .footnote(.bold) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) HStack(spacing: .zero) { Text(product.displayPrice) .subheadline(.semibold) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) .padding(.top, .xxxSmall) Text(product.displayPeriod) .caption2() - .foregroundColor(.onSurfaceMediumEmphasis) + .foregroundColor(.onSurfaceSecondary) .padding(.top, .xxxSmall) } .padding(.top, .xxxSmall) @@ -209,7 +209,7 @@ public struct StoreProductView: View { .fill(topLabelbackgroundColor) .frame(width: 20, height: 20) .overlay { - IconDeprecated(.checkMini, color: Color.onPrimaryHighEmphasis) + IconDeprecated(.checkMini, color: Color.onPrimary) } .padding(.top, .xxxSmall) .padding(.trailing, .xxxSmall) @@ -230,12 +230,12 @@ public struct StoreProductView: View { HStack { Text(product.displayName) .headline() - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) if isHaveSale, !isPurchased { Text("Save " + saleProcent + "%") .caption2(.bold) - .foregroundColor(.onPrimaryHighEmphasis) + .foregroundColor(.onPrimary) .padding(.horizontal, .xxSmall) .padding(.vertical, .xxxSmall) .background { @@ -267,14 +267,14 @@ public struct StoreProductView: View { .caption2() .padding(.top, .xxxSmall) } - .foregroundColor(.onSurfaceDisabled) + .foregroundColor(.onSurfaceTertiary) .padding(.vertical, .xxSmall) #if os(iOS) if isHaveSale, !isPurchased { Text("Save " + saleProcent + "%") .caption2(.bold) - .foregroundColor(.onPrimaryHighEmphasis) + .foregroundColor(.onPrimary) .padding(.vertical, .xxxSmall) .frame(maxWidth: .infinity) .background { @@ -299,7 +299,7 @@ public struct StoreProductView: View { VStack(alignment: .trailing, spacing: .xxSmall) { Text(product.displayPriceWithPeriod) .headline(.semibold) - .foregroundColor(.onSurfaceMediumEmphasis) + .foregroundColor(.onSurfaceSecondary) if let subscriptionUnit = product.subscription?.subscriptionPeriod.unit, subscriptionUnit == .year { HStack(spacing: 2) { @@ -307,12 +307,12 @@ public struct StoreProductView: View { Text(monthSubscriptionProduct.displayPrice) .strikethrough() .subheadline() - .foregroundColor(.onSurfaceDisabled) + .foregroundColor(.onSurfaceTertiary) } Text(product.displayMonthPrice + product.displayMonthPricePeriod) .subheadline() - .foregroundColor(.onSurfaceMediumEmphasis) + .foregroundColor(.onSurfaceSecondary) } } } @@ -379,7 +379,7 @@ public struct StoreProductView: View { var topLabelForegroundColor: Color { if isPurchased || isSelected { - return .onPrimaryHighEmphasis + return .onPrimary } else { return Palette.violet.color } @@ -387,7 +387,7 @@ public struct StoreProductView: View { var descriptionForegroundColor: Color { if isPurchased || product.type != .autoRenewable { - return .onSurfaceMediumEmphasis + return .onSurfaceSecondary } else { return .warning } diff --git a/Sources/OversizeKit/StoreKit/Views/SubscriptionPrivacyView.swift b/Sources/OversizeKit/StoreKit/Views/SubscriptionPrivacyView.swift index 692e91e..6b8ec96 100644 --- a/Sources/OversizeKit/StoreKit/Views/SubscriptionPrivacyView.swift +++ b/Sources/OversizeKit/StoreKit/Views/SubscriptionPrivacyView.swift @@ -21,11 +21,11 @@ struct SubscriptionPrivacyView: View { VStack(spacing: .xxSmall) { Text("About \(Info.store.subscriptionsName) subscription") .subheadline(.bold) - .foregroundColor(Color.onSurfaceDisabled) + .foregroundColor(Color.onSurfaceTertiary) Text("\(Info.store.subscriptionsName) subscription is required to get access to all functions. Regardless whether the subscription has free trial period or not it automatically renews with the price and duration given above unless it is canceled at least 24 hours before the end of the current period. Payment will be charged to your Apple ID account at the confirmation of purchase. Your account will be charged for renewal within 24 hours prior to the end of the current period. You can manage and cancel your subscriptions by going to your account settings on the App Store after purchase. Any unused portion of a free trial period, if offered, will be forfeited when the user purchases a subscription to that publication, where applicable.") .caption() - .foregroundColor(Color.onSurfaceMediumEmphasis) + .foregroundColor(Color.onSurfaceSecondary) #if os(iOS) HStack(spacing: .xxSmall) { @@ -62,7 +62,7 @@ struct SubscriptionPrivacyView: View { } } .subheadline(.bold) - .foregroundColor(Color.onSurfaceDisabled) + .foregroundColor(Color.onSurfaceTertiary) .padding(.top, .xxxSmall) #endif } diff --git a/Sources/OversizeKit/SystemKit/SystemServices.swift b/Sources/OversizeKit/SystemKit/SystemServices.swift index 733abbf..a9cc75f 100644 --- a/Sources/OversizeKit/SystemKit/SystemServices.swift +++ b/Sources/OversizeKit/SystemKit/SystemServices.swift @@ -4,27 +4,25 @@ // import Factory -import OversizeCore -import OversizeLocalizable import OversizeServices import OversizeStoreService import OversizeUI import SwiftUI public struct SystemServicesModifier: ViewModifier { - @Injected(\.appStateService) var appState: AppStateService - @Injected(\.settingsService) var settingsService: SettingsServiceProtocol - @Injected(\.appStoreReviewService) var appStoreReviewService: AppStoreReviewServiceProtocol + @Injected(\.appStateService) private var appState: AppStateService + @Injected(\.settingsService) private var settingsService: SettingsServiceProtocol + @Injected(\.appStoreReviewService) private var appStoreReviewService: AppStoreReviewServiceProtocol - @Environment(\.scenePhase) var scenePhase: ScenePhase - @Environment(\.theme) var theme: ThemeSettings - @AppStorage("AppState.PremiumState") var isPremium: Bool = false + @Environment(\.scenePhase) private var scenePhase: ScenePhase + @Environment(\.theme) private var theme: ThemeSettings + @AppStorage("AppState.PremiumState") private var isPremium: Bool = false - @StateObject var hudState = HUDDeprecated() - @State var blurRadius: CGFloat = 0 - @State var oppacity: CGFloat = 1 + @State private var blurRadius: CGFloat = 0 + @State private var oppacity: CGFloat = 1 + @State private var screnSize: ScreenSize = .init(width: 375, height: 667) - enum FullScreenSheet: Identifiable, Equatable { + private enum FullScreenSheet: Identifiable, Equatable, Sendable { case onboarding case payWall case lockscreen @@ -33,53 +31,59 @@ public struct SystemServicesModifier: ViewModifier { } } - public init() {} + public nonisolated init() {} public func body(content: Content) -> some View { GeometryReader { geometry in content - .onChange(of: scenePhase, perform: { value in - switch value { - case .active: - if settingsService.blurMinimizeEnabend { - withAnimation { - blurRadius = 0 - } - } - case .background: - if settingsService.blurMinimizeEnabend { - withAnimation { - blurRadius = 10 - } - } - case .inactive: - if settingsService.blurMinimizeEnabend { - withAnimation { - blurRadius = 10 - } - } - @unknown default: - break - } - }) .blur(radius: blurRadius) .preferredColorScheme(theme.appearance.colorScheme) + .premiumStatus(isPremium) + .theme(ThemeSettings()) + .screenSize(screnSize) #if os(iOS) .accentColor(theme.accentColor) #endif - .premiumStatus(isPremium) - .theme(ThemeSettings()) - .screenSize(geometry) - .hudDeprecated(isPresented: $hudState.isPresented, type: $hudState.type) { - HUDContent(title: hudState.title, image: hudState.image, type: hudState.type) + .onAppear(perform: { onAppear(geometry: geometry) }) + .onChange(of: scenePhase) { _, phase in + onChangeScenePhase(phase) + } + } + } + + private func onChangeScenePhase(_ phase: ScenePhase) { + switch phase { + case .active: + if settingsService.blurMinimizeEnabend { + withAnimation { + blurRadius = 0 + } + } + case .background: + if settingsService.blurMinimizeEnabend { + withAnimation { + blurRadius = 10 } - .environmentObject(hudState) + } + case .inactive: + if settingsService.blurMinimizeEnabend { + withAnimation { + blurRadius = 10 + } + } + @unknown default: + break } } + + private func onAppear(geometry: GeometryProxy) { + let updatedScreenSize = ScreenSize(geometry: geometry) + screnSize = updatedScreenSize + } } public extension View { - func systemServices() -> some View { + nonisolated func systemServices() -> some View { modifier(SystemServicesModifier()) } } diff --git a/Sources/OversizeLocationKit/AddressField/AddressField.swift b/Sources/OversizeLocationKit/AddressField/AddressField.swift index 1a8faa4..2582a24 100644 --- a/Sources/OversizeLocationKit/AddressField/AddressField.swift +++ b/Sources/OversizeLocationKit/AddressField/AddressField.swift @@ -50,14 +50,16 @@ public struct AddressField: View { public var body: some View { Button { - isShowPicker.toggle() + #if !os(watchOS) + isShowPicker.toggle() + #endif } label: { VStack(alignment: .leading, spacing: .xSmall) { if fieldPlaceholderPosition == .adjacent { HStack { Text(title) .subheadline(.medium) - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) Spacer() } } @@ -68,7 +70,7 @@ public struct AddressField: View { Text(title) .font(!isSlectedAddress ? .headline : .subheadline) .fontWeight(!isSlectedAddress ? .medium : .semibold) - .onSurfaceDisabledForegroundColor() + .onSurfaceTertiaryForeground() .offset(y: !isSlectedAddress ? 0 : -13) .opacity(!isSlectedAddress ? 0 : 1) } @@ -79,16 +81,18 @@ public struct AddressField: View { .lineLimit(1) } Spacer() - IconDeprecated(.chevronDown, color: .onSurfaceHighEmphasis) + IconDeprecated(.chevronDown, color: .onSurfacePrimary) } } .contentShape(Rectangle()) } - .foregroundColor(.onSurfaceHighEmphasis) + .foregroundColor(.onSurfacePrimary) .buttonStyle(.field) - .sheet(isPresented: $isShowPicker) { - AddressPicker(address: $seletedAddress, location: $seletedLocation, place: $seletedPlace) - } + #if !os(watchOS) + .sheet(isPresented: $isShowPicker) { + AddressPicker(address: $seletedAddress, location: $seletedLocation, place: $seletedPlace) + } + #endif } private var fieldOffset: CGFloat { diff --git a/Sources/OversizeLocationKit/AddressPicker/AddressPicker.swift b/Sources/OversizeLocationKit/AddressPicker/AddressPicker.swift index 33b1775..c46b204 100644 --- a/Sources/OversizeLocationKit/AddressPicker/AddressPicker.swift +++ b/Sources/OversizeLocationKit/AddressPicker/AddressPicker.swift @@ -11,225 +11,227 @@ import OversizeLocationService import OversizeUI import SwiftUI -public struct AddressPicker: View { - @Environment(\.dismiss) private var dismiss - @StateObject private var viewModel = AddressPickerViewModel() - @FocusState private var isFocusSearth - - @Binding private var seletedAddress: String? - @Binding private var seletedLocation: CLLocationCoordinate2D? - @Binding private var seletedPlace: LocationAddress? - - public init( - address: Binding = .constant(nil), - location: Binding = .constant(nil), - place: Binding = .constant(nil) - ) { - _seletedAddress = address - _seletedLocation = location - _seletedPlace = place - } - - public var body: some View { - PageView("Location") { - LazyVStack(spacing: .zero) { - if viewModel.appError != nil { - currentLocation - } +#if !os(watchOS) + public struct AddressPicker: View { + @Environment(\.dismiss) private var dismiss + @StateObject private var viewModel = AddressPickerViewModel() + @FocusState private var isFocusSearth + + @Binding private var seletedAddress: String? + @Binding private var seletedLocation: CLLocationCoordinate2D? + @Binding private var seletedPlace: LocationAddress? + + public init( + address: Binding = .constant(nil), + location: Binding = .constant(nil), + place: Binding = .constant(nil) + ) { + _seletedAddress = address + _seletedLocation = location + _seletedPlace = place + } - if viewModel.searchTerm.isEmpty, !viewModel.lastSearchAddresses.isEmpty { - HStack(spacing: .zero) { - Text("Recent") - Spacer() + public var body: some View { + PageView("Location") { + LazyVStack(spacing: .zero) { + if viewModel.appError != nil { + currentLocation } - .title3() - .onSurfaceMediumEmphasisForegroundColor() - .padding(.vertical, .xxSmall) - .paddingContent(.horizontal) - recentResults - } else { - results + if viewModel.searchTerm.isEmpty, !viewModel.lastSearchAddresses.isEmpty { + HStack(spacing: .zero) { + Text("Recent") + Spacer() + } + .title3() + .onSurfaceSecondaryForeground() + .padding(.vertical, .xxSmall) + .paddingContent(.horizontal) + + recentResults + } else { + results + } } } - } - .leadingBar { - BarButton(.close) - } - .topToolbar { - TextField("Search places or addresses", text: $viewModel.searchTerm) - .submitScope(viewModel.searchTerm.count < 2) - .textFieldStyle(DefaultPlaceholderTextFieldStyle()) - .focused($isFocusSearth) - .submitLabel(.done) - .onSubmit { - if viewModel.searchTerm.count > 2 { - viewModel.isSaveFromSearth = true - seletedAddress = viewModel.searchTerm - Task { - let coordinate = try? await viewModel.locationService.fetchCoordinateFromAddress(viewModel.searchTerm) - if let coordinate { - let address = try? await viewModel.locationService.fetchAddressFromLocation(coordinate) - seletedLocation = coordinate - seletedPlace = address - } else { - seletedPlace = nil - seletedLocation = nil + .leadingBar { + BarButton(.close) + } + .topToolbar { + TextField("Search places or addresses", text: $viewModel.searchTerm) + .submitScope(viewModel.searchTerm.count < 2) + .textFieldStyle(.default) + .focused($isFocusSearth) + .submitLabel(.done) + .onSubmit { + if viewModel.searchTerm.count > 2 { + viewModel.isSaveFromSearth = true + seletedAddress = viewModel.searchTerm + Task { + let coordinate = try? await viewModel.locationService.fetchCoordinateFromAddress(viewModel.searchTerm) + if let coordinate { + let address = try? await viewModel.locationService.fetchAddressFromLocation(coordinate) + seletedLocation = coordinate + seletedPlace = address + } else { + seletedPlace = nil + seletedLocation = nil + } + viewModel.isSaveFromSearth = false + saveToHistory() + dismiss() } - viewModel.isSaveFromSearth = false - saveToHistory() - dismiss() } } - } - .overlay(alignment: .trailing) { - if viewModel.isSaveFromSearth { - ProgressView() - .padding(.trailing, .xSmall) + .overlay(alignment: .trailing) { + if viewModel.isSaveFromSearth { + ProgressView() + .padding(.trailing, .xSmall) + } } - } - } - // .scrollDismissesKeyboard(.immediately) - .task(priority: .background) { - do { - try await viewModel.updateCurrentPosition() - if viewModel.isSaveCurentPositon { - onSaveCurrntPosition() - } - } catch {} - } - .onAppear { - isFocusSearth = true - } - } - - private var currentLocation: some View { - Row("Current Location") { - if viewModel.isFetchUpdatePositon { - viewModel.isSaveCurentPositon = true - } else { - onSaveCurrntPosition() } - } leading: { - IconDeprecated(.navigation) - .iconOnSurface() + // .scrollDismissesKeyboard(.immediately) + .task(priority: .background) { + do { + try await viewModel.updateCurrentPosition() + if viewModel.isSaveCurentPositon { + onSaveCurrntPosition() + } + } catch {} + } + .onAppear { + isFocusSearth = true + } } - .padding(.bottom, viewModel.searchTerm.isEmpty ? .small : .zero) - .loading(viewModel.isSaveCurentPositon) - } - - private var recentResults: some View { - ForEach(viewModel.lastSearchAddresses.reversed()) { address in - Row(address.address ?? address.place?.address ?? "Latitude: \(address.location?.latitude ?? 0), longitude:longitude \(address.location?.longitude ?? 0)") { - if let latitude = address.location?.latitude, let longitude = address.location?.longitude { - onCompleteSearth(seletedAddress: address.address, seletedLocation: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), seletedPlace: address.place, saveToHistory: false) + private var currentLocation: some View { + Row("Current Location") { + if viewModel.isFetchUpdatePositon { + viewModel.isSaveCurentPositon = true } else { - onCompleteSearth(seletedAddress: address.address, seletedLocation: nil, seletedPlace: address.place, saveToHistory: false) + onSaveCurrntPosition() } } leading: { - IconDeprecated(.mapPin) + IconDeprecated(.navigation) .iconOnSurface() } - .rowClearButton { - if let fooOffset = viewModel.lastSearchAddresses.firstIndex(where: { $0.id == address.id }) { - viewModel.lastSearchAddresses.remove(at: fooOffset) + .padding(.bottom, viewModel.searchTerm.isEmpty ? .small : .zero) + .loading(viewModel.isSaveCurentPositon) + } + + private var recentResults: some View { + ForEach(viewModel.lastSearchAddresses.reversed()) { address in + + Row(address.address ?? address.place?.address ?? "Latitude: \(address.location?.latitude ?? 0), longitude:longitude \(address.location?.longitude ?? 0)") { + if let latitude = address.location?.latitude, let longitude = address.location?.longitude { + onCompleteSearth(seletedAddress: address.address, seletedLocation: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), seletedPlace: address.place, saveToHistory: false) + } else { + onCompleteSearth(seletedAddress: address.address, seletedLocation: nil, seletedPlace: address.place, saveToHistory: false) + } + } leading: { + IconDeprecated(.mapPin) + .iconOnSurface() + } + .rowClearButton { + if let fooOffset = viewModel.lastSearchAddresses.firstIndex(where: { $0.id == address.id }) { + viewModel.lastSearchAddresses.remove(at: fooOffset) + } } } } - } - private var results: some View { - ForEach(viewModel.locationResults, id: \.self) { location in + private var results: some View { + ForEach(viewModel.locationResults, id: \.self) { location in - Row(location.title, subtitle: location.subtitle) { - reverseGeo(location: location) - } leading: { - IconDeprecated(.mapPin) - .iconOnSurface() + Row(location.title, subtitle: location.subtitle) { + reverseGeo(location: location) + } leading: { + IconDeprecated(.mapPin) + .iconOnSurface() + } } } - } - func reverseGeo(location: MKLocalSearchCompletion) { - let searchRequest = MKLocalSearch.Request(completion: location) - let search = MKLocalSearch(request: searchRequest) - var coordinateK: CLLocationCoordinate2D? - search.start { response, error in - if error == nil, let coordinate = response?.mapItems.first?.placemark.coordinate { - coordinateK = coordinate - } + func reverseGeo(location: MKLocalSearchCompletion) { + let searchRequest = MKLocalSearch.Request(completion: location) + let search = MKLocalSearch(request: searchRequest) + var coordinateK: CLLocationCoordinate2D? + search.start { response, error in + if error == nil, let coordinate = response?.mapItems.first?.placemark.coordinate { + coordinateK = coordinate + } - if let c = coordinateK { - let location = CLLocation(latitude: c.latitude, longitude: c.longitude) - CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in + if let c = coordinateK { + let location = CLLocation(latitude: c.latitude, longitude: c.longitude) + CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in - guard let placemark = placemarks?.first else { - let errorString = error?.localizedDescription ?? "Unexpected Error" - print("Unable to reverse geocode, \(errorString)") - return - } + guard let placemark = placemarks?.first else { + let errorString = error?.localizedDescription ?? "Unexpected Error" + print("Unable to reverse geocode, \(errorString)") + return + } - let reversedGeoLocation = LocationAddress(with: placemark) + let reversedGeoLocation = LocationAddress(with: placemark) - let address = "\(reversedGeoLocation.streetName) \(reversedGeoLocation.streetNumber)".capitalizingFirstLetter() - onCompleteSearth(seletedAddress: address, seletedLocation: c, seletedPlace: reversedGeoLocation) + let address = "\(reversedGeoLocation.streetName) \(reversedGeoLocation.streetNumber)".capitalizingFirstLetter() + onCompleteSearth(seletedAddress: address, seletedLocation: c, seletedPlace: reversedGeoLocation) + } } } } - } - func onCompleteSearth(seletedAddress: String?, seletedLocation: CLLocationCoordinate2D?, seletedPlace: LocationAddress?, saveToHistory: Bool = true) { - if let seletedAddress { - self.seletedAddress = seletedAddress - } else { - self.seletedAddress = seletedPlace?.address + func onCompleteSearth(seletedAddress: String?, seletedLocation: CLLocationCoordinate2D?, seletedPlace: LocationAddress?, saveToHistory: Bool = true) { + if let seletedAddress { + self.seletedAddress = seletedAddress + } else { + self.seletedAddress = seletedPlace?.address + } + self.seletedLocation = seletedLocation + self.seletedPlace = seletedPlace + if saveToHistory { + self.saveToHistory() + } + dismiss() } - self.seletedLocation = seletedLocation - self.seletedPlace = seletedPlace - if saveToHistory { - self.saveToHistory() + + private func onSaveCurrntPosition() { + Task { + let address = try? await viewModel.locationService.fetchAddressFromLocation(viewModel.currentLocation) + if let address { + seletedAddress = address.address + seletedPlace = address + } else { + seletedAddress = nil + seletedPlace = nil + } + seletedLocation = viewModel.currentLocation + saveToHistory() + dismiss() + } } - dismiss() - } - private func onSaveCurrntPosition() { - Task { - let address = try? await viewModel.locationService.fetchAddressFromLocation(viewModel.currentLocation) - if let address { - seletedAddress = address.address - seletedPlace = address + func saveToHistory() { + let lastSearth: SearchHistoryAddress + if let seletedLocation { + lastSearth = SearchHistoryAddress( + id: UUID().uuidString, + address: seletedAddress, + location: SearchHistoryLocationCoordinate(coordinate: seletedLocation), + place: seletedPlace + ) } else { - seletedAddress = nil - seletedPlace = nil + lastSearth = SearchHistoryAddress( + id: UUID().uuidString, + address: seletedAddress, + location: nil, + place: seletedPlace + ) } - seletedLocation = viewModel.currentLocation - saveToHistory() - dismiss() + viewModel.lastSearchAddresses.append(lastSearth) } - } - func saveToHistory() { - let lastSearth: SearchHistoryAddress - if let seletedLocation { - lastSearth = SearchHistoryAddress( - id: UUID().uuidString, - address: seletedAddress, - location: SearchHistoryLocationCoordinate(coordinate: seletedLocation), - place: seletedPlace - ) - } else { - lastSearth = SearchHistoryAddress( - id: UUID().uuidString, - address: seletedAddress, - location: nil, - place: seletedPlace - ) + private func onDoneAction() { + dismiss() } - viewModel.lastSearchAddresses.append(lastSearth) - } - - private func onDoneAction() { - dismiss() } -} +#endif diff --git a/Sources/OversizeLocationKit/AddressPicker/AddressPickerViewModel.swift b/Sources/OversizeLocationKit/AddressPicker/AddressPickerViewModel.swift index 92e6fd5..6db6c07 100644 --- a/Sources/OversizeLocationKit/AddressPicker/AddressPickerViewModel.swift +++ b/Sources/OversizeLocationKit/AddressPicker/AddressPickerViewModel.swift @@ -11,75 +11,77 @@ import OversizeLocationService import OversizeModels import SwiftUI -@MainActor -class AddressPickerViewModel: NSObject, ObservableObject { - @Injected(\.locationService) var locationService: LocationServiceProtocol +#if !os(watchOS) + @MainActor + class AddressPickerViewModel: NSObject, ObservableObject { + @Injected(\.locationService) var locationService: LocationServiceProtocol - @Published var locationResults: [MKLocalSearchCompletion] = .init() - @Published var searchTerm: String = .init() - @AppStorage("AppState.LastSearchAddresses") var lastSearchAddresses: [SearchHistoryAddress] = .init() + @Published var locationResults: [MKLocalSearchCompletion] = .init() + @Published var searchTerm: String = .init() + @AppStorage("AppState.LastSearchAddresses") var lastSearchAddresses: [SearchHistoryAddress] = .init() - @Published var currentLocation: CLLocationCoordinate2D = .init(latitude: 0, longitude: 0) + @Published var currentLocation: CLLocationCoordinate2D = .init(latitude: 0, longitude: 0) - @Published var isFetchUpdatePositon: Bool = .init(false) - @Published var isSaveCurentPositon: Bool = .init(false) - @Published var isSaveFromSearth: Bool = .init(false) + @Published var isFetchUpdatePositon: Bool = .init(false) + @Published var isSaveCurentPositon: Bool = .init(false) + @Published var isSaveFromSearth: Bool = .init(false) - private var cancellables: Set = [] + private var cancellables: Set = [] - private var searchCompleter = MKLocalSearchCompleter() - private var currentPromise: ((Result<[MKLocalSearchCompletion], Error>) -> Void)? + private var searchCompleter = MKLocalSearchCompleter() + private var currentPromise: ((Result<[MKLocalSearchCompletion], Error>) -> Void)? - @State var appError: AppError? + @State var appError: AppError? - override init() { - super.init() - searchCompleter.delegate = self - searchCompleter.resultTypes = MKLocalSearchCompleter.ResultType([.address]) + override init() { + super.init() + searchCompleter.delegate = self + searchCompleter.resultTypes = MKLocalSearchCompleter.ResultType([.address]) - $searchTerm - .debounce(for: .seconds(0.2), scheduler: RunLoop.main) - .removeDuplicates() - .flatMap { currentSearchTerm in - self.searchTermToResults(searchTerm: currentSearchTerm) + $searchTerm + .debounce(for: .seconds(0.2), scheduler: RunLoop.main) + .removeDuplicates() + .flatMap { currentSearchTerm in + self.searchTermToResults(searchTerm: currentSearchTerm) + } + .sink(receiveCompletion: { _ in + // Handle error + }, receiveValue: { results in + self.locationResults = results + }) + .store(in: &cancellables) + } + + func searchTermToResults(searchTerm: String) -> Future<[MKLocalSearchCompletion], Error> { + Future { promise in + self.searchCompleter.queryFragment = searchTerm + self.currentPromise = promise } - .sink(receiveCompletion: { _ in - // Handle error - }, receiveValue: { results in - self.locationResults = results - }) - .store(in: &cancellables) + } } - func searchTermToResults(searchTerm: String) -> Future<[MKLocalSearchCompletion], Error> { - Future { promise in - self.searchCompleter.queryFragment = searchTerm - self.currentPromise = promise + extension AddressPickerViewModel: @preconcurrency MKLocalSearchCompleterDelegate { + func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { + currentPromise?(.success(completer.results)) } - } -} -extension AddressPickerViewModel: MKLocalSearchCompleterDelegate { - func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { - currentPromise?(.success(completer.results)) + func completer(_: MKLocalSearchCompleter, didFailWithError _: Error) {} } - func completer(_: MKLocalSearchCompleter, didFailWithError _: Error) {} -} - -extension AddressPickerViewModel { - func updateCurrentPosition() async throws { - let status = locationService.permissionsStatus() - switch status { - case .success: - isFetchUpdatePositon = true - let currentPosition = try await locationService.currentLocation() - guard let newLocation = currentPosition else { return } - currentLocation = newLocation - print("📍 Location: \(newLocation.latitude), \(newLocation.longitude)") - isFetchUpdatePositon = false - case let .failure(error): - appError = error + extension AddressPickerViewModel { + func updateCurrentPosition() async throws { + let status = locationService.permissionsStatus() + switch status { + case .success: + isFetchUpdatePositon = true + let currentPosition = try await locationService.currentLocation() + guard let newLocation = currentPosition else { return } + currentLocation = newLocation + print("📍 Location: \(newLocation.latitude), \(newLocation.longitude)") + isFetchUpdatePositon = false + case let .failure(error): + appError = error + } } } -} +#endif diff --git a/Sources/OversizeLocationKit/MapCoordinateView/MapCoordinateView.swift b/Sources/OversizeLocationKit/MapCoordinateView/MapCoordinateView.swift index 93ed613..e36b40d 100644 --- a/Sources/OversizeLocationKit/MapCoordinateView/MapCoordinateView.swift +++ b/Sources/OversizeLocationKit/MapCoordinateView/MapCoordinateView.swift @@ -7,152 +7,156 @@ import MapKit import OversizeUI import SwiftUI -public struct MapCoordinateView: View { - @Environment(\.screenSize) var screenSize - @Environment(\.openURL) var openURL - @StateObject var viewModel: MapCoordinateViewModel +#if !os(watchOS) + public struct MapCoordinateView: View { + @Environment(\.screenSize) var screenSize + @Environment(\.openURL) var openURL + @StateObject var viewModel: MapCoordinateViewModel - public init(_ location: CLLocationCoordinate2D, annotation: String? = nil) { - _viewModel = StateObject(wrappedValue: MapCoordinateViewModel(location: location, annotation: annotation)) - } + public init(_ location: CLLocationCoordinate2D, annotation: String? = nil) { + _viewModel = StateObject(wrappedValue: MapCoordinateViewModel(location: location, annotation: annotation)) + } - public var body: some View { - VStack(spacing: 0) { - if #available(iOS 16.0, *) { - mapView - .ignoresSafeArea() - .safeAreaInset(edge: .top) { - ModalNavigationBar(title: viewModel.annotation ?? "", largeTitle: false, leadingBar: { - BarButton(.back) - }, trailingBar: { - BarButton(.icon(.map, action: { - viewModel.isShowRoutePickerSheet.toggle() - })) - }) - .background(.thickMaterial, ignoresSafeAreaEdges: .top) - } - #if os(iOS) - .toolbar(.hidden, for: .tabBar) - #endif - } else { - mapView - .safeAreaInset(edge: .top) { - ModalNavigationBar(title: viewModel.annotation ?? "", largeTitle: false, leadingBar: { - BarButton(.back) - }, trailingBar: { - BarButton(.icon(.map, action: { - viewModel.isShowRoutePickerSheet.toggle() - })) - }) - } + public var body: some View { + VStack(spacing: 0) { + if #available(iOS 16.0, *) { + mapView + .ignoresSafeArea() + .safeAreaInset(edge: .top) { + ModalNavigationBar(title: viewModel.annotation ?? "", largeTitle: false, leadingBar: { + BarButton(.back) + }, trailingBar: { + BarButton(.icon(.map, action: { + viewModel.isShowRoutePickerSheet.toggle() + })) + }) + .background(.thickMaterial, ignoresSafeAreaEdges: .top) + } + #if os(iOS) + .toolbar(.hidden, for: .tabBar) + #endif + } else { + mapView + .safeAreaInset(edge: .top) { + ModalNavigationBar(title: viewModel.annotation ?? "", largeTitle: false, leadingBar: { + BarButton(.back) + }, trailingBar: { + BarButton(.icon(.map, action: { + viewModel.isShowRoutePickerSheet.toggle() + })) + }) + } + } + } + .sheet(isPresented: $viewModel.isShowRoutePickerSheet) { + routeSheetView + .presentationDetents([.height(260)]) } } - .sheet(isPresented: $viewModel.isShowRoutePickerSheet) { - routeSheetView - .presentationDetents([.height(260)]) - } - } - var mapView: some View { - ZStack(alignment: .trailing) { - Map(coordinateRegion: region, showsUserLocation: true, userTrackingMode: $viewModel.userTrackingMode, annotationItems: viewModel.annotations) { - MapMarker(coordinate: $0.coordinate) + var mapView: some View { + ZStack(alignment: .trailing) { + Map(coordinateRegion: region, showsUserLocation: true, userTrackingMode: $viewModel.userTrackingMode, annotationItems: viewModel.annotations) { + MapMarker(coordinate: $0.coordinate) + } + controlButtons } - controlButtons } - } - private var region: Binding { - Binding { - viewModel.region - } set: { region in - DispatchQueue.main.async { - viewModel.region = region + private var region: Binding { + Binding { + viewModel.region + } set: { region in + DispatchQueue.main.async { + viewModel.region = region + } } } - } - var controlButtons: some View { - VStack { - Spacer() - VStack(spacing: .zero) { - Button { - viewModel.zoomIn() - } label: { - IconDeprecated(.plus) - .onSurfaceMediumEmphasisForegroundColor() - .padding(.xxSmall) - } + var controlButtons: some View { + VStack { + Spacer() + VStack(spacing: .zero) { + Button { + viewModel.zoomIn() + } label: { + IconDeprecated(.plus) + .onSurfaceSecondaryForeground() + .padding(.xxSmall) + } + Button { + viewModel.zoomOut() + } label: { + IconDeprecated(.minus) + .onSurfaceSecondaryForeground() + .padding(.xxSmall) + } + } + .background { + Capsule() + .fillSurfacePrimary() + .shadowElevaton(.z1) + } + Spacer() + } + .overlay(alignment: .bottomTrailing, content: { Button { - viewModel.zoomOut() + viewModel.positionInLocation() + } label: { - IconDeprecated(.minus) - .onSurfaceMediumEmphasisForegroundColor() + IconDeprecated(.navigation) + .onSurfaceSecondaryForeground() .padding(.xxSmall) } - } - .background { - Capsule() - .fillSurfacePrimary() - .shadowElevaton(.z1) - } - Spacer() + .background { + Capsule() + .fillSurfacePrimary() + .shadowElevaton(.z1) + } + }) + .padding(.trailing, 16) + .padding(.bottom, screenSize.safeAreaBottom) } - .overlay(alignment: .bottomTrailing, content: { - Button { - viewModel.positionInLocation() - - } label: { - IconDeprecated(.navigation) - .onSurfaceMediumEmphasisForegroundColor() - .padding(.xxSmall) - } - .background { - Capsule() - .fillSurfacePrimary() - .shadowElevaton(.z1) - } - }) - .padding(.trailing, 16) - .padding(.bottom, screenSize.safeAreaBottom) - } - var routeSheetView: some View { - PageView("Route") { - SectionView { - Row("Apple Maps") { - onTapAppleMaps() - } - Row("Google Maps") { - onTapGoogleMaps() + var routeSheetView: some View { + PageView("Route") { + SectionView { + Row("Apple Maps") { + onTapAppleMaps() + } + Row("Google Maps") { + onTapGoogleMaps() + } } } + .leadingBar(leadingBar: { + BarButton(.close) + }) + .backgroundSecondary() + .disableScrollShadow(true) + .surfaceContentRowMargins() } - .leadingBar(leadingBar: { - BarButton(.close) - }) - .backgroundSecondary() - .disableScrollShadow(true) - .surfaceContentRowMargins() - } - func onTapAppleMaps() { - let placemark = MKPlacemark(coordinate: viewModel.location, addressDictionary: nil) - let mapItem = MKMapItem(placemark: placemark) - mapItem.name = viewModel.annotation - mapItem.openInMaps() - viewModel.isShowRoutePickerSheet.toggle() - } + func onTapAppleMaps() { + #if !os(tvOS) + let placemark = MKPlacemark(coordinate: viewModel.location, addressDictionary: nil) + let mapItem = MKMapItem(placemark: placemark) + mapItem.name = viewModel.annotation + mapItem.openInMaps() + viewModel.isShowRoutePickerSheet.toggle() + #endif + } - func onTapGoogleMaps() { - guard let url = URL(string: "comgooglemaps://?saddr=\(viewModel.location.latitude),\(viewModel.location.longitude)") else { return } - openURL(url) + func onTapGoogleMaps() { + guard let url = URL(string: "comgooglemaps://?saddr=\(viewModel.location.latitude),\(viewModel.location.longitude)") else { return } + openURL(url) + } } -} -struct MapCoordinateView_Previews: PreviewProvider { - static var previews: some View { - MapCoordinateView(.init(latitude: 100, longitude: 100)) + struct MapCoordinateView_Previews: PreviewProvider { + static var previews: some View { + MapCoordinateView(.init(latitude: 100, longitude: 100)) + } } -} +#endif diff --git a/Sources/OversizeNotificationKit/LocalNotificationSetScreenViewModel.swift b/Sources/OversizeNotificationKit/LocalNotificationSetScreenViewModel.swift index be6e46c..94d24ed 100644 --- a/Sources/OversizeNotificationKit/LocalNotificationSetScreenViewModel.swift +++ b/Sources/OversizeNotificationKit/LocalNotificationSetScreenViewModel.swift @@ -9,68 +9,77 @@ import OversizeModels import OversizeNotificationService import SwiftUI -@MainActor -class LocalNotificationSetScreenViewModel: ObservableObject { - @Injected(\.localNotificationService) var localNotificationService: LocalNotificationServiceProtocol - @Published var state = State.initial +#if !os(tvOS) + @MainActor + class LocalNotificationSetScreenViewModel: ObservableObject { + @Injected(\.localNotificationService) var localNotificationService: LocalNotificationServiceProtocol + @Published var state = State.initial - public let id: UUID - private let date: Date - private let title: String - private let body: String - private let userInfo: [AnyHashable: Any]? + public let id: UUID + private let date: Date + private let title: String + private let body: String + private let userInfo: [AnyHashable: Any]? - init( - id: UUID, - date: Date, - title: String, - body: String, - userInfo: [AnyHashable: Any]? = nil - ) { - self.id = id - self.date = date - self.title = title - self.body = body - self.userInfo = userInfo - } + init( + id: UUID, + date: Date, + title: String, + body: String, + userInfo: [AnyHashable: Any]? = nil + ) { + self.id = id + self.date = date + self.title = title + self.body = body + self.userInfo = userInfo + } - func setNotification(timeBefore: LocalNotificationTime) async { - let notificationTime = date.addingTimeInterval(timeBefore.timeInterval) - let dateComponents = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: notificationTime) - await localNotificationService.schedule(localNotification: .init( - id: id, - title: title, - body: body, - dateComponents: dateComponents, - repeats: false, - userInfo: userInfo - )) - } + func setNotification(timeBefore: LocalNotificationTime) async { + let notificationTime = date.addingTimeInterval(timeBefore.timeInterval) + let dateComponents = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: notificationTime) - func fetchPandingNotification() async -> Bool { - let ids = await localNotificationService.fetchPendingIds() - return ids.contains(id.uuidString) - } + let stringUserInfo = userInfo?.reduce(into: [String: String]()) { result, pair in + if let key = pair.key as? String, let value = pair.value as? String { + result[key] = value + } + } - func deleteNotification() { - localNotificationService.removeRequest(withIdentifier: id.uuidString) - } + await localNotificationService.schedule(localNotification: .init( + id: id, + title: title, + body: body, + dateComponents: dateComponents, + repeats: false, + userInfo: stringUserInfo + )) + } - func requestAccsess() async { - let result = await localNotificationService.requestAccess() - switch result { - case .success: - state = .result - case let .failure(error): - state = .error(error) + func fetchPandingNotification() async -> Bool { + let ids = await localNotificationService.fetchPendingIds() + return ids.contains(id.uuidString) + } + + func deleteNotification() { + localNotificationService.removeRequest(withIdentifier: id.uuidString) + } + + func requestAccsess() async { + let result = await localNotificationService.requestAccess() + switch result { + case .success: + state = .result + case let .failure(error): + state = .error(error) + } } } -} -extension LocalNotificationSetScreenViewModel { - enum State { - case initial - case result - case error(AppError) + extension LocalNotificationSetScreenViewModel { + enum State { + case initial + case result + case error(AppError) + } } -} +#endif diff --git a/Sources/OversizeNotificationKit/LocalNotificationView.swift b/Sources/OversizeNotificationKit/LocalNotificationView.swift index 87cbe51..e574636 100644 --- a/Sources/OversizeNotificationKit/LocalNotificationView.swift +++ b/Sources/OversizeNotificationKit/LocalNotificationView.swift @@ -9,106 +9,108 @@ import OversizeUI import SwiftUI import UserNotifications -public struct LocalNotificationView: View { - @Environment(\.dismiss) var dismiss - @Binding private var selection: LocalNotificationTime - @State private var isPendingNotification: Bool = false - @StateObject var viewModel: LocalNotificationSetScreenViewModel - private let saveAction: ((UUID?) -> Void)? +#if !os(tvOS) + public struct LocalNotificationView: View { + @Environment(\.dismiss) var dismiss + @Binding private var selection: LocalNotificationTime + @State private var isPendingNotification: Bool = false + @StateObject var viewModel: LocalNotificationSetScreenViewModel + private let saveAction: ((UUID?) -> Void)? - public init( - _ selection: Binding, - id: UUID, - title: String, - body: String, - date: Date, - userInfo: [AnyHashable: Any]? = nil, - saveAction: ((UUID?) -> Void)? = nil - ) { - _viewModel = StateObject(wrappedValue: LocalNotificationSetScreenViewModel( - id: id, - date: date, - title: title, - body: body, - userInfo: userInfo - )) - _selection = selection - self.saveAction = saveAction - } + public init( + _ selection: Binding, + id: UUID, + title: String, + body: String, + date: Date, + userInfo: [AnyHashable: Any]? = nil, + saveAction: ((UUID?) -> Void)? = nil + ) { + _viewModel = StateObject(wrappedValue: LocalNotificationSetScreenViewModel( + id: id, + date: date, + title: title, + body: body, + userInfo: userInfo + )) + _selection = selection + self.saveAction = saveAction + } - public var body: some View { - switch viewModel.state { - case .initial: - contnent - .task { - await viewModel.requestAccsess() - } - case .result: - contnent - .task { - let pendingStatus = await viewModel.fetchPandingNotification() - if pendingStatus { - isPendingNotification = true + public var body: some View { + switch viewModel.state { + case .initial: + contnent + .task { + await viewModel.requestAccsess() } + case .result: + contnent + .task { + let pendingStatus = await viewModel.fetchPandingNotification() + if pendingStatus { + isPendingNotification = true + } + } + case let .error(error): + PageView("Notification") { + ErrorView(error) + } + .leadingBar { + BarButton(.close) } - case let .error(error): - PageView("Notification") { - ErrorView(error) - } - .leadingBar { - BarButton(.close) } } - } - public var contnent: some View { - // let notificationDate = viewModel.date.addingTimeInterval(selection.timeInterval) - PageView("Notification") { - VStack(spacing: .zero) { - SectionView { - LazyVStack(spacing: .zero) { - ForEach(LocalNotificationTime.allCases) { notificationTime in + public var contnent: some View { + // let notificationDate = viewModel.date.addingTimeInterval(selection.timeInterval) + PageView("Notification") { + VStack(spacing: .zero) { + SectionView { + LazyVStack(spacing: .zero) { + ForEach(LocalNotificationTime.allCases) { notificationTime in // let notificationDate = viewModel.date.addingTimeInterval(notificationTime.timeInterval) // if notificationDate viewModel.date { - Radio(notificationTime.title, isOn: selection.id == notificationTime.id) { - selection = notificationTime + Radio(notificationTime.title, isOn: selection.id == notificationTime.id) { + selection = notificationTime + } + // } } - // } } } - } - .surfaceContentRowMargins() - if isPendingNotification { - SectionView { - VStack(spacing: .zero) { - Row("Delete notification") { - viewModel.deleteNotification() - saveAction?(nil) - isPendingNotification = false - dismiss() - } trailing: { - IconDeprecated(.trash) - .iconColor(Color.error) + .surfaceContentRowMargins() + if isPendingNotification { + SectionView { + VStack(spacing: .zero) { + Row("Delete notification") { + viewModel.deleteNotification() + saveAction?(nil) + isPendingNotification = false + dismiss() + } trailing: { + IconDeprecated(.trash) + .iconColor(Color.error) + } } } + .surfaceContentRowMargins() } - .surfaceContentRowMargins() } } - } - .backgroundSecondary() - .leadingBar { - BarButton(.close) - } - .trailingBar { - BarButton(.accent("Done", action: { - Task { - await viewModel.setNotification(timeBefore: selection) - saveAction?(viewModel.id) - isPendingNotification = true - dismiss() - } - })) + .backgroundSecondary() + .leadingBar { + BarButton(.close) + } + .trailingBar { + BarButton(.accent("Done", action: { + Task { + await viewModel.setNotification(timeBefore: selection) + saveAction?(viewModel.id) + isPendingNotification = true + dismiss() + } + })) + } } } -} +#endif diff --git a/Sources/OversizeNotificationKit/Model/LocalNotificationAlertsTimes.swift b/Sources/OversizeNotificationKit/Model/LocalNotificationAlertsTimes.swift index 2c45852..3ace115 100644 --- a/Sources/OversizeNotificationKit/Model/LocalNotificationAlertsTimes.swift +++ b/Sources/OversizeNotificationKit/Model/LocalNotificationAlertsTimes.swift @@ -3,10 +3,9 @@ // LocalNotificationAlertsTimes.swift // -import EventKit import Foundation -public enum LocalNotificationTime: CaseIterable, Equatable, Identifiable { +public enum LocalNotificationTime: CaseIterable, Equatable, Identifiable, @unchecked Sendable { case oneMinuteBefore, fiveMinutesBefore, tenMinutesBefore, thirtyMinutesBefore, oneHourBefore, twoHoursBefore, oneDayBefore, twoDaysBefore, oneWeekBefore public var title: String { @@ -59,5 +58,5 @@ public enum LocalNotificationTime: CaseIterable, Equatable, Identifiable { title } - public static var allCases: [LocalNotificationTime] = [.oneMinuteBefore, .fiveMinutesBefore, .tenMinutesBefore, .thirtyMinutesBefore, .oneHourBefore, .twoHoursBefore, .oneDayBefore, .twoDaysBefore, .oneWeekBefore] + public static let allCases: [LocalNotificationTime] = [.oneMinuteBefore, .fiveMinutesBefore, .tenMinutesBefore, .thirtyMinutesBefore, .oneHourBefore, .twoHoursBefore, .oneDayBefore, .twoDaysBefore, .oneWeekBefore] } diff --git a/Sources/OversizeOnboardingKit/OnboardingItem.swift b/Sources/OversizeOnboardingKit/OnboardingItem.swift deleted file mode 100644 index 76e1028..0000000 --- a/Sources/OversizeOnboardingKit/OnboardingItem.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright © 2022 Alexander Romanov -// OnboardingItem.swift -// - -import SwiftUI - -public struct OnboardingItem: Identifiable, Equatable { - public var id = UUID() - public var image: Image? - public var title: String? - public var subtitle: String? - - public init(id: UUID = UUID(), image: Image? = nil, title: String? = nil, subtitle: String? = nil) { - self.id = id - self.image = image - self.title = title - self.subtitle = subtitle - } -} diff --git a/Sources/OversizeOnboardingKit/OnboardingItemPreferenceKey.swift b/Sources/OversizeOnboardingKit/OnboardingItemPreferenceKey.swift deleted file mode 100644 index 0335c7f..0000000 --- a/Sources/OversizeOnboardingKit/OnboardingItemPreferenceKey.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Copyright © 2022 Alexander Romanov -// OnboardingItemPreferenceKey.swift -// - -import SwiftUI - -struct OnboardingItemPreferenceKey: PreferenceKey { - static var defaultValue: [OnboardingItem] = [] - - static func reduce(value: inout [OnboardingItem], nextValue: () -> [OnboardingItem]) { - value += nextValue() - } -} diff --git a/Sources/OversizeOnboardingKit/OnboardingItemViewModifier.swift b/Sources/OversizeOnboardingKit/OnboardingItemViewModifier.swift deleted file mode 100644 index 23590f6..0000000 --- a/Sources/OversizeOnboardingKit/OnboardingItemViewModifier.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright © 2022 Alexander Romanov -// OnboardingItemViewModifier.swift -// - -import SwiftUI - -struct OnboardingItemViewModifier: ViewModifier { - let onboardingItem: OnboardingItem - - func body(content: Content) -> some View { - content - .preference(key: OnboardingItemPreferenceKey.self, value: [onboardingItem]) - } -} - -public extension View { - func onboardingItem(_ label: () -> OnboardingItem) -> some View { - modifier(OnboardingItemViewModifier(onboardingItem: label())) - } -} diff --git a/Sources/OversizeOnboardingKit/OnboardingView.swift b/Sources/OversizeOnboardingKit/OnboardingView.swift index a12d76e..9b0235b 100644 --- a/Sources/OversizeOnboardingKit/OnboardingView.swift +++ b/Sources/OversizeOnboardingKit/OnboardingView.swift @@ -1,111 +1,131 @@ // -// Copyright © 2023 Alexander Romanov -// OnboardingView.swift +// Copyright © 2024 Alexander Romanov +// OnboardView.swift, created on 16.09.2024 // import OversizeUI import SwiftUI -public struct OnboardingView: View { - @Environment(\.screenSize) var screenSize - - @Binding private var selection: Int - - @Namespace private var onboardingItem - - @State private var slides: [OnboardingItem] = [] - - private let finishAction: (() -> Void)? - - private var content: Content - - public init(selection: Binding, finishAction: (() -> Void)? = nil, @ViewBuilder content: () -> Content) { +public struct OnboardView: View where A: View, C: View { + private let content: C + private let actions: Group + private let backAction: (() -> Void)? + private let skipAction: (() -> Void)? + private let helpAction: (() -> Void)? + + public init( + @ViewBuilder content: () -> C, + @ViewBuilder actions: () -> A, + backAction: (() -> Void)? = nil, + skipAction: (() -> Void)? = nil, + helpAction: (() -> Void)? = nil + ) { self.content = content() - self.finishAction = finishAction - _selection = selection + self.actions = Group { actions() } + self.backAction = backAction + self.skipAction = skipAction + self.helpAction = helpAction } public var body: some View { - ZStack { - VStack { - PageIndexView($selection, maxIndex: slides.count) - .padding(.top, .large) - Spacer() - } - - TabView(selection: $selection) { - // ForEach(Array(slides.enumerated()), id: \.offset) { index, element in + content + .ignoresSafeArea(.all) + .frame( + maxWidth: .infinity, + maxHeight: .infinity + ) + .safeAreaInset(edge: .top, content: topButtons) + .safeAreaInset(edge: .bottom, content: bottomButtons) + } - tabItem(tabItem: OnboardingItem(title: "Title", subtitle: "Sub"), index: 0) - // .tag(index) - // } - } + private func topButtons() -> some View { + HStack { #if os(iOS) - .tabViewStyle(.page(indexDisplayMode: .never)) - .indexViewStyle(.page(backgroundDisplayMode: .never)) + if helpAction != nil { + Button { + helpAction?() + } label: { + Text("Help") + } + .buttonStyle(.tertiary) + .controlBorderShape(.capsule) + .accent() + .controlSize(.mini) + } #endif + + Spacer() + + if skipAction != nil { + Button { + skipAction?() + } label: { + Text("Skip") + } + .buttonStyle(.tertiary) + .controlBorderShape(.capsule) + .accent() + #if !os(tvOS) + .controlSize(.mini) + #endif + } } - .background( - Color.backgroundSecondary.ignoresSafeArea() - ) - .onPreferenceChange(OnboardingItemPreferenceKey.self) { value in - slides = value - } + .padding(.medium) } - private func tabItem(tabItem: OnboardingItem, index _: Int) -> some View { - VStack(spacing: .small) { - if let image = tabItem.image { - image - } + private func bottomButtons() -> some View { + #if os(iOS) + HStack(spacing: .small) { + if let backAction { + Button { + backAction() + } label: { + Image.Base.arrowLeft.icon() + } + .buttonStyle(.quaternary) + .accentColor(.secondary) + } - VStack { - if let title = tabItem.title { - Text(title) - .largeTitle() - .foregroundColor(.onSurfaceHighEmphasis) - .padding(.bottom, .small) + VStack(spacing: .xxxSmall) { + actions + } + } + .padding(.medium) + #else + HStack(spacing: .xSmall) { + if let helpAction { + Button("Help", action: helpAction) + .help("Help") + #if !os(tvOS) + .controlSize(.extraLarge) + .buttonStyle(.bordered) + #endif } - if let subtitle = tabItem.subtitle { - Text(subtitle) - .foregroundColor(.onSurfaceMediumEmphasis) - .fontWeight(.regular) - .font(.system(size: 19)) + Spacer() + + if let backAction { + Button( + "Back", + action: backAction + ) + #if !os(tvOS) + .controlSize(.extraLarge) + #endif + .buttonStyle(.bordered) } + + actions + #if !os(tvOS) + .controlSize(.extraLarge) + #endif + .buttonStyle(.borderedProminent) } - .offset(y: screenSize.height < 812 ? -50 : 0) - } - .multilineTextAlignment(.center) - .frame(maxWidth: 320) - .offset(y: -50) - .padding(.bottom, .xLarge) + .padding(.small) + .background(Color.surfacePrimary) + .overlay(alignment: .top) { + Separator() + } + #endif } } - -// struct FloatingTabBarExample: View { -// @State var selection = 0 -// -// var body: some View { -// FloatingTabBar(selection: $selection, plusAction: { print("plus") }) { -// Color.gray -// .floatingTabItem { -// TabItem(icon: Image(systemName: "star")) -// } -// .opacity(selection == 0 ? 1 : 0) -// -// Color.blue -// .floatingTabItem { -// TabItem(icon: Image(systemName: "plus")) -// } -// .opacity(selection == 1 ? 1 : 0) -// } -// } -// } - -// struct FloatingTabBar_Previews: PreviewProvider { -// static var previews: some View { -// FloatingTabBarExample() -// } -// } -// diff --git a/Sources/OversizePhotoKit/PhotoOptionsView.swift b/Sources/OversizePhotoKit/PhotoOptionsView.swift index 8c4434f..904582c 100644 --- a/Sources/OversizePhotoKit/PhotoOptionsView.swift +++ b/Sources/OversizePhotoKit/PhotoOptionsView.swift @@ -25,7 +25,7 @@ public struct PhotoOptionsView: View where A: View { private let actions: Group? private let deleteAction: (() -> Void)? - @State private var isShowAlert: Bool = false + @State private var isShowAlert = false public init( image: Image, @@ -63,20 +63,21 @@ public struct PhotoOptionsView: View where A: View { VStack(spacing: .medium) { SectionView { VStack { - if #available(iOS 16.0, *) { - ShareLink( - item: photo, - preview: SharePreview( - "Photo", - image: photo.image - ) - ) { - Row("Share") { - Image.Base.upload + #if !os(tvOS) + if #available(iOS 16.0, *) { + ShareLink( + item: photo, + preview: SharePreview( + "Photo", + image: photo.image + ) + ) { + Row("Share") { + Image.Base.upload + } } } - } - + #endif actions } .buttonStyle(.row) diff --git a/Sources/OversizePhotoKit/PhotosGalleryView.swift b/Sources/OversizePhotoKit/PhotosGalleryView.swift index 7aff10e..5c13e1c 100644 --- a/Sources/OversizePhotoKit/PhotosGalleryView.swift +++ b/Sources/OversizePhotoKit/PhotosGalleryView.swift @@ -23,7 +23,7 @@ public struct PhotosGalleryView: View { if images.isEmpty { Text("Not photos") .title3() - .onSurfaceHighEmphasisForegroundColor() + .onSurfacePrimaryForeground() } else { ImageGridView(images, columnCount: .constant(3)) { image in let index = images.firstIndex(of: image)