From 7dde3bd63f14da2515e7a49621b72b98f436d6d9 Mon Sep 17 00:00:00 2001 From: Alexander Lisovik Date: Tue, 19 Sep 2023 12:27:18 +0400 Subject: [PATCH 01/11] Add Verify 'isScam' field, update WalletApp UI --- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../AuthRequest/AuthRequestPresenter.swift | 4 +- .../Wallet/AuthRequest/AuthRequestView.swift | 165 +++++++++++++++--- .../SessionProposalPresenter.swift | 4 +- .../SessionProposal/SessionProposalView.swift | 162 ++++++++++++++--- .../SessionRequestPresenter.swift | 4 +- .../SessionRequest/SessionRequestView.swift | 157 +++++++++++++++-- .../Wallet/WalletRequestSubscriber.swift | 6 +- .../Engine/Common/ApproveEngine.swift | 9 +- .../Engine/Common/SessionEngine.swift | 6 +- .../WalletConnectVerify/OriginVerifier.swift | 4 +- .../Register/VerifyResponse.swift | 5 +- .../WalletConnectVerify/VerifyClient.swift | 21 ++- .../WalletConnectVerify/VerifyContext.swift | 1 + 14 files changed, 461 insertions(+), 91 deletions(-) diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9d6fdc5ce..d4ad48aad 100644 --- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -69,8 +69,8 @@ "repositoryURL": "https://github.com/getsentry/sentry-cocoa.git", "state": { "branch": null, - "revision": "04bee4ad86d74d4cb4d7101ff826d6e355301ba9", - "version": "8.9.4" + "revision": "12998398eb51e2e8ff7098163fa97d305eee6d87", + "version": "8.11.0" } }, { diff --git a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift index 4a9cae866..f1fe362ef 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestPresenter.swift @@ -9,7 +9,7 @@ final class AuthRequestPresenter: ObservableObject { let importAccount: ImportAccount let request: AuthRequest - let verified: Bool? + let validationStatus: VerifyContext.ValidationStatus? var message: String { return interactor.formatted(request: request, account: importAccount.account) @@ -29,7 +29,7 @@ final class AuthRequestPresenter: ObservableObject { self.router = router self.importAccount = importAccount self.request = request - self.verified = (context?.validation == .valid) ? true : (context?.validation == .unknown ? nil : false) + self.validationStatus = context?.validation } @MainActor diff --git a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift index 067a6b00e..d857db13a 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift @@ -17,28 +17,10 @@ struct AuthRequestView: View { .resizable() .scaledToFit() - HStack { - Text(presenter.request.payload.domain) - .foregroundColor(.grey8) - .font(.system(size: 22, weight: .bold, design: .rounded)) - - if let verified = presenter.verified { - if verified { - Image(systemName: "checkmark.shield.fill") - .symbolRenderingMode(.palette) - .foregroundStyle(.white, .green) - } else { - Image(systemName: "xmark.shield.fill") - .symbolRenderingMode(.palette) - .foregroundStyle(.white, .red) - } - } else { - Image(systemName: "exclamationmark.shield.fill") - .symbolRenderingMode(.palette) - .foregroundStyle(.white, .orange) - } - } - .padding(.top, 10) + Text(presenter.request.payload.domain) + .foregroundColor(.grey8) + .font(.system(size: 22, weight: .bold, design: .rounded)) + .padding(.top, 10) Text("would like to connect") .foregroundColor(.grey8) @@ -51,8 +33,145 @@ struct AuthRequestView: View { .lineSpacing(4) .padding(.top, 8) + switch presenter.validationStatus { + case .unknown: + HStack(spacing: 5) { + Image(systemName: "exclamationmark.circle.fill") + .font(.system(size: 14, weight: .semibold, design: .rounded)) + .foregroundColor(.orange) + + Text("Cannot verify") + .foregroundColor(.orange) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + } + .padding(5) + .background(Color.orange.opacity(0.15)) + .cornerRadius(10) + .padding(.top, 8) + + case .valid: + HStack(spacing: 5) { + Image(systemName: "checkmark.seal.fill") + .font(.system(size: 14, weight: .semibold, design: .rounded)) + .foregroundColor(.blue) + + Text("Verified domain") + .foregroundColor(.blue) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + } + .padding(5) + .background(Color.blue.opacity(0.15)) + .cornerRadius(10) + .padding(.top, 8) + + case .invalid: + HStack(spacing: 5) { + Image(systemName: "exclamationmark.triangle.fill") + .font(.system(size: 14, weight: .semibold, design: .rounded)) + .foregroundColor(.red) + + Text("Invalid domain") + .foregroundColor(.red) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + } + .padding(5) + .background(Color.red.opacity(0.15)) + .cornerRadius(10) + .padding(.top, 8) + + case .scam: + HStack(spacing: 5) { + Image(systemName: "exclamationmark.shield.fill") + .font(.system(size: 14, weight: .semibold, design: .rounded)) + .foregroundColor(.red) + + Text("Security risk") + .foregroundColor(.red) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + } + .padding(5) + .background(Color.red.opacity(0.15)) + .cornerRadius(10) + .padding(.top, 8) + + default: + EmptyView() + } + authRequestView() + switch presenter.validationStatus { + case .invalid: + HStack(spacing: 15) { + Image(systemName: "exclamationmark.shield.fill") + .font(.system(size: 20, design: .rounded)) + .foregroundColor(.red) + + VStack(alignment: .leading, spacing: 5) { + Text("Invalid domain") + .foregroundColor(.red) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + Text("This website has a domain that does not match the sender of this request. Approving may lead to loss of funds.") + .foregroundColor(.grey8) + .font(.system(size: 14, weight: .medium, design: .rounded)) + } + } + .frame(maxWidth: .infinity) + .padding() + .background(Color.red.opacity(0.15)) + .cornerRadius(20) + + case .unknown: + HStack(spacing: 15) { + Image(systemName: "exclamationmark.shield.fill") + .font(.system(size: 20, design: .rounded)) + .foregroundColor(.orange) + + VStack(alignment: .leading, spacing: 5) { + Text("Unknown domain") + .foregroundColor(.orange) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + Text("This domain cannot be verified. Check the request carefully before approving.") + .foregroundColor(.grey8) + .font(.system(size: 14, weight: .medium, design: .rounded)) + } + } + .frame(maxWidth: .infinity) + .padding() + .background(Color.orange.opacity(0.15)) + .cornerRadius(20) + + case .scam: + HStack(spacing: 15) { + Image(systemName: "exclamationmark.shield.fill") + .font(.system(size: 20, design: .rounded)) + .foregroundColor(.red) + + VStack(alignment: .leading, spacing: 5) { + Text("Security risk") + .foregroundColor(.red) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + Text("This website is flagged as unsafe by multiple security providers. Leave immediately to protect your assets.") + .foregroundColor(.grey8) + .font(.system(size: 14, weight: .medium, design: .rounded)) + } + } + .frame(maxWidth: .infinity) + .padding() + .background(Color.red.opacity(0.15)) + .cornerRadius(20) + + default: + EmptyView() + } + HStack(spacing: 20) { Button { Task(priority: .userInitiated) { try await @@ -143,7 +262,7 @@ struct AuthRequestView: View { .background(.thinMaterial) .cornerRadius(25, corners: .allCorners) } - .padding(.top, 30) + .padding(.vertical, 30) } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift index ad02dd7a4..be919a381 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift @@ -9,7 +9,7 @@ final class SessionProposalPresenter: ObservableObject { let importAccount: ImportAccount let sessionProposal: Session.Proposal - let verified: Bool? + let validationStatus: VerifyContext.ValidationStatus? @Published var showError = false @Published var errorMessage = "Error" @@ -28,7 +28,7 @@ final class SessionProposalPresenter: ObservableObject { self.router = router self.sessionProposal = proposal self.importAccount = importAccount - self.verified = (context?.validation == .valid) ? true : (context?.validation == .unknown ? nil : false) + self.validationStatus = context?.validation } @MainActor diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift index 8082ce1ee..06ad2a554 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift @@ -22,22 +22,6 @@ struct SessionProposalView: View { Text(presenter.sessionProposal.proposer.name) .foregroundColor(.grey8) .font(.system(size: 22, weight: .bold, design: .rounded)) - - if let verified = presenter.verified { - if verified { - Image(systemName: "checkmark.shield.fill") - .symbolRenderingMode(.palette) - .foregroundStyle(.white, .green) - } else { - Image(systemName: "xmark.shield.fill") - .symbolRenderingMode(.palette) - .foregroundStyle(.white, .red) - } - } else { - Image(systemName: "exclamationmark.shield.fill") - .symbolRenderingMode(.palette) - .foregroundStyle(.white, .orange) - } } .padding(.top, 10) @@ -46,13 +30,82 @@ struct SessionProposalView: View { .foregroundColor(.grey8) .font(.system(size: 22, weight: .medium, design: .rounded)) - Text(presenter.sessionProposal.proposer.name) + Text(presenter.sessionProposal.proposer.url) .foregroundColor(.grey50) .font(.system(size: 13, weight: .semibold, design: .rounded)) .multilineTextAlignment(.center) .lineSpacing(4) .padding(.top, 8) + switch presenter.validationStatus { + case .unknown: + HStack(spacing: 5) { + Image(systemName: "exclamationmark.circle.fill") + .font(.system(size: 14, weight: .semibold, design: .rounded)) + .foregroundColor(.orange) + + Text("Cannot verify") + .foregroundColor(.orange) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + } + .padding(5) + .background(Color.orange.opacity(0.15)) + .cornerRadius(10) + .padding(.top, 8) + + case .valid: + HStack(spacing: 5) { + Image(systemName: "checkmark.seal.fill") + .font(.system(size: 14, weight: .semibold, design: .rounded)) + .foregroundColor(.blue) + + Text("Verified domain") + .foregroundColor(.blue) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + } + .padding(5) + .background(Color.blue.opacity(0.15)) + .cornerRadius(10) + .padding(.top, 8) + + case .invalid: + HStack(spacing: 5) { + Image(systemName: "exclamationmark.triangle.fill") + .font(.system(size: 14, weight: .semibold, design: .rounded)) + .foregroundColor(.red) + + Text("Invalid domain") + .foregroundColor(.red) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + } + .padding(5) + .background(Color.red.opacity(0.15)) + .cornerRadius(10) + .padding(.top, 8) + + case .scam: + HStack(spacing: 5) { + Image(systemName: "exclamationmark.shield.fill") + .font(.system(size: 14, weight: .semibold, design: .rounded)) + .foregroundColor(.red) + + Text("Security risk") + .foregroundColor(.red) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + } + .padding(5) + .background(Color.red.opacity(0.15)) + .cornerRadius(10) + .padding(.top, 8) + + default: + EmptyView() + } + Divider() .padding(.top, 12) .padding(.horizontal, -18) @@ -80,7 +133,7 @@ struct SessionProposalView: View { .lineSpacing(4) .padding(.vertical, 12) } - + ForEach(optionalNamespaces.keys.sorted(), id: \.self) { chain in if let namespaces = optionalNamespaces[chain] { sessionProposalView(namespaces: namespaces) @@ -89,7 +142,76 @@ struct SessionProposalView: View { } } .frame(height: 250) - .padding(.top, 12) + .cornerRadius(20) + .padding(.vertical, 12) + + switch presenter.validationStatus { + case .invalid: + HStack(spacing: 15) { + Image(systemName: "exclamationmark.shield.fill") + .font(.system(size: 20, design: .rounded)) + .foregroundColor(.red) + + VStack(alignment: .leading, spacing: 5) { + Text("Invalid domain") + .foregroundColor(.red) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + Text("This website has a domain that does not match the sender of this request. Approving may lead to loss of funds.") + .foregroundColor(.grey8) + .font(.system(size: 14, weight: .medium, design: .rounded)) + } + } + .frame(maxWidth: .infinity) + .padding() + .background(Color.red.opacity(0.15)) + .cornerRadius(20) + + case .unknown: + HStack(spacing: 15) { + Image(systemName: "exclamationmark.shield.fill") + .font(.system(size: 20, design: .rounded)) + .foregroundColor(.orange) + + VStack(alignment: .leading, spacing: 5) { + Text("Unknown domain") + .foregroundColor(.orange) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + Text("This domain cannot be verified. Check the request carefully before approving.") + .foregroundColor(.grey8) + .font(.system(size: 14, weight: .medium, design: .rounded)) + } + } + .frame(maxWidth: .infinity) + .padding() + .background(Color.orange.opacity(0.15)) + .cornerRadius(20) + + case .scam: + HStack(spacing: 15) { + Image(systemName: "exclamationmark.shield.fill") + .font(.system(size: 20, design: .rounded)) + .foregroundColor(.red) + + VStack(alignment: .leading, spacing: 5) { + Text("Security risk") + .foregroundColor(.red) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + Text("This website is flagged as unsafe by multiple security providers. Leave immediately to protect your assets.") + .foregroundColor(.grey8) + .font(.system(size: 14, weight: .medium, design: .rounded)) + } + } + .frame(maxWidth: .infinity) + .padding() + .background(Color.red.opacity(0.15)) + .cornerRadius(20) + + default: + EmptyView() + } HStack(spacing: 20) { Button { @@ -151,7 +273,7 @@ struct SessionProposalView: View { } .edgesIgnoringSafeArea(.all) } - //private func sessionProposalView(chain: String) -> some View { + private func sessionProposalView(namespaces: ProposalNamespace) -> some View { VStack { VStack(alignment: .leading) { diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift index 99ee1cc7c..59099c781 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift @@ -9,7 +9,7 @@ final class SessionRequestPresenter: ObservableObject { private let importAccount: ImportAccount let sessionRequest: Request - let verified: Bool? + let validationStatus: VerifyContext.ValidationStatus? var message: String { return String(describing: sessionRequest.params.value) @@ -32,7 +32,7 @@ final class SessionRequestPresenter: ObservableObject { self.router = router self.sessionRequest = sessionRequest self.importAccount = importAccount - self.verified = (context?.validation == .valid) ? true : (context?.validation == .unknown ? nil : false) + self.validationStatus = context?.validation } @MainActor diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift index dc56b2e24..1c1f1bfce 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift @@ -17,33 +17,152 @@ struct SessionRequestView: View { .resizable() .scaledToFit() - HStack { - Text(presenter.sessionRequest.method) - .foregroundColor(.grey8) - .font(.system(size: 22, weight: .bold, design: .rounded)) + Text(presenter.sessionRequest.method) + .foregroundColor(.grey8) + .font(.system(size: 22, weight: .bold, design: .rounded)) + .padding(.top, 10) + + switch presenter.validationStatus { + case .unknown: + HStack(spacing: 5) { + Image(systemName: "exclamationmark.circle.fill") + .font(.system(size: 14, weight: .semibold, design: .rounded)) + .foregroundColor(.orange) + + Text("Cannot verify") + .foregroundColor(.orange) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + } + .padding(5) + .background(Color.orange.opacity(0.15)) + .cornerRadius(10) + .padding(.top, 8) - if let verified = presenter.verified { - if verified { - Image(systemName: "checkmark.shield.fill") - .symbolRenderingMode(.palette) - .foregroundStyle(.white, .green) - } else { - Image(systemName: "xmark.shield.fill") - .symbolRenderingMode(.palette) - .foregroundStyle(.white, .red) - } - } else { + case .valid: + HStack(spacing: 5) { + Image(systemName: "checkmark.seal.fill") + .font(.system(size: 14, weight: .semibold, design: .rounded)) + .foregroundColor(.blue) + + Text("Verified domain") + .foregroundColor(.blue) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + } + .padding(5) + .background(Color.blue.opacity(0.15)) + .cornerRadius(10) + .padding(.top, 8) + + case .invalid: + HStack(spacing: 5) { + Image(systemName: "exclamationmark.triangle.fill") + .font(.system(size: 14, weight: .semibold, design: .rounded)) + .foregroundColor(.red) + + Text("Invalid domain") + .foregroundColor(.red) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + } + .padding(5) + .background(Color.red.opacity(0.15)) + .cornerRadius(10) + .padding(.top, 8) + + case .scam: + HStack(spacing: 5) { Image(systemName: "exclamationmark.shield.fill") - .symbolRenderingMode(.palette) - .foregroundStyle(.white, .orange) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + .foregroundColor(.red) + + Text("Security risk") + .foregroundColor(.red) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + } + .padding(5) + .background(Color.red.opacity(0.15)) + .cornerRadius(10) + .padding(.top, 8) + + default: + EmptyView() } - .padding(.top, 10) if presenter.message != "[:]" { authRequestView() } + switch presenter.validationStatus { + case .invalid: + HStack(spacing: 15) { + Image(systemName: "exclamationmark.shield.fill") + .font(.system(size: 20, design: .rounded)) + .foregroundColor(.red) + + VStack(alignment: .leading, spacing: 5) { + Text("Invalid domain") + .foregroundColor(.red) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + Text("This website has a domain that does not match the sender of this request. Approving may lead to loss of funds.") + .foregroundColor(.grey8) + .font(.system(size: 14, weight: .medium, design: .rounded)) + } + } + .frame(maxWidth: .infinity) + .padding() + .background(Color.red.opacity(0.15)) + .cornerRadius(20) + + case .unknown: + HStack(spacing: 15) { + Image(systemName: "exclamationmark.shield.fill") + .font(.system(size: 20, design: .rounded)) + .foregroundColor(.orange) + + VStack(alignment: .leading, spacing: 5) { + Text("Unknown domain") + .foregroundColor(.orange) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + Text("This domain cannot be verified. Check the request carefully before approving.") + .foregroundColor(.grey8) + .font(.system(size: 14, weight: .medium, design: .rounded)) + } + } + .frame(maxWidth: .infinity) + .padding() + .background(Color.orange.opacity(0.15)) + .cornerRadius(20) + + case .scam: + HStack(spacing: 15) { + Image(systemName: "exclamationmark.shield.fill") + .font(.system(size: 20, design: .rounded)) + .foregroundColor(.red) + + VStack(alignment: .leading, spacing: 5) { + Text("Security risk") + .foregroundColor(.red) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + Text("This website is flagged as unsafe by multiple security providers. Leave immediately to protect your assets.") + .foregroundColor(.grey8) + .font(.system(size: 14, weight: .medium, design: .rounded)) + } + } + .frame(maxWidth: .infinity) + .padding() + .background(Color.red.opacity(0.15)) + .cornerRadius(20) + + default: + EmptyView() + } + HStack(spacing: 20) { Button { Task(priority: .userInitiated) { try await @@ -137,7 +256,7 @@ struct SessionRequestView: View { .background(.thinMaterial) .cornerRadius(25, corners: .allCorners) } - .padding(.top, 30) + .padding(.vertical, 30) } } diff --git a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift index c2a7030c0..af26070ab 100644 --- a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift +++ b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift @@ -44,12 +44,12 @@ class WalletRequestSubscriber { Task(priority: .high) { let assertionId = payload.decryptedPayload.sha256().toHexString() do { - let origin = try await verifyClient.verifyOrigin(assertionId: assertionId) - let verifyContext = verifyClient.createVerifyContext(origin: origin, domain: payload.request.payloadParams.domain) + let response = try await verifyClient.verifyOrigin(assertionId: assertionId) + let verifyContext = verifyClient.createVerifyContext(origin: response.origin, domain: payload.request.payloadParams.domain, isScam: response.isScam) verifyContextStore.set(verifyContext, forKey: request.id.string) onRequest?((request, verifyContext)) } catch { - let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: payload.request.payloadParams.domain) + let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: payload.request.payloadParams.domain, isScam: nil) verifyContextStore.set(verifyContext, forKey: request.id.string) onRequest?((request, verifyContext)) return diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index dd77da949..7e26a2725 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -314,15 +314,16 @@ private extension ApproveEngine { Task(priority: .high) { let assertionId = payload.decryptedPayload.sha256().toHexString() do { - let origin = try await verifyClient.verifyOrigin(assertionId: assertionId) + let response = try await verifyClient.verifyOrigin(assertionId: assertionId) let verifyContext = verifyClient.createVerifyContext( - origin: origin, - domain: payload.request.proposer.metadata.url + origin: response.origin, + domain: payload.request.proposer.metadata.url, + isScam: response.isScam ) verifyContextStore.set(verifyContext, forKey: proposal.proposer.publicKey) onSessionProposal?(proposal.publicRepresentation(pairingTopic: payload.topic), verifyContext) } catch { - let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: payload.request.proposer.metadata.url) + let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: payload.request.proposer.metadata.url, isScam: nil) onSessionProposal?(proposal.publicRepresentation(pairingTopic: payload.topic), verifyContext) return } diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index b90a06fb5..3e6cec6bf 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -246,12 +246,12 @@ private extension SessionEngine { Task(priority: .high) { let assertionId = payload.decryptedPayload.sha256().toHexString() do { - let origin = try await verifyClient.verifyOrigin(assertionId: assertionId) - let verifyContext = verifyClient.createVerifyContext(origin: origin, domain: session.peerParticipant.metadata.url) + let response = try await verifyClient.verifyOrigin(assertionId: assertionId) + let verifyContext = verifyClient.createVerifyContext(origin: response.origin, domain: session.peerParticipant.metadata.url, isScam: response.isScam) verifyContextStore.set(verifyContext, forKey: request.id.string) onSessionRequest?(request, verifyContext) } catch { - let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: session.peerParticipant.metadata.url) + let verifyContext = verifyClient.createVerifyContext(origin: nil, domain: session.peerParticipant.metadata.url, isScam: nil) verifyContextStore.set(verifyContext, forKey: request.id.string) onSessionRequest?(request, verifyContext) return diff --git a/Sources/WalletConnectVerify/OriginVerifier.swift b/Sources/WalletConnectVerify/OriginVerifier.swift index 2ab3267ae..10b403f81 100644 --- a/Sources/WalletConnectVerify/OriginVerifier.swift +++ b/Sources/WalletConnectVerify/OriginVerifier.swift @@ -14,7 +14,7 @@ public final class OriginVerifier { self.verifyHost = verifyHost } - func verifyOrigin(assertionId: String) async throws -> String { + func verifyOrigin(assertionId: String) async throws -> VerifyResponse { let sessionConfiguration = URLSessionConfiguration.default sessionConfiguration.timeoutIntervalForRequest = 5.0 sessionConfiguration.timeoutIntervalForResource = 5.0 @@ -30,7 +30,7 @@ public final class OriginVerifier { guard let origin = response.origin else { throw Errors.registrationFailed } - return origin + return response } catch { if (error as? HTTPError) == .couldNotConnect && !fallback { fallback = true diff --git a/Sources/WalletConnectVerify/Register/VerifyResponse.swift b/Sources/WalletConnectVerify/Register/VerifyResponse.swift index ed2e59460..24fba83eb 100644 --- a/Sources/WalletConnectVerify/Register/VerifyResponse.swift +++ b/Sources/WalletConnectVerify/Register/VerifyResponse.swift @@ -1,5 +1,6 @@ import Foundation -struct VerifyResponse: Decodable { - let origin: String? +public struct VerifyResponse: Decodable { + public let origin: String? + public let isScam: Bool? } diff --git a/Sources/WalletConnectVerify/VerifyClient.swift b/Sources/WalletConnectVerify/VerifyClient.swift index b1364f347..37b74dfea 100644 --- a/Sources/WalletConnectVerify/VerifyClient.swift +++ b/Sources/WalletConnectVerify/VerifyClient.swift @@ -2,8 +2,8 @@ import DeviceCheck import Foundation public protocol VerifyClientProtocol { - func verifyOrigin(assertionId: String) async throws -> String - func createVerifyContext(origin: String?, domain: String) -> VerifyContext + func verifyOrigin(assertionId: String) async throws -> VerifyResponse + func createVerifyContext(origin: String?, domain: String, isScam: Bool?) -> VerifyContext } public actor VerifyClient: VerifyClientProtocol { @@ -33,11 +33,18 @@ public actor VerifyClient: VerifyClientProtocol { try await appAttestationRegistrer.registerAttestationIfNeeded() } - public func verifyOrigin(assertionId: String) async throws -> String { + public func verifyOrigin(assertionId: String) async throws -> VerifyResponse { return try await originVerifier.verifyOrigin(assertionId: assertionId) } - nonisolated public func createVerifyContext(origin: String?, domain: String) -> VerifyContext { + nonisolated public func createVerifyContext(origin: String?, domain: String, isScam: Bool?) -> VerifyContext { + guard isScam == nil else { + return VerifyContext( + origin: origin, + validation: .scam, + verifyUrl: verifyHost + ) + } if let origin, let originUrl = URL(string: origin), let domainUrl = URL(string: domain) { return VerifyContext( origin: origin, @@ -63,11 +70,11 @@ public actor VerifyClient: VerifyClientProtocol { public struct VerifyClientMock: VerifyClientProtocol { public init() {} - public func verifyOrigin(assertionId: String) async throws -> String { - return "domain.com" + public func verifyOrigin(assertionId: String) async throws -> VerifyResponse { + return VerifyResponse(origin: "domain.com", isScam: nil) } - public func createVerifyContext(origin: String?, domain: String) -> VerifyContext { + public func createVerifyContext(origin: String?, domain: String, isScam: Bool?) -> VerifyContext { return VerifyContext(origin: "domain.com", validation: .valid, verifyUrl: "verify.walletconnect.com") } } diff --git a/Sources/WalletConnectVerify/VerifyContext.swift b/Sources/WalletConnectVerify/VerifyContext.swift index 62b0be4a2..85382e090 100644 --- a/Sources/WalletConnectVerify/VerifyContext.swift +++ b/Sources/WalletConnectVerify/VerifyContext.swift @@ -3,6 +3,7 @@ public struct VerifyContext: Equatable, Hashable, Codable { case unknown case valid case invalid + case scam } public let origin: String? From 3846e697ae774c974b07bcd1ef0bbfd3b72e3fae Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 19 Sep 2023 15:21:48 +0300 Subject: [PATCH 02/11] enable notify logs --- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- Example/WalletApp/ApplicationLayer/ProfilingService.swift | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9d6fdc5ce..60fae593d 100644 --- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git", "state": { "branch": null, - "revision": "32f641cf24fc7abc1c591a2025e9f2f572648b0f", - "version": "1.7.2" + "revision": "db51c407d3be4a051484a141bf0bff36c43d3b1e", + "version": "1.8.0" } }, { @@ -69,8 +69,8 @@ "repositoryURL": "https://github.com/getsentry/sentry-cocoa.git", "state": { "branch": null, - "revision": "04bee4ad86d74d4cb4d7101ff826d6e355301ba9", - "version": "8.9.4" + "revision": "14aa6e47b03b820fd2b338728637570b9e969994", + "version": "8.12.0" } }, { diff --git a/Example/WalletApp/ApplicationLayer/ProfilingService.swift b/Example/WalletApp/ApplicationLayer/ProfilingService.swift index 1e87c8a0d..5fade9146 100644 --- a/Example/WalletApp/ApplicationLayer/ProfilingService.swift +++ b/Example/WalletApp/ApplicationLayer/ProfilingService.swift @@ -2,6 +2,7 @@ import Foundation import Mixpanel import WalletConnectNetworking import Combine +import WalletConnectNotify final class ProfilingService { public static var instance = ProfilingService() @@ -31,6 +32,7 @@ final class ProfilingService { mixpanel.people.set(properties: ["$name": account, "account": account]) handleLogs(from: Networking.instance.logsPublisher) + handleLogs(from: Notify.instance.logsPublisher) } private func handleLogs(from publisher: AnyPublisher) { From 8c7fe984861bf532d6aca806ca75929a1187ac64 Mon Sep 17 00:00:00 2001 From: llbartekll Date: Tue, 19 Sep 2023 12:55:39 +0000 Subject: [PATCH 03/11] Set User Agent --- Sources/WalletConnectRelay/PackageConfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index 21c0f27c7..d9ac4532e 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.8.1"} +{"version": "1.8.2"} From 5a756f92740569521c8f2bd0029a7cc3cc1a6ef4 Mon Sep 17 00:00:00 2001 From: Alexander Lisovik Date: Wed, 20 Sep 2023 13:35:03 +0400 Subject: [PATCH 04/11] Update UI --- .../Wallet/AuthRequest/AuthRequestView.swift | 171 ++++++------------ .../SessionProposal/SessionProposalView.swift | 159 +++++----------- .../SessionRequest/SessionRequestView.swift | 159 +++++----------- 3 files changed, 145 insertions(+), 344 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift index d857db13a..6a249cde1 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/AuthRequest/AuthRequestView.swift @@ -35,68 +35,16 @@ struct AuthRequestView: View { switch presenter.validationStatus { case .unknown: - HStack(spacing: 5) { - Image(systemName: "exclamationmark.circle.fill") - .font(.system(size: 14, weight: .semibold, design: .rounded)) - .foregroundColor(.orange) - - Text("Cannot verify") - .foregroundColor(.orange) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - } - .padding(5) - .background(Color.orange.opacity(0.15)) - .cornerRadius(10) - .padding(.top, 8) + verifyBadgeView(imageName: "exclamationmark.circle.fill", title: "Cannot verify", color: .orange) case .valid: - HStack(spacing: 5) { - Image(systemName: "checkmark.seal.fill") - .font(.system(size: 14, weight: .semibold, design: .rounded)) - .foregroundColor(.blue) - - Text("Verified domain") - .foregroundColor(.blue) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - } - .padding(5) - .background(Color.blue.opacity(0.15)) - .cornerRadius(10) - .padding(.top, 8) + verifyBadgeView(imageName: "checkmark.seal.fill", title: "Verified domain", color: .blue) case .invalid: - HStack(spacing: 5) { - Image(systemName: "exclamationmark.triangle.fill") - .font(.system(size: 14, weight: .semibold, design: .rounded)) - .foregroundColor(.red) - - Text("Invalid domain") - .foregroundColor(.red) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - } - .padding(5) - .background(Color.red.opacity(0.15)) - .cornerRadius(10) - .padding(.top, 8) + verifyBadgeView(imageName: "exclamationmark.triangle.fill", title: "Invalid domain", color: .red) case .scam: - HStack(spacing: 5) { - Image(systemName: "exclamationmark.shield.fill") - .font(.system(size: 14, weight: .semibold, design: .rounded)) - .foregroundColor(.red) - - Text("Security risk") - .foregroundColor(.red) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - } - .padding(5) - .background(Color.red.opacity(0.15)) - .cornerRadius(10) - .padding(.top, 8) + verifyBadgeView(imageName: "exclamationmark.shield.fill", title: "Security risk", color: .red) default: EmptyView() @@ -104,72 +52,20 @@ struct AuthRequestView: View { authRequestView() - switch presenter.validationStatus { - case .invalid: - HStack(spacing: 15) { - Image(systemName: "exclamationmark.shield.fill") - .font(.system(size: 20, design: .rounded)) - .foregroundColor(.red) + Group { + switch presenter.validationStatus { + case .invalid: + verifyDescriptionView(imageName: "exclamationmark.triangle.fill", title: "Invalid domain", description: "This domain cannot be verified. Check the request carefully before approving.", color: .red) - VStack(alignment: .leading, spacing: 5) { - Text("Invalid domain") - .foregroundColor(.red) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - Text("This website has a domain that does not match the sender of this request. Approving may lead to loss of funds.") - .foregroundColor(.grey8) - .font(.system(size: 14, weight: .medium, design: .rounded)) - } - } - .frame(maxWidth: .infinity) - .padding() - .background(Color.red.opacity(0.15)) - .cornerRadius(20) - - case .unknown: - HStack(spacing: 15) { - Image(systemName: "exclamationmark.shield.fill") - .font(.system(size: 20, design: .rounded)) - .foregroundColor(.orange) + case .unknown: + verifyDescriptionView(imageName: "exclamationmark.circle.fill", title: "Unknown domain", description: "This domain cannot be verified. Check the request carefully before approving.", color: .orange) - VStack(alignment: .leading, spacing: 5) { - Text("Unknown domain") - .foregroundColor(.orange) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - Text("This domain cannot be verified. Check the request carefully before approving.") - .foregroundColor(.grey8) - .font(.system(size: 14, weight: .medium, design: .rounded)) - } - } - .frame(maxWidth: .infinity) - .padding() - .background(Color.orange.opacity(0.15)) - .cornerRadius(20) - - case .scam: - HStack(spacing: 15) { - Image(systemName: "exclamationmark.shield.fill") - .font(.system(size: 20, design: .rounded)) - .foregroundColor(.red) + case .scam: + verifyDescriptionView(imageName: "exclamationmark.shield.fill", title: "Security risk", description: "This website is flagged as unsafe by multiple security providers. Leave immediately to protect your assets.", color: .red) - VStack(alignment: .leading, spacing: 5) { - Text("Security risk") - .foregroundColor(.red) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - Text("This website is flagged as unsafe by multiple security providers. Leave immediately to protect your assets.") - .foregroundColor(.grey8) - .font(.system(size: 14, weight: .medium, design: .rounded)) - } + default: + EmptyView() } - .frame(maxWidth: .infinity) - .padding() - .background(Color.red.opacity(0.15)) - .cornerRadius(20) - - default: - EmptyView() } HStack(spacing: 20) { @@ -264,6 +160,45 @@ struct AuthRequestView: View { } .padding(.vertical, 30) } + + private func verifyBadgeView(imageName: String, title: String, color: Color) -> some View { + HStack(spacing: 5) { + Image(systemName: imageName) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + .foregroundColor(color) + + Text(title) + .foregroundColor(color) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + } + .padding(5) + .background(color.opacity(0.15)) + .cornerRadius(10) + .padding(.top, 8) + } + + private func verifyDescriptionView(imageName: String, title: String, description: String, color: Color) -> some View { + HStack(spacing: 15) { + Image(systemName: imageName) + .font(.system(size: 20, design: .rounded)) + .foregroundColor(color) + + VStack(alignment: .leading, spacing: 5) { + Text(title) + .foregroundColor(color) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + Text(description) + .foregroundColor(.grey8) + .font(.system(size: 14, weight: .medium, design: .rounded)) + } + } + .frame(maxWidth: .infinity) + .padding() + .background(color.opacity(0.15)) + .cornerRadius(20) + } } #if DEBUG diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift index 06ad2a554..280c29a71 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift @@ -39,68 +39,16 @@ struct SessionProposalView: View { switch presenter.validationStatus { case .unknown: - HStack(spacing: 5) { - Image(systemName: "exclamationmark.circle.fill") - .font(.system(size: 14, weight: .semibold, design: .rounded)) - .foregroundColor(.orange) - - Text("Cannot verify") - .foregroundColor(.orange) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - } - .padding(5) - .background(Color.orange.opacity(0.15)) - .cornerRadius(10) - .padding(.top, 8) + verifyBadgeView(imageName: "exclamationmark.circle.fill", title: "Cannot verify", color: .orange) case .valid: - HStack(spacing: 5) { - Image(systemName: "checkmark.seal.fill") - .font(.system(size: 14, weight: .semibold, design: .rounded)) - .foregroundColor(.blue) - - Text("Verified domain") - .foregroundColor(.blue) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - } - .padding(5) - .background(Color.blue.opacity(0.15)) - .cornerRadius(10) - .padding(.top, 8) + verifyBadgeView(imageName: "checkmark.seal.fill", title: "Verified domain", color: .blue) case .invalid: - HStack(spacing: 5) { - Image(systemName: "exclamationmark.triangle.fill") - .font(.system(size: 14, weight: .semibold, design: .rounded)) - .foregroundColor(.red) - - Text("Invalid domain") - .foregroundColor(.red) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - } - .padding(5) - .background(Color.red.opacity(0.15)) - .cornerRadius(10) - .padding(.top, 8) + verifyBadgeView(imageName: "exclamationmark.triangle.fill", title: "Invalid domain", color: .red) case .scam: - HStack(spacing: 5) { - Image(systemName: "exclamationmark.shield.fill") - .font(.system(size: 14, weight: .semibold, design: .rounded)) - .foregroundColor(.red) - - Text("Security risk") - .foregroundColor(.red) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - } - .padding(5) - .background(Color.red.opacity(0.15)) - .cornerRadius(10) - .padding(.top, 8) + verifyBadgeView(imageName: "exclamationmark.shield.fill", title: "Security risk", color: .red) default: EmptyView() @@ -147,67 +95,13 @@ struct SessionProposalView: View { switch presenter.validationStatus { case .invalid: - HStack(spacing: 15) { - Image(systemName: "exclamationmark.shield.fill") - .font(.system(size: 20, design: .rounded)) - .foregroundColor(.red) - - VStack(alignment: .leading, spacing: 5) { - Text("Invalid domain") - .foregroundColor(.red) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - Text("This website has a domain that does not match the sender of this request. Approving may lead to loss of funds.") - .foregroundColor(.grey8) - .font(.system(size: 14, weight: .medium, design: .rounded)) - } - } - .frame(maxWidth: .infinity) - .padding() - .background(Color.red.opacity(0.15)) - .cornerRadius(20) + verifyDescriptionView(imageName: "exclamationmark.triangle.fill", title: "Invalid domain", description: "This domain cannot be verified. Check the request carefully before approving.", color: .red) case .unknown: - HStack(spacing: 15) { - Image(systemName: "exclamationmark.shield.fill") - .font(.system(size: 20, design: .rounded)) - .foregroundColor(.orange) - - VStack(alignment: .leading, spacing: 5) { - Text("Unknown domain") - .foregroundColor(.orange) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - Text("This domain cannot be verified. Check the request carefully before approving.") - .foregroundColor(.grey8) - .font(.system(size: 14, weight: .medium, design: .rounded)) - } - } - .frame(maxWidth: .infinity) - .padding() - .background(Color.orange.opacity(0.15)) - .cornerRadius(20) + verifyDescriptionView(imageName: "exclamationmark.circle.fill", title: "Unknown domain", description: "This domain cannot be verified. Check the request carefully before approving.", color: .orange) case .scam: - HStack(spacing: 15) { - Image(systemName: "exclamationmark.shield.fill") - .font(.system(size: 20, design: .rounded)) - .foregroundColor(.red) - - VStack(alignment: .leading, spacing: 5) { - Text("Security risk") - .foregroundColor(.red) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - Text("This website is flagged as unsafe by multiple security providers. Leave immediately to protect your assets.") - .foregroundColor(.grey8) - .font(.system(size: 14, weight: .medium, design: .rounded)) - } - } - .frame(maxWidth: .infinity) - .padding() - .background(Color.red.opacity(0.15)) - .cornerRadius(20) + verifyDescriptionView(imageName: "exclamationmark.shield.fill", title: "Security risk", description: "This website is flagged as unsafe by multiple security providers. Leave immediately to protect your assets.", color: .red) default: EmptyView() @@ -351,6 +245,45 @@ struct SessionProposalView: View { } .padding(.bottom, 15) } + + private func verifyBadgeView(imageName: String, title: String, color: Color) -> some View { + HStack(spacing: 5) { + Image(systemName: imageName) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + .foregroundColor(color) + + Text(title) + .foregroundColor(color) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + } + .padding(5) + .background(color.opacity(0.15)) + .cornerRadius(10) + .padding(.top, 8) + } + + private func verifyDescriptionView(imageName: String, title: String, description: String, color: Color) -> some View { + HStack(spacing: 15) { + Image(systemName: imageName) + .font(.system(size: 20, design: .rounded)) + .foregroundColor(color) + + VStack(alignment: .leading, spacing: 5) { + Text(title) + .foregroundColor(color) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + Text(description) + .foregroundColor(.grey8) + .font(.system(size: 14, weight: .medium, design: .rounded)) + } + } + .frame(maxWidth: .infinity) + .padding() + .background(color.opacity(0.15)) + .cornerRadius(20) + } } #if DEBUG diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift index 1c1f1bfce..92da9ada7 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestView.swift @@ -24,68 +24,16 @@ struct SessionRequestView: View { switch presenter.validationStatus { case .unknown: - HStack(spacing: 5) { - Image(systemName: "exclamationmark.circle.fill") - .font(.system(size: 14, weight: .semibold, design: .rounded)) - .foregroundColor(.orange) - - Text("Cannot verify") - .foregroundColor(.orange) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - } - .padding(5) - .background(Color.orange.opacity(0.15)) - .cornerRadius(10) - .padding(.top, 8) + verifyBadgeView(imageName: "exclamationmark.circle.fill", title: "Cannot verify", color: .orange) case .valid: - HStack(spacing: 5) { - Image(systemName: "checkmark.seal.fill") - .font(.system(size: 14, weight: .semibold, design: .rounded)) - .foregroundColor(.blue) - - Text("Verified domain") - .foregroundColor(.blue) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - } - .padding(5) - .background(Color.blue.opacity(0.15)) - .cornerRadius(10) - .padding(.top, 8) + verifyBadgeView(imageName: "checkmark.seal.fill", title: "Verified domain", color: .blue) case .invalid: - HStack(spacing: 5) { - Image(systemName: "exclamationmark.triangle.fill") - .font(.system(size: 14, weight: .semibold, design: .rounded)) - .foregroundColor(.red) - - Text("Invalid domain") - .foregroundColor(.red) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - } - .padding(5) - .background(Color.red.opacity(0.15)) - .cornerRadius(10) - .padding(.top, 8) + verifyBadgeView(imageName: "exclamationmark.triangle.fill", title: "Invalid domain", color: .red) case .scam: - HStack(spacing: 5) { - Image(systemName: "exclamationmark.shield.fill") - .font(.system(size: 14, weight: .semibold, design: .rounded)) - .foregroundColor(.red) - - Text("Security risk") - .foregroundColor(.red) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - } - .padding(5) - .background(Color.red.opacity(0.15)) - .cornerRadius(10) - .padding(.top, 8) + verifyBadgeView(imageName: "exclamationmark.shield.fill", title: "Security risk", color: .red) default: EmptyView() @@ -97,67 +45,13 @@ struct SessionRequestView: View { switch presenter.validationStatus { case .invalid: - HStack(spacing: 15) { - Image(systemName: "exclamationmark.shield.fill") - .font(.system(size: 20, design: .rounded)) - .foregroundColor(.red) - - VStack(alignment: .leading, spacing: 5) { - Text("Invalid domain") - .foregroundColor(.red) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - Text("This website has a domain that does not match the sender of this request. Approving may lead to loss of funds.") - .foregroundColor(.grey8) - .font(.system(size: 14, weight: .medium, design: .rounded)) - } - } - .frame(maxWidth: .infinity) - .padding() - .background(Color.red.opacity(0.15)) - .cornerRadius(20) + verifyDescriptionView(imageName: "exclamationmark.triangle.fill", title: "Invalid domain", description: "This domain cannot be verified. Check the request carefully before approving.", color: .red) case .unknown: - HStack(spacing: 15) { - Image(systemName: "exclamationmark.shield.fill") - .font(.system(size: 20, design: .rounded)) - .foregroundColor(.orange) - - VStack(alignment: .leading, spacing: 5) { - Text("Unknown domain") - .foregroundColor(.orange) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - Text("This domain cannot be verified. Check the request carefully before approving.") - .foregroundColor(.grey8) - .font(.system(size: 14, weight: .medium, design: .rounded)) - } - } - .frame(maxWidth: .infinity) - .padding() - .background(Color.orange.opacity(0.15)) - .cornerRadius(20) + verifyDescriptionView(imageName: "exclamationmark.circle.fill", title: "Unknown domain", description: "This domain cannot be verified. Check the request carefully before approving.", color: .orange) case .scam: - HStack(spacing: 15) { - Image(systemName: "exclamationmark.shield.fill") - .font(.system(size: 20, design: .rounded)) - .foregroundColor(.red) - - VStack(alignment: .leading, spacing: 5) { - Text("Security risk") - .foregroundColor(.red) - .font(.system(size: 14, weight: .semibold, design: .rounded)) - - Text("This website is flagged as unsafe by multiple security providers. Leave immediately to protect your assets.") - .foregroundColor(.grey8) - .font(.system(size: 14, weight: .medium, design: .rounded)) - } - } - .frame(maxWidth: .infinity) - .padding() - .background(Color.red.opacity(0.15)) - .cornerRadius(20) + verifyDescriptionView(imageName: "exclamationmark.shield.fill", title: "Security risk", description: "This website is flagged as unsafe by multiple security providers. Leave immediately to protect your assets.", color: .red) default: EmptyView() @@ -258,6 +152,45 @@ struct SessionRequestView: View { } .padding(.vertical, 30) } + + private func verifyBadgeView(imageName: String, title: String, color: Color) -> some View { + HStack(spacing: 5) { + Image(systemName: imageName) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + .foregroundColor(color) + + Text(title) + .foregroundColor(color) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + } + .padding(5) + .background(color.opacity(0.15)) + .cornerRadius(10) + .padding(.top, 8) + } + + private func verifyDescriptionView(imageName: String, title: String, description: String, color: Color) -> some View { + HStack(spacing: 15) { + Image(systemName: imageName) + .font(.system(size: 20, design: .rounded)) + .foregroundColor(color) + + VStack(alignment: .leading, spacing: 5) { + Text(title) + .foregroundColor(color) + .font(.system(size: 14, weight: .semibold, design: .rounded)) + + Text(description) + .foregroundColor(.grey8) + .font(.system(size: 14, weight: .medium, design: .rounded)) + } + } + .frame(maxWidth: .infinity) + .padding() + .background(color.opacity(0.15)) + .cornerRadius(20) + } } #if DEBUG From 3d88f7c3b7b2ec2791d6136250e087ec88548948 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 20 Sep 2023 18:06:52 +0800 Subject: [PATCH 05/11] Add for external group only --- fastlane/Fastfile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index ef829c945..114bce38f 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -112,10 +112,7 @@ platform :ios do distribute_external: true, notify_external_testers: true, skip_waiting_for_build_processing: false, - groups: [ - "WalletConnect", - "WalletConnect Users" - ] + groups: ["WalletConnect Users"] ) clean_build_artifacts() end From ce8025e41ac5687f08494e998651af11dffe3896 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 19 Sep 2023 18:57:09 +0800 Subject: [PATCH 06/11] Subscription screen --- Example/ExampleApp.xcodeproj/project.pbxproj | 4 + .../Configurator/AppearanceConfigurator.swift | 9 - .../ListingsSertice/Listings.swift | 27 +-- .../ListingsSertice/ListingsAPI.swift | 4 +- .../ListingsNetworkService.swift | 4 +- .../Common/VIPER/SceneViewController.swift | 8 + .../Notifications/NotificationsView.swift | 13 +- .../NotifySubscriptionViewModel.swift | 23 +++ .../PushMessages/PushMessagesInteractor.swift | 6 + .../PushMessages/PushMessagesModule.swift | 2 +- .../PushMessages/PushMessagesPresenter.swift | 25 ++- .../PushMessages/PushMessagesRouter.swift | 4 + .../PushMessages/PushMessagesView.swift | 168 ++++++++++++------ 13 files changed, 190 insertions(+), 107 deletions(-) create mode 100644 Example/WalletApp/PresentationLayer/Wallet/PushMessages/NotifySubscriptionViewModel.swift diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 2eabffb63..324415404 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -69,6 +69,7 @@ 84FE684628ACDB4700C893FF /* RequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FE684528ACDB4700C893FF /* RequestParams.swift */; }; A507BE1A29E8032E0038EF70 /* EIP55Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A507BE1929E8032E0038EF70 /* EIP55Tests.swift */; }; A50C036528AAD32200FE72D3 /* ClientDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50C036428AAD32200FE72D3 /* ClientDelegate.swift */; }; + A50D53BA2AB9D44200A4FD8B /* NotifySubscriptionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50D53B92AB9D44200A4FD8B /* NotifySubscriptionViewModel.swift */; }; A50DF19D2A25084A0036EA6C /* WalletConnectHistory in Frameworks */ = {isa = PBXBuildFile; productRef = A50DF19C2A25084A0036EA6C /* WalletConnectHistory */; }; A50F3946288005B200064555 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50F3945288005B200064555 /* Types.swift */; }; A51606F82A2F47BD00CACB92 /* DefaultBIP44Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51606F72A2F47BD00CACB92 /* DefaultBIP44Provider.swift */; }; @@ -431,6 +432,7 @@ 84FE684528ACDB4700C893FF /* RequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestParams.swift; sourceTree = ""; }; A507BE1929E8032E0038EF70 /* EIP55Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EIP55Tests.swift; sourceTree = ""; }; A50C036428AAD32200FE72D3 /* ClientDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientDelegate.swift; sourceTree = ""; }; + A50D53B92AB9D44200A4FD8B /* NotifySubscriptionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifySubscriptionViewModel.swift; sourceTree = ""; }; A50F3945288005B200064555 /* Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Types.swift; sourceTree = ""; }; A51606F72A2F47BD00CACB92 /* DefaultBIP44Provider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultBIP44Provider.swift; sourceTree = ""; }; A51811972A52E21A00A52B15 /* ConfigurationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationService.swift; sourceTree = ""; }; @@ -874,6 +876,7 @@ children = ( 84B8154F2991217900FAD54E /* PushMessagesModule.swift */, 84B815502991217900FAD54E /* PushMessagesPresenter.swift */, + A50D53B92AB9D44200A4FD8B /* NotifySubscriptionViewModel.swift */, 84B815512991217900FAD54E /* PushMessagesRouter.swift */, 84B815522991217900FAD54E /* PushMessagesInteractor.swift */, 84B815532991217900FAD54E /* PushMessagesView.swift */, @@ -2366,6 +2369,7 @@ C56EE270293F56D7004840D1 /* String.swift in Sources */, A51811A12A52E83100A52B15 /* SettingsRouter.swift in Sources */, C56EE279293F56D7004840D1 /* Color.swift in Sources */, + A50D53BA2AB9D44200A4FD8B /* NotifySubscriptionViewModel.swift in Sources */, 847BD1E6298A806800076C90 /* NotificationsRouter.swift in Sources */, C55D3483295DD7140004314A /* AuthRequestView.swift in Sources */, C56EE243293F566D004840D1 /* ScanView.swift in Sources */, diff --git a/Example/WalletApp/ApplicationLayer/Configurator/AppearanceConfigurator.swift b/Example/WalletApp/ApplicationLayer/Configurator/AppearanceConfigurator.swift index 793e9a835..ca41f7f63 100644 --- a/Example/WalletApp/ApplicationLayer/Configurator/AppearanceConfigurator.swift +++ b/Example/WalletApp/ApplicationLayer/Configurator/AppearanceConfigurator.swift @@ -3,15 +3,6 @@ import UIKit struct AppearanceConfigurator: Configurator { func configure() { - let appearance = UINavigationBarAppearance() - appearance.backgroundColor = .w_background - appearance.shadowColor = .clear - appearance.titleTextAttributes = [ - .foregroundColor: UIColor.w_foreground - ] - UINavigationBar.appearance().standardAppearance = appearance - UINavigationBar.appearance().scrollEdgeAppearance = appearance - UINavigationBar.appearance().compactAppearance = appearance } } diff --git a/Example/WalletApp/BusinessLayer/ListingsSertice/Listings.swift b/Example/WalletApp/BusinessLayer/ListingsSertice/Listings.swift index 56e4158a5..176fc4d83 100644 --- a/Example/WalletApp/BusinessLayer/ListingsSertice/Listings.swift +++ b/Example/WalletApp/BusinessLayer/ListingsSertice/Listings.swift @@ -11,35 +11,10 @@ struct Listing: Codable { @FailableDecodable private(set) var lg: URL? } - struct App: Codable { - @FailableDecodable - private(set) var ios: URL? - - @FailableDecodable - private(set) var android: URL? - - @FailableDecodable - private(set) var browser: URL? - } - struct Mobile: Codable { - let native: String? - let universal: String? - } - struct Metadata: Codable { - struct Colors: Codable { - let primary: String? - let secondary: String? - } - let shortName: String - let colors: Colors - } let id: String let name: String let description: String let homepage: String let image_url: ImageURL - let app: App - let mobile: Mobile - let metadata: Metadata - let chains: [String] + let dapp_url: String } diff --git a/Example/WalletApp/BusinessLayer/ListingsSertice/ListingsAPI.swift b/Example/WalletApp/BusinessLayer/ListingsSertice/ListingsAPI.swift index f718a5b8a..8e3700db7 100644 --- a/Example/WalletApp/BusinessLayer/ListingsSertice/ListingsAPI.swift +++ b/Example/WalletApp/BusinessLayer/ListingsSertice/ListingsAPI.swift @@ -4,7 +4,7 @@ import HTTPClient enum ListingsAPI: HTTPService { var path: String { - return "/v3/dapps" + return "/w3i/v1/projects" } var method: HTTPMethod { @@ -16,7 +16,7 @@ enum ListingsAPI: HTTPService { } var queryParameters: [String : String]? { - return ["projectId": InputConfig.projectId, "is_notify_enabled": "true"] + return ["projectId": InputConfig.projectId, "entries": "100", "is_verified": "false"] } var additionalHeaderFields: [String : String]? { diff --git a/Example/WalletApp/BusinessLayer/ListingsSertice/ListingsNetworkService.swift b/Example/WalletApp/BusinessLayer/ListingsSertice/ListingsNetworkService.swift index e11f30bdb..cd0676d9e 100644 --- a/Example/WalletApp/BusinessLayer/ListingsSertice/ListingsNetworkService.swift +++ b/Example/WalletApp/BusinessLayer/ListingsSertice/ListingsNetworkService.swift @@ -4,12 +4,12 @@ import HTTPClient final class ListingsNetworkService { struct ListingsResponse: Codable { - let listings: [String: Listing] + let projects: [String: Listing] } func getListings() async throws -> [Listing] { let httpClient = HTTPNetworkClient(host: "explorer-api.walletconnect.com") let response = try await httpClient.request(ListingsResponse.self, at: ListingsAPI.notifyDApps) - return response.listings.values.compactMap { $0 } + return response.projects.values.compactMap { $0 } } } diff --git a/Example/WalletApp/Common/VIPER/SceneViewController.swift b/Example/WalletApp/Common/VIPER/SceneViewController.swift index 0c990baf7..a545e4e2f 100644 --- a/Example/WalletApp/Common/VIPER/SceneViewController.swift +++ b/Example/WalletApp/Common/VIPER/SceneViewController.swift @@ -2,6 +2,7 @@ import SwiftUI enum NavigationBarStyle { case translucent(UIColor) + case clear } protocol SceneViewModel { @@ -80,8 +81,15 @@ private extension SceneViewController { func setupNavigationBarStyle() { switch viewModel.navigationBarStyle { case .translucent(let color): + navigationController?.navigationBar.backgroundColor = .w_background navigationController?.navigationBar.barTintColor = color navigationController?.navigationBar.isTranslucent = true + case .clear: + navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) + navigationController?.navigationBar.shadowImage = UIImage() + navigationController?.navigationBar.isTranslucent = true + navigationController?.navigationBar.backgroundColor = .clear + navigationController?.navigationBar.barTintColor = .clear } } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift index 66de2b936..109e2b9dc 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift @@ -18,7 +18,7 @@ struct NotificationsView: View { content: { item, isSelected in Text(item) .font(.headline) - .foregroundColor(isSelected ? Color.black : Color.gray ) + .foregroundColor(isSelected ? Color.primary : Color.secondary ) .padding(.horizontal, 16) .padding(.vertical, 8) }, @@ -26,7 +26,7 @@ struct NotificationsView: View { VStack(spacing: 0) { Spacer() Rectangle() - .fill(Color.black) + .fill(Color.primary) .frame(height: 1) } }) @@ -89,6 +89,7 @@ struct NotificationsView: View { subscriptionRow(subscription: subscription) .listRowSeparator(.hidden) .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 16, trailing: 0)) + .listRowBackground(Color.clear) } .onDelete { indexSet in Task(priority: .high) { @@ -178,11 +179,15 @@ struct NotificationsView: View { if let subscription = presenter.subscription(forListing: listing) { AsyncButton("Unsubscribe") { try await presenter.unsubscribe(subscription: subscription) - }.padding(16.0) + } + .foregroundColor(.red) + .padding(16.0) } else { AsyncButton("Subscribe") { try await presenter.subscribe(listing: listing) - }.padding(16.0) + } + .foregroundColor(.primary) + .padding(16.0) } } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/NotifySubscriptionViewModel.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/NotifySubscriptionViewModel.swift new file mode 100644 index 000000000..9571402fe --- /dev/null +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/NotifySubscriptionViewModel.swift @@ -0,0 +1,23 @@ +import Foundation +import WalletConnectNotify + +struct NotifySubscriptionViewModel { + + let subscription: NotifySubscription + + var name: String { + return subscription.metadata.name + } + + var description: String { + return subscription.metadata.description + } + + var domain: String { + return subscription.metadata.url + } + + var iconUrl: URL? { + return try? subscription.metadata.icons.first?.asURL() + } +} diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift index b1953934e..04f05215e 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift @@ -20,4 +20,10 @@ final class PushMessagesInteractor { func deletePushMessage(id: String) { Notify.instance.deleteNotifyMessage(id: id) } + + func deleteSubscription(_ subscription: NotifySubscription) { + Task(priority: .high) { + try await Notify.instance.deleteSubscription(topic: subscription.topic) + } + } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesModule.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesModule.swift index 447c08ce3..01d973b45 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesModule.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesModule.swift @@ -7,7 +7,7 @@ final class PushMessagesModule { static func create(app: Application, subscription: NotifySubscription) -> UIViewController { let router = PushMessagesRouter(app: app) let interactor = PushMessagesInteractor(subscription: subscription) - let presenter = PushMessagesPresenter(interactor: interactor, router: router) + let presenter = PushMessagesPresenter(subscription: subscription, interactor: interactor, router: router) let view = PushMessagesView().environmentObject(presenter) let viewController = SceneViewController(viewModel: presenter, content: view) diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift index ec8b91c8f..b0f36a84e 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift @@ -4,20 +4,26 @@ import WalletConnectNotify final class PushMessagesPresenter: ObservableObject { + private let subscription: NotifySubscription private let interactor: PushMessagesInteractor private let router: PushMessagesRouter private var disposeBag = Set() - + @Published private var pushMessages: [NotifyMessageRecord] = [] + var subscriptionViewModel: NotifySubscriptionViewModel { + return NotifySubscriptionViewModel(subscription: subscription) + } + var messages: [PushMessageViewModel] { return pushMessages .sorted { $0.publishedAt > $1.publishedAt } .map { PushMessageViewModel(pushMessageRecord: $0) } } - init(interactor: PushMessagesInteractor, router: PushMessagesRouter) { + init(subscription: NotifySubscription, interactor: PushMessagesInteractor, router: PushMessagesRouter) { defer { setupInitialState() } + self.subscription = subscription self.interactor = interactor self.router = router setUpMessagesRefresh() @@ -32,23 +38,28 @@ final class PushMessagesPresenter: ObservableObject { }).store(in: &disposeBag) } - func deletePushMessage(at indexSet: IndexSet) { if let index = indexSet.first { interactor.deletePushMessage(id: pushMessages[index].id) } } + + func unsubscribe() { + interactor.deleteSubscription(subscription) + router.dismiss() + } } // MARK: SceneViewModel extension PushMessagesPresenter: SceneViewModel { - var sceneTitle: String? { - return interactor.subscription.metadata.name - } var largeTitleDisplayMode: UINavigationItem.LargeTitleDisplayMode { - return .always + return .never + } + + var navigationBarStyle: NavigationBarStyle { + return .clear } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesRouter.swift index 0dd264603..a237b6994 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesRouter.swift @@ -9,4 +9,8 @@ final class PushMessagesRouter { init(app: Application) { self.app = app } + + func dismiss() { + viewController.pop() + } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift index 7f22c53f6..55ab26e0c 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift @@ -5,92 +5,148 @@ struct PushMessagesView: View { @EnvironmentObject var presenter: PushMessagesPresenter var body: some View { - ZStack { - Color.grey100 - .edgesIgnoringSafeArea(.all) - - VStack(alignment: .leading, spacing: 16) { - ZStack { - if presenter.messages.isEmpty { - VStack(spacing: 10) { - Image(systemName: "bell.badge.fill") - .resizable() - .frame(width: 32, height: 32) - .aspectRatio(contentMode: .fit) - .foregroundColor(.grey50) - - Text("Notifications from connected apps will appear here. To enable notifications, visit the app in your browser and look for a \(Image(systemName: "bell.fill")) notifications toggle \(Image(systemName: "switch.2"))") - .foregroundColor(.grey50) - .font(.system(size: 15, weight: .regular, design: .rounded)) - .multilineTextAlignment(.center) - .lineSpacing(4) - } - .padding(20) - } + VStack(spacing: 0) { + List { + headerView() + .listRowSeparator(.hidden) + .listRowBackground(Color.clear) - VStack { - if !presenter.messages.isEmpty { - List { - ForEach(presenter.messages, id: \.id) { pm in - notificationView(pushMessage: pm) - .listRowSeparator(.hidden) - .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 16, trailing: 0)) - } - .onDelete { indexSet in - presenter.deletePushMessage(at: indexSet) - } - } - .listStyle(PlainListStyle()) - } + if !presenter.messages.isEmpty { + ForEach(presenter.messages, id: \.id) { pm in + notificationView(pushMessage: pm) + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 32, trailing: 0)) + .listRowBackground(Color.clear) + } + .onDelete { indexSet in + presenter.deletePushMessage(at: indexSet) } + } else { + emptyStateView() + .listRowSeparator(.hidden) + .listRowBackground(Color.clear) } } - .padding(.vertical, 20) + .listStyle(PlainListStyle()) } + .ignoresSafeArea(.container) } - - private func notificationView(pushMessage: PushMessageViewModel) -> some View { - VStack { + VStack(alignment: .center) { HStack(spacing: 10) { AsyncImage(url: URL(string: pushMessage.imageUrl)) { phase in if let image = phase.image { image .resizable() - .frame(width: 60, height: 60) + .frame(width: 48, height: 48) .background(Color.black) - .cornerRadius(30, corners: .allCorners) + .cornerRadius(10, corners: .allCorners) } else { Color.black - .frame(width: 60, height: 60) - .cornerRadius(30, corners: .allCorners) + .frame(width: 48, height: 48) + .cornerRadius(10, corners: .allCorners) } } .padding(.leading, 20) - - + + VStack(alignment: .leading, spacing: 2) { - Text(pushMessage.title) - .foregroundColor(.grey8) - .font(.system(size: 20, weight: .semibold, design: .rounded)) - HStack { - Text(pushMessage.subtitle) - .foregroundColor(.grey50) - .font(.system(size: 13, weight: .medium, design: .rounded)) - + Text(pushMessage.title) + .foregroundColor(.primary) + .font(.system(size: 14, weight: .semibold)) + Spacer() - + Text(pushMessage.publishedAt) .foregroundColor(.grey50) - .font(.system(size: 13, weight: .medium, design: .rounded)) + .font(.system(size: 11)) } + + Text(pushMessage.subtitle) + .foregroundColor(.grey50) + .font(.system(size: 13)) + } .padding(.trailing, 20) } } } + + func headerView() -> some View { + VStack(spacing: 0) { + AsyncImage(url: presenter.subscriptionViewModel.iconUrl) { phase in + if let image = phase.image { + image + .resizable() + .frame(width: 64, height: 64) + } else { + Color.black + .frame(width: 64, height: 64) + } + } + .clipShape(Circle()) + .padding(.top, 50.0) + .padding(.bottom, 8.0) + + Text(presenter.subscriptionViewModel.name) + .font(.headline) + .foregroundColor(.primary) + .padding(.bottom, 8.0) + + Text(presenter.subscriptionViewModel.domain) + .font(.caption) + .foregroundColor(.secondary) + .padding(.bottom, 16.0) + + Text(presenter.subscriptionViewModel.description) + .font(.footnote) + .foregroundColor(.primary) + .padding(.bottom, 16.0) + + Menu { + Button(role: .destructive, action: { + presenter.unsubscribe() + }) { + Label("Unsubscribe", systemImage: "x.circle") + } + } label: { + HStack(spacing: 16.0) { + Text("Subscribed") + .font(.subheadline) + .foregroundColor(.secondary) + + Image(systemName: "checkmark") + .foregroundColor(.secondary) + } + .padding(.horizontal, 16.0) + .padding(.vertical, 8.0) + .overlay( + RoundedRectangle(cornerRadius: 20) + .stroke(Color.grey95, lineWidth: 1) + ) + } + } + .frame(maxWidth: .infinity) + } + + func emptyStateView() -> some View { + VStack(spacing: 10) { + Image(systemName: "bell.badge.fill") + .resizable() + .frame(width: 32, height: 32) + .aspectRatio(contentMode: .fit) + .foregroundColor(.grey50) + + Text("Notifications from connected apps will appear here. To enable notifications, visit the app in your browser and look for a \(Image(systemName: "bell.fill")) notifications toggle \(Image(systemName: "switch.2"))") + .foregroundColor(.grey50) + .font(.system(size: 15, weight: .regular, design: .rounded)) + .multilineTextAlignment(.center) + .lineSpacing(4) + } + .padding(20) + } } #if DEBUG From 267b2d05afe92d82ae6301fbe398737db119adcc Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 20 Sep 2023 02:36:11 +0800 Subject: [PATCH 07/11] Preferences module --- Example/ExampleApp.xcodeproj/project.pbxproj | 32 ++++++++++++++-- .../Common/VIPER/SceneViewController.swift | 1 + .../Models/SubscriptionsViewModel.swift | 10 +++-- .../NotificationsPresenter.swift | 2 +- .../Notifications/NotificationsView.swift | 2 +- .../NotifyPreferencesInteractor.swift | 3 ++ .../NotifyPreferencesModule.swift | 19 ++++++++++ .../NotifyPreferencesPresenter.swift | 37 +++++++++++++++++++ .../NotifyPreferencesRouter.swift | 12 ++++++ .../NotifyPreferencesView.swift | 18 +++++++++ .../NotifySubscriptionViewModel.swift | 23 ------------ .../PushMessages/PushMessagesPresenter.swift | 17 ++++++++- .../PushMessages/PushMessagesRouter.swift | 8 ++++ .../PushMessages/PushMessagesView.swift | 2 +- 14 files changed, 151 insertions(+), 35 deletions(-) create mode 100644 Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesInteractor.swift create mode 100644 Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesModule.swift create mode 100644 Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesPresenter.swift create mode 100644 Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesRouter.swift create mode 100644 Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesView.swift delete mode 100644 Example/WalletApp/PresentationLayer/Wallet/PushMessages/NotifySubscriptionViewModel.swift diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 324415404..d0e5c2d21 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -69,7 +69,11 @@ 84FE684628ACDB4700C893FF /* RequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FE684528ACDB4700C893FF /* RequestParams.swift */; }; A507BE1A29E8032E0038EF70 /* EIP55Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A507BE1929E8032E0038EF70 /* EIP55Tests.swift */; }; A50C036528AAD32200FE72D3 /* ClientDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50C036428AAD32200FE72D3 /* ClientDelegate.swift */; }; - A50D53BA2AB9D44200A4FD8B /* NotifySubscriptionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50D53B92AB9D44200A4FD8B /* NotifySubscriptionViewModel.swift */; }; + A50D53C12ABA055700A4FD8B /* NotifyPreferencesModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50D53BC2ABA055700A4FD8B /* NotifyPreferencesModule.swift */; }; + A50D53C22ABA055700A4FD8B /* NotifyPreferencesPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50D53BD2ABA055700A4FD8B /* NotifyPreferencesPresenter.swift */; }; + A50D53C32ABA055700A4FD8B /* NotifyPreferencesRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50D53BE2ABA055700A4FD8B /* NotifyPreferencesRouter.swift */; }; + A50D53C42ABA055700A4FD8B /* NotifyPreferencesInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50D53BF2ABA055700A4FD8B /* NotifyPreferencesInteractor.swift */; }; + A50D53C52ABA055700A4FD8B /* NotifyPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50D53C02ABA055700A4FD8B /* NotifyPreferencesView.swift */; }; A50DF19D2A25084A0036EA6C /* WalletConnectHistory in Frameworks */ = {isa = PBXBuildFile; productRef = A50DF19C2A25084A0036EA6C /* WalletConnectHistory */; }; A50F3946288005B200064555 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50F3945288005B200064555 /* Types.swift */; }; A51606F82A2F47BD00CACB92 /* DefaultBIP44Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51606F72A2F47BD00CACB92 /* DefaultBIP44Provider.swift */; }; @@ -432,7 +436,11 @@ 84FE684528ACDB4700C893FF /* RequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestParams.swift; sourceTree = ""; }; A507BE1929E8032E0038EF70 /* EIP55Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EIP55Tests.swift; sourceTree = ""; }; A50C036428AAD32200FE72D3 /* ClientDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientDelegate.swift; sourceTree = ""; }; - A50D53B92AB9D44200A4FD8B /* NotifySubscriptionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifySubscriptionViewModel.swift; sourceTree = ""; }; + A50D53BC2ABA055700A4FD8B /* NotifyPreferencesModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyPreferencesModule.swift; sourceTree = ""; }; + A50D53BD2ABA055700A4FD8B /* NotifyPreferencesPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyPreferencesPresenter.swift; sourceTree = ""; }; + A50D53BE2ABA055700A4FD8B /* NotifyPreferencesRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyPreferencesRouter.swift; sourceTree = ""; }; + A50D53BF2ABA055700A4FD8B /* NotifyPreferencesInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyPreferencesInteractor.swift; sourceTree = ""; }; + A50D53C02ABA055700A4FD8B /* NotifyPreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyPreferencesView.swift; sourceTree = ""; }; A50F3945288005B200064555 /* Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Types.swift; sourceTree = ""; }; A51606F72A2F47BD00CACB92 /* DefaultBIP44Provider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultBIP44Provider.swift; sourceTree = ""; }; A51811972A52E21A00A52B15 /* ConfigurationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationService.swift; sourceTree = ""; }; @@ -876,7 +884,6 @@ children = ( 84B8154F2991217900FAD54E /* PushMessagesModule.swift */, 84B815502991217900FAD54E /* PushMessagesPresenter.swift */, - A50D53B92AB9D44200A4FD8B /* NotifySubscriptionViewModel.swift */, 84B815512991217900FAD54E /* PushMessagesRouter.swift */, 84B815522991217900FAD54E /* PushMessagesInteractor.swift */, 84B815532991217900FAD54E /* PushMessagesView.swift */, @@ -982,6 +989,18 @@ path = PNDecryptionService; sourceTree = ""; }; + A50D53BB2ABA053600A4FD8B /* NotifySettings */ = { + isa = PBXGroup; + children = ( + A50D53BC2ABA055700A4FD8B /* NotifyPreferencesModule.swift */, + A50D53BD2ABA055700A4FD8B /* NotifyPreferencesPresenter.swift */, + A50D53BE2ABA055700A4FD8B /* NotifyPreferencesRouter.swift */, + A50D53BF2ABA055700A4FD8B /* NotifyPreferencesInteractor.swift */, + A50D53C02ABA055700A4FD8B /* NotifyPreferencesView.swift */, + ); + path = NotifySettings; + sourceTree = ""; + }; A50F3944288005A700064555 /* Types */ = { isa = PBXGroup; children = ( @@ -1573,6 +1592,7 @@ C56EE229293F5668004840D1 /* Wallet */ = { isa = PBXGroup; children = ( + A50D53BB2ABA053600A4FD8B /* NotifySettings */, A51811992A52E82100A52B15 /* Settings */, 847BD1DB2989493F00076C90 /* Main */, C55D3477295DD4AA0004314A /* Welcome */, @@ -2341,6 +2361,8 @@ 84B815552991217900FAD54E /* PushMessagesPresenter.swift in Sources */, A5A0844029D2F626000B9B17 /* DefaultCryptoProvider.swift in Sources */, 847BD1DD2989494F00076C90 /* TabPage.swift in Sources */, + A50D53C42ABA055700A4FD8B /* NotifyPreferencesInteractor.swift in Sources */, + A50D53C22ABA055700A4FD8B /* NotifyPreferencesPresenter.swift in Sources */, C55D348B295DD8CA0004314A /* PasteUriRouter.swift in Sources */, 84B815572991217900FAD54E /* PushMessagesInteractor.swift in Sources */, 847BD1E4298A806800076C90 /* NotificationsModule.swift in Sources */, @@ -2349,6 +2371,7 @@ C55D3497295DFA750004314A /* WelcomeView.swift in Sources */, A57879722A4F225E00F8D10B /* ImportAccount.swift in Sources */, C5B2F71029705827000DBA0E /* EthereumTransaction.swift in Sources */, + A50D53C32ABA055700A4FD8B /* NotifyPreferencesRouter.swift in Sources */, A5D610C82AB31EE800C20083 /* SegmentedPicker.swift in Sources */, C56EE271293F56D7004840D1 /* View.swift in Sources */, C5B2F6FD297055B0000DBA0E /* Signer.swift in Sources */, @@ -2369,7 +2392,6 @@ C56EE270293F56D7004840D1 /* String.swift in Sources */, A51811A12A52E83100A52B15 /* SettingsRouter.swift in Sources */, C56EE279293F56D7004840D1 /* Color.swift in Sources */, - A50D53BA2AB9D44200A4FD8B /* NotifySubscriptionViewModel.swift in Sources */, 847BD1E6298A806800076C90 /* NotificationsRouter.swift in Sources */, C55D3483295DD7140004314A /* AuthRequestView.swift in Sources */, C56EE243293F566D004840D1 /* ScanView.swift in Sources */, @@ -2393,6 +2415,7 @@ C56EE273293F56D7004840D1 /* UIColor.swift in Sources */, A51811982A52E21A00A52B15 /* ConfigurationService.swift in Sources */, C5F32A322954816C00A6476E /* ConnectionDetailsPresenter.swift in Sources */, + A50D53C52ABA055700A4FD8B /* NotifyPreferencesView.swift in Sources */, 84B815562991217900FAD54E /* PushMessagesRouter.swift in Sources */, C56EE246293F566D004840D1 /* ScanRouter.swift in Sources */, C55D3481295DD7140004314A /* AuthRequestRouter.swift in Sources */, @@ -2410,6 +2433,7 @@ C56EE274293F56D7004840D1 /* SceneViewController.swift in Sources */, A5D610D42AB35BED00C20083 /* FailableDecodable.swift in Sources */, 847BD1E5298A806800076C90 /* NotificationsPresenter.swift in Sources */, + A50D53C12ABA055700A4FD8B /* NotifyPreferencesModule.swift in Sources */, C55D3496295DFA750004314A /* WelcomeInteractor.swift in Sources */, C5B2F6FC297055B0000DBA0E /* SOLSigner.swift in Sources */, A518119F2A52E83100A52B15 /* SettingsModule.swift in Sources */, diff --git a/Example/WalletApp/Common/VIPER/SceneViewController.swift b/Example/WalletApp/Common/VIPER/SceneViewController.swift index a545e4e2f..f8c3a97b9 100644 --- a/Example/WalletApp/Common/VIPER/SceneViewController.swift +++ b/Example/WalletApp/Common/VIPER/SceneViewController.swift @@ -90,6 +90,7 @@ private extension SceneViewController { navigationController?.navigationBar.isTranslucent = true navigationController?.navigationBar.backgroundColor = .clear navigationController?.navigationBar.barTintColor = .clear + navigationController?.navigationBar.tintColor = .w_foreground } } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift index 7029134ad..326bce3fe 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift @@ -12,15 +12,19 @@ struct SubscriptionsViewModel: Identifiable { return try? subscription.metadata.icons.first?.asURL() } - var title: String { + var subtitle: String { + return subscription.metadata.description + } + + var name: String { return subscription.metadata.name } - var subtitle: String { + var description: String { return subscription.metadata.description } - var url: String { + var domain: String { return subscription.metadata.url } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsPresenter.swift index ba525f5bf..fde2fb9ef 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsPresenter.swift @@ -23,7 +23,7 @@ final class NotificationsPresenter: ObservableObject { } func subscription(forListing listing: ListingViewModel) -> SubscriptionsViewModel? { - return subscriptions.first(where: { $0.url == listing.appDomain }) + return subscriptions.first(where: { $0.domain == listing.appDomain }) } func subscribe(listing: ListingViewModel) async throws { diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift index 109e2b9dc..8bcbfb0d1 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift @@ -127,7 +127,7 @@ struct NotificationsView: View { .padding(.leading, 20) VStack(alignment: .leading, spacing: 2) { - Text(subscription.title) + Text(subscription.name) .foregroundColor(.grey8) .font(.system(size: 20, weight: .semibold, design: .rounded)) diff --git a/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesInteractor.swift new file mode 100644 index 000000000..20470ca66 --- /dev/null +++ b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesInteractor.swift @@ -0,0 +1,3 @@ +final class NotifyPreferencesInteractor { + +} diff --git a/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesModule.swift b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesModule.swift new file mode 100644 index 000000000..d914a29c0 --- /dev/null +++ b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesModule.swift @@ -0,0 +1,19 @@ +import SwiftUI +import WalletConnectNotify + +final class NotifyPreferencesModule { + + @discardableResult + static func create(app: Application, subscription: NotifySubscription) -> UIViewController { + let router = NotifyPreferencesRouter(app: app) + let interactor = NotifyPreferencesInteractor() + let presenter = NotifyPreferencesPresenter(subscription: subscription, interactor: interactor, router: router) + let view = NotifyPreferencesView().environmentObject(presenter) + let viewController = SceneViewController(viewModel: presenter, content: view) + + router.viewController = viewController + + return viewController + } + +} diff --git a/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesPresenter.swift new file mode 100644 index 000000000..af44f5187 --- /dev/null +++ b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesPresenter.swift @@ -0,0 +1,37 @@ +import UIKit +import Combine +import WalletConnectNotify + +final class NotifyPreferencesPresenter: ObservableObject { + + private let subscription: NotifySubscription + private let interactor: NotifyPreferencesInteractor + private let router: NotifyPreferencesRouter + private var disposeBag = Set() + + var subscriptionViewModel: SubscriptionsViewModel { + return SubscriptionsViewModel(subscription: subscription) + } + + init(subscription: NotifySubscription, interactor: NotifyPreferencesInteractor, router: NotifyPreferencesRouter) { + defer { setupInitialState() } + self.subscription = subscription + self.interactor = interactor + self.router = router + } +} + +// MARK: SceneViewModel + +extension NotifyPreferencesPresenter: SceneViewModel { + +} + +// MARK: Privates + +private extension NotifyPreferencesPresenter { + + func setupInitialState() { + + } +} diff --git a/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesRouter.swift new file mode 100644 index 000000000..adf581cbb --- /dev/null +++ b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesRouter.swift @@ -0,0 +1,12 @@ +import UIKit + +final class NotifyPreferencesRouter { + + weak var viewController: UIViewController! + + private let app: Application + + init(app: Application) { + self.app = app + } +} diff --git a/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesView.swift b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesView.swift new file mode 100644 index 000000000..d6b1787f0 --- /dev/null +++ b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesView.swift @@ -0,0 +1,18 @@ +import SwiftUI + +struct NotifyPreferencesView: View { + + @EnvironmentObject var viewModel: NotifyPreferencesPresenter + + var body: some View { + Text("NotifyPreferences module") + } +} + +#if DEBUG +struct NotifyPreferencesView_Previews: PreviewProvider { + static var previews: some View { + NotifyPreferencesView() + } +} +#endif diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/NotifySubscriptionViewModel.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/NotifySubscriptionViewModel.swift deleted file mode 100644 index 9571402fe..000000000 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/NotifySubscriptionViewModel.swift +++ /dev/null @@ -1,23 +0,0 @@ -import Foundation -import WalletConnectNotify - -struct NotifySubscriptionViewModel { - - let subscription: NotifySubscription - - var name: String { - return subscription.metadata.name - } - - var description: String { - return subscription.metadata.description - } - - var domain: String { - return subscription.metadata.url - } - - var iconUrl: URL? { - return try? subscription.metadata.icons.first?.asURL() - } -} diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift index b0f36a84e..5ffa1ccbb 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift @@ -11,8 +11,8 @@ final class PushMessagesPresenter: ObservableObject { @Published private var pushMessages: [NotifyMessageRecord] = [] - var subscriptionViewModel: NotifySubscriptionViewModel { - return NotifySubscriptionViewModel(subscription: subscription) + var subscriptionViewModel: SubscriptionsViewModel { + return SubscriptionsViewModel(subscription: subscription) } var messages: [PushMessageViewModel] { @@ -48,6 +48,10 @@ final class PushMessagesPresenter: ObservableObject { interactor.deleteSubscription(subscription) router.dismiss() } + + @objc func preferencesDidPress() { + router.presentPreferences(subscription: subscription) + } } // MARK: SceneViewModel @@ -61,6 +65,15 @@ extension PushMessagesPresenter: SceneViewModel { var navigationBarStyle: NavigationBarStyle { return .clear } + + var rightBarButtonItem: UIBarButtonItem? { + return UIBarButtonItem( + image: UIImage(systemName: "gearshape"), + style: .plain, + target: self, + action: #selector(preferencesDidPress) + ) + } } // MARK: Privates diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesRouter.swift index a237b6994..c5ec07a04 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesRouter.swift @@ -1,4 +1,5 @@ import UIKit +import WalletConnectNotify final class PushMessagesRouter { @@ -13,4 +14,11 @@ final class PushMessagesRouter { func dismiss() { viewController.pop() } + + func presentPreferences(subscription: NotifySubscription) { + let controller = NotifyPreferencesModule.create(app: app, subscription: subscription) + controller.sheetPresentationController?.detents = [.medium()] + controller.sheetPresentationController?.prefersGrabberVisible = true + controller.present(from: viewController) + } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift index 55ab26e0c..8658cdf17 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift @@ -76,7 +76,7 @@ struct PushMessagesView: View { func headerView() -> some View { VStack(spacing: 0) { - AsyncImage(url: presenter.subscriptionViewModel.iconUrl) { phase in + AsyncImage(url: presenter.subscriptionViewModel.imageUrl) { phase in if let image = phase.image { image .resizable() From 0cc6872d076e378020703f1152d20acc5c9e768c Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 20 Sep 2023 03:48:56 +0800 Subject: [PATCH 08/11] Preferences screen --- .../Models/SubscriptionsViewModel.swift | 6 ++ .../NotifyPreferencesInteractor.swift | 5 ++ .../NotifyPreferencesPresenter.swift | 23 +++++++- .../NotifyPreferencesRouter.swift | 4 ++ .../NotifyPreferencesView.swift | 59 ++++++++++++++++++- .../PushMessages/PushMessagesInteractor.swift | 4 ++ .../PushMessages/PushMessagesPresenter.swift | 9 ++- .../PushMessages/PushMessagesRouter.swift | 2 +- .../PushMessages/PushMessagesView.swift | 1 - .../DataStructures/NotifySubscription.swift | 9 ++- 10 files changed, 115 insertions(+), 7 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift index 326bce3fe..366ce4409 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift @@ -1,6 +1,8 @@ import Foundation import WalletConnectNotify +typealias SubscriptionScope = [String: ScopeValue] + struct SubscriptionsViewModel: Identifiable { let subscription: NotifySubscription @@ -27,4 +29,8 @@ struct SubscriptionsViewModel: Identifiable { var domain: String { return subscription.metadata.url } + + var scope: SubscriptionScope { + return subscription.scope + } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesInteractor.swift index 20470ca66..85ce91040 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesInteractor.swift @@ -1,3 +1,8 @@ +import WalletConnectNotify + final class NotifyPreferencesInteractor { + func updatePreferences(subscription: NotifySubscription, scope: Set) async throws { + try await Notify.instance.update(topic: subscription.topic, scope: scope) + } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesPresenter.swift index af44f5187..c811c4314 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesPresenter.swift @@ -13,12 +13,33 @@ final class NotifyPreferencesPresenter: ObservableObject { return SubscriptionsViewModel(subscription: subscription) } + var preferences: [String] { + return subscriptionViewModel.scope.keys.sorted() + } + + var isUpdateDisabled: Bool { + return update == subscription.scope + } + + @Published var update: SubscriptionScope = [:] + init(subscription: NotifySubscription, interactor: NotifyPreferencesInteractor, router: NotifyPreferencesRouter) { defer { setupInitialState() } self.subscription = subscription self.interactor = interactor self.router = router } + + @MainActor + func updateDidPress() async throws { + let scope = update + .filter { $0.value.enabled } + .map { $0.key } + + try await interactor.updatePreferences(subscription: subscription, scope: Set(scope)) + + router.dismiss() + } } // MARK: SceneViewModel @@ -32,6 +53,6 @@ extension NotifyPreferencesPresenter: SceneViewModel { private extension NotifyPreferencesPresenter { func setupInitialState() { - + update = subscription.scope } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesRouter.swift index adf581cbb..124db05f5 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesRouter.swift @@ -9,4 +9,8 @@ final class NotifyPreferencesRouter { init(app: Application) { self.app = app } + + func dismiss() { + viewController.dismiss() + } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesView.swift b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesView.swift index d6b1787f0..bfbbfb584 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/NotifySettings/NotifyPreferencesView.swift @@ -1,11 +1,68 @@ import SwiftUI +import AsyncButton +import WalletConnectNotify struct NotifyPreferencesView: View { @EnvironmentObject var viewModel: NotifyPreferencesPresenter var body: some View { - Text("NotifyPreferences module") + List { + VStack(spacing: 0) { + Text("Notification Preferences") + .font(.headline) + .foregroundColor(.primary) + .padding(.top, 16.0) + + Text("for \(viewModel.subscriptionViewModel.name)") + .font(.caption) + .foregroundColor(.secondary) + .padding(4.0) + } + .frame(maxWidth: .infinity) + .alignmentGuide(.listRowSeparatorLeading) { _ in -50 } + + ForEach(Array(viewModel.preferences.enumerated()), id: \.offset) { i, preference in + if let value = viewModel.subscriptionViewModel.scope[preference] { + preferenceRow(title: preference, value: value) + .listRowSeparator(i == viewModel.preferences.count-1 ? .hidden : .visible) + } + } + + AsyncButton { + try await viewModel.updateDidPress() + } label: { + Text("Update") + .frame(maxWidth: .infinity) + .frame(height: 44.0) + .foregroundColor(.white) + .font(.system(size: 16, weight: .semibold)) + .background(Color.blue100) + .cornerRadius(20) + } + .buttonStyle(.plain) + .disabled(viewModel.isUpdateDisabled) + } + .listStyle(.plain) + } + + private func preferenceRow(title: String, value: ScopeValue) -> some View { + Toggle(isOn: .init(get: { + viewModel.update[title]?.enabled ?? value.enabled + }, set: { newValue in + viewModel.update[title] = ScopeValue(description: value.description, enabled: newValue) + })) { + VStack(alignment: .leading, spacing: 4) { + Text(title) + .foregroundColor(.primary) + .font(.system(size: 14, weight: .semibold)) + + Text(value.description) + .foregroundColor(.grey50) + .font(.system(size: 13)) + } + } + .padding(8.0) } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift index 04f05215e..c47f7c7f5 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift @@ -12,6 +12,10 @@ final class PushMessagesInteractor { var messagesPublisher: AnyPublisher<[NotifyMessageRecord], Never> { return Notify.instance.messagesPublisher(topic: subscription.topic) } + + var subscriptionPublisher: AnyPublisher<[NotifySubscription], Never> { + return Notify.instance.subscriptionsPublisher + } func getPushMessages() -> [NotifyMessageRecord] { return Notify.instance.getMessageHistory(topic: subscription.topic) diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift index 5ffa1ccbb..252e235e0 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift @@ -4,7 +4,7 @@ import WalletConnectNotify final class PushMessagesPresenter: ObservableObject { - private let subscription: NotifySubscription + private var subscription: NotifySubscription private let interactor: PushMessagesInteractor private let router: PushMessagesRouter private var disposeBag = Set() @@ -90,6 +90,13 @@ private extension PushMessagesPresenter { self.pushMessages = self.interactor.getPushMessages() } .store(in: &disposeBag) + + interactor.subscriptionPublisher + .sink { [unowned self] subscriptions in + if let updated = subscriptions.first(where: { $0.topic == subscription.topic }) { + subscription = updated + } + }.store(in: &disposeBag) } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesRouter.swift index c5ec07a04..595563e5c 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesRouter.swift @@ -17,7 +17,7 @@ final class PushMessagesRouter { func presentPreferences(subscription: NotifySubscription) { let controller = NotifyPreferencesModule.create(app: app, subscription: subscription) - controller.sheetPresentationController?.detents = [.medium()] + controller.sheetPresentationController?.detents = [.custom(resolver: { _ in UIScreen.main.bounds.height * 2/3 })] controller.sheetPresentationController?.prefersGrabberVisible = true controller.present(from: viewController) } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift index 8658cdf17..05b74ffce 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift @@ -50,7 +50,6 @@ struct PushMessagesView: View { } .padding(.leading, 20) - VStack(alignment: .leading, spacing: 2) { HStack { Text(pushMessage.title) diff --git a/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift b/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift index b44189c80..38676d60a 100644 --- a/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift +++ b/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift @@ -15,6 +15,11 @@ public struct NotifySubscription: DatabaseObject { } public struct ScopeValue: Codable, Equatable { - let description: String - let enabled: Bool + public let description: String + public let enabled: Bool + + public init(description: String, enabled: Bool) { + self.description = description + self.enabled = enabled + } } From 99a471216c044a1ddd8c60a0094602353cdcf59e Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 20 Sep 2023 04:04:26 +0800 Subject: [PATCH 09/11] Dark mode adjustments --- .../Wallet/Notifications/NotificationsView.swift | 9 +++++---- .../Wallet/PushMessages/PushMessagesView.swift | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift index 8bcbfb0d1..04ce1dc5d 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift @@ -52,6 +52,7 @@ struct NotificationsView: View { listingRow(listing: listing) .listRowSeparator(.hidden) .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 16, trailing: 0)) + .listRowBackground(Color.clear) } } .listStyle(PlainListStyle()) @@ -116,10 +117,10 @@ struct NotificationsView: View { image .resizable() .frame(width: 60, height: 60) - .background(Color.black.opacity(0.1)) + .background(Color.grey8.opacity(0.1)) .cornerRadius(30, corners: .allCorners) } else { - Color.black.opacity(0.1) + Color.grey8.opacity(0.1) .frame(width: 60, height: 60) .cornerRadius(30, corners: .allCorners) } @@ -154,10 +155,10 @@ struct NotificationsView: View { image .resizable() .frame(width: 60, height: 60) - .background(Color.black.opacity(0.1)) + .background(Color.grey8.opacity(0.1)) .cornerRadius(30, corners: .allCorners) } else { - Color.black.opacity(0.1) + Color.grey8.opacity(0.1) .frame(width: 60, height: 60) .cornerRadius(30, corners: .allCorners) } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift index 05b74ffce..b6eeefe7c 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift @@ -21,6 +21,7 @@ struct PushMessagesView: View { .onDelete { indexSet in presenter.deletePushMessage(at: indexSet) } + Spacer().frame(height: 50.0) } else { emptyStateView() .listRowSeparator(.hidden) From 9508d7e03ab9d502f0ea80e21fc86a744a5ee3b3 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 20 Sep 2023 20:45:47 +0800 Subject: [PATCH 10/11] Push message renamed to Subscription --- Example/ExampleApp.xcodeproj/project.pbxproj | 48 +++++++++---------- .../xcshareddata/swiftpm/Package.resolved | 8 ++-- .../Notifications/NotificationsRouter.swift | 2 +- ...del.swift => NotifyMessageViewModel.swift} | 2 +- ...tor.swift => SubscriptionInteractor.swift} | 2 +- ...sModule.swift => SubscriptionModule.swift} | 10 ++-- ...nter.swift => SubscriptionPresenter.swift} | 16 +++---- ...sRouter.swift => SubscriptionRouter.swift} | 2 +- ...sagesView.swift => SubscriptionView.swift} | 8 ++-- 9 files changed, 49 insertions(+), 49 deletions(-) rename Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/{PushMessageViewModel.swift => NotifyMessageViewModel.swift} (92%) rename Example/WalletApp/PresentationLayer/Wallet/PushMessages/{PushMessagesInteractor.swift => SubscriptionInteractor.swift} (96%) rename Example/WalletApp/PresentationLayer/Wallet/PushMessages/{PushMessagesModule.swift => SubscriptionModule.swift} (59%) rename Example/WalletApp/PresentationLayer/Wallet/PushMessages/{PushMessagesPresenter.swift => SubscriptionPresenter.swift} (84%) rename Example/WalletApp/PresentationLayer/Wallet/PushMessages/{PushMessagesRouter.swift => SubscriptionRouter.swift} (95%) rename Example/WalletApp/PresentationLayer/Wallet/PushMessages/{PushMessagesView.swift => SubscriptionView.swift} (96%) diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index d0e5c2d21..295f4ee58 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -41,12 +41,7 @@ 84A6E3C32A386BBC008A0571 /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A6E3C22A386BBC008A0571 /* Publisher.swift */; }; 84AA01DB28CF0CD7005D48D8 /* XCTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AA01DA28CF0CD7005D48D8 /* XCTest.swift */; }; 84B8154E2991099000FAD54E /* BuildConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8154D2991099000FAD54E /* BuildConfiguration.swift */; }; - 84B815542991217900FAD54E /* PushMessagesModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8154F2991217900FAD54E /* PushMessagesModule.swift */; }; - 84B815552991217900FAD54E /* PushMessagesPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B815502991217900FAD54E /* PushMessagesPresenter.swift */; }; - 84B815562991217900FAD54E /* PushMessagesRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B815512991217900FAD54E /* PushMessagesRouter.swift */; }; - 84B815572991217900FAD54E /* PushMessagesInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B815522991217900FAD54E /* PushMessagesInteractor.swift */; }; - 84B815582991217900FAD54E /* PushMessagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B815532991217900FAD54E /* PushMessagesView.swift */; }; - 84B8155B2992A18D00FAD54E /* PushMessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8155A2992A18D00FAD54E /* PushMessageViewModel.swift */; }; + 84B8155B2992A18D00FAD54E /* NotifyMessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8155A2992A18D00FAD54E /* NotifyMessageViewModel.swift */; }; 84CE641F27981DED00142511 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE641E27981DED00142511 /* AppDelegate.swift */; }; 84CE642127981DED00142511 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE642027981DED00142511 /* SceneDelegate.swift */; }; 84CE642827981DF000142511 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84CE642727981DF000142511 /* Assets.xcassets */; }; @@ -178,6 +173,11 @@ A5A8E47E293A1CFE00FEB97D /* DefaultSignerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59CF4F5292F83D50031A42F /* DefaultSignerFactory.swift */; }; A5A8E47F293A1D0000FEB97D /* DefaultSocketFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629AEF2877F73000094373 /* DefaultSocketFactory.swift */; }; A5A8E480293A1D0000FEB97D /* DefaultSignerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59CF4F5292F83D50031A42F /* DefaultSignerFactory.swift */; }; + A5B4F7C22ABB20AE0099AF7C /* SubscriptionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B4F7BD2ABB20AE0099AF7C /* SubscriptionPresenter.swift */; }; + A5B4F7C32ABB20AE0099AF7C /* SubscriptionInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B4F7BE2ABB20AE0099AF7C /* SubscriptionInteractor.swift */; }; + A5B4F7C42ABB20AE0099AF7C /* SubscriptionModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B4F7BF2ABB20AE0099AF7C /* SubscriptionModule.swift */; }; + A5B4F7C52ABB20AE0099AF7C /* SubscriptionRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B4F7C02ABB20AE0099AF7C /* SubscriptionRouter.swift */; }; + A5B4F7C62ABB20AE0099AF7C /* SubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B4F7C12ABB20AE0099AF7C /* SubscriptionView.swift */; }; A5B6C0F12A6EAB0800927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F02A6EAB0800927332 /* WalletConnectNotify */; }; A5B6C0F32A6EAB1700927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F22A6EAB1700927332 /* WalletConnectNotify */; }; A5B6C0F52A6EAB2800927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F42A6EAB2800927332 /* WalletConnectNotify */; }; @@ -403,12 +403,7 @@ 84A6E3C22A386BBC008A0571 /* Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publisher.swift; sourceTree = ""; }; 84AA01DA28CF0CD7005D48D8 /* XCTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTest.swift; sourceTree = ""; }; 84B8154D2991099000FAD54E /* BuildConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildConfiguration.swift; sourceTree = ""; }; - 84B8154F2991217900FAD54E /* PushMessagesModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessagesModule.swift; sourceTree = ""; }; - 84B815502991217900FAD54E /* PushMessagesPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessagesPresenter.swift; sourceTree = ""; }; - 84B815512991217900FAD54E /* PushMessagesRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessagesRouter.swift; sourceTree = ""; }; - 84B815522991217900FAD54E /* PushMessagesInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessagesInteractor.swift; sourceTree = ""; }; - 84B815532991217900FAD54E /* PushMessagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessagesView.swift; sourceTree = ""; }; - 84B8155A2992A18D00FAD54E /* PushMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessageViewModel.swift; sourceTree = ""; }; + 84B8155A2992A18D00FAD54E /* NotifyMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyMessageViewModel.swift; sourceTree = ""; }; 84CE641C27981DED00142511 /* DApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 84CE641E27981DED00142511 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 84CE642027981DED00142511 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -524,6 +519,11 @@ A5A0843B29D2F60A000B9B17 /* DefaultCryptoProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultCryptoProvider.swift; sourceTree = ""; }; A5A4FC722840C12C00BBEC1E /* UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A5A4FC762840C12C00BBEC1E /* RegressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegressionTests.swift; sourceTree = ""; }; + A5B4F7BD2ABB20AE0099AF7C /* SubscriptionPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPresenter.swift; sourceTree = ""; }; + A5B4F7BE2ABB20AE0099AF7C /* SubscriptionInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionInteractor.swift; sourceTree = ""; }; + A5B4F7BF2ABB20AE0099AF7C /* SubscriptionModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionModule.swift; sourceTree = ""; }; + A5B4F7C02ABB20AE0099AF7C /* SubscriptionRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionRouter.swift; sourceTree = ""; }; + A5B4F7C12ABB20AE0099AF7C /* SubscriptionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionView.swift; sourceTree = ""; }; A5BB7F9E28B69B7100707FC6 /* SignCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignCoordinator.swift; sourceTree = ""; }; A5BB7FA028B69F3400707FC6 /* AuthCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthCoordinator.swift; sourceTree = ""; }; A5BB7FA628B6A5F600707FC6 /* AuthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthView.swift; sourceTree = ""; }; @@ -882,11 +882,11 @@ 84B815592991217F00FAD54E /* PushMessages */ = { isa = PBXGroup; children = ( - 84B8154F2991217900FAD54E /* PushMessagesModule.swift */, - 84B815502991217900FAD54E /* PushMessagesPresenter.swift */, - 84B815512991217900FAD54E /* PushMessagesRouter.swift */, - 84B815522991217900FAD54E /* PushMessagesInteractor.swift */, - 84B815532991217900FAD54E /* PushMessagesView.swift */, + A5B4F7BE2ABB20AE0099AF7C /* SubscriptionInteractor.swift */, + A5B4F7BF2ABB20AE0099AF7C /* SubscriptionModule.swift */, + A5B4F7BD2ABB20AE0099AF7C /* SubscriptionPresenter.swift */, + A5B4F7C02ABB20AE0099AF7C /* SubscriptionRouter.swift */, + A5B4F7C12ABB20AE0099AF7C /* SubscriptionView.swift */, 84B8155C2992A19200FAD54E /* Models */, ); path = PushMessages; @@ -895,7 +895,7 @@ 84B8155C2992A19200FAD54E /* Models */ = { isa = PBXGroup; children = ( - 84B8155A2992A18D00FAD54E /* PushMessageViewModel.swift */, + 84B8155A2992A18D00FAD54E /* NotifyMessageViewModel.swift */, ); path = Models; sourceTree = ""; @@ -2358,13 +2358,11 @@ A51606FB2A2F47BD00CACB92 /* DefaultBIP44Provider.swift in Sources */, C56EE250293F566D004840D1 /* ScanTargetView.swift in Sources */, C56EE28F293F5757004840D1 /* MigrationConfigurator.swift in Sources */, - 84B815552991217900FAD54E /* PushMessagesPresenter.swift in Sources */, A5A0844029D2F626000B9B17 /* DefaultCryptoProvider.swift in Sources */, 847BD1DD2989494F00076C90 /* TabPage.swift in Sources */, A50D53C42ABA055700A4FD8B /* NotifyPreferencesInteractor.swift in Sources */, A50D53C22ABA055700A4FD8B /* NotifyPreferencesPresenter.swift in Sources */, C55D348B295DD8CA0004314A /* PasteUriRouter.swift in Sources */, - 84B815572991217900FAD54E /* PushMessagesInteractor.swift in Sources */, 847BD1E4298A806800076C90 /* NotificationsModule.swift in Sources */, C55D348C295DD8CA0004314A /* PasteUriInteractor.swift in Sources */, 847BD1D92989492500076C90 /* MainPresenter.swift in Sources */, @@ -2374,14 +2372,16 @@ A50D53C32ABA055700A4FD8B /* NotifyPreferencesRouter.swift in Sources */, A5D610C82AB31EE800C20083 /* SegmentedPicker.swift in Sources */, C56EE271293F56D7004840D1 /* View.swift in Sources */, + A5B4F7C62ABB20AE0099AF7C /* SubscriptionView.swift in Sources */, C5B2F6FD297055B0000DBA0E /* Signer.swift in Sources */, C56EE24D293F566D004840D1 /* WalletRouter.swift in Sources */, A5D610D02AB35AD500C20083 /* ListingsNetworkService.swift in Sources */, C5F32A342954817600A6476E /* ConnectionDetailsView.swift in Sources */, C55D348A295DD8CA0004314A /* PasteUriPresenter.swift in Sources */, A51811A22A52E83100A52B15 /* SettingsInteractor.swift in Sources */, + A5B4F7C22ABB20AE0099AF7C /* SubscriptionPresenter.swift in Sources */, C56EE28E293F5757004840D1 /* ApplicationConfigurator.swift in Sources */, - 84B8155B2992A18D00FAD54E /* PushMessageViewModel.swift in Sources */, + 84B8155B2992A18D00FAD54E /* NotifyMessageViewModel.swift in Sources */, C55D347F295DD7140004314A /* AuthRequestModule.swift in Sources */, C56EE242293F566D004840D1 /* ScanPresenter.swift in Sources */, C56EE28B293F5757004840D1 /* SceneDelegate.swift in Sources */, @@ -2400,7 +2400,7 @@ 847BD1D62989492500076C90 /* MainViewController.swift in Sources */, C5B2F6FA29705293000DBA0E /* SessionRequestInteractor.swift in Sources */, C55D34AE2965FB750004314A /* SessionProposalModule.swift in Sources */, - 84B815582991217900FAD54E /* PushMessagesView.swift in Sources */, + A5B4F7C42ABB20AE0099AF7C /* SubscriptionModule.swift in Sources */, C55D34B02965FB750004314A /* SessionProposalRouter.swift in Sources */, C55D3495295DFA750004314A /* WelcomeRouter.swift in Sources */, C5B2F6F729705293000DBA0E /* SessionRequestRouter.swift in Sources */, @@ -2408,15 +2408,14 @@ C55D34B22965FB750004314A /* SessionProposalView.swift in Sources */, C56EE248293F566D004840D1 /* ScanQR.swift in Sources */, 847BD1EB298A87AB00076C90 /* SubscriptionsViewModel.swift in Sources */, - 84B815542991217900FAD54E /* PushMessagesModule.swift in Sources */, C55D349B2965BC2F0004314A /* TagsView.swift in Sources */, 84B8154E2991099000FAD54E /* BuildConfiguration.swift in Sources */, C56EE289293F5757004840D1 /* Application.swift in Sources */, C56EE273293F56D7004840D1 /* UIColor.swift in Sources */, A51811982A52E21A00A52B15 /* ConfigurationService.swift in Sources */, C5F32A322954816C00A6476E /* ConnectionDetailsPresenter.swift in Sources */, + A5B4F7C32ABB20AE0099AF7C /* SubscriptionInteractor.swift in Sources */, A50D53C52ABA055700A4FD8B /* NotifyPreferencesView.swift in Sources */, - 84B815562991217900FAD54E /* PushMessagesRouter.swift in Sources */, C56EE246293F566D004840D1 /* ScanRouter.swift in Sources */, C55D3481295DD7140004314A /* AuthRequestRouter.swift in Sources */, C5B2F6F829705293000DBA0E /* SessionRequestView.swift in Sources */, @@ -2434,6 +2433,7 @@ A5D610D42AB35BED00C20083 /* FailableDecodable.swift in Sources */, 847BD1E5298A806800076C90 /* NotificationsPresenter.swift in Sources */, A50D53C12ABA055700A4FD8B /* NotifyPreferencesModule.swift in Sources */, + A5B4F7C52ABB20AE0099AF7C /* SubscriptionRouter.swift in Sources */, C55D3496295DFA750004314A /* WelcomeInteractor.swift in Sources */, C5B2F6FC297055B0000DBA0E /* SOLSigner.swift in Sources */, A518119F2A52E83100A52B15 /* SettingsModule.swift in Sources */, diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 60fae593d..9d6fdc5ce 100644 --- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git", "state": { "branch": null, - "revision": "db51c407d3be4a051484a141bf0bff36c43d3b1e", - "version": "1.8.0" + "revision": "32f641cf24fc7abc1c591a2025e9f2f572648b0f", + "version": "1.7.2" } }, { @@ -69,8 +69,8 @@ "repositoryURL": "https://github.com/getsentry/sentry-cocoa.git", "state": { "branch": null, - "revision": "14aa6e47b03b820fd2b338728637570b9e969994", - "version": "8.12.0" + "revision": "04bee4ad86d74d4cb4d7101ff826d6e355301ba9", + "version": "8.9.4" } }, { diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsRouter.swift index eebcfd7b3..843fcc14d 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsRouter.swift @@ -12,7 +12,7 @@ final class NotificationsRouter { } func presentNotifications(subscription: NotifySubscription) { - PushMessagesModule.create(app: app, subscription: subscription) + SubscriptionModule.create(app: app, subscription: subscription) .push(from: viewController) } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/PushMessageViewModel.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/NotifyMessageViewModel.swift similarity index 92% rename from Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/PushMessageViewModel.swift rename to Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/NotifyMessageViewModel.swift index cfb200823..9e937154c 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/PushMessageViewModel.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/NotifyMessageViewModel.swift @@ -1,7 +1,7 @@ import Foundation import WalletConnectNotify -struct PushMessageViewModel: Identifiable { +struct NotifyMessageViewModel: Identifiable { let pushMessageRecord: NotifyMessageRecord diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionInteractor.swift similarity index 96% rename from Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift rename to Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionInteractor.swift index c47f7c7f5..9b0c84ff7 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionInteractor.swift @@ -1,7 +1,7 @@ import WalletConnectNotify import Combine -final class PushMessagesInteractor { +final class SubscriptionInteractor { let subscription: NotifySubscription diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesModule.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionModule.swift similarity index 59% rename from Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesModule.swift rename to Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionModule.swift index 01d973b45..2a84b6332 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesModule.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionModule.swift @@ -1,14 +1,14 @@ import SwiftUI import WalletConnectNotify -final class PushMessagesModule { +final class SubscriptionModule { @discardableResult static func create(app: Application, subscription: NotifySubscription) -> UIViewController { - let router = PushMessagesRouter(app: app) - let interactor = PushMessagesInteractor(subscription: subscription) - let presenter = PushMessagesPresenter(subscription: subscription, interactor: interactor, router: router) - let view = PushMessagesView().environmentObject(presenter) + let router = SubscriptionRouter(app: app) + let interactor = SubscriptionInteractor(subscription: subscription) + let presenter = SubscriptionPresenter(subscription: subscription, interactor: interactor, router: router) + let view = SubscriptionView().environmentObject(presenter) let viewController = SceneViewController(viewModel: presenter, content: view) router.viewController = viewController diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift similarity index 84% rename from Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift rename to Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift index 252e235e0..118cdc674 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionPresenter.swift @@ -2,11 +2,11 @@ import UIKit import Combine import WalletConnectNotify -final class PushMessagesPresenter: ObservableObject { +final class SubscriptionPresenter: ObservableObject { private var subscription: NotifySubscription - private let interactor: PushMessagesInteractor - private let router: PushMessagesRouter + private let interactor: SubscriptionInteractor + private let router: SubscriptionRouter private var disposeBag = Set() @Published private var pushMessages: [NotifyMessageRecord] = [] @@ -15,13 +15,13 @@ final class PushMessagesPresenter: ObservableObject { return SubscriptionsViewModel(subscription: subscription) } - var messages: [PushMessageViewModel] { + var messages: [NotifyMessageViewModel] { return pushMessages .sorted { $0.publishedAt > $1.publishedAt } - .map { PushMessageViewModel(pushMessageRecord: $0) } + .map { NotifyMessageViewModel(pushMessageRecord: $0) } } - init(subscription: NotifySubscription, interactor: PushMessagesInteractor, router: PushMessagesRouter) { + init(subscription: NotifySubscription, interactor: SubscriptionInteractor, router: SubscriptionRouter) { defer { setupInitialState() } self.subscription = subscription self.interactor = interactor @@ -56,7 +56,7 @@ final class PushMessagesPresenter: ObservableObject { // MARK: SceneViewModel -extension PushMessagesPresenter: SceneViewModel { +extension SubscriptionPresenter: SceneViewModel { var largeTitleDisplayMode: UINavigationItem.LargeTitleDisplayMode { return .never @@ -78,7 +78,7 @@ extension PushMessagesPresenter: SceneViewModel { // MARK: Privates -private extension PushMessagesPresenter { +private extension SubscriptionPresenter { func setupInitialState() { pushMessages = interactor.getPushMessages() diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionRouter.swift similarity index 95% rename from Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesRouter.swift rename to Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionRouter.swift index 595563e5c..edcef5f8f 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionRouter.swift @@ -1,7 +1,7 @@ import UIKit import WalletConnectNotify -final class PushMessagesRouter { +final class SubscriptionRouter { weak var viewController: UIViewController! diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift similarity index 96% rename from Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift rename to Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift index b6eeefe7c..f6efdf897 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift @@ -1,8 +1,8 @@ import SwiftUI -struct PushMessagesView: View { +struct SubscriptionView: View { - @EnvironmentObject var presenter: PushMessagesPresenter + @EnvironmentObject var presenter: SubscriptionPresenter var body: some View { VStack(spacing: 0) { @@ -33,7 +33,7 @@ struct PushMessagesView: View { .ignoresSafeArea(.container) } - private func notificationView(pushMessage: PushMessageViewModel) -> some View { + private func notificationView(pushMessage: NotifyMessageViewModel) -> some View { VStack(alignment: .center) { HStack(spacing: 10) { AsyncImage(url: URL(string: pushMessage.imageUrl)) { phase in @@ -152,7 +152,7 @@ struct PushMessagesView: View { #if DEBUG struct PushMessagesView_Previews: PreviewProvider { static var previews: some View { - PushMessagesView() + SubscriptionView() } } #endif From c1ef1f02fe76f430f0bc4e3ccf9ea51788667ce3 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 20 Sep 2023 20:49:46 +0800 Subject: [PATCH 11/11] Image cache --- Example/ExampleApp.xcodeproj/project.pbxproj | 4 ++ .../Common/Helpers/CacheAsyncImage.swift | 52 +++++++++++++++++++ .../Notifications/NotificationsView.swift | 4 +- .../PushMessages/SubscriptionView.swift | 4 +- 4 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 Example/WalletApp/Common/Helpers/CacheAsyncImage.swift diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 295f4ee58..377f84260 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -178,6 +178,7 @@ A5B4F7C42ABB20AE0099AF7C /* SubscriptionModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B4F7BF2ABB20AE0099AF7C /* SubscriptionModule.swift */; }; A5B4F7C52ABB20AE0099AF7C /* SubscriptionRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B4F7C02ABB20AE0099AF7C /* SubscriptionRouter.swift */; }; A5B4F7C62ABB20AE0099AF7C /* SubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B4F7C12ABB20AE0099AF7C /* SubscriptionView.swift */; }; + A5B4F7C82ABB21190099AF7C /* CacheAsyncImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B4F7C72ABB21190099AF7C /* CacheAsyncImage.swift */; }; A5B6C0F12A6EAB0800927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F02A6EAB0800927332 /* WalletConnectNotify */; }; A5B6C0F32A6EAB1700927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F22A6EAB1700927332 /* WalletConnectNotify */; }; A5B6C0F52A6EAB2800927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F42A6EAB2800927332 /* WalletConnectNotify */; }; @@ -524,6 +525,7 @@ A5B4F7BF2ABB20AE0099AF7C /* SubscriptionModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionModule.swift; sourceTree = ""; }; A5B4F7C02ABB20AE0099AF7C /* SubscriptionRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionRouter.swift; sourceTree = ""; }; A5B4F7C12ABB20AE0099AF7C /* SubscriptionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionView.swift; sourceTree = ""; }; + A5B4F7C72ABB21190099AF7C /* CacheAsyncImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheAsyncImage.swift; sourceTree = ""; }; A5BB7F9E28B69B7100707FC6 /* SignCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignCoordinator.swift; sourceTree = ""; }; A5BB7FA028B69F3400707FC6 /* AuthCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthCoordinator.swift; sourceTree = ""; }; A5BB7FA628B6A5F600707FC6 /* AuthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthView.swift; sourceTree = ""; }; @@ -1751,6 +1753,7 @@ isa = PBXGroup; children = ( C56EE2A2293F6BAF004840D1 /* UIPasteboardWrapper.swift */, + A5B4F7C72ABB21190099AF7C /* CacheAsyncImage.swift */, C55D349A2965BC2F0004314A /* TagsView.swift */, A74D32B92A1E25AD00CB8536 /* QueryParameters.swift */, A5D610C72AB31EE800C20083 /* SegmentedPicker.swift */, @@ -2449,6 +2452,7 @@ A57879732A4F248200F8D10B /* AccountStorage.swift in Sources */, C55D34AF2965FB750004314A /* SessionProposalPresenter.swift in Sources */, A5D610D22AB35B1100C20083 /* Listings.swift in Sources */, + A5B4F7C82ABB21190099AF7C /* CacheAsyncImage.swift in Sources */, C5F32A302954816100A6476E /* ConnectionDetailsInteractor.swift in Sources */, 847BD1E8298A806800076C90 /* NotificationsView.swift in Sources */, ); diff --git a/Example/WalletApp/Common/Helpers/CacheAsyncImage.swift b/Example/WalletApp/Common/Helpers/CacheAsyncImage.swift new file mode 100644 index 000000000..baafc2c98 --- /dev/null +++ b/Example/WalletApp/Common/Helpers/CacheAsyncImage.swift @@ -0,0 +1,52 @@ +import SwiftUI + +struct CacheAsyncImage: View where Content: View{ + + private let url: URL? + private let scale: CGFloat + private let transaction: Transaction + private let content: (AsyncImagePhase) -> Content + + init( + url: URL?, + scale: CGFloat = 1.0, + transaction: Transaction = Transaction(), + @ViewBuilder content: @escaping (AsyncImagePhase) -> Content + ){ + self.url = url + self.scale = scale + self.transaction = transaction + self.content = content + } + + var body: some View{ + if let url, let cached = ImageCache[url] { + content(.success(cached)) + } else { + AsyncImage( + url: url, + scale: scale, + transaction: transaction + ){ phase in + cacheAndRender(phase: phase) + } + } + } + func cacheAndRender(phase: AsyncImagePhase) -> some View{ + if case .success (let image) = phase, let url { + ImageCache[url] = image + } + return content(phase) + } +} +fileprivate class ImageCache{ + static private var cache: [URL: Image] = [:] + static subscript(url: URL) -> Image?{ + get{ + ImageCache.cache[url] + } + set{ + ImageCache.cache[url] = newValue + } + } +} diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift index 04ce1dc5d..1098135dd 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsView.swift @@ -112,7 +112,7 @@ struct NotificationsView: View { } label: { VStack { HStack(spacing: 10) { - AsyncImage(url: subscription.imageUrl) { phase in + CacheAsyncImage(url: subscription.imageUrl) { phase in if let image = phase.image { image .resizable() @@ -150,7 +150,7 @@ struct NotificationsView: View { private func listingRow(listing: ListingViewModel) -> some View { VStack { HStack(spacing: 10) { - AsyncImage(url: listing.imageUrl) { phase in + CacheAsyncImage(url: listing.imageUrl) { phase in if let image = phase.image { image .resizable() diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift index f6efdf897..b4e99af8f 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/SubscriptionView.swift @@ -36,7 +36,7 @@ struct SubscriptionView: View { private func notificationView(pushMessage: NotifyMessageViewModel) -> some View { VStack(alignment: .center) { HStack(spacing: 10) { - AsyncImage(url: URL(string: pushMessage.imageUrl)) { phase in + CacheAsyncImage(url: URL(string: pushMessage.imageUrl)) { phase in if let image = phase.image { image .resizable() @@ -76,7 +76,7 @@ struct SubscriptionView: View { func headerView() -> some View { VStack(spacing: 0) { - AsyncImage(url: presenter.subscriptionViewModel.imageUrl) { phase in + CacheAsyncImage(url: presenter.subscriptionViewModel.imageUrl) { phase in if let image = phase.image { image .resizable()