Skip to content
Snippets Groups Projects
Commit c5c24bbb authored by Bruno Muniz's avatar Bruno Muniz :apple:
Browse files

Implemented filtering for banned/blocked users and reporting

parent 3184df5a
No related branches found
No related tags found
3 merge requests!71Releasing v1.1.5 (214),!69Implemented filtering for banned/blocked users and reporting,!67v1.1.5 b(203)
Showing
with 180 additions and 29 deletions
......@@ -5,6 +5,7 @@ final class SheetController: UIViewController {
enum Action {
case clear
case details
case report
}
lazy private var screenView = SheetView()
......@@ -23,7 +24,7 @@ final class SheetController: UIViewController {
public override func viewDidLoad() {
super.viewDidLoad()
screenView.clear
screenView.clearButton
.publisher(for: .touchUpInside)
.sink { [unowned self] in
dismiss(animated: true) { [weak actionRelay] in
......@@ -31,12 +32,20 @@ final class SheetController: UIViewController {
}
}.store(in: &cancellables)
screenView.details
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)
}
}
......@@ -187,6 +187,7 @@ public final class SingleChatController: UIViewController {
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: moreButton)
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: infoView)
navigationItem.leftItemsSupplementBackButton = true
}
private func setupInputController() {
......@@ -249,6 +250,8 @@ public final class SingleChatController: UIViewController {
presentDeleteAllDrawer()
case .details:
coordinator.toContact(viewModel.contact, from: self)
case .report:
presentReportDrawer()
}
}.store(in: &cancellables)
......@@ -263,6 +266,12 @@ public final class SingleChatController: UIViewController {
}
}.store(in: &cancellables)
viewModel.reportPopupPublisher
.receive(on: DispatchQueue.main)
.sink { [unowned self] in
presentReportDrawer()
}.store(in: &cancellables)
viewModel.isOnline
.removeDuplicates()
.receive(on: DispatchQueue.main)
......@@ -383,6 +392,77 @@ public final class SingleChatController: UIViewController {
return drawer
}
private func presentReportDrawer() {
let cancelButton = CapsuleButton()
cancelButton.setStyle(.seeThrough)
cancelButton.setTitle(Localized.Chat.Report.cancel, for: .normal)
let reportButton = CapsuleButton()
reportButton.setStyle(.red)
reportButton.setTitle(Localized.Chat.Report.action, for: .normal)
let drawer = DrawerController(with: [
DrawerImage(
image: Asset.drawerNegative.image
),
DrawerText(
font: Fonts.Mulish.semiBold.font(size: 18.0),
text: Localized.Chat.Report.title,
color: Asset.neutralActive.color
),
DrawerText(
font: Fonts.Mulish.semiBold.font(size: 14.0),
text: Localized.Chat.Report.subtitle,
color: Asset.neutralWeak.color,
lineHeightMultiple: 1.35,
spacingAfter: 25
),
DrawerStack(
axis: .vertical,
spacing: 20.0,
views: [reportButton, cancelButton]
)
])
reportButton.publisher(for: .touchUpInside)
.receive(on: DispatchQueue.main)
.sink {
drawer.dismiss(animated: true) { [weak self] in
guard let self = self else { return }
self.drawerCancellables.removeAll()
self.didProceedWithReport()
}
}.store(in: &drawerCancellables)
cancelButton.publisher(for: .touchUpInside)
.receive(on: DispatchQueue.main)
.sink {
drawer.dismiss(animated: true) { [weak self] in
self?.drawerCancellables.removeAll()
}
}.store(in: &drawerCancellables)
coordinator.toDrawer(drawer, from: self)
}
private func didProceedWithReport() {
var screenshotImage: UIImage?
let layer = UIApplication.shared.keyWindow!.layer
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);
guard let context = UIGraphicsGetCurrentContext() else { return }
layer.render(in: context)
if let image = UIGraphicsGetImageFromCurrentImageContext() {
UIGraphicsEndImageContext()
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
viewModel.uploadReport(screenshot: image)
navigationController?.popViewController(animated: true)
}
}
private func presentDeleteAllDrawer() {
let clearButton = CapsuleButton()
clearButton.setStyle(.red)
......@@ -632,7 +712,8 @@ extension SingleChatController: UICollectionViewDelegate {
ActionFactory.build(from: item, action: .copy, closure: self.viewModel.didRequestCopy(_:)),
ActionFactory.build(from: item, action: .retry, closure: self.viewModel.didRequestRetry(_:)),
ActionFactory.build(from: item, action: .reply, closure: self.viewModel.didRequestReply(_:)),
ActionFactory.build(from: item, action: .delete, closure: self.viewModel.didRequestDeleteSingle(_:))
ActionFactory.build(from: item, action: .delete, closure: self.viewModel.didRequestDeleteSingle(_:)),
ActionFactory.build(from: item, action: .report, closure: self.viewModel.didRequestReport(_:))
].compactMap { $0 })
}
}
......
......@@ -399,6 +399,7 @@ struct ActionFactory {
case retry
case reply
case delete
case report
var title: String {
switch self {
......@@ -411,6 +412,8 @@ struct ActionFactory {
return Localized.Chat.BubbleMenu.reply
case .delete:
return Localized.Chat.BubbleMenu.delete
case .report:
return Localized.Chat.BubbleMenu.report
}
}
}
......@@ -422,6 +425,8 @@ struct ActionFactory {
) -> UIAction? {
switch action {
case .report:
guard item.status == .received else { return nil }
case .reply:
guard item.status == .received || item.status == .sent else { return nil }
case .retry:
......
......@@ -34,6 +34,7 @@ final class SingleChatViewModel {
private let replySubject = PassthroughSubject<(String, String), Never>()
private let navigationRoutes = PassthroughSubject<SingleChatNavigationRoutes, Never>()
private let sectionsRelay = CurrentValueSubject<[ArraySection<ChatSection, Message>], Never>([])
private let reportPopupSubject = PassthroughSubject<Void, Never>()
var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
......@@ -44,6 +45,10 @@ final class SingleChatViewModel {
var navigation: AnyPublisher<SingleChatNavigationRoutes, Never> { navigationRoutes.eraseToAnyPublisher() }
var shouldDisplayEmptyView: AnyPublisher<Bool, Never> { sectionsRelay.map { $0.isEmpty }.eraseToAnyPublisher() }
var reportPopupPublisher: AnyPublisher<Void, Never> {
reportPopupSubject.eraseToAnyPublisher()
}
var messages: AnyPublisher<[ArraySection<ChatSection, Message>], Never> {
sectionsRelay.map { sections -> [ArraySection<ChatSection, Message>] in
var snapshot = [ArraySection<ChatSection, Message>]()
......@@ -172,6 +177,10 @@ final class SingleChatViewModel {
didRequestDelete([model])
}
func didRequestReport(_: Message) {
reportPopupSubject.send()
}
func abortReply() {
stagedReply = nil
}
......@@ -211,6 +220,14 @@ final class SingleChatViewModel {
return (contactTitle, message.text)
}
func uploadReport(screenshot: UIImage) {
UIImageWriteToSavedPhotosAlbum(screenshot, nil, nil, nil)
var contact = contact
contact.isBlocked = true
_ = try? session.dbManager.saveContact(contact)
}
func showRoundFrom(_ roundURL: String?) {
if let urlString = roundURL, !urlString.isEmpty {
navigationRoutes.send(.webview(urlString))
......
......@@ -2,9 +2,10 @@ import UIKit
import Shared
final class SheetView: UIView {
let stack = UIStackView()
let clear = SheetButton()
let details = SheetButton()
let stackView = UIStackView()
let clearButton = SheetButton()
let reportButton = SheetButton()
let detailsButton = SheetButton()
init() {
super.init(frame: .zero)
......@@ -13,23 +14,28 @@ final class SheetView: UIView {
layer.masksToBounds = true
backgroundColor = Asset.neutralWhite.color
clear.image.image = Asset.chatListDeleteSwipe.image
clear.title.text = Localized.Chat.SheetMenu.clear
clearButton.image.image = Asset.chatListDeleteSwipe.image
clearButton.title.text = Localized.Chat.SheetMenu.clear
details.tintColor = Asset.neutralDark.color
details.image.image = Asset.searchUsername.image
details.title.text = Localized.Chat.SheetMenu.details
detailsButton.tintColor = Asset.neutralDark.color
detailsButton.image.image = Asset.searchUsername.image
detailsButton.title.text = Localized.Chat.SheetMenu.details
stack.axis = .vertical
stack.distribution = .fillEqually
stack.addArrangedSubview(clear)
stack.addArrangedSubview(details)
addSubview(stack)
reportButton.tintColor = Asset.accentDanger.color
reportButton.image.image = Asset.searchUsername.image
reportButton.title.text = Localized.Chat.SheetMenu.report
stack.snp.makeConstraints { make in
make.top.equalToSuperview().offset(25)
make.left.right.equalToSuperview()
make.bottom.equalTo(safeAreaLayoutGuide)
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)
}
}
......
......@@ -107,7 +107,7 @@ final class ChatListViewModel {
.confirmationFailed,
.verificationFailed,
.verificationInProgress
])
], isBlocked: false, isBanned: false)
return Publishers.CombineLatest(
session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(),
......@@ -127,7 +127,9 @@ final class ChatListViewModel {
ChatInfo.Query(
contactChatInfoQuery: .init(
userId: session.myId,
authStatus: [.friend]
authStatus: [.friend],
isBlocked: false,
isBanned: false
),
groupChatInfoQuery: GroupChatInfo.Query(
authStatus: [.participating]
......
......@@ -8,7 +8,9 @@ final class ContactListViewModel {
@Dependency private var session: SessionType
var contacts: AnyPublisher<[Contact], Never> {
session.dbManager.fetchContactsPublisher(.init(authStatus: [.friend]))
let query = Contact.Query(authStatus: [.friend], isBlocked: false, isBanned: false)
return session.dbManager.fetchContactsPublisher(query)
.assertNoFailure()
.map { $0.filter { $0.id != self.session.myId }}
.eraseToAnyPublisher()
......@@ -22,7 +24,7 @@ final class ContactListViewModel {
.confirmationFailed,
.verificationFailed,
.verificationInProgress
])
], isBlocked: false, isBanned: false)
return Publishers.CombineLatest(
session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(),
......
......@@ -42,7 +42,7 @@ final class CreateGroupViewModel {
// MARK: Lifecycle
init() {
session.dbManager.fetchContactsPublisher(.init(authStatus: [.friend]))
session.dbManager.fetchContactsPublisher(.init(authStatus: [.friend], isBlocked: false, isBanned: false))
.assertNoFailure()
.map { $0.filter { $0.id != self.session.myId }}
.map { $0.sorted(by: { $0.username! < $1.username! })}
......
......@@ -19,7 +19,7 @@ final class MenuViewModel {
.confirmationFailed,
.verificationFailed,
.verificationInProgress
])
], isBlocked: false, isBanned: false)
return Publishers.CombineLatest(
session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(),
......
......@@ -65,7 +65,7 @@ final class RequestsReceivedViewModel {
.verified,
.verificationFailed,
.verificationInProgress
])
], isBlocked: false, isBanned: false)
let groupStream = session.dbManager.fetchGroupsPublisher(groupsQuery).assertNoFailure()
let contactsStream = session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure()
......
......@@ -36,7 +36,7 @@ final class RequestsSentViewModel {
let query = Contact.Query(authStatus: [
.requested,
.requesting
])
], isBlocked: false, isBanned: false)
session.dbManager.fetchContactsPublisher(query)
.assertNoFailure()
......
......@@ -144,7 +144,7 @@ final class SearchLeftViewModel {
var snapshot = SearchSnapshot()
if var user = user {
if let contact = try? session.dbManager.fetchContacts(.init(id: [user.id])).first {
if let contact = try? session.dbManager.fetchContacts(.init(id: [user.id], isBlocked: false, isBanned: false)).first {
user.authStatus = contact.authStatus
}
......
......@@ -324,6 +324,8 @@ public enum Localized {
public static let delete = Localized.tr("Localizable", "chat.bubbleMenu.delete")
/// Reply
public static let reply = Localized.tr("Localizable", "chat.bubbleMenu.reply")
/// Report
public static let report = Localized.tr("Localizable", "chat.bubbleMenu.report")
/// Retry
public static let retry = Localized.tr("Localizable", "chat.bubbleMenu.retry")
/// Select
......@@ -351,6 +353,16 @@ public enum Localized {
/// All
public static let deleteAll = Localized.tr("Localizable", "chat.menu.deleteAll")
}
public enum Report {
/// Confirm and Report
public static let action = Localized.tr("Localizable", "chat.report.action")
/// Cancel
public static let cancel = Localized.tr("Localizable", "chat.report.cancel")
/// Reporting this user will block them, delete them from your connections and you won’t see direct messages from them again. In case this user is marked as banned user by us you won’t also see any new group chat msgs from this user
public static let subtitle = Localized.tr("Localizable", "chat.report.subtitle")
/// Report user
public static let title = Localized.tr("Localizable", "chat.report.title")
}
public enum RetrySheet {
/// Cancel
public static let cancel = Localized.tr("Localizable", "chat.retrySheet.cancel")
......@@ -370,6 +382,8 @@ public enum Localized {
public static let clear = Localized.tr("Localizable", "chat.sheetMenu.clear")
/// View contact profile
public static let details = Localized.tr("Localizable", "chat.sheetMenu.details")
/// Report user
public static let report = Localized.tr("Localizable", "chat.sheetMenu.report")
}
}
......
......@@ -114,6 +114,8 @@
= "Select";
"chat.bubbleMenu.retry"
= "Retry";
"chat.bubbleMenu.report"
= "Report";
"chat.e2e.placeholder"
= "You and %@ now have a #quantum-secure#, completely private channel for messaging.\n#Say hello#!";
......@@ -125,6 +127,8 @@
= "Clear chat";
"chat.sheetMenu.details"
= "View contact profile";
"chat.sheetMenu.report"
= "Report user";
"chat.retrySheet.retry"
= "Try again";
"chat.retrySheet.delete"
......@@ -174,6 +178,17 @@
"chat.clear.cancel"
= "Cancel";
// ChatFeature - Report
"chat.report.title"
= "Report user";
"chat.report.subtitle"
= "Reporting this user will block them, delete them from your connections and you won’t see direct messages from them again. In case this user is marked as banned user by us you won’t also see any new group chat msgs from this user";
"chat.report.action"
= "Confirm and Report";
"chat.report.cancel"
= "Cancel";
// ScanFeature
"scan.status.reading"
......
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