Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ struct JustInTimeMessageAnnouncementCardViewModel: AnnouncementCardViewModelProt
// No-op
}

let onCTATapped: (() -> Void)?

func ctaTapped() {
// No-op
onCTATapped?()
}

var showDismissConfirmation: Bool = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@ extension UpsellCardReadersCampaign {
static let cardReaderWebViewTitle = NSLocalizedString(
"Purchase Card Reader",
comment: "Title for the WebView opened to upsell card readers")

static let cardReaderWebViewDoneButtonTitle = NSLocalizedString(
"Done",
comment: "Title for the Done button on the WebView opened to upsell card readers")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ final class DashboardViewController: UIViewController {
observeStatsVersionForDashboardUIUpdates()
trackProductsOnboardingEligibility()
observeAnnouncements()
observeShowWebViewSheet()
if hasAnnouncementFeatureFlag {
viewModel.syncAnnouncements(for: siteID)
}
Expand Down Expand Up @@ -305,6 +306,23 @@ private extension DashboardViewController {
}.store(in: &subscriptions)
}

func observeShowWebViewSheet() {
viewModel.$showWebViewSheet.sink { [weak self] viewModel in
guard let self = self else { return }
guard let viewModel = viewModel else { return }
self.openWebView(viewModel: viewModel)
}
.store(in: &subscriptions)
}

private func openWebView(viewModel: WebViewSheetViewModel) {
let cardReaderWebview = WebViewSheet(viewModel: viewModel) { [weak self] in
self?.dismiss(animated: true)
}
let hostingController = UIHostingController(rootView: cardReaderWebview)
present(hostingController, animated: true, completion: nil)
}

// This is used so we have a specific type for the view while applying modifiers.
struct AnnouncementCardWrapper: View {
let cardView: FeatureAnnouncementCardView
Expand All @@ -323,8 +341,8 @@ private extension DashboardViewController {
}

let cardView = FeatureAnnouncementCardView(viewModel: viewModel,
dismiss: {},
callToAction: {})
dismiss: {},
callToAction: {})

self.showAnnouncement(AnnouncementCardWrapper(cardView: cardView))
}
Expand Down
27 changes: 24 additions & 3 deletions WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ final class DashboardViewModel {

@Published private(set) var announcementViewModel: AnnouncementCardViewModelProtocol? = nil

@Published private(set) var showWebViewSheet: WebViewSheetViewModel? = nil

private let stores: StoresManager

init(stores: StoresManager = ServiceLocator.stores) {
Expand Down Expand Up @@ -107,9 +109,20 @@ final class DashboardViewModel {
hook: .adminNotices) { result in
switch result {
case let .success(.some(message)):
let viewModel = JustInTimeMessageAnnouncementCardViewModel(title: message.title,
message: message.detail,
buttonTitle: message.buttonTitle)
let viewModel = JustInTimeMessageAnnouncementCardViewModel(
title: message.title,
message: message.detail,
buttonTitle: message.buttonTitle,
onCTATapped: { [weak self] in
guard let self = self,
let url = URL(string: message.url)
else { return }
let webViewModel = WebViewSheetViewModel(
url: url,
navigationTitle: message.title,
wpComAuthenticated: self.needsAuthenticatedWebView(url: url))
self.showWebViewSheet = webViewModel
})
self.announcementViewModel = viewModel
default:
break
Expand All @@ -118,6 +131,13 @@ final class DashboardViewModel {
stores.dispatch(action)
}
}

private func needsAuthenticatedWebView(url: URL) -> Bool {
guard let host = url.host else {
return false
}
return Constants.trustedDomains.contains(host)
}
}

// MARK: - Constants
Expand All @@ -126,5 +146,6 @@ private extension DashboardViewModel {
enum Constants {
static let topEarnerStatsLimit: Int = 5
static let dashboardScreenName = "my_store"
static let trustedDomains = ["woocommerce.com", "wordpress.com"]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -434,30 +434,18 @@ extension OrderListViewController: SyncingCoordinatorDelegate {
private func openCardReaderProductPageInWebView() {
let configuration = CardPresentConfigurationLoader().configuration
let url = configuration.purchaseCardReaderUrl(utmProvider: viewModel.upsellCardReadersCampaign.utmProvider)
let cardReaderWebview = makeCardReaderProductPageWebView(url: url)
let cardReaderWebview = WebViewSheet(
viewModel: WebViewSheetViewModel(
url: url,
navigationTitle: UpsellCardReadersCampaign.Localization.cardReaderWebViewTitle,
wpComAuthenticated: true),
done: { [weak self] in
self?.dismiss(animated: true)
})
let hostingController = UIHostingController(rootView: cardReaderWebview)
present(hostingController, animated: true, completion: nil)
}

private func makeCardReaderProductPageWebView(url: URL) -> some View {
return NavigationView {
AuthenticatedWebView(isPresented: .constant(true),
url: url)
.navigationTitle(UpsellCardReadersCampaign.Localization.cardReaderWebViewTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button(action: { [weak self] in
self?.dismiss(animated: true)
}, label: {
Text(UpsellCardReadersCampaign.Localization.cardReaderWebViewDoneButtonTitle)
})
}
}
}
.wooNavigationBarStyle()
}

func updateUpsellCardReaderTopBannerVisibility(with newCollection: UITraitCollection) {
guard viewModel.topBanner == .upsellCardReaders else {
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,20 +104,14 @@ struct PaymentMethodsView: View {
}
}
.sheet(isPresented: $showingPurchaseCardReaderView) {
NavigationView {
AuthenticatedWebView(isPresented: .constant(true),
url: viewModel.purchaseCardReaderUrl)
.navigationTitle(Text(UpsellCardReadersCampaign.Localization.cardReaderWebViewTitle))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(UpsellCardReadersCampaign.Localization.cardReaderWebViewDoneButtonTitle) {
showingPurchaseCardReaderView = false
}
}
}
}
.wooNavigationBarStyle()
WebViewSheet(
viewModel: WebViewSheetViewModel(
url: viewModel.purchaseCardReaderUrl,
navigationTitle: UpsellCardReadersCampaign.Localization.cardReaderWebViewTitle,
wpComAuthenticated: true),
done: {
showingPurchaseCardReaderView = false
})
.navigationViewStyle(.stack)
}
.shareSheet(isPresented: $sharingPaymentLink) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import SwiftUI
import WebKit
import Alamofire
import class Networking.UserAgent

/// Mirror of AuthenticatedWebView, for equivalent display of URLs in `WKWebView` that do not need authentication on WPCom.
struct WebView: UIViewRepresentable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering, since most of this code is duplicated with AuthenticatedWebView, is there a way to collapse both structs into one with some sort of configuration to authenticate when necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@toupper Probably... I'll take a look towards the end of the week in another PR, as I'm trying to de-risk delivery in 11.1 as much as possible. Other than that, would you be happy to approve?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's great! sure thing 👍

@Environment(\.presentationMode) var presentation
@Binding var isPresented: Bool {
didSet {
if !isPresented {
presentation.wrappedValue.dismiss()
}
}
}

let url: URL

/// Optional URL or part of URL to trigger exit
///
var urlToTriggerExit: String?

/// Callback that will be triggered if the destination url containts the `urlToTriggerExit`
///
var exitTrigger: (() -> Void)?

private let credentials = ServiceLocator.stores.sessionManager.defaultCredentials

func makeCoordinator() -> WebViewCoordinator {
WebViewCoordinator(self)
}

func makeUIView(context: Context) -> WKWebView {
let webview = WKWebView()
webview.customUserAgent = UserAgent.defaultUserAgent
webview.navigationDelegate = context.coordinator

webview.load(URLRequest(url: url))
return webview
}

func updateUIView(_ uiView: WKWebView, context: Context) {

}

class WebViewCoordinator: NSObject, WKNavigationDelegate {
private var parent: WebView

init(_ uiWebView: WebView) {
parent = uiWebView
}

func webView(_ webView: WKWebView, decidePolicyFor
navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = webView.url?.absoluteString, let urlTrigger = parent.urlToTriggerExit, url.contains(urlTrigger) {
parent.exitTrigger?()
decisionHandler(.cancel)
webView.navigationDelegate = nil
return
}
decisionHandler(.allow)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import SwiftUI

struct WebViewSheetViewModel {
let url: URL
let navigationTitle: String
let wpComAuthenticated: Bool
}

struct WebViewSheet: View {
let viewModel: WebViewSheetViewModel

let done: () -> Void

var body: some View {
WooNavigationSheet(viewModel: .init(navigationTitle: viewModel.navigationTitle,
done: done)) {
switch viewModel.wpComAuthenticated {
case true:
AuthenticatedWebView(isPresented: .constant(true),
url: viewModel.url)
case false:
WebView(isPresented: .constant(true),
url: viewModel.url)
}
}
}
}

struct WebViewSheet_Previews: PreviewProvider {
static var previews: some View {
WebViewSheet(
viewModel: WebViewSheetViewModel.init(
url: URL(string: "https://woocommerce.com")!,
navigationTitle: "WooCommerce.com",
wpComAuthenticated: true),
done: { })
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import SwiftUI

struct WooNavigationSheetViewModel {
let navigationTitle: String
let done: () -> Void
let doneButtonTitle = NSLocalizedString(
"Done",
comment: "Title for the Done button on a WebView modal sheet")
}

struct WooNavigationSheet<Content: View>: View {
let content: Content

let viewModel: WooNavigationSheetViewModel

init(viewModel: WooNavigationSheetViewModel,
@ViewBuilder content: () -> Content) {
self.content = content()
self.viewModel = viewModel
}

var body: some View {
NavigationView {
content
.navigationTitle(viewModel.navigationTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button(action: viewModel.done,
label: {
Text(viewModel.doneButtonTitle)
})
}
}
.wooNavigationBarStyle()
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
12 changes: 12 additions & 0 deletions WooCommerce/WooCommerce.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,9 @@
0304E35E28BDC86D00A80191 /* LearnMoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0304E35D28BDC86D00A80191 /* LearnMoreViewModel.swift */; };
0304E36428BE1EDE00A80191 /* LeftImageTitleSubtitleToggleTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0304E36328BE1EDE00A80191 /* LeftImageTitleSubtitleToggleTableViewCell.xib */; };
0304E36628BE1EED00A80191 /* LeftImageTitleSubtitleToggleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0304E36528BE1EED00A80191 /* LeftImageTitleSubtitleToggleTableViewCell.swift */; };
03076D36290C162E008EE839 /* WebViewSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03076D35290C162E008EE839 /* WebViewSheet.swift */; };
03076D38290C223E008EE839 /* WooNavigationSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03076D37290C223D008EE839 /* WooNavigationSheet.swift */; };
03076D3A290C22BE008EE839 /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03076D39290C22BE008EE839 /* WebView.swift */; };
0313651128AB81B100EEE571 /* InPersonPaymentsCashOnDeliveryPaymentGatewayNotSetUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0313651028AB81B100EEE571 /* InPersonPaymentsCashOnDeliveryPaymentGatewayNotSetUpView.swift */; };
0313651328ABCB2D00EEE571 /* InPersonPaymentsOnboardingErrorMainContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0313651228ABCB2D00EEE571 /* InPersonPaymentsOnboardingErrorMainContentView.swift */; };
0313651728ACE9F400EEE571 /* InPersonPaymentsCashOnDeliveryPaymentGatewayNotSetUpViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0313651628ACE9F400EEE571 /* InPersonPaymentsCashOnDeliveryPaymentGatewayNotSetUpViewModel.swift */; };
Expand Down Expand Up @@ -2362,6 +2365,9 @@
0304E35D28BDC86D00A80191 /* LearnMoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LearnMoreViewModel.swift; sourceTree = "<group>"; };
0304E36328BE1EDE00A80191 /* LeftImageTitleSubtitleToggleTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LeftImageTitleSubtitleToggleTableViewCell.xib; sourceTree = "<group>"; };
0304E36528BE1EED00A80191 /* LeftImageTitleSubtitleToggleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftImageTitleSubtitleToggleTableViewCell.swift; sourceTree = "<group>"; };
03076D35290C162E008EE839 /* WebViewSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewSheet.swift; sourceTree = "<group>"; };
03076D37290C223D008EE839 /* WooNavigationSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooNavigationSheet.swift; sourceTree = "<group>"; };
03076D39290C22BE008EE839 /* WebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = "<group>"; };
0313651028AB81B100EEE571 /* InPersonPaymentsCashOnDeliveryPaymentGatewayNotSetUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsCashOnDeliveryPaymentGatewayNotSetUpView.swift; sourceTree = "<group>"; };
0313651228ABCB2D00EEE571 /* InPersonPaymentsOnboardingErrorMainContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsOnboardingErrorMainContentView.swift; sourceTree = "<group>"; };
0313651628ACE9F400EEE571 /* InPersonPaymentsCashOnDeliveryPaymentGatewayNotSetUpViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InPersonPaymentsCashOnDeliveryPaymentGatewayNotSetUpViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -5925,6 +5931,9 @@
B9E4364B287587D300883CFA /* FeatureAnnouncementCardView.swift */,
B9E4364D287589E200883CFA /* BadgeView.swift */,
6827140E28A3988300E6E3F6 /* DismissableNoticeView.swift */,
03076D39290C22BE008EE839 /* WebView.swift */,
03076D35290C162E008EE839 /* WebViewSheet.swift */,
03076D37290C223D008EE839 /* WooNavigationSheet.swift */,
);
path = "SwiftUI Components";
sourceTree = "<group>";
Expand Down Expand Up @@ -9675,6 +9684,7 @@
0240B3AC230A910C000A866C /* StoreStatsV4ChartAxisHelper.swift in Sources */,
31579028273EE2B1008CA3AF /* VersionHelpers.swift in Sources */,
CCD2F51C26D697860010E679 /* ShippingLabelServicePackageListViewModel.swift in Sources */,
03076D38290C223E008EE839 /* WooNavigationSheet.swift in Sources */,
E107FCE326C13A0D00BAF51B /* InPersonPaymentsSupportLink.swift in Sources */,
2662D90626E1571900E25611 /* ListSelector.swift in Sources */,
74D0A5302139CF1300E2919F /* String+Helpers.swift in Sources */,
Expand Down Expand Up @@ -9899,6 +9909,7 @@
AEACCB6D2785FF4A000D01F0 /* NavigationRow.swift in Sources */,
DE50294928BEF4CF00551736 /* WordPressOrgCredentials+Authenticator.swift in Sources */,
02E8B17E23E2C8D900A43403 /* ProductImageActionHandler.swift in Sources */,
03076D3A290C22BE008EE839 /* WebView.swift in Sources */,
023D877925EC8BCB00625963 /* UIScrollView+LargeTitleWorkaround.swift in Sources */,
2664210326F40FB1001FC5B4 /* View+ScrollModifiers.swift in Sources */,
02695770237281A9001BA0BF /* AztecTextViewAttachmentHandler.swift in Sources */,
Expand Down Expand Up @@ -10349,6 +10360,7 @@
260C32BE2527A2DE00157BC2 /* IssueRefundViewModel.swift in Sources */,
2678897C270E6E8B00BD249E /* SimplePaymentsAmount.swift in Sources */,
09F5DE5D27CF948000E5A4D2 /* BulkUpdateOptionsModel.swift in Sources */,
03076D36290C162E008EE839 /* WebViewSheet.swift in Sources */,
450C2CBA24D3127500D570DD /* ProductReviewsTableViewCell.swift in Sources */,
029D444922F13F8A00DEFA8A /* DashboardUI.swift in Sources */,
D8C2A28F231BD00500F503E9 /* ReviewsViewModel.swift in Sources */,
Expand Down