diff --git a/WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsView.swift b/WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsView.swift index c4fb1e7e01c..6fe9ef4c2dd 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsView.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsView.swift @@ -1,4 +1,5 @@ import SwiftUI +import WooFoundation struct PaymentMethodsView: View { /// Set this closure with UIKit dismiss code. Needed because we need access to the UIHostingController `dismiss` method. @@ -23,6 +24,8 @@ struct PaymentMethodsView: View { @State private var showingPurchaseCardReaderView = false + @State private var showingScanToPayView = false + private let learnMoreViewModel = LearnMoreViewModel.inPersonPayments(source: .paymentMethods) /// Environment safe areas @@ -88,7 +91,9 @@ struct PaymentMethodsView: View { if viewModel.showScanToPayRow { Divider() - MethodRow(icon: .scanToPayIcon, title: Localization.scanToPay, accessibilityID: Accessibility.scanToPayMethod) {} + MethodRow(icon: .scanToPayIcon, title: Localization.scanToPay, accessibilityID: Accessibility.scanToPayMethod) { + showingScanToPayView = true + } } } .padding(.horizontal) @@ -150,6 +155,13 @@ struct PaymentMethodsView: View { } } } + .fullScreenCover(isPresented: $showingScanToPayView) { + ScanToPayView(viewModel: ScanToPayViewModel(paymentURL: viewModel.paymentLink)) { + dismiss() + viewModel.performScanToPayFinishedTasks() + } + .background(FullScreenCoverClearBackgroundView()) + } } } diff --git a/WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModel.swift b/WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModel.swift index dd6f5511183..4969f519bfe 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModel.swift @@ -381,6 +381,10 @@ final class PaymentMethodsViewModel: ObservableObject { trackFlowCompleted(method: .paymentLink, cardReaderType: .none) } + func performScanToPayFinishedTasks() { + presentNoticeSubject.send(.created) + } + /// Track the flow cancel scenario. /// func userDidCancelFlow() { diff --git a/WooCommerce/Classes/ViewRelated/Orders/ScanToPay/ScanToPayView.swift b/WooCommerce/Classes/ViewRelated/Orders/ScanToPay/ScanToPayView.swift new file mode 100644 index 00000000000..b255ac0c891 --- /dev/null +++ b/WooCommerce/Classes/ViewRelated/Orders/ScanToPay/ScanToPayView.swift @@ -0,0 +1,78 @@ +import SwiftUI + +struct ScanToPayView: View { + let viewModel: ScanToPayViewModel + let onSuccess: (() -> Void) + + @Environment(\.dismiss) var dismiss + + var body: some View { + ZStack { + Color.black.opacity(Layout.backgroundOpacity).edgesIgnoringSafeArea(.all) + VStack { + VStack(alignment: .center, spacing: Layout.scanToPayBoxSpacing) { + if let qrCodeImage = viewModel.generateQRCodeImage() { + Text(Localization.title) + .foregroundColor(.white) + Image(uiImage: qrCodeImage) + .interpolation(.none) + .resizable() + .frame(width: Layout.qrCodeWidth, height: Layout.qrCodeHeight) + DoneButton() { + dismiss() + DispatchQueue.main.asyncAfter(deadline: .now() + Constants.onSuccessCallDelayAfterDismiss) { + onSuccess() + } + } + .buttonStyle(PrimaryButtonStyle()) + } else { + Text(Localization.errorMessage) + .foregroundColor(.white) + .multilineTextAlignment(.center) + DoneButton() { + dismiss() + } + } + } + .padding(Layout.scanToPayBoxSpacing) + .frame(maxWidth: .infinity, alignment: .center) + .background(Color(.gray(.shade70))) + .cornerRadius(Layout.scanToPayBoxCornerRadius) + + } + .padding(Layout.scanToPayBoxOutterPadding) + .frame(maxWidth: .infinity, alignment: .center) + } + } + + private struct DoneButton: View { + let onButtonTapped: (() -> Void) + var body: some View { + Button(Localization.doneButtontitle) { + onButtonTapped() + } + .buttonStyle(PrimaryButtonStyle()) + } + } +} + +extension ScanToPayView { + enum Constants { + static let onSuccessCallDelayAfterDismiss: TimeInterval = 1 + } + enum Localization { + static let title = NSLocalizedString("Scan QR and follow instructions", comment: "Title text on the Scan to Pay screen") + static let doneButtontitle = NSLocalizedString("Done", comment: "Button title to close the Scan to Pay screen") + static let errorMessage = NSLocalizedString("Error generating QR Code. Please try again later", + comment: "Error message in the Scan to Pay screen when the code cannot be generated.") + } + + enum Layout { + static let backgroundOpacity: CGFloat = 0.5 + static let scanToPayBoxSpacing: CGFloat = 20 + static let qrCodeWidth: CGFloat = 270 + static let qrCodeHeight: CGFloat = 300 + static let scanToPayBoxCornerRadius: CGFloat = 8 + static let scanToPayBoxOutterPadding: CGFloat = 50 + } +} diff --git a/WooCommerce/Classes/ViewRelated/Orders/ScanToPay/ScanToPayViewModel.swift b/WooCommerce/Classes/ViewRelated/Orders/ScanToPay/ScanToPayViewModel.swift new file mode 100644 index 00000000000..3d6401956c9 --- /dev/null +++ b/WooCommerce/Classes/ViewRelated/Orders/ScanToPay/ScanToPayViewModel.swift @@ -0,0 +1,28 @@ +import Foundation +import CoreImage.CIFilterBuiltins +import UIKit + +struct ScanToPayViewModel { + private let paymentURL: URL? + + init(paymentURL: URL?) { + self.paymentURL = paymentURL + } + + func generateQRCodeImage() -> UIImage? { + guard let paymentURLString = paymentURL?.absoluteString else { + return nil + } + + let context = CIContext() + let filter = CIFilter.qrCodeGenerator() + filter.message = Data(paymentURLString.utf8) + + guard let outputImage = filter.outputImage, + let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { + return nil + } + + return UIImage(cgImage: cgImage) + } +} diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index ca81fb4e66b..ce740b709b9 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -612,9 +612,9 @@ 03EF250228C615A5006A033E /* InPersonPaymentsMenuViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EF250128C615A5006A033E /* InPersonPaymentsMenuViewModel.swift */; }; 03EF250428C6283B006A033E /* InPersonPaymentsMenuViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EF250328C6283B006A033E /* InPersonPaymentsMenuViewModelTests.swift */; }; 03EF250628C75838006A033E /* PurchaseCardReaderWebViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03EF250528C75838006A033E /* PurchaseCardReaderWebViewViewModel.swift */; }; - 03F5CB832A0C3A1A0026877A /* AnimatedPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F5CB822A0C3A1A0026877A /* AnimatedPlaceholder.swift */; }; 03F5CAFF2A0BA37C0026877A /* JustInTimeMessageModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F5CAFE2A0BA37C0026877A /* JustInTimeMessageModal.swift */; }; 03F5CB012A0BA3D40026877A /* ModalOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F5CB002A0BA3D40026877A /* ModalOverlay.swift */; }; + 03F5CB832A0C3A1A0026877A /* AnimatedPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F5CB822A0C3A1A0026877A /* AnimatedPlaceholder.swift */; }; 03FBDA9D263AD49200ACE257 /* CouponListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FBDA9C263AD49100ACE257 /* CouponListViewController.swift */; }; 03FBDAA3263AED2F00ACE257 /* CouponListViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 03FBDAA2263AED2F00ACE257 /* CouponListViewController.xib */; }; 03FBDAF2263EE47C00ACE257 /* CouponListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FBDAF1263EE47C00ACE257 /* CouponListViewModel.swift */; }; @@ -1575,6 +1575,8 @@ B958B4DA2983E3F40010286B /* OrderDurationRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B958B4D92983E3F40010286B /* OrderDurationRecorder.swift */; }; B96B536B2816ECFC00F753E6 /* CardPresentPluginsDataProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96B536A2816ECFC00F753E6 /* CardPresentPluginsDataProviderTests.swift */; }; B96D6C0729081AE5003D2DC0 /* InAppPurchasesForWPComPlansManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96D6C0629081AE5003D2DC0 /* InAppPurchasesForWPComPlansManager.swift */; }; + B99686E02A13C8CC00D1AF62 /* ScanToPayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B99686DF2A13C8CC00D1AF62 /* ScanToPayView.swift */; }; + B99686E32A13C98200D1AF62 /* ScanToPayViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B99686E22A13C98200D1AF62 /* ScanToPayViewModel.swift */; }; B9B0391628A6824200DC1C83 /* PermanentNoticePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B0391528A6824200DC1C83 /* PermanentNoticePresenter.swift */; }; B9B0391828A6838400DC1C83 /* PermanentNoticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B0391728A6838400DC1C83 /* PermanentNoticeView.swift */; }; B9B0391A28A68ADE00DC1C83 /* ConstraintsUpdatingHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9B0391928A68ADE00DC1C83 /* ConstraintsUpdatingHostingController.swift */; }; @@ -2889,9 +2891,9 @@ 03EF250128C615A5006A033E /* InPersonPaymentsMenuViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsMenuViewModel.swift; sourceTree = ""; }; 03EF250328C6283B006A033E /* InPersonPaymentsMenuViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsMenuViewModelTests.swift; sourceTree = ""; }; 03EF250528C75838006A033E /* PurchaseCardReaderWebViewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseCardReaderWebViewViewModel.swift; sourceTree = ""; }; - 03F5CB822A0C3A1A0026877A /* AnimatedPlaceholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedPlaceholder.swift; sourceTree = ""; }; 03F5CAFE2A0BA37C0026877A /* JustInTimeMessageModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustInTimeMessageModal.swift; sourceTree = ""; }; 03F5CB002A0BA3D40026877A /* ModalOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalOverlay.swift; sourceTree = ""; }; + 03F5CB822A0C3A1A0026877A /* AnimatedPlaceholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedPlaceholder.swift; sourceTree = ""; }; 03FBDA9C263AD49100ACE257 /* CouponListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CouponListViewController.swift; sourceTree = ""; }; 03FBDAA2263AED2F00ACE257 /* CouponListViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CouponListViewController.xib; sourceTree = ""; }; 03FBDAF1263EE47C00ACE257 /* CouponListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CouponListViewModel.swift; sourceTree = ""; }; @@ -3853,6 +3855,8 @@ B958B4D92983E3F40010286B /* OrderDurationRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderDurationRecorder.swift; sourceTree = ""; }; B96B536A2816ECFC00F753E6 /* CardPresentPluginsDataProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentPluginsDataProviderTests.swift; sourceTree = ""; }; B96D6C0629081AE5003D2DC0 /* InAppPurchasesForWPComPlansManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchasesForWPComPlansManager.swift; sourceTree = ""; }; + B99686DF2A13C8CC00D1AF62 /* ScanToPayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToPayView.swift; sourceTree = ""; }; + B99686E22A13C98200D1AF62 /* ScanToPayViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToPayViewModel.swift; sourceTree = ""; }; B9B0391528A6824200DC1C83 /* PermanentNoticePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermanentNoticePresenter.swift; sourceTree = ""; }; B9B0391728A6838400DC1C83 /* PermanentNoticeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermanentNoticeView.swift; sourceTree = ""; }; B9B0391928A68ADE00DC1C83 /* ConstraintsUpdatingHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstraintsUpdatingHostingController.swift; sourceTree = ""; }; @@ -8409,6 +8413,15 @@ path = DurationRecorder; sourceTree = ""; }; + B99686E12A13C8D200D1AF62 /* ScanToPay */ = { + isa = PBXGroup; + children = ( + B99686DF2A13C8CC00D1AF62 /* ScanToPayView.swift */, + B99686E22A13C98200D1AF62 /* ScanToPayViewModel.swift */, + ); + path = ScanToPay; + sourceTree = ""; + }; B9B0391B28A690DA00DC1C83 /* PermanentNotice */ = { isa = PBXGroup; children = ( @@ -8916,6 +8929,7 @@ CE1CCB4920570B05000EE3AC /* Orders */ = { isa = PBXGroup; children = ( + B99686E12A13C8D200D1AF62 /* ScanToPay */, CEE006022077D0F80079161F /* Cells */, 268FD44827580A92008FDF9B /* Collect Payments */, CE35F1092343E482007B2A6B /* Order Details */, @@ -11267,6 +11281,7 @@ CCD2F51C26D697860010E679 /* ShippingLabelServicePackageListViewModel.swift in Sources */, 03076D38290C223E008EE839 /* WooNavigationSheet.swift in Sources */, 022CE91A29BB143000F210E0 /* ProductSelectorNavigationView.swift in Sources */, + B99686E02A13C8CC00D1AF62 /* ScanToPayView.swift in Sources */, E107FCE326C13A0D00BAF51B /* InPersonPaymentsSupportLink.swift in Sources */, 2662D90626E1571900E25611 /* ListSelector.swift in Sources */, 74D0A5302139CF1300E2919F /* String+Helpers.swift in Sources */, @@ -11964,6 +11979,7 @@ 45B9C63E23A8E50D007FC4C5 /* ProductPriceSettingsViewController.swift in Sources */, 318853362639FC9C00F66A9C /* PaymentSettingsFlowPresentingViewController.swift in Sources */, 452FE64B25657EC100EB54A0 /* LinkedProductsViewController.swift in Sources */, + B99686E32A13C98200D1AF62 /* ScanToPayViewModel.swift in Sources */, 450C6EEA286F4334002DB168 /* SitePlugin+Woo.swift in Sources */, 45B9C64123A9139A007FC4C5 /* Product+PriceSettingsViewModels.swift in Sources */, 038BC38129C4B8AC00EAF565 /* SetUpTapToPayTryPaymentPromptViewModel.swift in Sources */, diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModelTests.swift index 6b27daee116..6f0a5a05854 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModelTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModelTests.swift @@ -660,6 +660,33 @@ final class PaymentMethodsViewModelTests: XCTestCase { XCTAssertTrue(receivedCompleted) } + func test_view_model_attempts_created_notice_after_scan_to_pay() { + // Given + let noticeSubject = PassthroughSubject() + let dependencies = Dependencies(presentNoticeSubject: noticeSubject) + let viewModel = PaymentMethodsViewModel(formattedTotal: "$12.00", + flow: .simplePayment, + isTapToPayOnIPhoneEnabled: false, + dependencies: dependencies) + + // When + let receivedCompleted: Bool = waitFor { promise in + noticeSubject.sink { intent in + switch intent { + case .error, .completed: + promise(false) + case .created: + promise(true) + } + } + .store(in: &self.subscriptions) + viewModel.performScanToPayFinishedTasks() + } + + // Then + XCTAssertTrue(receivedCompleted) + } + func test_view_model_attempts_completed_notice_after_collecting_payment() { // Given let storage = MockStorageManager() diff --git a/WooFoundation/WooFoundation.xcodeproj/project.pbxproj b/WooFoundation/WooFoundation.xcodeproj/project.pbxproj index f2f1d59e792..fe37c3c7442 100644 --- a/WooFoundation/WooFoundation.xcodeproj/project.pbxproj +++ b/WooFoundation/WooFoundation.xcodeproj/project.pbxproj @@ -34,6 +34,7 @@ AE948D0D28CF6D50009F3246 /* DateStartAndEndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE948D0C28CF6D50009F3246 /* DateStartAndEndTests.swift */; }; B97190D1292CF3BC0065E413 /* Result+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B97190D0292CF3BC0065E413 /* Result+Extensions.swift */; }; B987B06F284540D300C53CF6 /* CurrencyCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B987B06E284540D300C53CF6 /* CurrencyCode.swift */; }; + B99686DE2A13B38B00D1AF62 /* FullScreenCoverClearBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B99686DD2A13B38B00D1AF62 /* FullScreenCoverClearBackgroundView.swift */; }; B9C9C63F283E703C001B879F /* WooFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9C9C635283E703C001B879F /* WooFoundation.framework */; }; B9C9C659283E7195001B879F /* NSDecimalNumber+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C9C658283E7195001B879F /* NSDecimalNumber+Helpers.swift */; }; B9C9C65D283E71C8001B879F /* CurrencyFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C9C65B283E71C8001B879F /* CurrencyFormatter.swift */; }; @@ -82,6 +83,7 @@ AE948D0C28CF6D50009F3246 /* DateStartAndEndTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateStartAndEndTests.swift; sourceTree = ""; }; B97190D0292CF3BC0065E413 /* Result+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Extensions.swift"; sourceTree = ""; }; B987B06E284540D300C53CF6 /* CurrencyCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrencyCode.swift; sourceTree = ""; }; + B99686DD2A13B38B00D1AF62 /* FullScreenCoverClearBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenCoverClearBackgroundView.swift; sourceTree = ""; }; B9AED558283E7553002A2668 /* Yosemite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Yosemite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B9AED55B283E755A002A2668 /* Hardware.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Hardware.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B9C9C635283E703C001B879F /* WooFoundation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WooFoundation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -153,6 +155,7 @@ 686BE910288EE09B00967C86 /* Utilities */ = { isa = PBXGroup; children = ( + B99686DD2A13B38B00D1AF62 /* FullScreenCoverClearBackgroundView.swift */, 686BE911288EE0D300967C86 /* TypedPredicates.swift */, 03597A9328F85686005E4A98 /* UTMParameters.swift */, 03597A9A28F87BFC005E4A98 /* WooCommerceComUTMProvider.swift */, @@ -499,6 +502,7 @@ 26AF1F5528B8362800937BA9 /* UIColor+ColorStudio.swift in Sources */, 26AF1F5428B8362800937BA9 /* ColorStudio.swift in Sources */, 68FBC5B328926B2C00A05461 /* Collection+Extensions.swift in Sources */, + B99686DE2A13B38B00D1AF62 /* FullScreenCoverClearBackgroundView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/WooFoundation/WooFoundation/Utilities/FullScreenCoverClearBackgroundView.swift b/WooFoundation/WooFoundation/Utilities/FullScreenCoverClearBackgroundView.swift new file mode 100644 index 00000000000..bfb8a55ec2b --- /dev/null +++ b/WooFoundation/WooFoundation/Utilities/FullScreenCoverClearBackgroundView.swift @@ -0,0 +1,31 @@ +import SwiftUI + +/// Use this view to clear the background of a view in SwiftUI after it's presented with `fullScreenCover` +/// +/// Use it as follows: +/// +///``` +/// .fullScreenCover(isPresented: $showingFooView) { +/// FooView() +/// .background(FullScreenCoverClearBackgroundView()) +/// } +/// ``` +/// +public struct FullScreenCoverClearBackgroundView: UIViewRepresentable { + public init() {} + + public func makeUIView(context: Context) -> UIView { + return InnerView() + } + + public func updateUIView(_ uiView: UIView, context: Context) { + } + + private class InnerView: UIView { + override func didMoveToWindow() { + super.didMoveToWindow() + + superview?.superview?.backgroundColor = .clear + } + } +}