diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index b64cf6e6e..d4fb17fdf 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -62,6 +62,8 @@ A5629AE02876CC6E00094373 /* InviteListRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629ADB2876CC6E00094373 /* InviteListRouter.swift */; }; A5629AE12876CC6E00094373 /* InviteListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629ADC2876CC6E00094373 /* InviteListInteractor.swift */; }; A5629AE22876CC6E00094373 /* InviteListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629ADD2876CC6E00094373 /* InviteListView.swift */; }; + A5629AE42876E6D200094373 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629AE32876E6D200094373 /* ThreadViewModel.swift */; }; + A5629AE828772A0100094373 /* InviteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629AE728772A0100094373 /* InviteViewModel.swift */; }; A578FA322873036400AA7720 /* InputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A578FA312873036400AA7720 /* InputView.swift */; }; A578FA35287304A300AA7720 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = A578FA34287304A300AA7720 /* Color.swift */; }; A578FA372873D8EE00AA7720 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A578FA362873D8EE00AA7720 /* UIColor.swift */; }; @@ -197,6 +199,8 @@ A5629ADB2876CC6E00094373 /* InviteListRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteListRouter.swift; sourceTree = ""; }; A5629ADC2876CC6E00094373 /* InviteListInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteListInteractor.swift; sourceTree = ""; }; A5629ADD2876CC6E00094373 /* InviteListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteListView.swift; sourceTree = ""; }; + A5629AE32876E6D200094373 /* ThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadViewModel.swift; sourceTree = ""; }; + A5629AE728772A0100094373 /* InviteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteViewModel.swift; sourceTree = ""; }; A578FA312873036400AA7720 /* InputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputView.swift; sourceTree = ""; }; A578FA34287304A300AA7720 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; A578FA362873D8EE00AA7720 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; @@ -476,6 +480,7 @@ A5629AB72876CBA700094373 /* ChatList */ = { isa = PBXGroup; children = ( + A5629AE5287729EF00094373 /* Models */, A5629AB82876CBC000094373 /* ChatListModule.swift */, A5629AB92876CBC000094373 /* ChatListPresenter.swift */, A5629ABA2876CBC000094373 /* ChatListRouter.swift */, @@ -500,6 +505,7 @@ A5629AD82876CC5B00094373 /* InviteList */ = { isa = PBXGroup; children = ( + A5629AE6287729F800094373 /* Models */, A5629AD92876CC6E00094373 /* InviteListModule.swift */, A5629ADA2876CC6E00094373 /* InviteListPresenter.swift */, A5629ADB2876CC6E00094373 /* InviteListRouter.swift */, @@ -509,6 +515,22 @@ path = InviteList; sourceTree = ""; }; + A5629AE5287729EF00094373 /* Models */ = { + isa = PBXGroup; + children = ( + A5629AE32876E6D200094373 /* ThreadViewModel.swift */, + ); + path = Models; + sourceTree = ""; + }; + A5629AE6287729F800094373 /* Models */ = { + isa = PBXGroup; + children = ( + A5629AE728772A0100094373 /* InviteViewModel.swift */, + ); + path = Models; + sourceTree = ""; + }; A578FA332873049400AA7720 /* Style */ = { isa = PBXGroup; children = ( @@ -1045,12 +1067,14 @@ A578FA35287304A300AA7720 /* Color.swift in Sources */, A5629ADE2876CC6E00094373 /* InviteListModule.swift in Sources */, A578FA322873036400AA7720 /* InputView.swift in Sources */, + A5629AE42876E6D200094373 /* ThreadViewModel.swift in Sources */, A58E7D0C2872A45B0082D443 /* MainModule.swift in Sources */, A58E7D0D2872A45B0082D443 /* MainPresenter.swift in Sources */, A5629AD62876CC5700094373 /* InviteInteractor.swift in Sources */, A5629AE12876CC6E00094373 /* InviteListInteractor.swift in Sources */, A58E7CED28729F550082D443 /* SceneDelegate.swift in Sources */, A578FA372873D8EE00AA7720 /* UIColor.swift in Sources */, + A5629AE828772A0100094373 /* InviteViewModel.swift in Sources */, A5629AA92876A23100094373 /* ChatService.swift in Sources */, A5629AC02876CBC000094373 /* ChatListInteractor.swift in Sources */, A5629AE22876CC6E00094373 /* InviteListView.swift in Sources */, diff --git a/Example/Showcase/Classes/ApplicationLayer/Configurator/AppearanceConfigurator.swift b/Example/Showcase/Classes/ApplicationLayer/Configurator/AppearanceConfigurator.swift index ca41f7f63..a8ae4581b 100644 --- a/Example/Showcase/Classes/ApplicationLayer/Configurator/AppearanceConfigurator.swift +++ b/Example/Showcase/Classes/ApplicationLayer/Configurator/AppearanceConfigurator.swift @@ -3,6 +3,14 @@ import UIKit struct AppearanceConfigurator: Configurator { func configure() { + let appearance = UINavigationBarAppearance() + appearance.titleTextAttributes = [ + .foregroundColor: UIColor.w_foreground + ] + + UINavigationBar.appearance().standardAppearance = appearance + UINavigationBar.appearance().scrollEdgeAppearance = appearance + UINavigationBar.appearance().compactAppearance = appearance } } diff --git a/Example/Showcase/Classes/DomainLayer/ChatService.swift b/Example/Showcase/Classes/DomainLayer/ChatService.swift index b44028b3f..e3769e83f 100644 --- a/Example/Showcase/Classes/DomainLayer/ChatService.swift +++ b/Example/Showcase/Classes/DomainLayer/ChatService.swift @@ -1,10 +1,12 @@ import Foundation import Combine -typealias MessageStream = AsyncPublisher> +typealias Stream = AsyncPublisher> final class ChatService { + static let authorAccount: String = "TODO" + private let messagesSubject: CurrentValueSubject<[Message], Never> = { return CurrentValueSubject([ Message(message: "✨gm", authorAccount: "2", timestamp: 0), @@ -19,21 +21,49 @@ final class ChatService { ]) }() - func getMessages(topic: String) -> MessageStream { + private let threadsSubject: CurrentValueSubject<[Thread], Never> = { + return CurrentValueSubject([ + Thread(topic: "topic1"), + Thread(topic: "topic2") + ]) + }() + + private let invitesSubject: CurrentValueSubject<[Invite], Never> = { + return CurrentValueSubject([ + Invite(message: "In a few minutes, bitch.", pubKey: "slava.eth") + ]) + }() + + func getMessages(topic: String) -> Stream<[Message]> { return messagesSubject.eraseToAnyPublisher().values } - func getAuthorAccount() async -> String { - return "1" + func getThreads() -> Stream<[Thread]> { + return threadsSubject.eraseToAnyPublisher().values + } + + func getInvites() -> Stream<[Invite]> { + return invitesSubject.eraseToAnyPublisher().values } func sendMessage(text: String) async throws { - let authorAccount = await getAuthorAccount() let message = Message( message: text, - authorAccount: authorAccount, + authorAccount: ChatService.authorAccount, timestamp: Int64(Date().timeIntervalSince1970) ) messagesSubject.send(messagesSubject.value + [message]) } + + func accept(invite: Invite) async throws { + + } + + func reject(invite: Invite) async throws { + + } + + func invite(account: String) async throws { + + } } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/ChatInteractor.swift b/Example/Showcase/Classes/PresentationLayer/Chat/ChatInteractor.swift index 53fefeff4..045a6e2e9 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/ChatInteractor.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/ChatInteractor.swift @@ -6,11 +6,7 @@ final class ChatInteractor { self.chatService = chatService } - func getCurrentAccount() async -> String { - return await chatService.getAuthorAccount() - } - - func getMessages(topic: String) -> MessageStream { + func getMessages(topic: String) -> Stream<[Message]> { return chatService.getMessages(topic: topic) } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/ChatModule.swift b/Example/Showcase/Classes/PresentationLayer/Chat/ChatModule.swift index 59d1ddc0d..778c43367 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/ChatModule.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/ChatModule.swift @@ -3,10 +3,10 @@ import SwiftUI final class ChatModule { @discardableResult - static func create(app: Application) -> UIViewController { + static func create(topic: String, app: Application) -> UIViewController { let router = ChatRouter(app: app) let interactor = ChatInteractor(chatService: app.chatService) - let presenter = ChatPresenter(topic: "", interactor: interactor, router: router) + let presenter = ChatPresenter(topic: topic, interactor: interactor, router: router) let view = ChatView().environmentObject(presenter) let viewController = SceneViewController(viewModel: presenter, content: view) diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/ChatPresenter.swift b/Example/Showcase/Classes/PresentationLayer/Chat/ChatPresenter.swift index a0acb5919..dd4f43cd2 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/ChatPresenter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/ChatPresenter.swift @@ -19,12 +19,10 @@ final class ChatPresenter: ObservableObject { @MainActor func setupInitialState() async { - let account = await interactor.getCurrentAccount() - for await messages in interactor.getMessages(topic: topic) { self.messages = messages .sorted(by: { $0.timestamp < $1.timestamp }) - .map { MessageViewModel(message: $0, currentAccount: account) } + .map { MessageViewModel(message: $0, currentAccount: "TODO") } } } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/Models/MessageViewModel.swift b/Example/Showcase/Classes/PresentationLayer/Chat/Models/MessageViewModel.swift index 440e71326..64166ad49 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/Models/MessageViewModel.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/Models/MessageViewModel.swift @@ -1,6 +1,6 @@ import Foundation -// TODO: After Chat SDK integration +// TODO: Delete after Chat SDK integration struct Message: Codable, Equatable { let message: String let authorAccount: String diff --git a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListInteractor.swift b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListInteractor.swift index 0fbca8538..9cc9bf4c0 100644 --- a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListInteractor.swift +++ b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListInteractor.swift @@ -1,3 +1,12 @@ final class ChatListInteractor { + private let chatService: ChatService + + init(chatService: ChatService) { + self.chatService = chatService + } + + func getThreads() -> Stream<[Thread]> { + return chatService.getThreads() + } } diff --git a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListModule.swift b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListModule.swift index 4ff550953..f8cd25691 100644 --- a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListModule.swift +++ b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListModule.swift @@ -5,7 +5,7 @@ final class ChatListModule { @discardableResult static func create(app: Application) -> UIViewController { let router = ChatListRouter(app: app) - let interactor = ChatListInteractor() + let interactor = ChatListInteractor(chatService: app.chatService) let presenter = ChatListPresenter(interactor: interactor, router: router) let view = ChatListView().environmentObject(presenter) let viewController = SceneViewController(viewModel: presenter, content: view) diff --git a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListPresenter.swift b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListPresenter.swift index 65ad6b531..c8f4558ea 100644 --- a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListPresenter.swift +++ b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListPresenter.swift @@ -7,6 +7,8 @@ final class ChatListPresenter: ObservableObject { private let router: ChatListRouter private var disposeBag = Set() + @Published var threads: [ThreadViewModel] = [] + init(interactor: ChatListInteractor, router: ChatListRouter) { self.interactor = interactor self.router = router @@ -14,7 +16,15 @@ final class ChatListPresenter: ObservableObject { @MainActor func setupInitialState() async { + for await threads in interactor.getThreads() { + self.threads = threads + .sorted(by: { $0.topic < $1.topic }) + .map { ThreadViewModel(thread: $0) } + } + } + func didPressThread(_ thread: ThreadViewModel) { + router.presentChat(topic: thread.topic) } func didPressChatRequests() { diff --git a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListRouter.swift b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListRouter.swift index d0247aba1..ce8ba6cde 100644 --- a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListRouter.swift +++ b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListRouter.swift @@ -11,10 +11,16 @@ final class ChatListRouter { } func presentInvite() { - InviteModule.create(app: app).push(from: viewController) + InviteModule.create(app: app) + .wrapToNavigationController() + .present(from: viewController) } func presentInviteList() { InviteListModule.create(app: app).push(from: viewController) } + + func presentChat(topic: String) { + ChatModule.create(topic: topic, app: app).push(from: viewController) + } } diff --git a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListView.swift b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListView.swift index aa1235bdb..72d8bcf78 100644 --- a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListView.swift +++ b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListView.swift @@ -5,11 +5,55 @@ struct ChatListView: View { @EnvironmentObject var presenter: ChatListPresenter var body: some View { - VStack { - Spacer() + ScrollView { + VStack { + Button(action: { + presenter.didPressChatRequests() + }) { + HStack(spacing: 8.0) { + Text("1") + .frame(width: 24.0, height: 24.0) + .background(Color.w_greenForground) + .foregroundColor(.w_greenBackground) + .font(.system(size: 17.0, weight: .bold)) + .clipShape(Circle()) - Button("Chat Requests") { - presenter.didPressChatRequests() + Text("Chat Requests") + .foregroundColor(.w_greenForground) + .font(.system(size: 17.0, weight: .bold)) + } + } + .frame(height: 44.0) + .frame(maxWidth: .infinity) + .background(Color.w_greenBackground) + .clipShape(Capsule()) + .padding(16.0) + + ForEach(presenter.threads) { thread in + Button(action: { + presenter.didPressThread(thread) + }) { + HStack(spacing: 16.0) { + Image("avatar") + .resizable() + .frame(width: 64.0, height: 64.0) + + VStack(alignment: .leading) { + Text(thread.title) + .font(.title3) + .foregroundColor(.w_foreground) + .lineLimit(1) + + Text(thread.subtitle) + .font(.subheadline) + .foregroundColor(.w_secondaryForeground) + .multilineTextAlignment(.leading) + } + } + .frame(height: 64.0) + } + } + .padding(16.0) } } .task { diff --git a/Example/Showcase/Classes/PresentationLayer/ChatList/Models/ThreadViewModel.swift b/Example/Showcase/Classes/PresentationLayer/ChatList/Models/ThreadViewModel.swift new file mode 100644 index 000000000..0ee86de58 --- /dev/null +++ b/Example/Showcase/Classes/PresentationLayer/ChatList/Models/ThreadViewModel.swift @@ -0,0 +1,30 @@ +import Foundation + +// TODO: Delete after Chat SDK integration +struct Thread: Codable { + let topic: String +} + +struct ThreadViewModel: Identifiable { + private let thread: Thread + + init(thread: Thread) { + self.thread = thread + } + + var topic: String { + return thread.topic + } + + var id: String { + return thread.topic + } + + var title: String { + return thread.topic + } + + var subtitle: String { + return "Chicken, Peter, you’re just a little chicken. Cheep, cheep, cheep, cheep…" + } +} diff --git a/Example/Showcase/Classes/PresentationLayer/Invite/InviteInteractor.swift b/Example/Showcase/Classes/PresentationLayer/Invite/InviteInteractor.swift index f75fc1a7d..4b8537296 100644 --- a/Example/Showcase/Classes/PresentationLayer/Invite/InviteInteractor.swift +++ b/Example/Showcase/Classes/PresentationLayer/Invite/InviteInteractor.swift @@ -1,3 +1,11 @@ final class InviteInteractor { + private let chatService: ChatService + init(chatService: ChatService) { + self.chatService = chatService + } + + func invite(account: String) async { + try! await chatService.invite(account: account) + } } diff --git a/Example/Showcase/Classes/PresentationLayer/Invite/InviteModule.swift b/Example/Showcase/Classes/PresentationLayer/Invite/InviteModule.swift index 804f1913d..a405684d2 100644 --- a/Example/Showcase/Classes/PresentationLayer/Invite/InviteModule.swift +++ b/Example/Showcase/Classes/PresentationLayer/Invite/InviteModule.swift @@ -5,7 +5,7 @@ final class InviteModule { @discardableResult static func create(app: Application) -> UIViewController { let router = InviteRouter(app: app) - let interactor = InviteInteractor() + let interactor = InviteInteractor(chatService: app.chatService) let presenter = InvitePresenter(interactor: interactor, router: router) let view = InviteView().environmentObject(presenter) let viewController = SceneViewController(viewModel: presenter, content: view) diff --git a/Example/Showcase/Classes/PresentationLayer/Invite/InvitePresenter.swift b/Example/Showcase/Classes/PresentationLayer/Invite/InvitePresenter.swift index 0bb7d3e75..e9377d27d 100644 --- a/Example/Showcase/Classes/PresentationLayer/Invite/InvitePresenter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Invite/InvitePresenter.swift @@ -7,15 +7,38 @@ final class InvitePresenter: ObservableObject { private let router: InviteRouter private var disposeBag = Set() + @Published var input: String = .empty { + didSet { didInputChanged() } + } + + lazy var rightBarButtonItem: UIBarButtonItem? = { + let item = UIBarButtonItem( + title: "Invite", + style: .plain, + target: self, + action: #selector(invite) + ) + item.isEnabled = false + return item + }() + init(interactor: InviteInteractor, router: InviteRouter) { self.interactor = interactor self.router = router } + var isClearVisible: Bool { + return input.count > 0 + } + @MainActor func setupInitialState() async { } + + func didPressClear() { + input = .empty + } } // MARK: SceneViewModel @@ -35,4 +58,15 @@ extension InvitePresenter: SceneViewModel { private extension InvitePresenter { + @MainActor + @objc func invite() { + Task(priority: .userInitiated) { + await interactor.invite(account: input) + router.dismiss() + } + } + + func didInputChanged() { + rightBarButtonItem?.isEnabled = !input.isEmpty + } } diff --git a/Example/Showcase/Classes/PresentationLayer/Invite/InviteRouter.swift b/Example/Showcase/Classes/PresentationLayer/Invite/InviteRouter.swift index 956b67c28..5ef3c420b 100644 --- a/Example/Showcase/Classes/PresentationLayer/Invite/InviteRouter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Invite/InviteRouter.swift @@ -9,4 +9,8 @@ final class InviteRouter { init(app: Application) { self.app = app } + + func dismiss() { + viewController.dismiss() + } } diff --git a/Example/Showcase/Classes/PresentationLayer/Invite/InviteView.swift b/Example/Showcase/Classes/PresentationLayer/Invite/InviteView.swift index baa901287..9cb713434 100644 --- a/Example/Showcase/Classes/PresentationLayer/Invite/InviteView.swift +++ b/Example/Showcase/Classes/PresentationLayer/Invite/InviteView.swift @@ -5,10 +5,42 @@ struct InviteView: View { @EnvironmentObject var presenter: InvitePresenter var body: some View { - Text("Invite module") - .task { - await presenter.setupInitialState() + VStack { + VStack(alignment: .leading) { + Text("ENS Name or Public Key") + .font(.subheadline) + .foregroundColor(.w_secondaryForeground) + .padding(.horizontal, 16.0) + + HStack { + TextField("username.eth or 0x0…", text: $presenter.input) + .font(.body) + .foregroundColor(.w_foreground) + + if presenter.isClearVisible { + Button(action: { presenter.didPressClear() }, label: { + Image(systemName: "xmark.circle.fill") + .frame(width: 17.0, height: 17.0) + }) + } + } + .padding(.horizontal, 16.0) } + .frame(height: 72.0) + .background( + RoundedRectangle(cornerRadius: 14.0) + .foregroundColor(.w_secondaryBackground) + ) + .overlay( + RoundedRectangle(cornerRadius: 14.0) + .stroke(Color.w_tertiaryBackground, lineWidth: 0.5) + ) + .padding(16.0) + + Spacer() + }.task { + await presenter.setupInitialState() + } } } diff --git a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListInteractor.swift b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListInteractor.swift index 0c17b35c9..7706577b4 100644 --- a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListInteractor.swift +++ b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListInteractor.swift @@ -1,3 +1,19 @@ final class InviteListInteractor { + private let chatService: ChatService + init(chatService: ChatService) { + self.chatService = chatService + } + + func getInvites() -> Stream<[Invite]> { + return chatService.getInvites() + } + + func accept(invite: Invite) async { + try! await chatService.accept(invite: invite) + } + + func reject(invite: Invite) async { + try! await chatService.reject(invite: invite) + } } diff --git a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListModule.swift b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListModule.swift index a312b44b4..e34f4442c 100644 --- a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListModule.swift +++ b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListModule.swift @@ -5,7 +5,7 @@ final class InviteListModule { @discardableResult static func create(app: Application) -> UIViewController { let router = InviteListRouter(app: app) - let interactor = InviteListInteractor() + let interactor = InviteListInteractor(chatService: app.chatService) let presenter = InviteListPresenter(interactor: interactor, router: router) let view = InviteListView().environmentObject(presenter) let viewController = SceneViewController(viewModel: presenter, content: view) diff --git a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListPresenter.swift b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListPresenter.swift index e32b3a67f..ac88ff5ef 100644 --- a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListPresenter.swift +++ b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListPresenter.swift @@ -1,5 +1,6 @@ import UIKit import Combine +import Chat final class InviteListPresenter: ObservableObject { @@ -7,6 +8,8 @@ final class InviteListPresenter: ObservableObject { private let router: InviteListRouter private var disposeBag = Set() + @Published var invites: [InviteViewModel] = [] + init(interactor: InviteListInteractor, router: InviteListRouter) { self.interactor = interactor self.router = router @@ -14,7 +17,23 @@ final class InviteListPresenter: ObservableObject { @MainActor func setupInitialState() async { + for await invites in interactor.getInvites() { + self.invites = invites + .sorted(by: { $0.pubKey < $1.pubKey }) + .map { InviteViewModel(invite: $0) } + } + } + + func didPressAccept(invite: InviteViewModel) { + Task { + await interactor.accept(invite: invite.invite) + } + } + func didPressReject(invite: InviteViewModel) { + Task { + await interactor.reject(invite: invite.invite) + } } } diff --git a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListView.swift b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListView.swift index bb8aa5ecb..1a14aa497 100644 --- a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListView.swift +++ b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListView.swift @@ -5,10 +5,61 @@ struct InviteListView: View { @EnvironmentObject var presenter: InviteListPresenter var body: some View { - Text("InviteList module") - .task { - await presenter.setupInitialState() + ScrollView { + VStack { + Spacer() + .frame(height: 16.0) + + ForEach(presenter.invites, id: \.subtitle) { invite in + HStack(spacing: 16.0) { + Image("avatar") + .resizable() + .frame(width: 64.0, height: 64.0) + + VStack(alignment: .leading) { + Text(invite.title) + .font(.title3) + .foregroundColor(.w_foreground) + .lineLimit(1) + + Text(invite.subtitle) + .font(.subheadline) + .foregroundColor(.w_secondaryForeground) + .multilineTextAlignment(.leading) + } + + Spacer() + + HStack(spacing: 8.0) { + Button(action: { presenter.didPressAccept(invite: invite) }) { + Image("checkmark_icon") + .resizable() + .frame(width: 32, height: 32) + } + Button(action: { presenter.didPressReject(invite: invite) }) { + Image("cross_icon") + .resizable() + .frame(width: 32, height: 32) + } + } + .padding(4.0) + .background( + Capsule() + .foregroundColor(.w_secondaryBackground) + ) + .overlay( + Capsule() + .stroke(Color.w_tertiaryBackground, lineWidth: 0.5) + ) + } + .frame(height: 64.0) + } + .padding(16.0) } + } + .task { + await presenter.setupInitialState() + } } } diff --git a/Example/Showcase/Classes/PresentationLayer/InviteList/Models/InviteViewModel.swift b/Example/Showcase/Classes/PresentationLayer/InviteList/Models/InviteViewModel.swift new file mode 100644 index 000000000..0c4784a87 --- /dev/null +++ b/Example/Showcase/Classes/PresentationLayer/InviteList/Models/InviteViewModel.swift @@ -0,0 +1,23 @@ +import Foundation + +// TODO: Delete after Chat SDK integration +struct Invite { + let message: String + let pubKey: String +} + +struct InviteViewModel { + let invite: Invite + + init(invite: Invite) { + self.invite = invite + } + + var title: String { + return invite.pubKey + } + + var subtitle: String { + return invite.message + } +} diff --git a/Example/Showcase/Classes/PresentationLayer/Main/MainRouter.swift b/Example/Showcase/Classes/PresentationLayer/Main/MainRouter.swift index 703a3517d..089fdbdaf 100644 --- a/Example/Showcase/Classes/PresentationLayer/Main/MainRouter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Main/MainRouter.swift @@ -7,7 +7,7 @@ final class MainRouter { private let app: Application var chatViewController: UIViewController { - return ChatModule.create(app: app).wrapToNavigationController() + return ChatListModule.create(app: app).wrapToNavigationController() } init(app: Application) { diff --git a/Example/Showcase/Common/Style/Color.swift b/Example/Showcase/Common/Style/Color.swift index 1ab5dc61f..3e2ae042b 100644 --- a/Example/Showcase/Common/Style/Color.swift +++ b/Example/Showcase/Common/Style/Color.swift @@ -11,6 +11,9 @@ extension Color { static let w_purpleBackground: Color = Color(UIColor.w_purpleBackground) static let w_purpleForeground: Color = Color(UIColor.w_purpleForeground) + + static let w_greenBackground: Color = Color(UIColor.w_greenBackground) + static let w_greenForground: Color = Color(UIColor.w_greenForground) } extension UIColor { @@ -23,4 +26,7 @@ extension UIColor { static let w_purpleBackground: UIColor = UIColor(rgb: 0x794CFF) static let w_purpleForeground: UIColor = UIColor(rgb: 0x987DE8) + + static let w_greenBackground: UIColor = UIColor(rgb: 0x1B3229) + static let w_greenForground: UIColor = UIColor(rgb: 0x2BEE6C) } diff --git a/Example/Showcase/Common/VIPER/SceneViewController.swift b/Example/Showcase/Common/VIPER/SceneViewController.swift index 888e60b5d..7558ef776 100644 --- a/Example/Showcase/Common/VIPER/SceneViewController.swift +++ b/Example/Showcase/Common/VIPER/SceneViewController.swift @@ -1,7 +1,7 @@ import SwiftUI enum NavigationBarStyle { - case color(UIColor) + case translucent(UIColor) } protocol SceneViewModel { @@ -11,6 +11,8 @@ protocol SceneViewModel { var rightBarButtonItem: UIBarButtonItem? { get } var navigationBarStyle: NavigationBarStyle { get } var preferredStatusBarStyle: UIStatusBarStyle { get } + var isNavigationBarTranslucent: Bool { get } + } extension SceneViewModel { @@ -27,11 +29,14 @@ extension SceneViewModel { return .none } var navigationBarStyle: NavigationBarStyle { - return .color(.w_background) + return .translucent(.w_background) } var preferredStatusBarStyle: UIStatusBarStyle { return .default } + var isNavigationBarTranslucent: Bool { + return true + } } class SceneViewController: UIHostingController { @@ -77,8 +82,9 @@ private extension SceneViewController { func setupNavigationBarStyle() { switch viewModel.navigationBarStyle { - case .color(let color): + case .translucent(let color): navigationController?.navigationBar.barTintColor = color + navigationController?.navigationBar.isTranslucent = true } } } diff --git a/Example/Showcase/Other/Assets.xcassets/AccentColor.colorset/Contents.json b/Example/Showcase/Other/Assets.xcassets/AccentColor.colorset/Contents.json index eb8789700..4208acb0e 100644 --- a/Example/Showcase/Other/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/Example/Showcase/Other/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,6 +1,15 @@ { "colors" : [ { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.424", + "green" : "0.933", + "red" : "0.169" + } + }, "idiom" : "universal" } ], diff --git a/Example/Showcase/Other/Assets.xcassets/FAB.imageset/Contents.json b/Example/Showcase/Other/Assets.xcassets/FAB.imageset/Contents.json deleted file mode 100644 index 6061e729b..000000000 --- a/Example/Showcase/Other/Assets.xcassets/FAB.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "FAB.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Example/Showcase/Other/Assets.xcassets/FAB.imageset/FAB.svg b/Example/Showcase/Other/Assets.xcassets/FAB.imageset/FAB.svg deleted file mode 100644 index 4e569421c..000000000 --- a/Example/Showcase/Other/Assets.xcassets/FAB.imageset/FAB.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/Example/Showcase/Other/Assets.xcassets/checkmark_icon.imageset/Contents.json b/Example/Showcase/Other/Assets.xcassets/checkmark_icon.imageset/Contents.json new file mode 100644 index 000000000..1b2738498 --- /dev/null +++ b/Example/Showcase/Other/Assets.xcassets/checkmark_icon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Icon.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/Showcase/Other/Assets.xcassets/checkmark_icon.imageset/Icon.png b/Example/Showcase/Other/Assets.xcassets/checkmark_icon.imageset/Icon.png new file mode 100644 index 000000000..4b3aacc03 Binary files /dev/null and b/Example/Showcase/Other/Assets.xcassets/checkmark_icon.imageset/Icon.png differ diff --git a/Example/Showcase/Other/Assets.xcassets/cross_icon.imageset/Contents.json b/Example/Showcase/Other/Assets.xcassets/cross_icon.imageset/Contents.json new file mode 100644 index 000000000..f1046f53f --- /dev/null +++ b/Example/Showcase/Other/Assets.xcassets/cross_icon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Icon-1.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/Showcase/Other/Assets.xcassets/cross_icon.imageset/Icon-1.png b/Example/Showcase/Other/Assets.xcassets/cross_icon.imageset/Icon-1.png new file mode 100644 index 000000000..9abe6037e Binary files /dev/null and b/Example/Showcase/Other/Assets.xcassets/cross_icon.imageset/Icon-1.png differ diff --git a/Example/Showcase/Other/Assets.xcassets/plus_icon.imageset/Contents.json b/Example/Showcase/Other/Assets.xcassets/plus_icon.imageset/Contents.json new file mode 100644 index 000000000..9b4a9e400 --- /dev/null +++ b/Example/Showcase/Other/Assets.xcassets/plus_icon.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "FAB.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/Example/Showcase/Other/Assets.xcassets/plus_icon.imageset/FAB.png b/Example/Showcase/Other/Assets.xcassets/plus_icon.imageset/FAB.png new file mode 100644 index 000000000..48069ba5b Binary files /dev/null and b/Example/Showcase/Other/Assets.xcassets/plus_icon.imageset/FAB.png differ diff --git a/Sources/Chat/Chat.swift b/Sources/Chat/Chat.swift index f6495eac1..e5a8f81d4 100644 --- a/Sources/Chat/Chat.swift +++ b/Sources/Chat/Chat.swift @@ -141,4 +141,3 @@ class Chat { } } } - diff --git a/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift b/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift index 5ceba106c..f15f8575f 100644 --- a/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift +++ b/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift @@ -87,7 +87,7 @@ class InvitationHandlingService { } private func getInviteResponseTopic(_ payload: RequestSubscriptionPayload, _ invite: Invite) throws -> String { - //todo - remove topicToInvitationPubKeyStore ? + // todo - remove topicToInvitationPubKeyStore ? guard let selfPubKeyHex = try? topicToInvitationPubKeyStore.get(key: payload.topic) else { logger.debug("PubKey for invitation topic not found") diff --git a/Sources/Chat/Types/Message.swift b/Sources/Chat/Types/Message.swift index ba732b6a2..9df268964 100644 --- a/Sources/Chat/Types/Message.swift +++ b/Sources/Chat/Types/Message.swift @@ -1,7 +1,7 @@ import Foundation struct Message: Codable, Equatable { - let message : String + let message: String let authorAccount: String let timestamp: Int64 } diff --git a/Sources/WalletConnectKMS/Serialiser/Serializer.swift b/Sources/WalletConnectKMS/Serialiser/Serializer.swift index 2fedb74f8..87c87198c 100644 --- a/Sources/WalletConnectKMS/Serialiser/Serializer.swift +++ b/Sources/WalletConnectKMS/Serialiser/Serializer.swift @@ -68,7 +68,7 @@ public class Serializer: Serializing { guard let selfPubKey = kms.getPublicKey(for: topic), case let .type1(peerPubKey) = envelope.type else { return nil } do { - //self pub key is good + // self pub key is good let agreementKeys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPubKey.toHexString()) let decodedType: T? = try decode(sealbox: envelope.sealbox, symmetricKey: agreementKeys.sharedKey.rawRepresentation) let newTopic = agreementKeys.derivedTopic()