Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Push messages screen #710

Merged
merged 12 commits into from
Feb 16, 2023
40 changes: 40 additions & 0 deletions Example/ExampleApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@
84B7677D2954554A00E92316 /* PushDecryptionService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 84B767762954554A00E92316 /* PushDecryptionService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
84B76789295494D500E92316 /* WalletConnectPush in Frameworks */ = {isa = PBXBuildFile; productRef = 84B76788295494D500E92316 /* WalletConnectPush */; };
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 */; };
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 */; };
Expand Down Expand Up @@ -398,6 +404,12 @@
84B767782954554A00E92316 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
84B7677A2954554A00E92316 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
84B8154D2991099000FAD54E /* BuildConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildConfiguration.swift; sourceTree = "<group>"; };
84B8154F2991217900FAD54E /* PushMessagesModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessagesModule.swift; sourceTree = "<group>"; };
84B815502991217900FAD54E /* PushMessagesPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessagesPresenter.swift; sourceTree = "<group>"; };
84B815512991217900FAD54E /* PushMessagesRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessagesRouter.swift; sourceTree = "<group>"; };
84B815522991217900FAD54E /* PushMessagesInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessagesInteractor.swift; sourceTree = "<group>"; };
84B815532991217900FAD54E /* PushMessagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessagesView.swift; sourceTree = "<group>"; };
84B8155A2992A18D00FAD54E /* PushMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessageViewModel.swift; sourceTree = "<group>"; };
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 = "<group>"; };
84CE642027981DED00142511 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -887,6 +899,27 @@
path = PushDecryptionService;
sourceTree = "<group>";
};
84B815592991217F00FAD54E /* PushMessages */ = {
isa = PBXGroup;
children = (
84B8154F2991217900FAD54E /* PushMessagesModule.swift */,
84B815502991217900FAD54E /* PushMessagesPresenter.swift */,
84B815512991217900FAD54E /* PushMessagesRouter.swift */,
84B815522991217900FAD54E /* PushMessagesInteractor.swift */,
84B815532991217900FAD54E /* PushMessagesView.swift */,
84B8155C2992A19200FAD54E /* Models */,
);
path = PushMessages;
sourceTree = "<group>";
};
84B8155C2992A19200FAD54E /* Models */ = {
isa = PBXGroup;
children = (
84B8155A2992A18D00FAD54E /* PushMessageViewModel.swift */,
);
path = Models;
sourceTree = "<group>";
};
84CE641D27981DED00142511 /* DApp */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1542,6 +1575,7 @@
C56EE22A293F5668004840D1 /* Wallet */,
84E6B8592981625A00428BAF /* PushRequest */,
847BD1E9298A807000076C90 /* Notifications */,
84B815592991217F00FAD54E /* PushMessages */,
);
path = Wallet;
sourceTree = "<group>";
Expand Down Expand Up @@ -2259,8 +2293,10 @@
C56EE240293F566D004840D1 /* ScanQRView.swift in Sources */,
C56EE250293F566D004840D1 /* ScanTargetView.swift in Sources */,
C56EE28F293F5757004840D1 /* MigrationConfigurator.swift in Sources */,
84B815552991217900FAD54E /* PushMessagesPresenter.swift in Sources */,
847BD1DD2989494F00076C90 /* TabPage.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 */,
Expand All @@ -2272,6 +2308,7 @@
C5F32A342954817600A6476E /* ConnectionDetailsView.swift in Sources */,
C55D348A295DD8CA0004314A /* PasteUriPresenter.swift in Sources */,
C56EE28E293F5757004840D1 /* ApplicationConfigurator.swift in Sources */,
84B8155B2992A18D00FAD54E /* PushMessageViewModel.swift in Sources */,
C55D347F295DD7140004314A /* AuthRequestModule.swift in Sources */,
C56EE242293F566D004840D1 /* ScanPresenter.swift in Sources */,
C56EE28B293F5757004840D1 /* SceneDelegate.swift in Sources */,
Expand All @@ -2290,18 +2327,21 @@
847BD1D62989492500076C90 /* MainViewController.swift in Sources */,
C5B2F6FA29705293000DBA0E /* SessionRequestInteractor.swift in Sources */,
C55D34AE2965FB750004314A /* SessionProposalModule.swift in Sources */,
84B815582991217900FAD54E /* PushMessagesView.swift in Sources */,
C55D34B02965FB750004314A /* SessionProposalRouter.swift in Sources */,
C55D3495295DFA750004314A /* WelcomeRouter.swift in Sources */,
C5B2F6F729705293000DBA0E /* SessionRequestRouter.swift in Sources */,
C56EE24F293F566D004840D1 /* WalletView.swift in Sources */,
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 */,
C5F32A322954816C00A6476E /* ConnectionDetailsPresenter.swift in Sources */,
84B815562991217900FAD54E /* PushMessagesRouter.swift in Sources */,
C56EE246293F566D004840D1 /* ScanRouter.swift in Sources */,
C55D349D2965F8D40004314A /* Proposal.swift in Sources */,
C55D3481295DD7140004314A /* AuthRequestRouter.swift in Sources */,
Expand Down
8 changes: 4 additions & 4 deletions Example/IntegrationTests/Push/PushTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,10 @@ final class PushTests: XCTestCase {
Task(priority: .userInitiated) { try! await dappPushClient.notify(topic: subscription.topic, message: pushMessage) }
}.store(in: &publishers)

walletPushClient.pushMessagePublisher.sink { [unowned self] receivedPushMessage in
walletPushClient.pushMessagePublisher.sink { [unowned self] receivedPushMessageRecord in
let messageHistory = walletPushClient.getMessageHistory(topic: pushSubscription.topic)
XCTAssertEqual(pushMessage, receivedPushMessage)
XCTAssertTrue(messageHistory.contains(receivedPushMessage))
XCTAssertEqual(pushMessage, receivedPushMessageRecord.message)
XCTAssertTrue(messageHistory.contains(receivedPushMessageRecord))
expectation.fulfill()
}.store(in: &publishers)

Expand All @@ -194,7 +194,7 @@ final class PushTests: XCTestCase {
return
}
subscriptionTopic = subscription.topic
Task(priority: .userInitiated) { try! await walletPushClient.delete(topic: subscription.topic)}
Task(priority: .userInitiated) { try! await walletPushClient.deleteSubscription(topic: subscription.topic)}
}.store(in: &publishers)

dappPushClient.deleteSubscriptionPublisher.sink { topic in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ final class NotificationsInteractor {
}

func getSubscriptions() -> [PushSubscription] {
Push.wallet.getActiveSubscriptions()
let subs = Push.wallet.getActiveSubscriptions()
return subs
}

func removeSubscription(_ subscription: PushSubscription) async {
do {
try await Push.wallet.delete(topic: subscription.topic)
try await Push.wallet.deleteSubscription(topic: subscription.topic)
} catch {
print(error)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ private extension NotificationsPresenter {

func setupSubscriptions() {
self.subscriptions = interactor.getSubscriptions()
.map { SubscriptionsViewModel(subscription: $0) }
.map {
return SubscriptionsViewModel(subscription: $0)
}
interactor.subscriptionsPublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] pushSubscriptions in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ final class NotificationsRouter {
}

func presentNotifications(subscription: WalletConnectPush.PushSubscription) {

PushMessagesModule.create(app: app, subscription: subscription)
.push(from: viewController)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ struct NotificationsView: View {
VStack {
if !presenter.subscriptions.isEmpty {
List {
ForEach(presenter.subscriptions, id: \.title) { subscription in
ForEach(presenter.subscriptions, id: \.id) { subscription in
subscriptionsView(subscription: subscription)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 16, trailing: 0))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

import Foundation
import WalletConnectPush

struct PushMessageViewModel: Identifiable {

let pushMessageRecord: WalletConnectPush.PushMessageRecord

var id: String {
return pushMessageRecord.id
}

var imageUrl: String {
return pushMessageRecord.message.icon
}

var title: String {
return pushMessageRecord.message.title
}

var subtitle: String {
return pushMessageRecord.message.body
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import WalletConnectPush
import Combine

final class PushMessagesInteractor {

let subscription: PushSubscription

init(subscription: PushSubscription) {
self.subscription = subscription
}

func getPushMessages() -> [PushMessageRecord] {
return Push.wallet.getMessageHistory(topic: subscription.topic)
}

func deletePushMessage(id: String) {
Push.wallet.deletePushMessage(id: id)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import SwiftUI
import WalletConnectPush

final class PushMessagesModule {

@discardableResult
static func create(app: Application, subscription: PushSubscription) -> UIViewController {
let router = PushMessagesRouter(app: app)
let interactor = PushMessagesInteractor(subscription: subscription)
let presenter = PushMessagesPresenter(interactor: interactor, router: router)
let view = PushMessagesView().environmentObject(presenter)
let viewController = SceneViewController(viewModel: presenter, content: view)

router.viewController = viewController

return viewController
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import UIKit
import Combine
import WalletConnectPush

final class PushMessagesPresenter: ObservableObject {

private let interactor: PushMessagesInteractor
private let router: PushMessagesRouter
private var disposeBag = Set<AnyCancellable>()
@Published var pushMessages: [PushMessageViewModel] = []

init(interactor: PushMessagesInteractor, router: PushMessagesRouter) {
defer { reloadPushMessages() }
self.interactor = interactor
self.router = router
}

func deletePushMessage(at indexSet: IndexSet) {
if let index = indexSet.first {
interactor.deletePushMessage(id: pushMessages[index].id)
}
reloadPushMessages()
}
}

// MARK: SceneViewModel

extension PushMessagesPresenter: SceneViewModel {
var sceneTitle: String? {
return interactor.subscription.metadata.name
}

var largeTitleDisplayMode: UINavigationItem.LargeTitleDisplayMode {
return .always
}
}

// MARK: Privates

private extension PushMessagesPresenter {

func reloadPushMessages() {
self.pushMessages = interactor.getPushMessages().map({ pushMessageRecord in
PushMessageViewModel(pushMessageRecord: pushMessageRecord)
})
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import UIKit

final class PushMessagesRouter {

weak var viewController: UIViewController!

private let app: Application

init(app: Application) {
self.app = app
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import SwiftUI

struct PushMessagesView: View {

@EnvironmentObject var presenter: PushMessagesPresenter

var body: some View {
ZStack {
Color.grey100
.edgesIgnoringSafeArea(.all)

VStack(alignment: .leading, spacing: 16) {
ZStack {
if presenter.pushMessages.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 {
if !presenter.pushMessages.isEmpty {
List {
ForEach(presenter.pushMessages, 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())
}
}
}
}
.padding(.vertical, 20)
}
}



private func notificationView(pushMessage: PushMessageViewModel) -> some View {
VStack {
HStack(spacing: 10) {
AsyncImage(url: URL(string: pushMessage.imageUrl)) { phase in
if let image = phase.image {
image
.resizable()
.frame(width: 60, height: 60)
.background(Color.black)
.cornerRadius(30, corners: .allCorners)
} else {
Color.black
.frame(width: 60, height: 60)
.cornerRadius(30, corners: .allCorners)
}
}
.padding(.leading, 20)

VStack(alignment: .leading, spacing: 2) {
Text(pushMessage.title)
.foregroundColor(.grey8)
.font(.system(size: 20, weight: .semibold, design: .rounded))

Text(pushMessage.subtitle)
.foregroundColor(.grey50)
.font(.system(size: 13, weight: .medium, design: .rounded))
}
}
}
}
}

#if DEBUG
struct PushMessagesView_Previews: PreviewProvider {
static var previews: some View {
PushMessagesView()
}
}
#endif
Loading