From dffbe77ff25fa6c90afa0198c679c9980d6c69c5 Mon Sep 17 00:00:00 2001 From: Bruno Muniz Azevedo Filho <bruno@elixxir.io> Date: Tue, 6 Dec 2022 19:01:17 -0300 Subject: [PATCH] Fixes chat 'more' and 'retry message' flows --- Package.swift | 20 +++++ Sources/AppFeature/Dependencies.swift | 8 ++ Sources/AppNavigation/PresentChatMore.swift | 78 +++++++++++++++++++ Sources/AppNavigation/PresentContact.swift | 2 +- .../AppNavigation/PresentRetryMessage.swift | 78 +++++++++++++++++++ .../Controllers/RetrySheetController.swift | 62 --------------- .../Controllers/SheetController.swift | 52 ------------- .../Controllers/SingleChatController.swift | 43 +++++----- .../ChatFeature/Views/RetrySheetView.swift | 51 ------------ Sources/ChatFeature/Views/SheetButton.swift | 56 ------------- Sources/ChatFeature/Views/SheetView.swift | 44 ----------- Sources/ChatMoreFeature/ChatMoreButton.swift | 31 ++++++++ .../ChatMoreFeature/ChatMoreController.swift | 50 ++++++++++++ Sources/ChatMoreFeature/ChatMoreView.swift | 44 +++++++++++ .../RetryMessageButton.swift | 31 ++++++++ .../RetryMessageController.swift | 50 ++++++++++++ .../RetryMessageView.swift | 42 ++++++++++ 17 files changed, 457 insertions(+), 285 deletions(-) create mode 100644 Sources/AppNavigation/PresentChatMore.swift create mode 100644 Sources/AppNavigation/PresentRetryMessage.swift delete mode 100644 Sources/ChatFeature/Controllers/RetrySheetController.swift delete mode 100644 Sources/ChatFeature/Controllers/SheetController.swift delete mode 100644 Sources/ChatFeature/Views/RetrySheetView.swift delete mode 100644 Sources/ChatFeature/Views/SheetButton.swift delete mode 100644 Sources/ChatFeature/Views/SheetView.swift create mode 100644 Sources/ChatMoreFeature/ChatMoreButton.swift create mode 100644 Sources/ChatMoreFeature/ChatMoreController.swift create mode 100644 Sources/ChatMoreFeature/ChatMoreView.swift create mode 100644 Sources/RetryMessageFeature/RetryMessageButton.swift create mode 100644 Sources/RetryMessageFeature/RetryMessageController.swift create mode 100644 Sources/RetryMessageFeature/RetryMessageView.swift diff --git a/Package.swift b/Package.swift index ef4dc8f0..ab38fe88 100644 --- a/Package.swift +++ b/Package.swift @@ -34,6 +34,7 @@ let package = Package( .library(name: "ContactFeature", targets: ["ContactFeature"]), .library(name: "FetchBannedList", targets: ["FetchBannedList"]), .library(name: "SettingsFeature", targets: ["SettingsFeature"]), + .library(name: "ChatMoreFeature", targets: ["ChatMoreFeature"]), .library(name: "ChatListFeature", targets: ["ChatListFeature"]), .library(name: "RequestsFeature", targets: ["RequestsFeature"]), .library(name: "ReportingFeature", targets: ["ReportingFeature"]), @@ -45,6 +46,7 @@ let package = Package( .library(name: "CountryListFeature", targets: ["CountryListFeature"]), .library(name: "PermissionsFeature", targets: ["PermissionsFeature"]), .library(name: "ContactListFeature", targets: ["ContactListFeature"]), + .library(name: "RetryMessageFeature", targets: ["RetryMessageFeature"]), .library(name: "RequestPermissionFeature", targets: ["RequestPermissionFeature"]), ], dependencies: [ @@ -135,6 +137,7 @@ let package = Package( .target(name: "WebsiteFeature"), .target(name: "RestoreFeature"), .target(name: "ProfileFeature"), + .target(name: "ChatMoreFeature"), .target(name: "ChatListFeature"), .target(name: "SettingsFeature"), .target(name: "RequestsFeature"), @@ -143,6 +146,7 @@ let package = Package( .target(name: "OnboardingFeature"), .target(name: "CreateGroupFeature"), .target(name: "ContactListFeature"), + .target(name: "RetryMessageFeature"), .target(name: "RequestPermissionFeature"), .product(name: "PulseUI", package: "Pulse"), // TO REMOVE .product(name: "PulseLogHandler", package: "Pulse"), // TO REMOVE @@ -287,6 +291,20 @@ let package = Package( ), ] ), + .target( + name: "ChatMoreFeature", + dependencies: [ + .target(name: "Shared"), + .target(name: "AppResources"), + ] + ), + .target( + name: "RetryMessageFeature", + dependencies: [ + .target(name: "Shared"), + .target(name: "AppResources"), + ] + ), .target( name: "CountryListFeature", dependencies: [ @@ -358,8 +376,10 @@ let package = Package( .target(name: "Keychain"), .target(name: "Voxophone"), .target(name: "DrawerFeature"), + .target(name: "ChatMoreFeature"), .target(name: "ChatInputFeature"), .target(name: "ReportingFeature"), + .target(name: "RetryMessageFeature"), .target(name: "RequestPermissionFeature"), .product(name: "ChatLayout", package: "ChatLayout"), .product(name: "DifferenceKit", package: "DifferenceKit"), diff --git a/Sources/AppFeature/Dependencies.swift b/Sources/AppFeature/Dependencies.swift index 16f2bf78..1b95b798 100644 --- a/Sources/AppFeature/Dependencies.swift +++ b/Sources/AppFeature/Dependencies.swift @@ -14,11 +14,13 @@ import ProfileFeature import ChatListFeature import SettingsFeature import RequestsFeature +import ChatMoreFeature import GroupDraftFeature import OnboardingFeature import CountryListFeature import CreateGroupFeature import ContactListFeature +import RetryMessageFeature import RequestPermissionFeature extension NavigatorKey: DependencyKey { @@ -35,6 +37,12 @@ extension NavigatorKey: DependencyKey { PresentPhotoLibraryNavigator(), PresentActivitySheetNavigator(), + PresentChatMoreNavigator( + ChatMoreController.init(_:_:_:) + ), + PresentRetryMessageNavigator( + RetryMessageController.init(_:_:_:) + ), PresentWebsiteNavigator( WebsiteController.init(_:) ), diff --git a/Sources/AppNavigation/PresentChatMore.swift b/Sources/AppNavigation/PresentChatMore.swift new file mode 100644 index 00000000..1762a739 --- /dev/null +++ b/Sources/AppNavigation/PresentChatMore.swift @@ -0,0 +1,78 @@ +import UIKit + +/// Opens up `ChatMore` on a given parent view controller +public struct PresentChatMore: Action { + /// - Parameters: + /// - didTapClear: Closure that will get called once the user taps on `clear` + /// - didTapReport: Closure that will get called once the user taps on `report` + /// - didTapDetails: Closure that will get called once the user taps on `details` + /// - parent: Parent view controller from which presentation should happen + /// - animated: Animate the transition + public init( + didTapClear: @escaping () -> Void, + didTapReport: @escaping () -> Void, + didTapDetails: @escaping () -> Void, + from parent: UIViewController, + animated: Bool = true + ) { + self.didTapClear = didTapClear + self.didTapReport = didTapReport + self.didTapDetails = didTapDetails + self.parent = parent + self.animated = animated + } + + /// Closure that will get called once the user taps on `clear` + public var didTapClear: () -> Void + + /// Closure that will get called once the user taps on `report` + public var didTapReport: () -> Void + + /// Closure that will get called once the user taps on `details` + public var didTapDetails: () -> Void + + /// Parent view controller from which presentation should happen + public var parent: UIViewController + + /// Animate the transition + public var animated: Bool +} + +/// Performs `PresentChatMore` action +public struct PresentChatMoreNavigator: TypedNavigator { + /// Custom transitioning delegate + let transitioningDelegate = BottomTransitioningDelegate() + + /// View controller which should be opened up + var viewController: ( + @escaping () -> Void, + @escaping () -> Void, + @escaping () -> Void + ) -> UIViewController + + /// - Parameters: + /// - viewController: view controller which should be presented + public init(_ viewController: @escaping ( + @escaping () -> Void, + @escaping () -> Void, + @escaping () -> Void + ) -> UIViewController) { + self.viewController = viewController + } + + public func perform(_ action: PresentChatMore, completion: @escaping () -> Void) { + let controller = viewController( + action.didTapClear, + action.didTapReport, + action.didTapDetails + ) + controller.transitioningDelegate = transitioningDelegate + controller.modalPresentationStyle = .overFullScreen + + action.parent.present( + controller, + animated: action.animated, + completion: completion + ) + } +} diff --git a/Sources/AppNavigation/PresentContact.swift b/Sources/AppNavigation/PresentContact.swift index dd1fca4b..0e23eb7d 100644 --- a/Sources/AppNavigation/PresentContact.swift +++ b/Sources/AppNavigation/PresentContact.swift @@ -17,7 +17,7 @@ public struct PresentContact: Action { self.animated = animated } - /// Model to build the view controller which will be pushed + /// Model to build the view controller which will be opened up public var contact: Contact /// Navigation controller on which push should happen diff --git a/Sources/AppNavigation/PresentRetryMessage.swift b/Sources/AppNavigation/PresentRetryMessage.swift new file mode 100644 index 00000000..60d65129 --- /dev/null +++ b/Sources/AppNavigation/PresentRetryMessage.swift @@ -0,0 +1,78 @@ +import UIKit + +/// Opens up `RetryMessage` on a given parent view controller +public struct PresentRetryMessage: Action { + /// - Parameters: + /// - didTapRetry: Closure that will get called once the user taps on `retry` + /// - didTapDelete: Closure that will get called once the user taps on `delete` + /// - didTapCancel: Closure that will get called once the user taps on `cancel` + /// - parent: Parent view controller from which presentation should happen + /// - animated: Animate the transition + public init( + didTapRetry: @escaping () -> Void, + didTapDelete: @escaping () -> Void, + didTapCancel: @escaping () -> Void, + from parent: UIViewController, + animated: Bool = true + ) { + self.didTapRetry = didTapRetry + self.didTapDelete = didTapDelete + self.didTapCancel = didTapCancel + self.parent = parent + self.animated = animated + } + + /// Closure that will get called once the user taps on `retry` + public var didTapRetry: () -> Void + + /// Closure that will get called once the user taps on `delete` + public var didTapDelete: () -> Void + + /// Closure that will get called once the user taps on `cancel` + public var didTapCancel: () -> Void + + /// Parent view controller from which presentation should happen + public var parent: UIViewController + + /// Animate the transition + public var animated: Bool +} + +/// Performs `PresentRetryMessage` action +public struct PresentRetryMessageNavigator: TypedNavigator { + /// Custom transitioning delegate + let transitioningDelegate = BottomTransitioningDelegate() + + /// View controller which should be opened up + var viewController: ( + @escaping () -> Void, + @escaping () -> Void, + @escaping () -> Void + ) -> UIViewController + + /// - Parameters: + /// - viewController: view controller which should be presented + public init(_ viewController: @escaping ( + @escaping () -> Void, + @escaping () -> Void, + @escaping () -> Void + ) -> UIViewController) { + self.viewController = viewController + } + + public func perform(_ action: PresentRetryMessage, completion: @escaping () -> Void) { + let controller = viewController( + action.didTapRetry, + action.didTapDelete, + action.didTapCancel + ) + controller.transitioningDelegate = transitioningDelegate + controller.modalPresentationStyle = .overFullScreen + + action.parent.present( + controller, + animated: action.animated, + completion: completion + ) + } +} diff --git a/Sources/ChatFeature/Controllers/RetrySheetController.swift b/Sources/ChatFeature/Controllers/RetrySheetController.swift deleted file mode 100644 index 4605017e..00000000 --- a/Sources/ChatFeature/Controllers/RetrySheetController.swift +++ /dev/null @@ -1,62 +0,0 @@ -import UIKit -import Combine - -public final class RetrySheetController: UIViewController { - enum Action { - case retry - case delete - case cancel - } - - // MARK: UI - - private lazy var screenView = RetrySheetView() - - // MARK: Properties - - var actionPublisher: AnyPublisher<Action, Never> { - actionRelay.eraseToAnyPublisher() - } - - private var cancellables = Set<AnyCancellable>() - private let actionRelay = PassthroughSubject<Action, Never>() - - // MARK: Lifecycle - - public override func loadView() { - view = screenView - } - - public override func viewDidLoad() { - super.viewDidLoad() - setupBindings() - } - - // MARK: Private - - private func setupBindings() { - screenView.retry - .publisher(for: .touchUpInside) - .sink { [unowned self] in - dismiss(animated: true) { [weak actionRelay] in - actionRelay?.send(.retry) - } - }.store(in: &cancellables) - - screenView.delete - .publisher(for: .touchUpInside) - .sink { [unowned self] in - dismiss(animated: true) { [weak actionRelay] in - actionRelay?.send(.delete) - } - }.store(in: &cancellables) - - screenView.cancel - .publisher(for: .touchUpInside) - .sink { [unowned self] in - dismiss(animated: true) { [weak actionRelay] in - actionRelay?.send(.cancel) - } - }.store(in: &cancellables) - } -} diff --git a/Sources/ChatFeature/Controllers/SheetController.swift b/Sources/ChatFeature/Controllers/SheetController.swift deleted file mode 100644 index 12dd2e5a..00000000 --- a/Sources/ChatFeature/Controllers/SheetController.swift +++ /dev/null @@ -1,52 +0,0 @@ -import UIKit -import Combine - -final class SheetController: UIViewController { - enum Action { - case clear - case details - case report - } - - private lazy var screenView = SheetView() - - var actionPublisher: AnyPublisher<Action, Never> { - actionRelay.eraseToAnyPublisher() - } - - private var cancellables = Set<AnyCancellable>() - private let actionRelay = PassthroughSubject<Action, Never>() - - public override func loadView() { - view = screenView - } - - public override func viewDidLoad() { - super.viewDidLoad() - - screenView - .clearButton - .publisher(for: .touchUpInside) - .sink { [unowned self] in - dismiss(animated: true) { [weak actionRelay] in - actionRelay?.send(.clear) - } - }.store(in: &cancellables) - - screenView.detailsButton - .publisher(for: .touchUpInside) - .sink { [unowned self] in - dismiss(animated: true) { [weak actionRelay] in - actionRelay?.send(.details) - } - }.store(in: &cancellables) - - screenView.reportButton - .publisher(for: .touchUpInside) - .sink { [unowned self] in - dismiss(animated: true) { [weak actionRelay] in - actionRelay?.send(.report) - } - }.store(in: &cancellables) - } -} diff --git a/Sources/ChatFeature/Controllers/SingleChatController.swift b/Sources/ChatFeature/Controllers/SingleChatController.swift index 5e69a83f..71a5a1c0 100644 --- a/Sources/ChatFeature/Controllers/SingleChatController.swift +++ b/Sources/ChatFeature/Controllers/SingleChatController.swift @@ -40,7 +40,6 @@ public final class SingleChatController: UIViewController { private lazy var moreButton = UIButton() private lazy var screenView = ChatView() - private lazy var sheet = SheetController() private let inputComponent: ChatInputView private var collectionView: UICollectionView! @@ -264,23 +263,6 @@ public final class SingleChatController: UIViewController { } private func setupBindings() { - sheet - .actionPublisher - .receive(on: DispatchQueue.main) - .sink { [unowned self] in - switch $0 { - case .clear: - presentDeleteAllDrawer() - case .details: - navigator.perform(PresentContact( - contact: viewModel.contact, - on: navigationController! - )) - case .report: - presentReportDrawer() - } - }.store(in: &cancellables) - viewModel .shouldDisplayEmptyView .removeDuplicates() @@ -517,7 +499,30 @@ public final class SingleChatController: UIViewController { } @objc private func didTapDots() { - //coordinator.toMenuSheet(sheet, from: self) + navigator.perform(PresentChatMore( + didTapClear: { [weak self] in + guard let self else { return } + self.navigator.perform(DismissModal(from: self)) { + self.presentDeleteAllDrawer() + } + }, + didTapReport: { [weak self] in + guard let self else { return } + self.navigator.perform(DismissModal(from: self)) { + self.presentReportDrawer() + } + }, + didTapDetails: { [weak self] in + guard let self else { return } + self.navigator.perform(DismissModal(from: self)) { + self.navigator.perform(PresentContact( + contact: self.viewModel.contact, + on: self.navigationController! + )) + } + }, + from: self + )) } @objc private func didTapInfo() { diff --git a/Sources/ChatFeature/Views/RetrySheetView.swift b/Sources/ChatFeature/Views/RetrySheetView.swift deleted file mode 100644 index 1b79665b..00000000 --- a/Sources/ChatFeature/Views/RetrySheetView.swift +++ /dev/null @@ -1,51 +0,0 @@ -import UIKit -import Shared -import AppResources - -final class RetrySheetView: UIView { - // MARK: UI - - let stack = UIStackView() - let retry = SheetButton() - let delete = SheetButton() - let cancel = SheetButton(.destructive) - - // MARK: Lifecycle - - init() { - super.init(frame: .zero) - setup() - } - - required init?(coder: NSCoder) { nil } - - // MARK: Private - - private func setup() { - layer.cornerRadius = 15 - layer.masksToBounds = true - backgroundColor = Asset.neutralWhite.color - - retry.title.text = Localized.Chat.RetrySheet.retry - delete.title.text = Localized.Chat.RetrySheet.delete - cancel.title.text = Localized.Chat.RetrySheet.cancel - - retry.image.image = Asset.lens.image - delete.image.image = Asset.lens.image - cancel.image.image = Asset.lens.image - - stack.axis = .vertical - stack.distribution = .fillEqually - stack.addArrangedSubview(retry) - stack.addArrangedSubview(delete) - stack.addArrangedSubview(cancel) - - addSubview(stack) - - stack.snp.makeConstraints { make in - make.top.equalToSuperview().offset(10) - make.left.right.equalToSuperview() - make.bottom.equalTo(safeAreaLayoutGuide) - } - } -} diff --git a/Sources/ChatFeature/Views/SheetButton.swift b/Sources/ChatFeature/Views/SheetButton.swift deleted file mode 100644 index f24e8a06..00000000 --- a/Sources/ChatFeature/Views/SheetButton.swift +++ /dev/null @@ -1,56 +0,0 @@ -import UIKit -import Shared -import AppResources - -final class SheetButton: UIControl { - enum Style { - case normal - case destructive - } - - // MARK: UI - - let title = UILabel() - let image = UIImageView() - - // MARK: Properties - - private let style: Style - override var isEnabled: Bool { - didSet { - title.alpha = isEnabled ? 1.0 : 0.5 - image.alpha = isEnabled ? 1.0 : 0.5 - } - } - - // MARK: Lifecycle - - init(_ style: Style = .normal) { - self.style = style - super.init(frame: .zero) - setup() - } - - required init?(coder: NSCoder) { nil } - - // MARK: Private - - private func setup() { - title.font = Fonts.Mulish.bold.font(size: 14.0) - title.textColor = style == .normal ? Asset.neutralBody.color : Asset.neutralBody.color - - addSubview(title) - addSubview(image) - - image.snp.makeConstraints { make in - make.left.equalToSuperview().offset(40) - make.centerY.equalToSuperview() - } - - title.snp.makeConstraints { make in - make.left.equalToSuperview().offset(84) - make.centerY.equalToSuperview() - make.top.equalToSuperview().offset(16) - } - } -} diff --git a/Sources/ChatFeature/Views/SheetView.swift b/Sources/ChatFeature/Views/SheetView.swift deleted file mode 100644 index 6e305a6f..00000000 --- a/Sources/ChatFeature/Views/SheetView.swift +++ /dev/null @@ -1,44 +0,0 @@ -import UIKit -import Shared -import AppResources - -final class SheetView: UIView { - let stackView = UIStackView() - let clearButton = SheetButton() - let reportButton = SheetButton() - let detailsButton = SheetButton() - - init() { - super.init(frame: .zero) - - layer.cornerRadius = 40 - layer.masksToBounds = true - backgroundColor = Asset.neutralWhite.color - - clearButton.image.image = Asset.chatListDeleteSwipe.image - clearButton.title.text = Localized.Chat.SheetMenu.clear - - detailsButton.tintColor = Asset.neutralDark.color - detailsButton.image.image = Asset.searchUsername.image - detailsButton.title.text = Localized.Chat.SheetMenu.details - - reportButton.tintColor = Asset.accentDanger.color - reportButton.image.image = Asset.searchUsername.image - reportButton.title.text = Localized.Chat.SheetMenu.report - - stackView.axis = .vertical - stackView.distribution = .fillEqually - stackView.addArrangedSubview(clearButton) - stackView.addArrangedSubview(detailsButton) - stackView.addArrangedSubview(reportButton) - addSubview(stackView) - - stackView.snp.makeConstraints { - $0.top.equalToSuperview().offset(25) - $0.left.right.equalToSuperview() - $0.bottom.equalTo(safeAreaLayoutGuide) - } - } - - required init?(coder: NSCoder) { nil } -} diff --git a/Sources/ChatMoreFeature/ChatMoreButton.swift b/Sources/ChatMoreFeature/ChatMoreButton.swift new file mode 100644 index 00000000..4aced830 --- /dev/null +++ b/Sources/ChatMoreFeature/ChatMoreButton.swift @@ -0,0 +1,31 @@ +import UIKit +import Shared +import AppResources + +final class ChatMoreButton: UIControl { + let titleLabel = UILabel() + let imageView = UIImageView() + + init() { + super.init(frame: .zero) + + titleLabel.font = Fonts.Mulish.bold.font(size: 14.0) + titleLabel.textColor = Asset.neutralBody.color + + addSubview(titleLabel) + addSubview(imageView) + + imageView.snp.makeConstraints { + $0.left.equalToSuperview().offset(40) + $0.centerY.equalToSuperview() + } + + titleLabel.snp.makeConstraints { + $0.left.equalToSuperview().offset(84) + $0.centerY.equalToSuperview() + $0.top.equalToSuperview().offset(16) + } + } + + required init?(coder: NSCoder) { nil } +} diff --git a/Sources/ChatMoreFeature/ChatMoreController.swift b/Sources/ChatMoreFeature/ChatMoreController.swift new file mode 100644 index 00000000..238358e1 --- /dev/null +++ b/Sources/ChatMoreFeature/ChatMoreController.swift @@ -0,0 +1,50 @@ +import UIKit +import Combine + +public final class ChatMoreController: UIViewController { + private lazy var screenView = ChatMoreView() + + private let didTapClear: () -> Void + private let didTapReport: () -> Void + private let didTapDetails: () -> Void + private var cancellables = Set<AnyCancellable>() + + public init( + _ didTapClear: @escaping () -> Void, + _ didTapReport: @escaping () -> Void, + _ didTapDetails: @escaping () -> Void + ) { + self.didTapClear = didTapClear + self.didTapReport = didTapReport + self.didTapDetails = didTapDetails + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { nil } + + public override func loadView() { + view = screenView + } + + public override func viewDidLoad() { + super.viewDidLoad() + + screenView + .clearButton + .publisher(for: .touchUpInside) + .sink { [unowned self] in didTapClear() } + .store(in: &cancellables) + + screenView + .detailsButton + .publisher(for: .touchUpInside) + .sink { [unowned self] in didTapDetails() } + .store(in: &cancellables) + + screenView + .reportButton + .publisher(for: .touchUpInside) + .sink { [unowned self] in didTapReport() } + .store(in: &cancellables) + } +} diff --git a/Sources/ChatMoreFeature/ChatMoreView.swift b/Sources/ChatMoreFeature/ChatMoreView.swift new file mode 100644 index 00000000..d994e572 --- /dev/null +++ b/Sources/ChatMoreFeature/ChatMoreView.swift @@ -0,0 +1,44 @@ +import UIKit +import Shared +import AppResources + +final class ChatMoreView: UIView { + let clearButton = ChatMoreButton() + let reportButton = ChatMoreButton() + let detailsButton = ChatMoreButton() + private let stackView = UIStackView() + + init() { + super.init(frame: .zero) + + layer.cornerRadius = 40 + layer.masksToBounds = true + backgroundColor = Asset.neutralWhite.color + + reportButton.tintColor = Asset.accentDanger.color + detailsButton.tintColor = Asset.neutralDark.color + + clearButton.titleLabel.text = Localized.Chat.SheetMenu.clear + reportButton.titleLabel.text = Localized.Chat.SheetMenu.report + detailsButton.titleLabel.text = Localized.Chat.SheetMenu.details + + reportButton.imageView.image = Asset.searchUsername.image + detailsButton.imageView.image = Asset.searchUsername.image + clearButton.imageView.image = Asset.chatListDeleteSwipe.image + + stackView.axis = .vertical + stackView.distribution = .fillEqually + stackView.addArrangedSubview(clearButton) + stackView.addArrangedSubview(detailsButton) + stackView.addArrangedSubview(reportButton) + addSubview(stackView) + + stackView.snp.makeConstraints { + $0.top.equalToSuperview().offset(25) + $0.left.right.equalToSuperview() + $0.bottom.equalTo(safeAreaLayoutGuide) + } + } + + required init?(coder: NSCoder) { nil } +} diff --git a/Sources/RetryMessageFeature/RetryMessageButton.swift b/Sources/RetryMessageFeature/RetryMessageButton.swift new file mode 100644 index 00000000..d4b7722c --- /dev/null +++ b/Sources/RetryMessageFeature/RetryMessageButton.swift @@ -0,0 +1,31 @@ +import UIKit +import Shared +import AppResources + +final class RetryMessageButton: UIControl { + let titleLabel = UILabel() + let imageView = UIImageView() + + init() { + super.init(frame: .zero) + + titleLabel.textColor = Asset.neutralBody.color + titleLabel.font = Fonts.Mulish.bold.font(size: 14.0) + + addSubview(titleLabel) + addSubview(imageView) + + imageView.snp.makeConstraints { + $0.left.equalToSuperview().offset(40) + $0.centerY.equalToSuperview() + } + + titleLabel.snp.makeConstraints { + $0.left.equalToSuperview().offset(84) + $0.centerY.equalToSuperview() + $0.top.equalToSuperview().offset(16) + } + } + + required init?(coder: NSCoder) { nil } +} diff --git a/Sources/RetryMessageFeature/RetryMessageController.swift b/Sources/RetryMessageFeature/RetryMessageController.swift new file mode 100644 index 00000000..3d93cbbc --- /dev/null +++ b/Sources/RetryMessageFeature/RetryMessageController.swift @@ -0,0 +1,50 @@ +import UIKit +import Combine + +public final class RetryMessageController: UIViewController { + private lazy var screenView = RetryMessageView() + + private let didTapRetry: () -> Void + private let didTapDelete: () -> Void + private let didTapCancel: () -> Void + private var cancellables = Set<AnyCancellable>() + + public init( + _ didTapRetry: @escaping () -> Void, + _ didTapDelete: @escaping () -> Void, + _ didTapCancel: @escaping () -> Void + ) { + self.didTapRetry = didTapRetry + self.didTapDelete = didTapDelete + self.didTapCancel = didTapCancel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { nil } + + public override func loadView() { + view = screenView + } + + public override func viewDidLoad() { + super.viewDidLoad() + + screenView + .retryButton + .publisher(for: .touchUpInside) + .sink { [unowned self] in didTapRetry() } + .store(in: &cancellables) + + screenView + .deleteButton + .publisher(for: .touchUpInside) + .sink { [unowned self] in didTapDelete() } + .store(in: &cancellables) + + screenView + .cancelButton + .publisher(for: .touchUpInside) + .sink { [unowned self] in didTapCancel() } + .store(in: &cancellables) + } +} diff --git a/Sources/RetryMessageFeature/RetryMessageView.swift b/Sources/RetryMessageFeature/RetryMessageView.swift new file mode 100644 index 00000000..a50eb7e9 --- /dev/null +++ b/Sources/RetryMessageFeature/RetryMessageView.swift @@ -0,0 +1,42 @@ +import UIKit +import Shared +import AppResources + +final class RetryMessageView: UIView { + private let stackView = UIStackView() + let retryButton = RetryMessageButton() + let deleteButton = RetryMessageButton() + let cancelButton = RetryMessageButton() + + init() { + super.init(frame: .zero) + + layer.cornerRadius = 15 + layer.masksToBounds = true + backgroundColor = Asset.neutralWhite.color + + retryButton.titleLabel.text = Localized.Chat.RetrySheet.retry + deleteButton.titleLabel.text = Localized.Chat.RetrySheet.delete + cancelButton.titleLabel.text = Localized.Chat.RetrySheet.cancel + + retryButton.imageView.image = Asset.lens.image + deleteButton.imageView.image = Asset.lens.image + cancelButton.imageView.image = Asset.lens.image + + stackView.axis = .vertical + stackView.distribution = .fillEqually + stackView.addArrangedSubview(retryButton) + stackView.addArrangedSubview(deleteButton) + stackView.addArrangedSubview(cancelButton) + + addSubview(stackView) + + stackView.snp.makeConstraints { + $0.top.equalToSuperview().offset(10) + $0.left.right.equalToSuperview() + $0.bottom.equalTo(safeAreaLayoutGuide) + } + } + + required init?(coder: NSCoder) { nil } +} -- GitLab