Skip to content
Snippets Groups Projects
Commit 4a1c6a51 authored by Raphaël Brulé's avatar Raphaël Brulé
Browse files

UI: Delete option on message long press

Adds a delete option when long pressing on a
conversation's message. Applicable for all types
of messages.

Change-Id: Ia1902a293009f10af962ff692d8fb6cd41484dc6
Gitlab: #34
parent b66afe2c
No related branches found
No related tags found
No related merge requests found
......@@ -2,6 +2,7 @@
* Copyright (C) 2017-2019 Savoir-faire Linux Inc.
*
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
* Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -165,6 +166,19 @@ final class InteractionDataHelper {
}
}
func delete (interactionId: Int64, dataBase: Connection) -> Bool {
let query = table.filter(id == interactionId)
do {
let deletedRows = try dataBase.run(query.delete())
guard deletedRows == 1 else {
return false
}
return true
} catch _ {
return false
}
}
func selectInteraction (interactionId: Int64, dataBase: Connection) throws -> Interaction? {
let query = table.filter(id == interactionId)
let items = try dataBase.prepare(query)
......
......@@ -2,6 +2,7 @@
* Copyright (C) 2017-2019 Savoir-faire Linux Inc.
*
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
* Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -142,6 +143,7 @@ enum DBBridgingError: Error {
case updateIntercationFailed
case deleteConversationFailed
case getProfileFailed
case deleteMessageFailed
}
enum InteractionType: String {
......@@ -393,6 +395,21 @@ class DBManager {
}
}
func deleteMessage(messagesId: Int64, accountId: String) -> Completable {
return Completable.create { [unowned self] completable in
if let dataBase = self.dbConnections.forAccount(account: accountId) {
if self.interactionHepler.delete(interactionId: messagesId, dataBase: dataBase) {
completable(.completed)
} else {
completable(.error(DBBridgingError.deleteMessageFailed))
}
} else {
completable(.error(DBBridgingError.deleteMessageFailed))
}
return Disposables.create { }
}
}
func clearAllHistoryFor(accountId: String) -> Completable {
return Completable.create { [unowned self] completable in
do {
......
......@@ -25,12 +25,15 @@
import UIKit
import Reusable
import RxSwift
import RxCocoa
import ActiveLabel
import SwiftyBeaver
// swiftlint:disable type_body_length
class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
// MARK: Properties
let log = SwiftyBeaver.self
@IBOutlet weak var avatarView: UIView!
......@@ -69,7 +72,19 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
var playerHeight = Variable<CGFloat>(0)
private(set) var messageId: Int64?
private var isCopyable: Bool = false
private let _deleteMessage = BehaviorRelay<Bool>(value: false)
var deleteMessage: Observable<Bool> { _deleteMessage.asObservable() }
private var longGestureRecognizer: UILongPressGestureRecognizer?
// MARK: prepareForReuse
override func prepareForReuse() {
self.prepareForReuseLongGesture()
self.setCellTimeLabelVisibility(hide: true)
if self.sendingIndicator != nil {
self.sendingIndicator.stopAnimating()
}
......@@ -78,11 +93,21 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
self.transferProgressView.removeFromSuperview()
self.playerView?.removeFromSuperview()
self.composingMsg.removeFromSuperview()
playerHeight.value = 0
self.playerHeight.value = 0
self.disposeBag = DisposeBag()
super.prepareForReuse()
}
private func prepareForReuseLongGesture() {
self.messageId = nil
self.isCopyable = false
self._deleteMessage.accept(false)
if let longGestureRecognizer = longGestureRecognizer {
self.bubble.removeGestureRecognizer(longGestureRecognizer)
self.longGestureRecognizer = nil
}
}
func startProgressMonitor(_ item: MessageViewModel,
_ conversationViewModel: ConversationViewModel) {
if self.outgoingImageProgressUpdater != nil {
......@@ -145,7 +170,17 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
}
}
func showCopyMenu() {
private func configureLongGesture(_ messageId: Int64, _ bubblePosition: BubblePosition, _ isTransfer: Bool) {
self.messageId = messageId
self.isCopyable = bubblePosition != .generated && !isTransfer
self.bubble.isUserInteractionEnabled = true
longGestureRecognizer = UILongPressGestureRecognizer()
longGestureRecognizer!.rx.event.bind(onNext: { [weak self] _ in self?.showCopyMenu() }).disposed(by: self.disposeBag)
self.bubble.addGestureRecognizer(longGestureRecognizer!)
}
private func showCopyMenu() {
becomeFirstResponder()
let menu = UIMenuController.shared
if !menu.isMenuVisible {
......@@ -154,40 +189,35 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
}
}
func setup() {
let longGestureRecognizer = UILongPressGestureRecognizer()
self.messageLabel.isUserInteractionEnabled = true
self.messageLabel.addGestureRecognizer(longGestureRecognizer)
longGestureRecognizer.rx.event.bind(onNext: { [weak self] _ in
self?.showCopyMenu()
}).disposed(by: self.disposeBag)
}
override func copy(_ sender: Any?) {
UIPasteboard.general.string = self.messageLabel.text
UIMenuController.shared.setMenuVisible(false, animated: true)
}
override func delete(_ sender: Any?) {
_deleteMessage.accept(true)
}
override var canBecomeFirstResponder: Bool {
return true
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(UIResponderStandardEditActions.copy) {
return true
}
return false
return action == #selector(UIResponderStandardEditActions.copy) && self.isCopyable ||
action == #selector(UIResponderStandardEditActions.delete)
}
private func setCellTimeLabelVisibility(hide: Bool) {
self.timeLabel.isHidden = hide
self.leftDivider.isHidden = hide
self.rightDivider.isHidden = hide
}
func formatCellTimeLabel(_ item: MessageViewModel) {
private func formatCellTimeLabel(_ item: MessageViewModel) {
// hide for potentially reused cell
self.timeLabel.isHidden = true
self.leftDivider.isHidden = true
self.rightDivider.isHidden = true
self.setCellTimeLabelVisibility(hide: true)
if item.timeStringShown == nil {
return
}
if item.timeStringShown == nil { return }
// setup the label
self.timeLabel.text = item.timeStringShown
......@@ -195,32 +225,26 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
self.timeLabel.font = UIFont.systemFont(ofSize: 12.0, weight: UIFont.Weight.medium)
// show the time
self.timeLabel.isHidden = false
self.leftDivider.isHidden = false
self.rightDivider.isHidden = false
self.setCellTimeLabelVisibility(hide: false)
}
// swiftlint:disable cyclomatic_complexity
func applyBubbleStyleToCell(_ items: [MessageViewModel]?, cellForRowAt indexPath: IndexPath) {
guard let items = items else {
return
}
guard let items = items else { return }
let item = items[indexPath.row]
let type = item.bubblePosition()
var bubbleColor: UIColor
if item.isTransfer {
if item.content.containsOnlyEmoji {
bubbleColor = UIColor.jamiMsgCellEmoji
} else {
bubbleColor = type == .received ? UIColor.jamiMsgCellReceived : UIColor(hex: 0xcfebf5, alpha: 1.0)
}
} else {
let bubbleColor: UIColor = { (bubblePosition: BubblePosition) -> UIColor in
if item.content.containsOnlyEmoji {
bubbleColor = UIColor.jamiMsgCellEmoji
return UIColor.jamiMsgCellEmoji
} else if bubblePosition == .received {
return UIColor.jamiMsgCellReceived
} else if item.isTransfer {
return UIColor(hex: 0xcfebf5, alpha: 1.0)
} else {
bubbleColor = type == .received ? UIColor.jamiMsgCellReceived : UIColor.jamiMsgCellSent
return UIColor.jamiMsgCellSent
}
}
}(item.bubblePosition())
if item.isTransfer {
self.messageLabel.enabledTypes = []
......@@ -233,7 +257,6 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
}
} else {
self.messageLabel.enabledTypes = [.url]
self.setup()
self.messageLabel.setTextWithLineSpacing(withText: item.content, withLineSpacing: 2)
self.messageLabel.handleURLTap { url in
let urlString = url.absoluteString
......@@ -303,18 +326,17 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
self.bubbleViewMask?.backgroundColor = UIColor.jamiMsgBackground
self.transferImageView.backgroundColor = UIColor.jamiMsgBackground
buttonsHeightConstraint?.priority = UILayoutPriority(rawValue: 999.0)
guard let item = items?[indexPath.row] else {
return
}
guard let item = items?[indexPath.row] else { return }
self.transferImageView.removeFromSuperview()
self.playerView?.removeFromSuperview()
self.composingMsg.removeFromSuperview()
playerHeight.value = 0
self.playerHeight.value = 0
self.bubbleViewMask?.isHidden = true
// hide/show time label
self.formatCellTimeLabel(item)
self.configureLongGesture(item.message.messageId, item.bubblePosition(), item.isTransfer)
if item.bubblePosition() == .generated {
self.bubble.backgroundColor = UIColor.jamiMsgCellReceived
......@@ -412,19 +434,7 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
.bind(to: self.failedStatusLabel.rx.isHidden)
.disposed(by: self.disposeBag)
if self.messageReadIndicator != nil {
Observable<(Data?, String, Bool)>.combineLatest(conversationViewModel.profileImageData.asObservable(),
conversationViewModel.bestName.asObservable(),
item.displayReadIndicator.asObservable()) { ($0, $1, $2) }
.observeOn(MainScheduler.instance)
.startWith((conversationViewModel.profileImageData.value, conversationViewModel.userName.value, item.displayReadIndicator.value))
.subscribe({ [weak self] profileData -> Void in
guard let bestName = profileData.element?.1 else { return }
self?.messageReadIndicator?.subviews.forEach({ $0.removeFromSuperview() })
if let displayReadIndicator = profileData.element?.2, displayReadIndicator {
self?.messageReadIndicator?.addSubview(AvatarView(profileImageData: profileData.element?.0, username: bestName, size: 12))
}
})
.disposed(by: self.disposeBag)
configureMessageReadAvatar(item, conversationViewModel)
}
}
} else if item.bubblePosition() == .received {
......@@ -443,33 +453,44 @@ class MessageCell: UITableViewCell, NibReusable, PlayerDelegate {
addComposingMsgView()
}
}
// received message avatar
Observable<(Data?, String)>.combineLatest(conversationViewModel.profileImageData.asObservable(),
conversationViewModel.userName.asObservable(),
conversationViewModel.displayName.asObservable()) { profileImage, username, displayName in
if let displayName = displayName, !displayName.isEmpty {
return (profileImage, displayName)
}
return (profileImage, username)
}
.observeOn(MainScheduler.instance)
.startWith((conversationViewModel.profileImageData.value, conversationViewModel.userName.value))
.subscribe({ [weak self] profileData -> Void in
guard let data = profileData.element?.1 else { return }
self?.avatarView
.subviews.forEach({ $0.removeFromSuperview() })
self?.avatarView
.addSubview(
AvatarView(profileImageData: profileData.element?.0,
username: data,
size: 32))
self?.avatarView.isHidden = !(item.sequencing == .lastOfSequence || item.sequencing == .singleMessage)
return
})
.disposed(by: self.disposeBag)
configureReceivedMessageAvatar(item.sequencing, conversationViewModel)
}
}
private func configureReceivedMessageAvatar(_ itemSequencing: MessageSequencing, _ conversationViewModel: ConversationViewModel) {
Observable<(Data?, String)>.combineLatest(conversationViewModel.profileImageData.asObservable(),
conversationViewModel.bestName.asObservable()) { ($0, $1) }
.observeOn(MainScheduler.instance)
.startWith((conversationViewModel.profileImageData.value, conversationViewModel.userName.value))
.subscribe({ [weak self] profileData in
guard let data = profileData.element?.1 else { return }
self?.avatarView.subviews.forEach({ $0.removeFromSuperview() })
if itemSequencing == .lastOfSequence || itemSequencing == .singleMessage {
self?.avatarView.addSubview(AvatarView(profileImageData: profileData.element?.0, username: data, size: 32))
}
})
.disposed(by: self.disposeBag)
}
fileprivate func configureMessageReadAvatar(_ item: MessageViewModel, _ conversationViewModel: ConversationViewModel) {
Observable<(Data?, String, Bool)>.combineLatest(conversationViewModel.profileImageData.asObservable(),
conversationViewModel.bestName.asObservable(),
item.displayReadIndicator.asObservable()) { ($0, $1, $2) }
.observeOn(MainScheduler.instance)
.startWith((conversationViewModel.profileImageData.value, conversationViewModel.userName.value, item.displayReadIndicator.value))
.subscribe({ [weak self] profileData in
guard let bestName = profileData.element?.1 else { return }
self?.messageReadIndicator?.subviews.forEach({ $0.removeFromSuperview() })
if let displayReadIndicator = profileData.element?.2, displayReadIndicator {
self?.messageReadIndicator?.addSubview(AvatarView(profileImageData: profileData.element?.0, username: bestName, size: 12))
}
})
.disposed(by: self.disposeBag)
}
func addComposingMsgView() {
self.composingMsg = UIView(frame: self.messageLabel.frame)
let size: CGFloat = 10
......
......@@ -5,6 +5,7 @@
* Author: Quentin Muret <quentin.muret@savoirfairelinux.com>
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
* Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -47,6 +48,7 @@ class ConversationViewController: UIViewController,
var bottomOffset: CGFloat = 0
let scrollOffsetThreshold: CGFloat = 600
var bottomHeight: CGFloat = 0.00
var isExecutingDeleteMessage: Bool = false
@IBOutlet weak var currentCallButton: UIButton!
@IBOutlet weak var currentCallLabel: UILabel!
......@@ -104,7 +106,7 @@ class ConversationViewController: UIViewController,
self.navigationController?.navigationBar.layer.shadowColor = UIColor.jamiNavigationBarShadow.cgColor
}
func importDocument() {
private func importDocument() {
let documentPicker = UIDocumentPickerViewController(documentTypes: ["public.item"], in: .import)
documentPicker.delegate = self
documentPicker.modalPresentationStyle = .formSheet
......@@ -128,7 +130,7 @@ class ConversationViewController: UIViewController,
}
}
func showNoPermissionsAlert(title: String) {
private func showNoPermissionsAlert(title: String) {
let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default) { (_: UIAlertAction!) -> Void in }
alert.addAction(okAction)
......@@ -664,9 +666,12 @@ class ConversationViewController: UIViewController,
}
fileprivate func scrollToBottomIfNeed() {
if self.isBottomContentOffset {
if self.isBottomContentOffset && !self.isExecutingDeleteMessage {
self.scrollToBottom(animated: false)
}
if self.isExecutingDeleteMessage {
self.isExecutingDeleteMessage = false
}
}
fileprivate func scrollToBottom(animated: Bool) {
......@@ -678,7 +683,7 @@ class ConversationViewController: UIViewController,
}
fileprivate var isBottomContentOffset: Bool {
updateBottomOffset()
self.updateBottomOffset()
let offset = abs((self.tableView.contentOffset.y + self.tableView.contentInset.top) - bottomOffset)
return offset <= scrollOffsetThreshold
}
......@@ -733,7 +738,7 @@ class ConversationViewController: UIViewController,
}
// MARK: - message formatting
func computeSequencing() {
private func computeSequencing() {
var lastShownTime: Date?
for (index, messageViewModel) in self.messageViewModels!.enumerated() {
// time labels
......@@ -759,7 +764,7 @@ class ConversationViewController: UIViewController,
}
}
func getMessageSequencing(forIndex index: Int) -> MessageSequencing {
private func getMessageSequencing(forIndex index: Int) -> MessageSequencing {
if let models = self.messageViewModels {
let messageItem = models[index]
let msgOwner = messageItem.bubblePosition()
......@@ -800,25 +805,22 @@ class ConversationViewController: UIViewController,
return MessageSequencing.unknown
}
func getTimeLabelString(forTime time: Date) -> String {
private func getTimeLabelString(forTime time: Date) -> String {
// get the current time
let currentDateTime = Date()
// prepare formatter
let dateFormatter = DateFormatter()
if Calendar.current.compare(currentDateTime, to: time, toGranularity: .year) == .orderedSame {
if Calendar.current.compare(currentDateTime, to: time, toGranularity: .weekOfYear) == .orderedSame {
if Calendar.current.compare(currentDateTime, to: time, toGranularity: .day) == .orderedSame {
// age: [0, received the previous day[
dateFormatter.dateFormat = "h:mma"
} else {
// age: [received the previous day, received 7 days ago[
dateFormatter.dateFormat = "E h:mma"
}
} else {
// age: [received 7 days ago, received the previous year[
dateFormatter.dateFormat = "MMM d, h:mma"
}
if Calendar.current.compare(currentDateTime, to: time, toGranularity: .day) == .orderedSame {
// age: [0, received the previous day[
dateFormatter.dateFormat = "h:mma"
} else if Calendar.current.compare(currentDateTime, to: time, toGranularity: .weekOfYear) == .orderedSame {
// age: [received the previous day, received 7 days ago[
dateFormatter.dateFormat = "E h:mma"
} else if Calendar.current.compare(currentDateTime, to: time, toGranularity: .year) == .orderedSame {
// age: [received 7 days ago, received the previous year[
dateFormatter.dateFormat = "MMM d, h:mma"
} else {
// age: [received the previous year, inf[
dateFormatter.dateFormat = "MMM d, yyyy h:mma"
......@@ -938,109 +940,128 @@ extension ConversationViewController: UITableViewDataSource {
return self.messageViewModels?.count ?? 0
}
// swiftlint:disable cyclomatic_complexity
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let item = self.messageViewModels?[indexPath.row] {
var type = MessageCell.self
if item.isTransfer {
type = item.bubblePosition() == .received ? MessageCellDataTransferReceived.self : MessageCellDataTransferSent.self
} else {
type = item.bubblePosition() == .received ? MessageCellReceived.self :
item.bubblePosition() == .sent ? MessageCellSent.self :
item.bubblePosition() == .generated ? MessageCellGenerated.self :
MessageCellGenerated.self
}
if item.message.incoming && item.message.status != .displayed && !item.message.isTransfer {
self.viewModel
.setMessageAsRead(daemonId: item.message.daemonId,
messageId: item.message.messageId)
if item.message.incoming &&
item.message.status != .displayed &&
!item.message.isTransfer {
self.viewModel.setMessageAsRead(daemonId: item.message.daemonId,
messageId: item.message.messageId)
}
let cell = tableView.dequeueReusableCell(for: indexPath, cellType: type)
let cellType = { (bubblePosition: BubblePosition, isTransfer: Bool) -> MessageCell.Type in
switch bubblePosition {
case .received: return isTransfer ? MessageCellDataTransferReceived.self : MessageCellReceived.self
case .sent: return isTransfer ? MessageCellDataTransferSent.self : MessageCellSent.self
case .generated: return MessageCellGenerated.self
}
}(item.bubblePosition(), item.isTransfer)
let cell = tableView.dequeueReusableCell(for: indexPath, cellType: cellType)
cell.configureFromItem(viewModel, self.messageViewModels, cellForRowAt: indexPath)
if item.isTransfer {
cell.acceptButton?.setTitle(L10n.DataTransfer.readableStatusAccept, for: .normal)
item.lastTransferStatus = .unknown
changeTransferStatus(cell, nil, item.message.transferStatus, item, viewModel)
item.transferStatus.asObservable()
.observeOn(MainScheduler.instance)
.filter {
return $0 != DataTransferStatus.unknown && $0 != item.lastTransferStatus && $0 != item.initialTransferStatus }
.subscribe(onNext: { [weak self, weak tableView, weak cell] status in
guard let cell = cell else {return}
guard let currentIndexPath = tableView?.indexPath(for: cell) else { return }
guard let transferId = item.daemonId else { return }
guard let model = self?.viewModel else { return }
self?.log.info("Transfer status change from: \(item.lastTransferStatus.description) to: \(status.description) for transferId: \(transferId) cell row: \(currentIndexPath.row)")
if item.bubblePosition() == .sent && item.shouldDisplayTransferedImage {
cell.displayTransferedImage(message: item, conversationID: model.conversation.value.conversationId, accountId: model.conversation.value.accountId)
} else {
self?.changeTransferStatus(cell, currentIndexPath, status, item, model)
cell.stopProgressMonitor()
transferCellSetup(item, cell, tableView, indexPath)
deleteCellSetup(cell)
return cell
}
return tableView.dequeueReusableCell(for: indexPath, cellType: MessageCellSent.self)
}
private func deleteCellSetup(_ cell: MessageCell) {
cell.deleteMessage
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self, weak cell] (shouldDelete) in
guard shouldDelete, let self = self, let cell = cell, let messageId = cell.messageId else { return }
self.isExecutingDeleteMessage = true
self.viewModel.deleteMessage(messageId: messageId)
})
.disposed(by: cell.disposeBag)
}
// swiftlint:disable cyclomatic_complexity
private func transferCellSetup(_ item: MessageViewModel, _ cell: MessageCell, _ tableView: UITableView, _ indexPath: IndexPath) {
if item.isTransfer {
cell.acceptButton?.setTitle(L10n.DataTransfer.readableStatusAccept, for: .normal)
item.lastTransferStatus = .unknown
changeTransferStatus(cell, nil, item.message.transferStatus, item, viewModel)
item.transferStatus.asObservable()
.observeOn(MainScheduler.instance)
.filter {
return $0 != DataTransferStatus.unknown && $0 != item.lastTransferStatus && $0 != item.initialTransferStatus }
.subscribe(onNext: { [weak self, weak tableView, weak cell] status in
guard let cell = cell else {return}
guard let currentIndexPath = tableView?.indexPath(for: cell) else { return }
guard let transferId = item.daemonId else { return }
guard let model = self?.viewModel else { return }
self?.log.info("Transfer status change from: \(item.lastTransferStatus.description) to: \(status.description) for transferId: \(transferId) cell row: \(currentIndexPath.row)")
if item.bubblePosition() == .sent && item.shouldDisplayTransferedImage {
cell.displayTransferedImage(message: item, conversationID: model.conversation.value.conversationId, accountId: model.conversation.value.accountId)
} else {
self?.changeTransferStatus(cell, currentIndexPath, status, item, model)
cell.stopProgressMonitor()
}
item.lastTransferStatus = status
item.initialTransferStatus = status
tableView?.reloadData()
})
.disposed(by: cell.disposeBag)
cell.cancelButton.rx.tap
.subscribe(onNext: { [weak self, weak tableView, weak cell] _ in
guard let cell = cell else {return}
guard let transferId = item.daemonId else { return }
self?.log.info("canceling transferId \(transferId)")
_ = self?.viewModel.cancelTransfer(transferId: transferId)
item.initialTransferStatus = .canceled
item.message.transferStatus = .canceled
cell.stopProgressMonitor()
tableView?.reloadData()
})
.disposed(by: cell.disposeBag)
cell.playerHeight
.asObservable()
.share()
.observeOn(MainScheduler.instance)
.subscribe(onNext: {[weak tableView] height in
if height > 0 {
UIView.performWithoutAnimation {
guard let sectionNumber = tableView?.numberOfSections,
let rowNumber = tableView?.numberOfRows(inSection: indexPath.section) else {return}
if indexPath.section < sectionNumber && indexPath.section >= 0 {
if indexPath.row < rowNumber &&
indexPath.row >= 0 &&
indexPath.row != tableView?.numberOfRows(inSection: indexPath.section) {
tableView?
.reloadItemsAtIndexPaths([indexPath],
animationStyle: .top)
}
}
}
item.lastTransferStatus = status
item.initialTransferStatus = status
tableView?.reloadData()
})
.disposed(by: cell.disposeBag)
}
}).disposed(by: cell.disposeBag)
cell.cancelButton.rx.tap
if item.bubblePosition() == .received {
cell.acceptButton?.rx.tap
.subscribe(onNext: { [weak self, weak tableView, weak cell] _ in
guard let cell = cell else {return}
guard let transferId = item.daemonId else { return }
self?.log.info("canceling transferId \(transferId)")
_ = self?.viewModel.cancelTransfer(transferId: transferId)
item.initialTransferStatus = .canceled
item.message.transferStatus = .canceled
cell.stopProgressMonitor()
tableView?.reloadData()
self?.log.info("accepting transferId \(transferId)")
if self?.viewModel.acceptTransfer(transferId: transferId, interactionID: item.messageId, messageContent: &item.message.content) != .success {
_ = self?.viewModel.cancelTransfer(transferId: transferId)
item.initialTransferStatus = .canceled
item.message.transferStatus = .canceled
cell.stopProgressMonitor()
tableView?.reloadData()
}
})
.disposed(by: cell.disposeBag)
cell.playerHeight
.asObservable()
.share()
.observeOn(MainScheduler.instance)
.subscribe(onNext: {[weak tableView] height in
if height > 0 {
UIView.performWithoutAnimation {
guard let sectionNumber = tableView?.numberOfSections,
let rowNumber = tableView?.numberOfRows(inSection: indexPath.section) else {return}
if indexPath.section < sectionNumber && indexPath.section >= 0 {
if indexPath.row < rowNumber &&
indexPath.row >= 0 &&
indexPath.row != tableView?.numberOfRows(inSection: indexPath.section) {
tableView?
.reloadItemsAtIndexPaths([indexPath],
animationStyle: .top)
}
}
}
}
}).disposed(by: cell.disposeBag)
if item.bubblePosition() == .received {
cell.acceptButton?.rx.tap
.subscribe(onNext: { [weak self, weak tableView, weak cell] _ in
guard let cell = cell else {return}
guard let transferId = item.daemonId else { return }
self?.log.info("accepting transferId \(transferId)")
if self?.viewModel.acceptTransfer(transferId: transferId, interactionID: item.messageId, messageContent: &item.message.content) != .success {
_ = self?.viewModel.cancelTransfer(transferId: transferId)
item.initialTransferStatus = .canceled
item.message.transferStatus = .canceled
cell.stopProgressMonitor()
tableView?.reloadData()
}
})
.disposed(by: cell.disposeBag)
}
if item.message.transferStatus == .success {
self.addShareAction(cell: cell, item: item)
}
}
return cell
if item.message.transferStatus == .success {
self.addShareAction(cell: cell, item: item)
}
}
return tableView.dequeueReusableCell(for: indexPath, cellType: MessageCellSent.self)
}
}
// swiftlint:enable type_body_length
......
......@@ -5,6 +5,7 @@
* Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
* Author: Quentin Muret <quentin.muret@savoirfairelinux.com>
* Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -277,11 +278,8 @@ class ConversationViewModel: Stateable, ViewModel {
return Observable
.combineLatest(userName.asObservable(),
displayName.asObservable()) {(userName, displayname) in
guard let name = displayname,
!name.isEmpty else {
return userName
}
return name
guard let displayname = displayname, !displayname.isEmpty else { return userName }
return displayname
}
}()
......@@ -403,6 +401,17 @@ class ConversationViewModel: Stateable, ViewModel {
}.first?.status = .displayed
}
func deleteMessage(messageId: Int64) {
guard let account = self.accountService.currentAccount else { return }
self.conversationsService
.deleteMessage(messagesId: messageId, accountId: account.id)
.subscribe(onCompleted: { [weak self] in
self?.log.debug("Messages was deleted")
})
.disposed(by: disposeBag)
self.messages.value.removeAll(where: { $0.messageId == messageId })
}
fileprivate var unreadMessagesCount: Int {
let unreadMessages = self.conversation.value.messages
.filter({ message in
......
......@@ -3,6 +3,7 @@
*
* Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
* Author: Quentin Muret <quentin.muret@savoirfairelinux.com>
* Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -359,6 +360,17 @@ class ConversationsService {
})
}
func deleteMessage(messagesId: Int64, accountId: String) -> Completable {
return Completable.create(subscribe: { [unowned self] completable in
self.dbManager
.deleteMessage(messagesId: messagesId, accountId: accountId)
.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background))
.subscribe(onCompleted: { completable(.completed) }, onError: { error in completable(.error(error))})
.disposed(by: self.disposeBag)
return Disposables.create { }
})
}
func getProfile(uri: String, accountId: String) -> Observable<Profile> {
return self.dbManager.profileObservable(for: uri, createIfNotExists: false, accountId: accountId)
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment