From 9982193f42d251c531ca37028197fadfd93e0700 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 7 Jul 2022 21:40:07 +0700 Subject: [PATCH 1/3] ChatList screen --- Example/ExampleApp.xcodeproj/project.pbxproj | 4 ++ .../Configurator/AppearanceConfigurator.swift | 2 +- .../Classes/DomainLayer/ChatService.swift | 20 +++++-- .../Chat/ChatInteractor.swift | 6 +- .../PresentationLayer/Chat/ChatModule.swift | 4 +- .../Chat/ChatPresenter.swift | 4 +- .../Chat/Models/MessageViewModel.swift | 2 +- .../Chat/Models/ThreadViewModel.swift | 30 ++++++++++ .../ChatList/ChatListInteractor.swift | 9 +++ .../ChatList/ChatListModule.swift | 2 +- .../ChatList/ChatListPresenter.swift | 10 ++++ .../ChatList/ChatListRouter.swift | 4 ++ .../ChatList/ChatListView.swift | 52 ++++++++++++++++-- .../PresentationLayer/Main/MainRouter.swift | 2 +- Example/Showcase/Common/Style/Color.swift | 6 ++ .../FAB.imageset/Contents.json | 12 ---- .../Assets.xcassets/FAB.imageset/FAB.svg | 5 -- .../plus_icon.imageset/Contents.json | 24 ++++++++ .../plus_icon.imageset/FAB.png | Bin 0 -> 3541 bytes 19 files changed, 157 insertions(+), 41 deletions(-) create mode 100644 Example/Showcase/Classes/PresentationLayer/Chat/Models/ThreadViewModel.swift delete mode 100644 Example/Showcase/Other/Assets.xcassets/FAB.imageset/Contents.json delete mode 100644 Example/Showcase/Other/Assets.xcassets/FAB.imageset/FAB.svg create mode 100644 Example/Showcase/Other/Assets.xcassets/plus_icon.imageset/Contents.json create mode 100644 Example/Showcase/Other/Assets.xcassets/plus_icon.imageset/FAB.png diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 537a8fd13..c5fdbfdfb 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -61,6 +61,7 @@ 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 */; }; 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 */; }; @@ -195,6 +196,7 @@ 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 = ""; }; 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 = ""; }; @@ -674,6 +676,7 @@ isa = PBXGroup; children = ( A58E7D472872EF610082D443 /* MessageViewModel.swift */, + A5629AE32876E6D200094373 /* ThreadViewModel.swift */, ); path = Models; sourceTree = ""; @@ -1042,6 +1045,7 @@ 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 */, diff --git a/Example/Showcase/Classes/ApplicationLayer/Configurator/AppearanceConfigurator.swift b/Example/Showcase/Classes/ApplicationLayer/Configurator/AppearanceConfigurator.swift index ca41f7f63..15fe7fecb 100644 --- a/Example/Showcase/Classes/ApplicationLayer/Configurator/AppearanceConfigurator.swift +++ b/Example/Showcase/Classes/ApplicationLayer/Configurator/AppearanceConfigurator.swift @@ -3,6 +3,6 @@ import UIKit struct AppearanceConfigurator: Configurator { func configure() { - + } } diff --git a/Example/Showcase/Classes/DomainLayer/ChatService.swift b/Example/Showcase/Classes/DomainLayer/ChatService.swift index b44028b3f..43f1bb0b8 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,19 +21,25 @@ final class ChatService { ]) }() - func getMessages(topic: String) -> MessageStream { + private let threadsSubject: CurrentValueSubject<[Thread], Never> = { + return CurrentValueSubject([ + Thread(topic: "topic1"), + Thread(topic: "topic2") + ]) + }() + + 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 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]) 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/Chat/Models/ThreadViewModel.swift b/Example/Showcase/Classes/PresentationLayer/Chat/Models/ThreadViewModel.swift new file mode 100644 index 000000000..0ee86de58 --- /dev/null +++ b/Example/Showcase/Classes/PresentationLayer/Chat/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/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..429927719 100644 --- a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListRouter.swift +++ b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListRouter.swift @@ -17,4 +17,8 @@ final class ChatListRouter { 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/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/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/plus_icon.imageset/Contents.json b/Example/Showcase/Other/Assets.xcassets/plus_icon.imageset/Contents.json new file mode 100644 index 000000000..f303aab0a --- /dev/null +++ b/Example/Showcase/Other/Assets.xcassets/plus_icon.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "FAB.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "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 0000000000000000000000000000000000000000..48069ba5bb05c79c2be312817b14031ee282af05 GIT binary patch literal 3541 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 P00000NkvXXu0mjfy|mjy literal 0 HcmV?d00001 From 935d923683c4f6a6d43553a2260388a4191daa77 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 7 Jul 2022 22:11:11 +0700 Subject: [PATCH 2/3] InvitesList screen --- Example/ExampleApp.xcodeproj/project.pbxproj | 22 ++++++- .../Configurator/AppearanceConfigurator.swift | 10 ++- .../Classes/DomainLayer/ChatService.swift | 18 ++++++ .../Models/ThreadViewModel.swift | 0 .../InviteList/InviteListInteractor.swift | 17 ++++++ .../InviteList/InviteListModule.swift | 2 +- .../InviteList/InviteListPresenter.swift | 19 ++++++ .../InviteList/InviteListView.swift | 57 +++++++++++++++++- .../InviteList/Models/InviteViewModel.swift | 23 +++++++ .../AccentColor.colorset/Contents.json | 9 +++ .../checkmark_icon.imageset/Contents.json | 21 +++++++ .../checkmark_icon.imageset/Icon.png | Bin 0 -> 1941 bytes .../cross_icon.imageset/Contents.json | 21 +++++++ .../cross_icon.imageset/Icon-1.png | Bin 0 -> 1718 bytes .../plus_icon.imageset/Contents.json | 2 +- 15 files changed, 214 insertions(+), 7 deletions(-) rename Example/Showcase/Classes/PresentationLayer/{Chat => ChatList}/Models/ThreadViewModel.swift (100%) create mode 100644 Example/Showcase/Classes/PresentationLayer/InviteList/Models/InviteViewModel.swift create mode 100644 Example/Showcase/Other/Assets.xcassets/checkmark_icon.imageset/Contents.json create mode 100644 Example/Showcase/Other/Assets.xcassets/checkmark_icon.imageset/Icon.png create mode 100644 Example/Showcase/Other/Assets.xcassets/cross_icon.imageset/Contents.json create mode 100644 Example/Showcase/Other/Assets.xcassets/cross_icon.imageset/Icon-1.png diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index c5fdbfdfb..bc8d8d60b 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -62,6 +62,7 @@ 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 +198,7 @@ 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 +478,7 @@ A5629AB72876CBA700094373 /* ChatList */ = { isa = PBXGroup; children = ( + A5629AE5287729EF00094373 /* Models */, A5629AB82876CBC000094373 /* ChatListModule.swift */, A5629AB92876CBC000094373 /* ChatListPresenter.swift */, A5629ABA2876CBC000094373 /* ChatListRouter.swift */, @@ -500,6 +503,7 @@ A5629AD82876CC5B00094373 /* InviteList */ = { isa = PBXGroup; children = ( + A5629AE6287729F800094373 /* Models */, A5629AD92876CC6E00094373 /* InviteListModule.swift */, A5629ADA2876CC6E00094373 /* InviteListPresenter.swift */, A5629ADB2876CC6E00094373 /* InviteListRouter.swift */, @@ -509,6 +513,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 = ( @@ -676,7 +696,6 @@ isa = PBXGroup; children = ( A58E7D472872EF610082D443 /* MessageViewModel.swift */, - A5629AE32876E6D200094373 /* ThreadViewModel.swift */, ); path = Models; sourceTree = ""; @@ -1052,6 +1071,7 @@ 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 15fe7fecb..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 43f1bb0b8..bf83ddd7f 100644 --- a/Example/Showcase/Classes/DomainLayer/ChatService.swift +++ b/Example/Showcase/Classes/DomainLayer/ChatService.swift @@ -28,6 +28,12 @@ final class ChatService { ]) }() + 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 } @@ -36,6 +42,10 @@ final class ChatService { return threadsSubject.eraseToAnyPublisher().values } + func getInvites() -> Stream<[Invite]> { + return invitesSubject.eraseToAnyPublisher().values + } + func sendMessage(text: String) async throws { let message = Message( message: text, @@ -44,4 +54,12 @@ final class ChatService { ) messagesSubject.send(messagesSubject.value + [message]) } + + func accept(invite: Invite) async throws { + + } + + func reject(invite: Invite) async throws { + + } } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/Models/ThreadViewModel.swift b/Example/Showcase/Classes/PresentationLayer/ChatList/Models/ThreadViewModel.swift similarity index 100% rename from Example/Showcase/Classes/PresentationLayer/Chat/Models/ThreadViewModel.swift rename to Example/Showcase/Classes/PresentationLayer/ChatList/Models/ThreadViewModel.swift diff --git a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListInteractor.swift b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListInteractor.swift index 0c17b35c9..9cd0450d8 100644 --- a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListInteractor.swift +++ b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListInteractor.swift @@ -1,3 +1,20 @@ 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..51495bd24 --- /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/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/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 0000000000000000000000000000000000000000..4b3aacc0344e5a05ee92c2cef6b6c75bafa2b3da GIT binary patch literal 1941 zcmV;G2Wt3F6KXU&zE=yzC?yvjx>#twW05}{Dhr{7;I2;a#!{Kl^ z9FAN-r~sG$*l1#H)DlQd0&F1Q_wgI@`}iwiKmG#rI|3bs5%$ji;v>w#Ez3;aBP%=s*KuU>K_h$s zL>o{d2%%F4f2X&Q@5G&}0CBkSQ1<#s_EI{qk0GtS{ARBUIUyh?mIrsYJP)3gxA7U7 z=E}cuX|VnPa>DuuC{3(a1)*Cz^4Dz74ORshH-1?1UY`W^YahHDHbt-|K=m8{7`q}^ z5nz5A{}_8B7#Cpv8vhtOA{Z4Q&hftvU(8?QA0tIT@0!WAg3&ZI_$t_*-^ORC89=ZF z1~DeURnDCee}Z|3aP`XDJH}S=Mncde*MALXhr`+j|C)*Uz{m&~Pr7G{MsPk}TxKS- z084M~uFiMdpMl)BFcFF}g`oM*dQv&8p}&|VE6OwiMsf%13^zX07)GN^qac$2Br^i2 z5Ja%^9{q*=3ws#Ko-1E?3>iU$d#%BS_D-I52;$g}f{aW=xgdAG_7`^7PQJW6SbqkT z&8dxu?*Df2=WWnHdjz0>+&uHFkX*rr;B8KQFT~8FKnT1EnrI2I^ylqXm0~|qwDW3V zdd}_vWw#aN2)ZhF`#=M|5#Whcs0iHq_Y=ZHgtP(%hy-YTj+OviLIM>bLhr_`_jr%m zlzpPxCdvY^?NF8QBeR~(S)qp+7YG>pZOzLAZW}d}1sK-CHdFu(J^u=SX6T8{L&F_O zgu^x{qAUPn)P^b$g`52uD^woEa&&aSG$>l2i259Wlgi{WBXjLed zK@puvSXt#fNy_;d8=>bDnxKf10Fxzmeg(7|B<1{Z0zqK2B(kAuODG9242Df8F=;vf z=-g<_b_Mp4;^!*~AW)RL_b282>|M#mH?rtT!XJ&AD%a;$8=Ar)R~$eFl5&0yJ!SQ- zlmw8oyzvn*v4|Jr z5p&LeX?JSQA8(l=~?= zGAcj>^{Jm1kAzWpE9J*ibN=`wPRV^MH34dMqXJ+Q%*;v-zJ1t}L+(B#{d{W9pZ_ta zk?;3au2&Ku$ywAvrnUFR?&@?nr$;$SPkS#d=P#;ONwtkM($H+_;{Ty$T7i=<{EpnY z?wx!+Dd)FrHzd(hyk1=ZIS)JslgMm`&yUS|n`>_5q|Ecd$MBby^ILH`PXXU50TfXd zKxn9(ij37sRc74S7(zTLr{??xmPtzCyP$}&02EReEFciKk~X&EqoX`2C;0p_^b}es zhC;nX<{1G}~TKL2fONOu49B1VyyGAUV`B*V4?O!68Ja(v0l} zD$pvI7D^X1&=NpI>VD1Xq2>_OO25Z;L$QZLaHLn}s9l<9F$~42%IQ$H6^DTL6EY3L3R0K7(+3Tn0IvD{t0U%5W}C z`cc}QQCoocPy*3YuuB7d@7HN|1-~W)8a=V7g>+-HSTvbV$Z#c?N23>DJ^R2u$^5_m zt{xBdqc2CTvfTEDH<1bxfVuj1y(1^g%9N^`ge{SSi+df&K;}JRo(ssRL01jZERoZa zSpc33L`Z9J)=02KR4`H%!9I=cw6sicAwg2#PF@C=wwv;KpIp6$27+pJ9cxZG##CT;K&j%Dmb2lmT|75!H8Ef~9 zTY7VQ6@{qIIpPH3#y2$Sp2S*hDF4Bekx=D1;m~H|GuH1L8k=d-&%uh_QzFjjFuY|W z?0ky#>S!*oinAjGtN(~$y8JiWZhR)!dq-EnHX_oT?) zK8?}|a1vfectSKogqA?~aa??b&FNQDpPwnlMY5j!8U$YVRS6$m<#0G04u`|xa5x+e b$JymSFL5v4yO$JC00000NkvXXu0mjfwm5v3 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9abe6037eb01eba8f131aced2428694203036b46 GIT binary patch literal 1718 zcmV;n21)seP) z+cp%(AE=LbshlA63F<65W2=kM6D!Rw>IovBz|9FFpCHx=BA+1X&QoVjmrA^y9D$<{R+42Dp0N-2Oec$KvwV9(I# z5#D73^oa4j*C@;{Fs=_!kQhovX$0U=)PVZv2sn@Mwj!_sAKoRkFQq+M@-{Khqn6$ zs{%}oU-nv`MT|q%eVZa!6JQODKf|gBRs>ihsVj=X z5SkVsJKc>!^mXrn0Am4otJzs|zrO$$QMe)_7qkJRo@EGEv~4mfGHU@Mg9R*v&7}d= zkx?r!6M*v$ki%oJ!*F_S+IT;MBk2h=(H6jh@9$xAe$3AQ*oI27jfwPQpra>U1zww- zUp~f)^9Ja^R&dt=O|<&}-rBpN6M*&E`R|9{e8xeui+5-in@1tpr^~LDYyWq(0gAg1 z8fdiu9yNDJYd^m~o%gadoJmVqV0=k6=>r)!A`;m?4fI@41v)?pyI<&+wMED>z71H= z1`YHC2>2_Ygx4}}URi`Z#=j3Dphe3j%6)*PumiS$@jg5M^E#_gF29|__|`(^>6{=| zd5d}rs09(wgTuJm1#f-7lT~PGODNL#f?yPZA{s78EFH9hnFvK2Ur>P^)wk&M0Z+gl zdLop;`0~c!87QLO2OKoNI9S7(zfXXvAX(+yrAdc!qNjk80BP`T^ab3ION3=O&ryrn zyh3nJ(FKh{a7Qk8ltn=c1eCmkngG?r;qq~bwg`o-&Zzz!suXv&Hq-!R5msqwjG6$v z=DG#UNJV%Vh06-2{ZxU*Pl8aT_$su36b?OhwtSW;J3!7(p(uhRRw}Mi7}&U&U5AQ+ zHRC9%L(~MA1nvdUjB2im2xU3@H~P!elE}DXu;fZZ$c$AWwLqB@#npAc>+9 zfiqzxo9YDPVz5nK<8!zmiK0>=HiavqGYa|)Hpv@U1O8zelWLSmgj-NVT>!4Gvck1> zqnhOqArU+A)le5;!UkXi^N@1!BCyE-6wz9iLLZWzzbxf{pP4_rkS~<|ym^Av?+B=1 z5VKz|w?PrTnJ@?1ndX9JDgXXiEw+;{c2rAFfD*3f8T15T$w#0hW9+OMSThb~E3>2$ zEka8Gxs{(2uCc6+I`dvOjA~|!K)EbMxF5~q4NQV@J)k08;2J)Bi72EzSA^*>mT+Rm zv^|3U+E5Ypt&VP}J7eJdRvt;lp>65f4ZQYi(rZ`h+j%0jbMFca;H|y>hHT8sVEG6x z3$uJcg?CB&f>oqaGn!Kv3Bb;-Vxa5`Rt+(EJTu!VX`T_A)K7pp2+)-c9QnxaZiuk} zG6k4$Z6CO@h}utZWC2M|SdM{{jdDdjjQjV20J^07eZ9L8u87G4JGwvwnE^st*7bH(c4A%ANf~V+uNb@1&>w?mlNV0yN@;w|<8((1W%(&KW2lXX6 z(4ugJ@qeS~bic;h$~bm@)t4<{wflsjP2&sfpBpM7k*xXw6BZ$?5~9Q9@RJR<^APK6 zLuG1EjE(@Tei2eQCfCJi$FlJS)?2_sTrQB(#_ zL|20@h9A3pMnD#&5n#r2D{Nct$QK)*&0B`3-6CoJ8G~pa%l2ri(h2Z2avqUXL+l`T z@VD=#$@FkR@fgH2#l-W(WYjVluz}3lc|0DE$K&yMJRXn7<0%FI07|aS6BBcB3jhEB M07*qoM6N<$g05%@t^fc4 literal 0 HcmV?d00001 diff --git a/Example/Showcase/Other/Assets.xcassets/plus_icon.imageset/Contents.json b/Example/Showcase/Other/Assets.xcassets/plus_icon.imageset/Contents.json index f303aab0a..9b4a9e400 100644 --- a/Example/Showcase/Other/Assets.xcassets/plus_icon.imageset/Contents.json +++ b/Example/Showcase/Other/Assets.xcassets/plus_icon.imageset/Contents.json @@ -1,7 +1,6 @@ { "images" : [ { - "filename" : "FAB.png", "idiom" : "universal", "scale" : "1x" }, @@ -10,6 +9,7 @@ "scale" : "2x" }, { + "filename" : "FAB.png", "idiom" : "universal", "scale" : "3x" } From cd83c7f7699a4e87a8c38178c27ca6b3f247f311 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 8 Jul 2022 11:04:30 +0700 Subject: [PATCH 3/3] Invite screen UI --- .../Classes/DomainLayer/ChatService.swift | 4 ++ .../ChatList/ChatListRouter.swift | 4 +- .../Invite/InviteInteractor.swift | 8 ++++ .../Invite/InviteModule.swift | 2 +- .../Invite/InvitePresenter.swift | 34 +++++++++++++++++ .../Invite/InviteRouter.swift | 4 ++ .../PresentationLayer/Invite/InviteView.swift | 38 +++++++++++++++++-- .../InviteList/InviteListInteractor.swift | 1 - .../InviteList/Models/InviteViewModel.swift | 2 +- .../Common/VIPER/SceneViewController.swift | 12 ++++-- Sources/Chat/Chat.swift | 1 - .../Invitee/InvitationHandlingService.swift | 2 +- Sources/Chat/Types/Message.swift | 2 +- .../Serialiser/Serializer.swift | 2 +- 14 files changed, 102 insertions(+), 14 deletions(-) diff --git a/Example/Showcase/Classes/DomainLayer/ChatService.swift b/Example/Showcase/Classes/DomainLayer/ChatService.swift index bf83ddd7f..e3769e83f 100644 --- a/Example/Showcase/Classes/DomainLayer/ChatService.swift +++ b/Example/Showcase/Classes/DomainLayer/ChatService.swift @@ -62,4 +62,8 @@ final class ChatService { func reject(invite: Invite) async throws { } + + func invite(account: String) async throws { + + } } diff --git a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListRouter.swift b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListRouter.swift index 429927719..ce8ba6cde 100644 --- a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListRouter.swift +++ b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListRouter.swift @@ -11,7 +11,9 @@ final class ChatListRouter { } func presentInvite() { - InviteModule.create(app: app).push(from: viewController) + InviteModule.create(app: app) + .wrapToNavigationController() + .present(from: viewController) } func presentInviteList() { 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 9cd0450d8..7706577b4 100644 --- a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListInteractor.swift +++ b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListInteractor.swift @@ -9,7 +9,6 @@ final class InviteListInteractor { return chatService.getInvites() } - func accept(invite: Invite) async { try! await chatService.accept(invite: invite) } diff --git a/Example/Showcase/Classes/PresentationLayer/InviteList/Models/InviteViewModel.swift b/Example/Showcase/Classes/PresentationLayer/InviteList/Models/InviteViewModel.swift index 51495bd24..0c4784a87 100644 --- a/Example/Showcase/Classes/PresentationLayer/InviteList/Models/InviteViewModel.swift +++ b/Example/Showcase/Classes/PresentationLayer/InviteList/Models/InviteViewModel.swift @@ -1,7 +1,7 @@ import Foundation // TODO: Delete after Chat SDK integration -struct Invite{ +struct Invite { let message: String let pubKey: String } 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/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()