Skip to content

Commit

Permalink
Merge pull request #615 from Adamant-im/dev/trello.com/c/NOZFKp1s
Browse files Browse the repository at this point in the history
[trello.com/c/NOZFKp1s] New swipe/reply architecture
  • Loading branch information
just-software-dev authored Dec 20, 2024
2 parents 6b721ba + d0f0d98 commit 5acce50
Show file tree
Hide file tree
Showing 25 changed files with 513 additions and 301 deletions.
24 changes: 20 additions & 4 deletions Adamant.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,6 @@
41A1994229D2D3920031AD75 /* SwipePanGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A1994129D2D3920031AD75 /* SwipePanGestureRecognizer.swift */; };
41A1994429D2D3CF0031AD75 /* MessageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A1994329D2D3CF0031AD75 /* MessageModel.swift */; };
41A1994629D2FCF80031AD75 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A1994529D2FCF80031AD75 /* ReplyView.swift */; };
41A1994829D325800031AD75 /* SwipeableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A1994729D325800031AD75 /* SwipeableView.swift */; };
41A1995229D42C460031AD75 /* ChatMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A1995129D42C460031AD75 /* ChatMessageCell.swift */; };
41A1995429D56E340031AD75 /* ChatMessageReplyCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A1995329D56E340031AD75 /* ChatMessageReplyCell.swift */; };
41A1995629D56EAA0031AD75 /* ChatMessageReplyCell+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A1995529D56EAA0031AD75 /* ChatMessageReplyCell+Model.swift */; };
Expand Down Expand Up @@ -356,6 +355,9 @@
9377FBDF296C2A2F00C9211B /* ChatTransactionContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9377FBDE296C2A2F00C9211B /* ChatTransactionContentView.swift */; };
9377FBE2296C2ACA00C9211B /* ChatTransactionContentView+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9377FBE1296C2ACA00C9211B /* ChatTransactionContentView+Model.swift */; };
937EDFC02C9CF6B300F219BB /* VersionFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 937EDFBF2C9CF6B300F219BB /* VersionFooterView.swift */; };
9380EF632D1119DD006939E1 /* ChatSwipeWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9380EF622D1119DD006939E1 /* ChatSwipeWrapper.swift */; };
9380EF662D111BD1006939E1 /* ChatSwipeWrapperModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9380EF652D111BD1006939E1 /* ChatSwipeWrapperModel.swift */; };
9380EF682D112BB9006939E1 /* ChatSwipeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9380EF672D112BB9006939E1 /* ChatSwipeManager.swift */; };
9382F61329DEC0A3005E6216 /* ChatModelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9382F61229DEC0A3005E6216 /* ChatModelView.swift */; };
938F7D582955C1DA001915CA /* MessageKit in Frameworks */ = {isa = PBXBuildFile; productRef = 938F7D572955C1DA001915CA /* MessageKit */; };
938F7D5B2955C8DA001915CA /* ChatDisplayManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 938F7D5A2955C8DA001915CA /* ChatDisplayManager.swift */; };
Expand Down Expand Up @@ -854,7 +856,6 @@
41A1994129D2D3920031AD75 /* SwipePanGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipePanGestureRecognizer.swift; sourceTree = "<group>"; };
41A1994329D2D3CF0031AD75 /* MessageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageModel.swift; sourceTree = "<group>"; };
41A1994529D2FCF80031AD75 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; };
41A1994729D325800031AD75 /* SwipeableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeableView.swift; sourceTree = "<group>"; };
41A1995129D42C460031AD75 /* ChatMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageCell.swift; sourceTree = "<group>"; };
41A1995329D56E340031AD75 /* ChatMessageReplyCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageReplyCell.swift; sourceTree = "<group>"; };
41A1995529D56EAA0031AD75 /* ChatMessageReplyCell+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatMessageReplyCell+Model.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1028,6 +1029,9 @@
9377FBDE296C2A2F00C9211B /* ChatTransactionContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTransactionContentView.swift; sourceTree = "<group>"; };
9377FBE1296C2ACA00C9211B /* ChatTransactionContentView+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatTransactionContentView+Model.swift"; sourceTree = "<group>"; };
937EDFBF2C9CF6B300F219BB /* VersionFooterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionFooterView.swift; sourceTree = "<group>"; };
9380EF622D1119DD006939E1 /* ChatSwipeWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatSwipeWrapper.swift; sourceTree = "<group>"; };
9380EF652D111BD1006939E1 /* ChatSwipeWrapperModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatSwipeWrapperModel.swift; sourceTree = "<group>"; };
9380EF672D112BB9006939E1 /* ChatSwipeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatSwipeManager.swift; sourceTree = "<group>"; };
9382F61229DEC0A3005E6216 /* ChatModelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatModelView.swift; sourceTree = "<group>"; };
938F7D5A2955C8DA001915CA /* ChatDisplayManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatDisplayManager.swift; sourceTree = "<group>"; };
938F7D5C2955C8F9001915CA /* ChatLayoutManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatLayoutManager.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1974,6 +1978,15 @@
path = ChatTransaction;
sourceTree = "<group>";
};
9380EF642D111BBF006939E1 /* ChatSwipeWrapper */ = {
isa = PBXGroup;
children = (
9380EF622D1119DD006939E1 /* ChatSwipeWrapper.swift */,
9380EF652D111BD1006939E1 /* ChatSwipeWrapperModel.swift */,
);
path = ChatSwipeWrapper;
sourceTree = "<group>";
};
938F7D552955C05D001915CA /* Chat */ = {
isa = PBXGroup;
children = (
Expand All @@ -1994,6 +2007,7 @@
938F7D602955C92B001915CA /* ChatDataSourceManager.swift */,
9390C5022976B42800270CDF /* ChatDialogManager.swift */,
932F77582989F999006D8801 /* ChatCellManager.swift */,
9380EF672D112BB9006939E1 /* ChatSwipeManager.swift */,
411DB8322A14D01F006AB158 /* ChatKeyboardManager.swift */,
418FDE4F2A25CA340055E3CD /* ChatMenuManager.swift */,
9340077F29AC341000A20622 /* ChatAction.swift */,
Expand Down Expand Up @@ -2033,6 +2047,7 @@
93996A9829682690008D080B /* Subviews */ = {
isa = PBXGroup;
children = (
9380EF642D111BBF006939E1 /* ChatSwipeWrapper */,
3A299C742B84CE1400B54C61 /* FilesToolBarView */,
3A299C672B838A7800B54C61 /* ChatMedia */,
41A1995029D42C160031AD75 /* ChatBaseMessage */,
Expand Down Expand Up @@ -2790,7 +2805,6 @@
93F3914F2962F5D400BFD6AE /* SpinnerView.swift */,
4133AED329769EEC00F3D017 /* UpdatingIndicatorView.swift */,
41A1994529D2FCF80031AD75 /* ReplyView.swift */,
41A1994729D325800031AD75 /* SwipeableView.swift */,
);
path = SharedViews;
sourceTree = "<group>";
Expand Down Expand Up @@ -3533,6 +3547,7 @@
3A4193912A580C85006A6B22 /* RichTransactionReactService.swift in Sources */,
93FC169B2B0197FD0062B507 /* BtcApiService.swift in Sources */,
3AA2D5F7280EADE3000ED971 /* SocketService.swift in Sources */,
9380EF632D1119DD006939E1 /* ChatSwipeWrapper.swift in Sources */,
E95F85802008C8D70070534A /* ChatListFactory.swift in Sources */,
41A1994429D2D3CF0031AD75 /* MessageModel.swift in Sources */,
93775E462A674FA9009061AC /* Markdown+Adamant.swift in Sources */,
Expand Down Expand Up @@ -3595,13 +3610,13 @@
4186B3302941E642006594A3 /* AdmWalletService+DynamicConstants.swift in Sources */,
E95F85852008CB3A0070534A /* ChatListViewController.swift in Sources */,
3AE0A42E2BC6A96B00BF7125 /* IPFS+Constants.swift in Sources */,
9380EF682D112BB9006939E1 /* ChatSwipeManager.swift in Sources */,
E9FEECA62143C300007DD7C8 /* EthWalletService+RichMessageProvider.swift in Sources */,
6403F5E622723FDA00D58779 /* DashWalletViewController.swift in Sources */,
931224AF2C7AA88E009E0ED0 /* InfoService.swift in Sources */,
93C7944E2B077C1F00408826 /* DashSendRawTransactionDTO.swift in Sources */,
4193AE1629FBEFBF002F21BE /* NSAttributedText+Adamant.swift in Sources */,
931224AB2C7AA212009E0ED0 /* InfoServiceRatesRequestDTO.swift in Sources */,
41A1994829D325800031AD75 /* SwipeableView.swift in Sources */,
4164A9D928F17DA700EEF16D /* AdamantChatTransactionService.swift in Sources */,
E993302221354BC300CD5200 /* EthWalletFactory.swift in Sources */,
418FDE502A25CA340055E3CD /* ChatMenuManager.swift in Sources */,
Expand Down Expand Up @@ -3693,6 +3708,7 @@
3AA2D5FA280EAF5D000ED971 /* AdamantSocketService.swift in Sources */,
649D6BEC21BD5A53009E727B /* UISuffixTextField.swift in Sources */,
E93B0D762028B28E00126346 /* AdamantChatsProvider.swift in Sources */,
9380EF662D111BD1006939E1 /* ChatSwipeWrapperModel.swift in Sources */,
3A33F9FA2A7A53DA002B8003 /* EmojiUpdateType.swift in Sources */,
934FD9A82C783E0C00336841 /* InfoServiceMapper.swift in Sources */,
936658932B0AC03700BDB2D3 /* CoinsNodesListStrings.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions Adamant/Modules/Chat/ChatFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ struct ChatFactory {
storedObjects: delegates.asArray + [dialogManager],
admWalletService: walletService,
screensFactory: screensFactory,
chatSwipeManager: .init(viewModel: viewModel),
sendTransaction: makeSendTransactionAction(
viewModel: viewModel,
screensFactory: screensFactory
Expand Down
28 changes: 15 additions & 13 deletions Adamant/Modules/Chat/View/ChatViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ final class ChatViewController: MessagesViewController {
private let walletServiceCompose: WalletServiceCompose
private let admWalletService: WalletService?
private let screensFactory: ScreensFactory
private let chatSwipeManager: ChatSwipeManager

let viewModel: ChatViewModel

Expand Down Expand Up @@ -90,6 +91,7 @@ final class ChatViewController: MessagesViewController {
storedObjects: [AnyObject],
admWalletService: WalletService?,
screensFactory: ScreensFactory,
chatSwipeManager: ChatSwipeManager,
sendTransaction: @escaping SendTransaction
) {
self.viewModel = viewModel
Expand All @@ -98,6 +100,7 @@ final class ChatViewController: MessagesViewController {
self.admWalletService = admWalletService
self.screensFactory = screensFactory
self.sendTransaction = sendTransaction
self.chatSwipeManager = chatSwipeManager
super.init(nibName: nil, bundle: nil)

inputBar.onAttachmentButtonTap = { [weak self] in
Expand Down Expand Up @@ -132,6 +135,7 @@ final class ChatViewController: MessagesViewController {
configureDropFiles()
setupObservers()
viewModel.loadFirstMessagesIfNeeded()
chatSwipeManager.configure(chatView: view)
}

override func viewWillLayoutSubviews() {
Expand Down Expand Up @@ -237,17 +241,6 @@ extension ChatViewController {
let velocity = panGesture.velocity(in: messagesCollectionView)
return abs(velocity.x) > abs(velocity.y)
}

private func swipeStateAction(_ state: SwipeableView.State) {
if state == .began {
chatMessagesCollectionView.stopDecelerating()
messagesCollectionView.isScrollEnabled = false
}

if state == .ended {
messagesCollectionView.isScrollEnabled = true
}
}
}

// MARK: Delegate Protocols
Expand Down Expand Up @@ -412,8 +405,8 @@ private extension ChatViewController {
}
.store(in: &subscriptions)

viewModel.$swipeState
.sink { [weak self] in self?.swipeStateAction($0) }
viewModel.enableScroll
.sink { [weak self] in self?.enableScroll($0) }
.store(in: &subscriptions)

viewModel.$isNeedToAnimateScroll
Expand Down Expand Up @@ -790,6 +783,15 @@ private extension ChatViewController {
}
}

func enableScroll(_ isEnabled: Bool) {
if isEnabled {
chatMessagesCollectionView.isScrollEnabled = true
} else {
chatMessagesCollectionView.stopDecelerating()
chatMessagesCollectionView.isScrollEnabled = false
}
}

func dismissTransferViewController(
andPresent viewController: UIViewController?,
didFinishWithTransfer: TransactionDetails?
Expand Down
3 changes: 1 addition & 2 deletions Adamant/Modules/Chat/View/Managers/ChatAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import CommonKit
enum ChatAction {
case forceUpdateTransactionStatus(id: String)
case openTransactionDetails(id: String)
case reply(message: MessageModel)
case reply(id: String)
case scrollTo(message: ChatMessageReplyCell.Model)
case swipeState(state: SwipeableView.State)
case copy(text: String)
case copyInPart(text:String)
case report(id: String)
Expand Down
22 changes: 10 additions & 12 deletions Adamant/Modules/Chat/View/Managers/ChatDataSourceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,10 @@ final class ChatDataSourceManager: MessagesDataSource {
return model.value
}

cell.transactionView.actionHandler = { [weak self] in self?.handleAction($0) }
cell.transactionView.chatMessagesListViewModel = viewModel.chatMessagesListViewModel
cell.transactionView.model = model.value
cell.transactionView.setSubscription(publisher: publisher, collection: messagesCollectionView)
cell.actionHandler = { [weak self] in self?.handleAction($0) }
cell.chatMessagesListViewModel = viewModel.chatMessagesListViewModel
cell.model = model.value
cell.setSubscription(publisher: publisher, collection: messagesCollectionView)
cell.configure(with: message, at: indexPath, and: messagesCollectionView)
return cell
}
Expand All @@ -163,10 +163,10 @@ final class ChatDataSourceManager: MessagesDataSource {
return model.value
}

cell.containerMediaView.actionHandler = { [weak self] in self?.handleAction($0) }
cell.containerMediaView.chatMessagesListViewModel = viewModel.chatMessagesListViewModel
cell.containerMediaView.model = model.value
cell.containerMediaView.setSubscription(publisher: publisher, collection: messagesCollectionView)
cell.actionHandler = { [weak self] in self?.handleAction($0) }
cell.chatMessagesListViewModel = viewModel.chatMessagesListViewModel
cell.model = model.value
cell.setSubscription(publisher: publisher, collection: messagesCollectionView)
cell.configure(with: message, at: indexPath, and: messagesCollectionView)
return cell
}
Expand All @@ -183,12 +183,10 @@ private extension ChatDataSourceManager {
viewModel.didTapTransfer.send(id)
case let .forceUpdateTransactionStatus(id):
viewModel.forceUpdateTransactionStatus(id: id)
case let .reply(message):
viewModel.replyMessageIfNeeded(message)
case let .reply(id):
viewModel.replyMessageIfNeeded(id: id)
case let .scrollTo(message):
viewModel.scroll(to: message)
case let .swipeState(state):
viewModel.swipeState = state
case let .copy(text):
viewModel.copyMessageAction(text)
case let .remove(id):
Expand Down
110 changes: 110 additions & 0 deletions Adamant/Modules/Chat/View/Managers/ChatSwipeManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//
// ChatSwipeManager.swift
// Adamant
//
// Created by Andrew G on 17.12.2024.
// Copyright © 2024 Adamant. All rights reserved.
//

import UIKit

@MainActor
final class ChatSwipeManager: NSObject {
private let viewModel: ChatViewModel
private var chatView: UIView?
private var vibrated = false

private var requiredSwipeOffset: CGFloat {
-UIScreen.main.bounds.size.width * 0.05
}

init(viewModel: ChatViewModel) {
self.viewModel = viewModel
super.init()
}

func configure(chatView: UIView) {
let recognizer = UIPanGestureRecognizer(
target: self,
action: #selector(onSwipe(_:))
)

recognizer.delegate = self
chatView.addGestureRecognizer(recognizer)
self.chatView = chatView
}
}

extension ChatSwipeManager: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(
_ recognizer: UIGestureRecognizer
) -> Bool {
guard let recognizer = recognizer as? UIPanGestureRecognizer else {
return false
}

let velocity = recognizer.velocity(in: chatView)
guard abs(velocity.x) > abs(velocity.y) else { return false }

let location = recognizer.location(in: chatView)
guard let id = findChatSwipeWrapperId(location) else { return false }

viewModel.updateSwipeableId(id)
return true
}

func gestureRecognizer(
_: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith _: UIGestureRecognizer
) -> Bool { true }
}

private extension ChatSwipeManager {
func findChatSwipeWrapperId(_ location: CGPoint) -> String? {
var view = chatView?.hitTest(location, with: nil)

while view != nil {
if let swipeWrapper = view as? ChatSwipeWrapper {
return swipeWrapper.model.id
} else {
view = view?.superview
}
}

return nil
}

@objc func onSwipe(_ recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: chatView)
let offset = translation.x <= .zero
? translation.x
: .zero

switch recognizer.state {
case .possible:
break
case .began:
vibrated = false
viewModel.enableScroll.send(false)
case .changed:
viewModel.updateSwipingOffset(offset)

if offset > requiredSwipeOffset {
vibrated = false
}

guard !vibrated else { break }
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
vibrated = true
case .ended, .cancelled, .failed:
if offset <= requiredSwipeOffset {
viewModel.replyMessageIfNeeded(id: viewModel.swipeableMessage.id)
}

viewModel.updateSwipeableId(nil)
viewModel.enableScroll.send(true)
@unknown default:
break
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ extension ChatMessageCell {
let opponentAddress: String
let isFake: Bool
var isHidden: Bool
var swipeState: ChatSwipeWrapperModel.State

static var `default`: Self {
Self(
Expand All @@ -30,7 +31,8 @@ extension ChatMessageCell {
address: "",
opponentAddress: "",
isFake: false,
isHidden: false
isHidden: false,
swipeState: .idle
)
}

Expand Down
Loading

0 comments on commit 5acce50

Please sign in to comment.