diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ChatFeature.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/ChatFeature.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..79ecae98fe19f74f098bcfc46a1b8bcbcf078ef5
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/ChatFeature.xcscheme
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1340"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "ChatFeature"
+               BuildableName = "ChatFeature"
+               BlueprintName = "ChatFeature"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      codeCoverageEnabled = "YES">
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "ChatFeatureTests"
+               BuildableName = "ChatFeatureTests"
+               BlueprintName = "ChatFeatureTests"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "ChatFeature"
+            BuildableName = "ChatFeature"
+            BlueprintName = "ChatFeature"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/Package.swift b/Package.swift
index b8f43d824ae319c2f02e73dbdc273420c745b043..f5678619dec83e0af6ca965e92d6303b0bc6f373 100644
--- a/Package.swift
+++ b/Package.swift
@@ -52,6 +52,7 @@ let package = Package(
         .library(name: "GoogleDriveFeature", targets: ["GoogleDriveFeature"]),
         .library(name: "ContactListFeature", targets: ["ContactListFeature"]),
         .library(name: "DependencyInjection", targets: ["DependencyInjection"]),
+        .library(name: "ReportingFeature", targets: ["ReportingFeature"]),
     ],
     dependencies: [
         .package(
@@ -170,6 +171,7 @@ let package = Package(
                 .target(name: "ChatListFeature"),
                 .target(name: "SettingsFeature"),
                 .target(name: "RequestsFeature"),
+                .target(name: "ReportingFeature"),
                 .target(name: "OnboardingFeature"),
                 .target(name: "GoogleDriveFeature"),
                 .target(name: "ContactListFeature"),
@@ -462,6 +464,7 @@ let package = Package(
                 .target(name: "Presentation"),
                 .target(name: "DrawerFeature"),
                 .target(name: "ChatInputFeature"),
+                .target(name: "ReportingFeature"),
                 .target(name: "DependencyInjection"),
                 .product(name: "ChatLayout", package: "ChatLayout"),
                 .product(name: "DifferenceKit", package: "DifferenceKit"),
@@ -510,8 +513,8 @@ let package = Package(
                 .target(name: "Permissions"),
                 .target(name: "DropboxFeature"),
                 .target(name: "VersionChecking"),
+                .target(name: "ReportingFeature"),
                 .target(name: "DependencyInjection"),
-                .product(name: "SwiftCSV", package: "SwiftCSV"),
             ]
         ),
         .target(
@@ -519,7 +522,8 @@ let package = Package(
             dependencies: [
                 .target(name: "Theme"),
                 .target(name: "Shared"),
-                .target(name: "Defaults")
+                .target(name: "Defaults"),
+                .target(name: "Presentation"),
             ]
         ),
         .target(
@@ -739,5 +743,17 @@ let package = Package(
                 .product(name: "CustomDump", package: "swift-custom-dump"),
             ]
         ),
+        .target(
+            name: "ReportingFeature",
+            dependencies: [
+                .target(name: "DrawerFeature"),
+                .target(name: "Shared"),
+                .product(name: "SwiftCSV", package: "SwiftCSV"),
+                .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
+            ],
+            resources: [
+                .process("Resources"),
+            ]
+        ),
     ]
 )
diff --git a/Sources/App/DependencyRegistrator.swift b/Sources/App/DependencyRegistrator.swift
index e24711457082d4ee7ba1bb51005e73762f656c10..1071b031ef96a87ac2e076d1fa6f52d4ec12e232 100644
--- a/Sources/App/DependencyRegistrator.swift
+++ b/Sources/App/DependencyRegistrator.swift
@@ -26,6 +26,7 @@ import CrashReporting
 import NetworkMonitor
 import DropboxFeature
 import VersionChecking
+import ReportingFeature
 import GoogleDriveFeature
 import DependencyInjection
 
@@ -102,6 +103,11 @@ struct DependencyRegistrator {
     static private func registerCommonDependencies() {
         container.register(Voxophone())
         container.register(BackupService())
+        container.register(MakeAppScreenshot.live)
+        container.register(SendReport.live)
+        container.register(FetchBannedList.live)
+        container.register(ProcessBannedList.live)
+        container.register(MakeReportDrawer.live)
 
         // MARK: Isolated
 
diff --git a/Sources/ChatFeature/Controllers/GroupChatController.swift b/Sources/ChatFeature/Controllers/GroupChatController.swift
index 2411786865669256bff58b618488bf5fbab36aa4..c6b0d6d224a35e3127b297267f5955cecb8be541 100644
--- a/Sources/ChatFeature/Controllers/GroupChatController.swift
+++ b/Sources/ChatFeature/Controllers/GroupChatController.swift
@@ -1,3 +1,4 @@
+import HUD
 import UIKit
 import Theme
 import Models
@@ -6,8 +7,10 @@ import Combine
 import XXModels
 import Voxophone
 import ChatLayout
+import Integration
 import DrawerFeature
 import DifferenceKit
+import ReportingFeature
 import ChatInputFeature
 import DependencyInjection
 
@@ -19,7 +22,11 @@ typealias OutgoingFailedGroupTextCell = CollectionCell<FlexibleSpace, StackMessa
 typealias OutgoingFailedGroupReplyCell = CollectionCell<FlexibleSpace, ReplyStackMessageView>
 
 public final class GroupChatController: UIViewController {
+    @Dependency private var hud: HUD
+    @Dependency private var session: SessionType
     @Dependency private var coordinator: ChatCoordinating
+    @Dependency private var makeReportDrawer: MakeReportDrawer
+    @Dependency private var makeAppScreenshot: MakeAppScreenshot
     @Dependency private var statusBarController: StatusBarStyleControlling
 
     private let members: MembersController
@@ -32,7 +39,6 @@ public final class GroupChatController: UIViewController {
     private let viewModel: GroupChatViewModel
     private let layoutDelegate = LayoutDelegate()
     private var cancellables = Set<AnyCancellable>()
-    private var drawerCancellables = Set<AnyCancellable>()
     private var sections = [ArraySection<ChatSection, Message>]()
     private var currentInterfaceActions = SetActor<Set<InterfaceActions>, ReactionTypes>()
 
@@ -176,6 +182,17 @@ public final class GroupChatController: UIViewController {
                 }
             }.store(in: &cancellables)
 
+        viewModel.hudPublisher
+            .receive(on: DispatchQueue.main)
+            .sink { [hud] in hud.update(with: $0) }
+            .store(in: &cancellables)
+
+        viewModel.reportPopupPublisher
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] contact in
+                presentReportDrawer(contact)
+            }.store(in: &cancellables)
+
         viewModel.messages
             .receive(on: DispatchQueue.main)
             .sink { [unowned self] sections in
@@ -229,6 +246,19 @@ public final class GroupChatController: UIViewController {
         coordinator.toMembersList(members, from: self)
     }
 
+    private func presentReportDrawer(_ contact: Contact) {
+        var config = MakeReportDrawer.Config()
+        config.onReport = { [weak self] in
+            guard let self = self else { return }
+            let screenshot = try! self.makeAppScreenshot()
+            self.viewModel.report(contact: contact, screenshot: screenshot) {
+                self.collectionView.reloadData()
+            }
+        }
+        let drawer = makeReportDrawer(config)
+        coordinator.toDrawer(drawer, from: self)
+    }
+
     private func makeWaitingRoundDrawer() -> UIViewController {
         let text = DrawerText(
             font: Fonts.Mulish.semiBold.font(size: 14.0),
@@ -248,11 +278,8 @@ public final class GroupChatController: UIViewController {
         button.action
             .receive(on: DispatchQueue.main)
             .sink { [weak drawer] in
-                drawer?.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
+                drawer?.dismiss(animated: true)
+            }.store(in: &drawer.cancellables)
 
         return drawer
     }
@@ -317,7 +344,7 @@ extension GroupChatController: UICollectionViewDataSource {
         cellForItemAt indexPath: IndexPath
     ) -> UICollectionViewCell {
 
-        let item = sections[indexPath.section].elements[indexPath.item]
+        var item = sections[indexPath.section].elements[indexPath.item]
         let canReply: () -> Bool = {
             (item.status == .sent || item.status == .received) && item.networkId != nil
         }
@@ -330,7 +357,30 @@ extension GroupChatController: UICollectionViewDataSource {
         let showRound: (String?) -> Void = viewModel.showRoundFrom(_:)
         let replyContent: (Data) -> (String, String) = viewModel.getReplyContent(for:)
 
+        var isSenderBanned = false
+
+        if let sender = try? session.dbManager.fetchContacts(.init(id: [item.senderId])).first {
+            isSenderBanned = sender.isBanned
+        }
+
         if item.status == .received {
+            guard isSenderBanned == false else {
+                item.text = "This user has been banned"
+
+                let cell: IncomingGroupTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+                Bubbler.buildGroup(
+                    bubble: cell.leftView,
+                    with: item,
+                    with: "Banned user"
+                )
+
+                cell.canReply = false
+                cell.performReply = {}
+                cell.leftView.didTapShowRound = {}
+
+                return cell
+            }
+
             if let replyMessageId = item.replyMessageId {
                 let cell: IncomingGroupReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
@@ -544,6 +594,10 @@ extension GroupChatController: UICollectionViewDelegate {
                 self?.viewModel.didRequestDelete([item])
             }
 
+            let report = UIAction(title: Localized.Chat.BubbleMenu.report, state: .off) { [weak self] _ in
+                self?.viewModel.didRequestReport(item)
+            }
+
             let retry = UIAction(title: Localized.Chat.BubbleMenu.retry, state: .off) { [weak self] _ in
                 self?.viewModel.retry(item)
             }
@@ -555,7 +609,7 @@ extension GroupChatController: UICollectionViewDelegate {
             } else if item.status == .sending {
                 menu = UIMenu(title: "", children: [copy])
             } else {
-                menu = UIMenu(title: "", children: [copy, reply, delete])
+                menu = UIMenu(title: "", children: [copy, reply, delete, report])
             }
 
             return menu
diff --git a/Sources/ChatFeature/Controllers/SheetController.swift b/Sources/ChatFeature/Controllers/SheetController.swift
index f0f4fde8279ddd39cf785ec6f1dd2e310563d4b6..974f086d5df232b982c0ab101b0f04f48f1bc578 100644
--- a/Sources/ChatFeature/Controllers/SheetController.swift
+++ b/Sources/ChatFeature/Controllers/SheetController.swift
@@ -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)
     }
 }
diff --git a/Sources/ChatFeature/Controllers/SingleChatController.swift b/Sources/ChatFeature/Controllers/SingleChatController.swift
index d1d7785af5663f567a8803fce22aa107cbfe0934..03b182e5bb886051eb9c22e352fd65ef52f0619b 100644
--- a/Sources/ChatFeature/Controllers/SingleChatController.swift
+++ b/Sources/ChatFeature/Controllers/SingleChatController.swift
@@ -12,6 +12,7 @@ import ChatLayout
 import DrawerFeature
 import DifferenceKit
 import ChatInputFeature
+import ReportingFeature
 import DependencyInjection
 import ScrollViewController
 
@@ -28,6 +29,8 @@ public final class SingleChatController: UIViewController {
     @Dependency private var logger: XXLogger
     @Dependency private var voxophone: Voxophone
     @Dependency private var coordinator: ChatCoordinating
+    @Dependency private var makeReportDrawer: MakeReportDrawer
+    @Dependency private var makeAppScreenshot: MakeAppScreenshot
     @Dependency private var statusBarController: StatusBarStyleControlling
 
     lazy private var infoView = UIControl()
@@ -46,7 +49,6 @@ public final class SingleChatController: UIViewController {
     private let viewModel: SingleChatViewModel
     private let layoutDelegate = LayoutDelegate()
     private var cancellables = Set<AnyCancellable>()
-    private var drawerCancellables = Set<AnyCancellable>()
     private var sections = [ArraySection<ChatSection, Message>]()
     private var currentInterfaceActions: SetActor<Set<InterfaceActions>, ReactionTypes> = SetActor()
 
@@ -187,6 +189,7 @@ public final class SingleChatController: UIViewController {
 
         navigationItem.rightBarButtonItem = UIBarButtonItem(customView: moreButton)
         navigationItem.leftBarButtonItem = UIBarButtonItem(customView: infoView)
+        navigationItem.leftItemsSupplementBackButton = true
     }
 
     private func setupInputController() {
@@ -249,6 +252,8 @@ public final class SingleChatController: UIViewController {
                     presentDeleteAllDrawer()
                 case .details:
                     coordinator.toContact(viewModel.contact, from: self)
+                case .report:
+                    presentReportDrawer()
                 }
             }.store(in: &cancellables)
 
@@ -263,6 +268,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)
@@ -373,16 +384,26 @@ public final class SingleChatController: UIViewController {
 
         button.action
             .receive(on: DispatchQueue.main)
-            .sink { [weak drawer] in
-                drawer?.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
+            .sink { [unowned drawer] in drawer.dismiss(animated: true) }
+            .store(in: &drawer.cancellables)
 
         return drawer
     }
 
+    private func presentReportDrawer() {
+        var config = MakeReportDrawer.Config()
+        config.onReport = { [weak self] in
+            guard let self = self else { return }
+            let screenshot = try! self.makeAppScreenshot()
+            self.viewModel.report(screenshot: screenshot) { success in
+                guard success else { return }
+                self.navigationController?.popViewController(animated: true)
+            }
+        }
+        let drawer = makeReportDrawer(config)
+        coordinator.toDrawer(drawer, from: self)
+    }
+
     private func presentDeleteAllDrawer() {
         let clearButton = CapsuleButton()
         clearButton.setStyle(.red)
@@ -416,21 +437,17 @@ public final class SingleChatController: UIViewController {
 
         clearButton.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.viewModel.didRequestDeleteAll()
+            .sink { [unowned drawer, weak self] in
+                drawer.dismiss(animated: true) {
+                    self?.viewModel.didRequestDeleteAll()
                 }
-            }.store(in: &drawerCancellables)
+            }
+            .store(in: &drawer.cancellables)
 
         cancelButton.publisher(for: .touchUpInside)
             .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    self?.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
+            .sink { [unowned drawer] in drawer.dismiss(animated: true) }
+            .store(in: &drawer.cancellables)
 
         coordinator.toDrawer(drawer, from: self)
     }
@@ -512,11 +529,21 @@ extension SingleChatController: KeyboardListenerDelegate {
     }
 
     func keyboardWillChangeFrame(info: KeyboardInfo) {
-        let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first
+        let keyWindow: UIWindow? = UIApplication.shared.connectedScenes
+            .filter { $0.activationState == .foregroundActive }
+            .compactMap { $0 as? UIWindowScene }
+            .first?
+            .windows
+            .first(where: \.isKeyWindow)
+
+        guard let keyWindow = keyWindow else {
+            fatalError("[keyboardWillChangeFrame]: Couldn't get key window")
+        }
+
+        let keyboardFrame = keyWindow.convert(info.frameEnd, to: view)
 
         guard !currentInterfaceActions.options.contains(.changingFrameSize),
               collectionView.contentInsetAdjustmentBehavior != .never,
-              let keyboardFrame = keyWindow?.convert(info.frameEnd, to: view),
               collectionView.convert(collectionView.bounds, to: keyWindow).maxY > info.frameEnd.minY else { return }
 
         currentInterfaceActions.options.insert(.changingKeyboardFrame)
@@ -632,7 +659,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 })
         }
     }
diff --git a/Sources/ChatFeature/Helpers/CellConfigurator.swift b/Sources/ChatFeature/Helpers/CellConfigurator.swift
index 4f0ad3ca31e71223f57f6b9593b918c1f745c2e1..d9b61fd9d3be46405ab170259dbe82aa0b1961a0 100644
--- a/Sources/ChatFeature/Helpers/CellConfigurator.swift
+++ b/Sources/ChatFeature/Helpers/CellConfigurator.swift
@@ -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:
diff --git a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
index aa1dbefc8ed34f582d84df609d70b65631c69013..e9ca955393db78a4ccfc23bb52089032647bfcde 100644
--- a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
+++ b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
@@ -1,10 +1,15 @@
+import HUD
 import UIKit
 import Models
+import Shared
 import Combine
 import XXModels
+import Defaults
 import Foundation
 import Integration
+import ToastFeature
 import DifferenceKit
+import ReportingFeature
 import DependencyInjection
 
 enum GroupChatNavigationRoutes: Equatable {
@@ -14,6 +19,18 @@ enum GroupChatNavigationRoutes: Equatable {
 
 final class GroupChatViewModel {
     @Dependency private var session: SessionType
+    @Dependency private var sendReport: SendReport
+    @Dependency private var toastController: ToastController
+
+    @KeyObject(.username, defaultValue: nil) var username: String?
+
+    var hudPublisher: AnyPublisher<HUDStatus, Never> {
+        hudSubject.eraseToAnyPublisher()
+    }
+
+    var reportPopupPublisher: AnyPublisher<Contact, Never> {
+        reportPopupSubject.eraseToAnyPublisher()
+    }
 
     var replyPublisher: AnyPublisher<(String, String), Never> {
         replySubject.eraseToAnyPublisher()
@@ -26,6 +43,8 @@ final class GroupChatViewModel {
     let info: GroupInfo
     private var stagedReply: Reply?
     private var cancellables = Set<AnyCancellable>()
+    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
+    private let reportPopupSubject = PassthroughSubject<Contact, Never>()
     private let replySubject = PassthroughSubject<(String, String), Never>()
     private let routesSubject = PassthroughSubject<GroupChatNavigationRoutes, Never>()
 
@@ -63,6 +82,12 @@ final class GroupChatViewModel {
         _ = try? session.dbManager.deleteMessages(.init(id: Set(messages.map(\.id))))
     }
 
+    func didRequestReport(_ message: Message) {
+        if let contact = try? session.dbManager.fetchContacts(.init(id: [message.senderId])).first {
+            reportPopupSubject.send(contact)
+        }
+    }
+
     func send(_ text: String) {
         session.send(.init(
             text: text.trimmingCharacters(in: .whitespacesAndNewlines),
@@ -103,7 +128,13 @@ final class GroupChatViewModel {
             return "[DELETED]"
         }
 
-        return (contact.nickname ?? contact.username) ?? "Fetching username..."
+        var name = (contact.nickname ?? contact.username) ?? "Fetching username..."
+
+        if contact.isBlocked {
+            name = "\(name) (Blocked)"
+        }
+
+        return name
     }
 
     func didRequestReply(_ message: Message) {
@@ -111,4 +142,57 @@ final class GroupChatViewModel {
         stagedReply = Reply(messageId: networkId, senderId: message.senderId)
         replySubject.send(getReplyContent(for: networkId))
     }
+
+    func report(contact: Contact, screenshot: UIImage, completion: @escaping () -> Void) {
+        let report = Report(
+            sender: .init(
+                userId: contact.id.base64EncodedString(),
+                username: contact.username!
+            ),
+            recipient: .init(
+                userId: session.myId.base64EncodedString(),
+                username: username!
+            ),
+            type: .group,
+            screenshot: screenshot.pngData()!,
+            partyName: info.group.name,
+            partyBlob: info.group.id.base64EncodedString(),
+            partyMembers: info.members.map { Report.ReportUser(
+                userId: $0.id.base64EncodedString(),
+                username: $0.username ?? "")
+            }
+        )
+
+        hudSubject.send(.on)
+        sendReport(report) { result in
+            switch result {
+            case .failure(let error):
+                DispatchQueue.main.async {
+                    self.hudSubject.send(.error(.init(with: error)))
+                }
+
+            case .success(_):
+                self.blockContact(contact)
+                DispatchQueue.main.async {
+                    self.hudSubject.send(.none)
+                    self.presentReportConfirmation(contact: contact)
+                    completion()
+                }
+            }
+        }
+    }
+
+    private func blockContact(_ contact: Contact) {
+        var contact = contact
+        contact.isBlocked = true
+        _ = try? session.dbManager.saveContact(contact)
+    }
+
+    private func presentReportConfirmation(contact: Contact) {
+        let name = (contact.nickname ?? contact.username) ?? "the contact"
+        toastController.enqueueToast(model: .init(
+            title: "Your report has been sent and \(name) is now blocked.",
+            leftImage: Asset.requestSentToaster.image
+        ))
+    }
 }
diff --git a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
index f51a893610402c317f938f0b29445317de9bb6d4..8ba933ad52ff63b6beca403e84bd688ccb043ac9 100644
--- a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
+++ b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
@@ -7,8 +7,11 @@ import XXLogger
 import XXModels
 import Foundation
 import Integration
+import Defaults
 import Permissions
+import ToastFeature
 import DifferenceKit
+import ReportingFeature
 import DependencyInjection
 
 enum SingleChatNavigationRoutes: Equatable {
@@ -22,10 +25,14 @@ enum SingleChatNavigationRoutes: Equatable {
     case webview(String)
 }
 
-final class SingleChatViewModel {
+final class SingleChatViewModel: NSObject {
     @Dependency private var logger: XXLogger
     @Dependency private var session: SessionType
     @Dependency private var permissions: PermissionHandling
+    @Dependency private var toastController: ToastController
+    @Dependency private var sendReport: SendReport
+
+    @KeyObject(.username, defaultValue: nil) var username: String?
 
     var contact: Contact { contactSubject.value }
     private var stagedReply: Reply?
@@ -34,6 +41,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 +52,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>]()
@@ -66,6 +78,7 @@ final class SingleChatViewModel {
 
     init(_ contact: Contact) {
         self.contactSubject = .init(contact)
+        super.init()
 
         updateRecentState(contact)
 
@@ -133,11 +146,11 @@ final class SingleChatViewModel {
         guard let id = message.id else { return }
         session.retryMessage(id)
     }
-   
+
     func didNavigateSomewhere() {
         navigationRoutes.send(.none)
     }
-    
+
     @discardableResult
     func didTest(permission: PermissionType) -> Bool {
         switch permission {
@@ -172,6 +185,10 @@ final class SingleChatViewModel {
         didRequestDelete([model])
     }
 
+    func didRequestReport(_: Message) {
+        reportPopupSubject.send()
+    }
+
     func abortReply() {
         stagedReply = nil
     }
@@ -237,4 +254,52 @@ final class SingleChatViewModel {
     func section(at index: Int) -> ChatSection? {
         sectionsRelay.value.count > 0 ? sectionsRelay.value[index].model : nil
     }
+
+    func report(screenshot: UIImage, completion: @escaping (Bool) -> Void) {
+        let report = Report(
+            sender: .init(
+                userId: contact.id.base64EncodedString(),
+                username: contact.username!
+            ),
+            recipient: .init(
+                userId: session.myId.base64EncodedString(),
+                username: username!
+            ),
+            type: .dm,
+            screenshot: screenshot.pngData()!
+        )
+
+        hudRelay.send(.on)
+        sendReport(report) { result in
+            switch result {
+            case .failure(let error):
+                DispatchQueue.main.async {
+                    self.hudRelay.send(.error(.init(with: error)))
+                    completion(false)
+                }
+
+            case .success(_):
+                self.blockContact()
+                DispatchQueue.main.async {
+                    self.hudRelay.send(.none)
+                    self.presentReportConfirmation()
+                    completion(true)
+                }
+            }
+        }
+    }
+
+    private func blockContact() {
+        var contact = contact
+        contact.isBlocked = true
+        _ = try? session.dbManager.saveContact(contact)
+    }
+
+    private func presentReportConfirmation() {
+        let name = (contact.nickname ?? contact.username) ?? "the contact"
+        toastController.enqueueToast(model: .init(
+            title: "Your report has been sent and \(name) is now blocked.",
+            leftImage: Asset.requestSentToaster.image
+        ))
+    }
 }
diff --git a/Sources/ChatFeature/Views/SheetView.swift b/Sources/ChatFeature/Views/SheetView.swift
index 86459e76e8947df8728e3daaa4569d62e8e5c7cd..a4cdfeb1348a6cd8b369115d6c34272df7649d87 100644
--- a/Sources/ChatFeature/Views/SheetView.swift
+++ b/Sources/ChatFeature/Views/SheetView.swift
@@ -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)
         }
     }
 
diff --git a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
index f481b96fb7e3e14b74096919090bf1df8c96c840..d3918b04206d620af85be2266599369bf9ab85ea 100644
--- a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
+++ b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
@@ -37,7 +37,8 @@ final class ChatListViewModel {
     }
 
     var recentsPublisher: AnyPublisher<RecentsSnapshot, Never> {
-        session.dbManager.fetchContactsPublisher(.init(isRecent: true))
+        let query = Contact.Query(isRecent: true, isBlocked: false, isBanned: false)
+        return session.dbManager.fetchContactsPublisher(query)
             .assertNoFailure()
             .map {
             let section = SectionId()
@@ -49,8 +50,13 @@ final class ChatListViewModel {
     }
 
     var searchPublisher: AnyPublisher<SearchSnapshot, Never> {
-        Publishers.CombineLatest3(
-            session.dbManager.fetchContactsPublisher(.init()).assertNoFailure(),
+        let contactsStream = session.dbManager
+            .fetchContactsPublisher(.init(isBlocked: false, isBanned: false))
+            .assertNoFailure()
+            .map { $0.filter { $0.id != self.session.myId }}
+
+        return Publishers.CombineLatest3(
+            contactsStream,
             chatsPublisher,
             searchSubject
                 .removeDuplicates()
@@ -107,7 +113,7 @@ final class ChatListViewModel {
             .confirmationFailed,
             .verificationFailed,
             .verificationInProgress
-        ])
+        ], isBlocked: false, isBanned: false)
 
         return Publishers.CombineLatest(
             session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(),
@@ -127,10 +133,13 @@ final class ChatListViewModel {
             ChatInfo.Query(
                 contactChatInfoQuery: .init(
                     userId: session.myId,
-                    authStatus: [.friend]
+                    authStatus: [.friend],
+                    isBlocked: false,
+                    isBanned: false
                 ),
                 groupChatInfoQuery: GroupChatInfo.Query(
-                    authStatus: [.participating]
+                    authStatus: [.participating],
+                    excludeBannedContactsMessages: true
                 ),
                 groupQuery: Group.Query(
                     withMessages: false,
diff --git a/Sources/ContactFeature/Controllers/ContactController.swift b/Sources/ContactFeature/Controllers/ContactController.swift
index c583654e08d1ec4ae54180a8f8dc6079c6aee9c6..30dd3f5e48444176c274a0f6f18502b12e172c17 100644
--- a/Sources/ContactFeature/Controllers/ContactController.swift
+++ b/Sources/ContactFeature/Controllers/ContactController.swift
@@ -33,7 +33,10 @@ public final class ContactController: UIViewController {
         navigationItem.backButtonTitle = ""
         statusBarController.style.send(.lightContent)
         navigationController?.navigationBar
-            .customize(backgroundColor: Asset.neutralBody.color)
+            .customize(
+                backgroundColor: Asset.neutralBody.color,
+                tint: Asset.neutralWhite.color
+            )
     }
 
     public override func viewSafeAreaInsetsDidChange() {
diff --git a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
index 0467446065b81d8f52a0f9517948160e623de1e2..d0915822da605d047de525c55470c6802d983d1f 100644
--- a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
+++ b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
@@ -8,21 +8,23 @@ 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()
     }
 
     var requestCount: AnyPublisher<Int, Never> {
-        let groupQuery = Group.Query(authStatus: [.pending])
+        let groupQuery = Group.Query(authStatus: [.pending], isLeaderBlocked: false, isLeaderBanned: false)
         let contactsQuery = Contact.Query(authStatus: [
             .verified,
             .confirming,
             .confirmationFailed,
             .verificationFailed,
             .verificationInProgress
-        ])
+        ], isBlocked: false, isBanned: false)
 
         return Publishers.CombineLatest(
             session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(),
diff --git a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
index a8f94de8042f3cd7f7d0da14a9de703f217f573b..668b396b4f3955ceb8fb494234cd3cadf4f7b852 100644
--- a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
+++ b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
@@ -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! })}
diff --git a/Sources/DrawerFeature/DrawerController.swift b/Sources/DrawerFeature/DrawerController.swift
index d2eba3624041cd025d10e26362c6c9b8c6806f4f..d907c26ba78956a919bcf58034b6e50720c0456a 100644
--- a/Sources/DrawerFeature/DrawerController.swift
+++ b/Sources/DrawerFeature/DrawerController.swift
@@ -1,9 +1,10 @@
 import UIKit
+import Combine
 
 public final class DrawerController: UIViewController {
     lazy private var screenView = DrawerView()
-
     private let content: [DrawerItem]
+    public var cancellables = Set<AnyCancellable>()
 
     public init(with content: [DrawerItem]) {
         self.content = content
diff --git a/Sources/Integration/Session/Session+Contacts.swift b/Sources/Integration/Session/Session+Contacts.swift
index 7bbe5254ab00b17b8053323586347ef819f10ff1..d30f78c24e014cb411b0608638904d5664f626aa 100644
--- a/Sources/Integration/Session/Session+Contacts.swift
+++ b/Sources/Integration/Session/Session+Contacts.swift
@@ -244,15 +244,15 @@ extension Session {
         ///
         //try dbManager.deleteContact(contact)
 
-        _ = try? dbManager.deleteMessages(Message.Query(chat: .direct(myId, contact.id)))
         var contact = contact
         contact.email = nil
         contact.phone = nil
         contact.photo = nil
         contact.isRecent = false
         contact.marshaled = nil
+        contact.isBlocked = true
         contact.authStatus = .stranger
         contact.nickname = contact.username
-        _ = try? dbManager.saveContact(contact)
+        _ = try! dbManager.saveContact(contact)
     }
 }
diff --git a/Sources/Integration/Session/Session+Group.swift b/Sources/Integration/Session/Session+Group.swift
index a3cf68964a307dceecb77893eb5f67fe15367bab..47652ee94cc4ec7d870005ff69d2d21ea3839ce3 100644
--- a/Sources/Integration/Session/Session+Group.swift
+++ b/Sources/Integration/Session/Session+Group.swift
@@ -53,7 +53,7 @@ extension Session {
                         recipientId: nil,
                         groupId: group.id,
                         date: group.createdAt,
-                        status: .received,
+                        status: .sent,
                         isUnread: false,
                         text: welcome,
                         replyMessageId: nil,
diff --git a/Sources/Integration/Session/Session.swift b/Sources/Integration/Session/Session.swift
index fb14ed89cb8a7a9a2203a166fba55c322dfa84ea..317a4bae5c3facf4a31635d6080ede594eb40fd9 100644
--- a/Sources/Integration/Session/Session.swift
+++ b/Sources/Integration/Session/Session.swift
@@ -442,6 +442,7 @@ public final class Session: SessionType {
         client.messages
             .sink { [unowned self] in
                 if var contact = try? dbManager.fetchContacts(.init(id: [$0.senderId])).first {
+                    guard contact.isBanned == false else { return }
                     contact.isRecent = false
                     _ = try? dbManager.saveContact(contact)
                 }
@@ -459,6 +460,12 @@ public final class Session: SessionType {
                     return
                 }
 
+                if let contact = try! dbManager.fetchContacts(.init(id: [request.0.leaderId])).first {
+                    if contact.isBanned || contact.isBlocked {
+                        return
+                    }
+                }
+
                 DispatchQueue.global().async { [weak self] in
                     self?.processGroupCreation(request.0, memberIds: request.1, welcome: request.2)
                 }
diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift
index 68f3fab87eff23ccdee9985c4bec27f3101f09df..399a87a87e183543ad4967ad805331ff4fc5b972 100644
--- a/Sources/LaunchFeature/LaunchViewModel.swift
+++ b/Sources/LaunchFeature/LaunchViewModel.swift
@@ -1,8 +1,6 @@
 import HUD
 import Shared
 import Models
-import SwiftCSV
-
 import Combine
 import Defaults
 import XXModels
@@ -10,8 +8,10 @@ import Keychain
 import Foundation
 import Integration
 import Permissions
+import ToastFeature
 import DropboxFeature
 import VersionChecking
+import ReportingFeature
 import CombineSchedulers
 import DependencyInjection
 
@@ -35,6 +35,10 @@ final class LaunchViewModel {
     @Dependency private var dropboxService: DropboxInterface
     @Dependency private var keychainHandler: KeychainHandling
     @Dependency private var permissionHandler: PermissionHandling
+    @Dependency private var fetchBannedList: FetchBannedList
+    @Dependency private var processBannedList: ProcessBannedList
+    @Dependency private var toastController: ToastController
+    @Dependency private var session: SessionType
 
     @KeyObject(.username, defaultValue: nil) var username: String?
     @KeyObject(.biometrics, defaultValue: false) var isBiometricsOn: Bool
@@ -55,8 +59,6 @@ final class LaunchViewModel {
         DispatchQueue.global().eraseToAnyScheduler()
     }()
 
-    var getSession: (String) throws -> SessionType = Session.init
-
     private var cancellables = Set<AnyCancellable>()
     private let routeSubject = PassthroughSubject<LaunchRoute, Never>()
     private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
@@ -83,75 +85,66 @@ final class LaunchViewModel {
     }
 
     func versionApproved() {
-        Task {
-            do {
-                network.writeLogs()
+        network.writeLogs()
 
-                // TODO: Retry inifitely if fails
-                let _ = try await fetchBannedList()
+        network.updateNDF { [weak self] in
+            guard let self = self else { return }
 
-                network.updateNDF { [weak self] in
-                    guard let self = self else { return }
+            switch $0 {
+            case .success(let ndf):
+                self.network.updateErrors()
 
-                    switch $0 {
-                    case .success(let ndf):
-                        self.network.updateErrors()
+                guard self.network.hasClient else {
+                    self.hudSubject.send(.none)
+                    self.routeSubject.send(.onboarding(ndf))
+                    self.dropboxService.unlink()
+                    try? self.keychainHandler.clear()
+                    return
+                }
 
-                        guard self.network.hasClient else {
-                            self.hudSubject.send(.none)
-                            self.routeSubject.send(.onboarding(ndf))
-                            self.dropboxService.unlink()
-                            try? self.keychainHandler.clear()
-                            return
-                        }
+                guard self.username != nil else {
+                    self.network.purgeFiles()
+                    self.hudSubject.send(.none)
+                    self.routeSubject.send(.onboarding(ndf))
+                    self.dropboxService.unlink()
+                    try? self.keychainHandler.clear()
+                    return
+                }
 
-                        guard self.username != nil else {
-                            self.network.purgeFiles()
-                            self.hudSubject.send(.none)
-                            self.routeSubject.send(.onboarding(ndf))
-                            self.dropboxService.unlink()
-                            try? self.keychainHandler.clear()
-                            return
-                        }
+                self.backgroundScheduler.schedule { [weak self] in
+                    guard let self = self else { return }
 
-                        self.backgroundScheduler.schedule { [weak self] in
-                            guard let self = self else { return }
+                    do {
+                        let session = try Session(ndf: ndf)
+                        DependencyInjection.Container.shared.register(session as SessionType)
 
-                            do {
-                                let session = try self.getSession(ndf)
-                                DependencyInjection.Container.shared.register(session as SessionType)
+                        self.updateBannedList {
+                            DispatchQueue.main.async {
                                 self.hudSubject.send(.none)
                                 self.checkBiometrics()
-                            } catch {
-                                self.hudSubject.send(.error(HUDError(with: error)))
                             }
                         }
-                    case .failure(let error):
-                        self.hudSubject.send(.error(HUDError(with: error)))
+                    } catch {
+                        DispatchQueue.main.async {
+                            self.hudSubject.send(.error(HUDError(with: error)))
+                        }
                     }
                 }
-            } catch {
+
+            case .failure(let error):
                 self.hudSubject.send(.error(HUDError(with: error)))
             }
         }
     }
 
     func getContactWith(userId: Data) -> Contact? {
-        guard let session = try? DependencyInjection.Container.shared.resolve() as SessionType,
-              let contact = try? session.dbManager.fetchContacts(.init(id: [userId])).first else {
-            return nil
-        }
-
-        return contact
+        let query = Contact.Query(id: [userId], isBlocked: false, isBanned: false)
+        return try! session.dbManager.fetchContacts(query).first
     }
 
     func getGroupInfoWith(groupId: Data) -> GroupInfo? {
-        guard let session: SessionType = try? DependencyInjection.Container.shared.resolve(),
-              let info = try? session.dbManager.fetchGroupInfos(.init(groupId: groupId)).first else {
-            return nil
-        }
-
-        return info
+        let query = GroupInfo.Query(groupId: groupId)
+        return try! session.dbManager.fetchGroupInfos(query).first
     }
 
     private func versionFailed(error: Error) {
@@ -209,17 +202,59 @@ final class LaunchViewModel {
         }
     }
 
-    private func fetchBannedList() async throws -> Data {
-        let url = URL(string: "https://elixxir-bins.s3.us-west-1.amazonaws.com/client/bannedUsers/banned.csv")
-        return try await withCheckedThrowingContinuation { continuation in
-            URLSession.shared.dataTask(with: url!) { data, _, error in
-                if let error = error {
-                    return continuation.resume(throwing: error)
+    private func updateBannedList(completion: @escaping () -> Void) {
+        fetchBannedList { result in
+            switch result {
+            case .failure(_):
+                DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
+                    self.updateBannedList(completion: completion)
                 }
-
-                guard let data = data else { fatalError("?") }
-                return continuation.resume(returning: data)
-            }.resume()
+            case .success(let data):
+                self.processBannedList(data, completion: completion)
+            }
         }
     }
+
+    private func processBannedList(_ data: Data, completion: @escaping () -> Void) {
+        processBannedList(
+            data: data,
+            forEach: { result in
+                switch result {
+                case .success(let userId):
+                    let query = Contact.Query(id: [userId])
+                    if var contact = try! self.session.dbManager.fetchContacts(query).first {
+                        if contact.isBanned == false {
+                            contact.isBanned = true
+                            try! self.session.dbManager.saveContact(contact)
+                            self.enqueueBanWarning(contact: contact)
+                        }
+                    } else {
+                        try! self.session.dbManager.saveContact(.init(id: userId, isBanned: true))
+                    }
+
+                case .failure(_):
+                    break
+                }
+            },
+            completion: { result in
+                switch result {
+                case .failure(_):
+                    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
+                        self.updateBannedList(completion: completion)
+                    }
+
+                case .success(_):
+                    completion()
+                }
+            }
+        )
+    }
+
+    private func enqueueBanWarning(contact: Contact) {
+        let name = (contact.nickname ?? contact.username) ?? "One of your contacts"
+        toastController.enqueueToast(model: .init(
+            title: "\(name) has been banned for offensive content.",
+            leftImage: Asset.requestSentToaster.image
+        ))
+    }
 }
diff --git a/Sources/MenuFeature/ViewModels/MenuViewModel.swift b/Sources/MenuFeature/ViewModels/MenuViewModel.swift
index b4ebd7571a0e3bafd60bb972310693af1c8fc02f..35dfd9ec60fcfbe99889d710c1078a8823b871c0 100644
--- a/Sources/MenuFeature/ViewModels/MenuViewModel.swift
+++ b/Sources/MenuFeature/ViewModels/MenuViewModel.swift
@@ -12,14 +12,14 @@ final class MenuViewModel {
     @KeyObject(.username, defaultValue: "") var username: String
 
     var requestCount: AnyPublisher<Int, Never> {
-        let groupQuery = Group.Query(authStatus: [.pending])
+        let groupQuery = Group.Query(authStatus: [.pending], isLeaderBlocked: false, isLeaderBanned: false)
         let contactsQuery = Contact.Query(authStatus: [
             .verified,
             .confirming,
             .confirmationFailed,
             .verificationFailed,
             .verificationInProgress
-        ])
+        ], isBlocked: false, isBanned: false)
 
         return Publishers.CombineLatest(
             session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(),
diff --git a/Sources/PushFeature/PushHandler.swift b/Sources/PushFeature/PushHandler.swift
index f750c575899229ba5f6212261197087755dc8442..9afec9261f1ee12d72eea3b68605ea989124895f 100644
--- a/Sources/PushFeature/PushHandler.swift
+++ b/Sources/PushFeature/PushHandler.swift
@@ -96,13 +96,6 @@ public final class PushHandler: PushHandling {
             return
         }
 
-        guard let showSender = defaults.value(forKey: Constants.usernamesSetting) as? Bool, showSender == true else {
-            pushes.map { ($0.type.unknownSenderContent!, $0) }
-                .map(contentsBuilder.build)
-                .forEach { completion($0) }
-            return
-        }
-
         let dbPath = FileManager.default
             .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
             .appendingPathComponent("xxm_database")
@@ -115,8 +108,16 @@ public final class PushHandler: PushHandling {
                 return ($0.type.unknownSenderContent!, $0)
             }
 
-            let name = (contact.nickname ?? contact.username) ?? ""
-            return ($0.type.knownSenderContent(name)!, $0)
+            if contact.isBlocked || contact.isBanned {
+                return nil
+            }
+
+            if let showSender = defaults.value(forKey: Constants.usernamesSetting) as? Bool, showSender == true {
+                let name = (contact.nickname ?? contact.username) ?? ""
+                return ($0.type.knownSenderContent(name)!, $0)
+            } else {
+                return ($0.type.unknownSenderContent!, $0)
+            }
         }
 
         tuples
diff --git a/Sources/ReportingFeature/FetchBannedList.swift b/Sources/ReportingFeature/FetchBannedList.swift
new file mode 100644
index 0000000000000000000000000000000000000000..2620b15c84e5d31316f663a363ff6a4526d1880d
--- /dev/null
+++ b/Sources/ReportingFeature/FetchBannedList.swift
@@ -0,0 +1,46 @@
+import Foundation
+import XCTestDynamicOverlay
+
+public struct FetchBannedList {
+    public enum Error: Swift.Error, Equatable {
+        case network(URLError)
+        case invalidResponse
+    }
+
+    public typealias Completion = (Result<Data, Error>) -> Void
+
+    public var run: (@escaping Completion) -> Void
+
+    public func callAsFunction(completion: @escaping Completion) {
+        run(completion)
+    }
+}
+
+extension FetchBannedList {
+    public static let live = FetchBannedList { completion in
+        let url = URL(string: "https://elixxir-bins.s3.us-west-1.amazonaws.com/client/bannedUsers/banned.csv")!
+        let session = URLSession.shared
+        let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData)
+        let task = session.dataTask(with: request) { data, response, error in
+            if let error = error {
+                completion(.failure(.network(error as! URLError)))
+                return
+            }
+            guard let response = response as? HTTPURLResponse,
+                  (200..<300).contains(response.statusCode),
+                  let data = data
+            else {
+                completion(.failure(.invalidResponse))
+                return
+            }
+            completion(.success(data))
+        }
+        task.resume()
+    }
+}
+
+extension FetchBannedList {
+    public static let unimplemented = FetchBannedList(
+        run: XCTUnimplemented("\(Self.self)")
+    )
+}
diff --git a/Sources/ReportingFeature/MakeAppScreenshot.swift b/Sources/ReportingFeature/MakeAppScreenshot.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7d44af879f35d792685f3e278cc6ce9c044d8a07
--- /dev/null
+++ b/Sources/ReportingFeature/MakeAppScreenshot.swift
@@ -0,0 +1,53 @@
+import Foundation
+import UIKit
+import XCTestDynamicOverlay
+
+public struct MakeAppScreenshot {
+    public enum Error: Swift.Error, Equatable {
+        case unableToGetForegroundWindowScene
+        case unableToGetKeyWindow
+    }
+
+    public var run: () throws -> UIImage
+
+    public func callAsFunction() throws -> UIImage {
+        try run()
+    }
+}
+
+extension MakeAppScreenshot {
+    public static let live = MakeAppScreenshot {
+        let scene: UIWindowScene? = UIApplication.shared.connectedScenes
+            .filter { $0.activationState == .foregroundActive }
+            .compactMap { $0 as? UIWindowScene }
+            .first
+
+        guard let scene = scene else {
+            throw Error.unableToGetForegroundWindowScene
+        }
+
+        let window: UIWindow? = scene.windows.first(where: \.isKeyWindow)
+
+        guard let keyWindow = window else {
+            throw Error.unableToGetKeyWindow
+        }
+
+        let rendererFormat = UIGraphicsImageRendererFormat()
+        rendererFormat.scale = scene.screen.scale
+
+        let renderer = UIGraphicsImageRenderer(
+            bounds: keyWindow.bounds,
+            format: rendererFormat
+        )
+
+        return renderer.image { ctx in
+            keyWindow.layer.render(in: ctx.cgContext)
+        }
+    }
+}
+
+extension MakeAppScreenshot {
+    public static let unimplemented = MakeAppScreenshot(
+        run: XCTUnimplemented("\(Self.self)")
+    )
+}
diff --git a/Sources/ReportingFeature/MakeReportDrawer.swift b/Sources/ReportingFeature/MakeReportDrawer.swift
new file mode 100644
index 0000000000000000000000000000000000000000..b8b4aa394481d3e292ef236711b1f31f82704d3e
--- /dev/null
+++ b/Sources/ReportingFeature/MakeReportDrawer.swift
@@ -0,0 +1,86 @@
+import DrawerFeature
+import Shared
+import UIKit
+import XCTestDynamicOverlay
+
+public struct MakeReportDrawer {
+    public struct Config {
+        public init(
+            onReport: @escaping () -> Void = {},
+            onCancel: @escaping () -> Void = {}
+        ) {
+            self.onReport = onReport
+            self.onCancel = onCancel
+        }
+
+        public var onReport: () -> Void
+        public var onCancel: () -> Void
+    }
+
+    public var run: (Config) -> UIViewController
+
+    public func callAsFunction(_ config: Config) -> UIViewController {
+        run(config)
+    }
+}
+
+extension MakeReportDrawer {
+    public static let live = MakeReportDrawer { config in
+        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 { [unowned drawer] in
+                drawer.dismiss(animated: true) {
+                    config.onReport()
+                }
+            }
+            .store(in: &drawer.cancellables)
+
+        cancelButton.publisher(for: .touchUpInside)
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned drawer] in
+                drawer.dismiss(animated: true) {
+                    config.onCancel()
+                }
+            }
+            .store(in: &drawer.cancellables)
+
+        return drawer
+    }
+}
+
+extension MakeReportDrawer {
+    public static let unimplemented = MakeReportDrawer(
+        run: XCTUnimplemented("\(Self.self)")
+    )
+}
diff --git a/Sources/ReportingFeature/ProcessBannedList.swift b/Sources/ReportingFeature/ProcessBannedList.swift
new file mode 100644
index 0000000000000000000000000000000000000000..3399a34ee3614bc41df393251d1e21a9309cdf52
--- /dev/null
+++ b/Sources/ReportingFeature/ProcessBannedList.swift
@@ -0,0 +1,64 @@
+import Foundation
+import SwiftCSV
+import XCTestDynamicOverlay
+
+public struct ProcessBannedList {
+    public enum ElementError: Swift.Error {
+        case missingUserId
+        case invalidUserId(String)
+    }
+
+    public enum Error: Swift.Error {
+        case invalidData
+        case csv(Swift.Error)
+    }
+
+    public typealias ForEach = (Result<Data, ElementError>) -> Void
+    public typealias Completion = (Result<Void, Error>) -> Void
+
+    public var run: (Data, ForEach, Completion) -> Void
+
+    public func callAsFunction(
+        data: Data,
+        forEach: ForEach,
+        completion: Completion
+    ) {
+        run(data, forEach, completion)
+    }
+}
+
+extension ProcessBannedList {
+    public static let live = ProcessBannedList { data, forEach, completion in
+        guard let csvString = String(data: data, encoding: .utf8) else {
+            completion(.failure(.invalidData))
+            return
+        }
+        let csv: EnumeratedCSV
+        do {
+            csv = try EnumeratedCSV(string: csvString)
+        }
+        catch {
+            completion(.failure(.csv(error)))
+            return
+        }
+        csv.rows.forEach { row in
+            guard let userIdString = row.first else {
+                forEach(.failure(.missingUserId))
+                return
+            }
+            guard let userId = Data(base64Encoded: userIdString) else {
+                forEach(.failure(.invalidUserId(userIdString)))
+                return
+            }
+            forEach(.success(userId))
+        }
+        completion(.success(()))
+    }
+}
+
+extension ProcessBannedList {
+    public static let unimplemented = ProcessBannedList { _, _, _ in
+        let run: () -> Void = XCTUnimplemented("\(Self.self)")
+        run()
+    }
+}
diff --git a/Sources/ReportingFeature/Report.swift b/Sources/ReportingFeature/Report.swift
new file mode 100644
index 0000000000000000000000000000000000000000..c2032b6a6b87d096f8f6883085d4c6e887630600
--- /dev/null
+++ b/Sources/ReportingFeature/Report.swift
@@ -0,0 +1,52 @@
+import Foundation
+
+public struct Report: Encodable {
+    public init(
+        sender: ReportUser,
+        recipient: ReportUser,
+        type: ReportType,
+        screenshot: Data,
+        partyName: String? = nil,
+        partyBlob: String? = nil,
+        partyMembers: [ReportUser]? = nil
+    ) {
+        self.sender = sender
+        self.recipient = recipient
+        self.type = type
+        self.screenshot = screenshot
+        self.partyName = partyName
+        self.partyBlob = partyBlob
+        self.partyMembers = partyMembers
+    }
+
+    public var sender: ReportUser
+    public var recipient: ReportUser
+    public var type: ReportType
+    public var screenshot: Data
+    public var partyName: String?
+    public var partyBlob: String?
+    public var partyMembers: [ReportUser]?
+}
+
+extension Report {
+    public struct ReportUser: Encodable {
+        public init(
+            userId: String,
+            username: String
+        ) {
+            self.userId = userId
+            self.username = username
+        }
+
+        public var userId: String
+        public var username: String
+    }
+}
+
+extension Report {
+    public enum ReportType: String, Encodable {
+        case dm
+        case group
+        case channel
+    }
+}
diff --git a/Sources/ReportingFeature/Resources/report_cert.der b/Sources/ReportingFeature/Resources/report_cert.der
new file mode 100644
index 0000000000000000000000000000000000000000..978f65098ea8f361f1f369194e24d68d258f4dc2
--- /dev/null
+++ b/Sources/ReportingFeature/Resources/report_cert.der
@@ -0,0 +1 @@
+[REPLACE THIS FILE WITH DER CERTIFICATE]
\ No newline at end of file
diff --git a/Sources/ReportingFeature/SendReport.swift b/Sources/ReportingFeature/SendReport.swift
new file mode 100644
index 0000000000000000000000000000000000000000..acc24a0ce640a40bb0ed1df5eede6053603c3c2f
--- /dev/null
+++ b/Sources/ReportingFeature/SendReport.swift
@@ -0,0 +1,82 @@
+import Foundation
+import XCTestDynamicOverlay
+
+public struct SendReport {
+    public typealias Completion = (Result<Void, Error>) -> Void
+
+    public var run: (Report, @escaping Completion) -> Void
+
+    public func callAsFunction(_ report: Report, completion: @escaping Completion) {
+        run(report, completion)
+    }
+}
+
+extension SendReport {
+    public static let live = SendReport { report, completion in
+        let url = URL(string: "https://3.74.237.181:11420/report")!
+        var request = URLRequest(url: url)
+        request.httpMethod = "POST"
+        do {
+            request.httpBody = try JSONEncoder().encode(report)
+        } catch {
+            completion(.failure(error))
+            return
+        }
+        let session = URLSession(
+            configuration: .default,
+            delegate: SessionDelegate(),
+            delegateQueue: nil
+        )
+        let task = session.dataTask(with: request) { _, _, error in
+            defer { session.invalidateAndCancel() }
+            if let error = error {
+                completion(.failure(error))
+                return
+            }
+            completion(.success(()))
+        }
+        task.resume()
+    }
+}
+
+extension SendReport {
+    public static let unimplemented = SendReport(
+        run: XCTUnimplemented("\(Self.self)")
+    )
+}
+
+private final class SessionDelegate: NSObject, URLSessionDelegate {
+    func urlSession(
+        _ session: URLSession,
+        didReceive challenge: URLAuthenticationChallenge,
+        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
+    ) {
+        let authMethod = challenge.protectionSpace.authenticationMethod
+        guard authMethod == NSURLAuthenticationMethodServerTrust else {
+            return completionHandler(.cancelAuthenticationChallenge, nil)
+        }
+
+        guard let serverTrust = challenge.protectionSpace.serverTrust else {
+            return completionHandler(.cancelAuthenticationChallenge, nil)
+        }
+
+        guard let serverCert = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
+            return completionHandler(.cancelAuthenticationChallenge, nil)
+        }
+
+        let serverCertCFData = SecCertificateCopyData(serverCert)
+        let serverCertData = Data(
+            bytes: CFDataGetBytePtr(serverCertCFData),
+            count: CFDataGetLength(serverCertCFData)
+        )
+
+        let localCertURL = Bundle.module.url(forResource: "report_cert", withExtension: "der")!
+        let localCertData = try! Data(contentsOf: localCertURL)
+
+        guard serverCertData == localCertData else {
+            return completionHandler(.cancelAuthenticationChallenge, nil)
+        }
+
+        completionHandler(.useCredential, URLCredential(trust: serverTrust))
+    }
+}
diff --git a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
index ded18f77a4da0d956d2bdb0b1779495c818d41f5..5da72d12b6a71ae29f19367485d69777d308d05e 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
@@ -56,7 +56,7 @@ final class RequestsReceivedViewModel {
             authStatus: [
                 .hidden,
                 .pending
-            ])
+            ], isLeaderBlocked: false, isLeaderBanned: false)
 
         let contactsQuery = Contact.Query(
             authStatus: [
@@ -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()
diff --git a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
index f94ed8f5164de5c5a419a9b9271499ae7daa4952..26a6f485f1cb67721a301325e8eff5aefb917be5 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
@@ -36,7 +36,7 @@ final class RequestsSentViewModel {
         let query = Contact.Query(authStatus: [
             .requested,
             .requesting
-        ])
+        ], isBlocked: false, isBanned: false)
 
         session.dbManager.fetchContactsPublisher(query)
             .assertNoFailure()
diff --git a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
index 9f9d0ffd312f03a1d583a1ddebe12f1e8779f0da..d5ea157daa163ad4380bea16df04c72195e9b075 100644
--- a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
+++ b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
@@ -144,17 +144,24 @@ 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])).first {
+                user.isBanned = contact.isBanned
+                user.isBlocked = contact.isBlocked
                 user.authStatus = contact.authStatus
             }
 
-            if user.authStatus != .friend {
+            if user.authStatus != .friend, !user.isBanned, !user.isBlocked {
                 snapshot.appendSections([.stranger])
                 snapshot.appendItems([.stranger(user)], toSection: .stranger)
             }
         }
 
-        let localsQuery = Contact.Query(text: stateSubject.value.input, authStatus: [.friend])
+        let localsQuery = Contact.Query(
+            text: stateSubject.value.input,
+            authStatus: [.friend],
+            isBlocked: false,
+            isBanned: false
+        )
 
         if let locals = try? session.dbManager.fetchContacts(localsQuery),
            let localsWithoutMe = removeMyself(from: locals),
diff --git a/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift
index 5cfe38db68c498a15522f745174e41415281ba17..148db4ca424581596ccdea381a139ebfbb4cad11 100644
--- a/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift
+++ b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift
@@ -78,6 +78,16 @@ final class SearchRightViewModel {
         /// that we already have
         ///
         if let alreadyContact = try? session.dbManager.fetchContacts(.init(id: [userId])).first {
+            if alreadyContact.isBlocked {
+                statusSubject.send(.failed(.unknown("You previously blocked this user.")))
+                return
+            }
+
+            if alreadyContact.isBanned {
+                statusSubject.send(.failed(.unknown("This user was banned.")))
+                return
+            }
+
             /// Show error accordingly to the auth status
             ///
             if alreadyContact.authStatus == .friend {
diff --git a/Sources/SettingsFeature/Controllers/SettingsController.swift b/Sources/SettingsFeature/Controllers/SettingsController.swift
index 52e78e17a42c3933f671917aca89b186362f349a..e58a347096e53ddefc3014517c4f11c946a06429 100644
--- a/Sources/SettingsFeature/Controllers/SettingsController.swift
+++ b/Sources/SettingsFeature/Controllers/SettingsController.swift
@@ -136,7 +136,7 @@ public final class SettingsController: UIViewController {
                     title: Localized.Settings.Drawer.title(Localized.Settings.privacyPolicy),
                     subtitle: Localized.Settings.Drawer.subtitle(Localized.Settings.privacyPolicy),
                     actionTitle: Localized.ChatList.Dashboard.open) {
-                        guard let url = URL(string: "https://xx.network/privategrity-corporation-privacy-policy") else { return }
+                        guard let url = URL(string: "https://elixxir.io/privategrity-corporation-privacy-policy/") else { return }
                         UIApplication.shared.open(url, options: [:])
                     }
             }.store(in: &cancellables)
@@ -149,7 +149,7 @@ public final class SettingsController: UIViewController {
                     title: Localized.Settings.Drawer.title(Localized.Settings.disclosures),
                     subtitle: Localized.Settings.Drawer.subtitle(Localized.Settings.disclosures),
                     actionTitle: Localized.ChatList.Dashboard.open) {
-                        guard let url = URL(string: "https://xx.network/privategrity-corporation-terms-of-use") else { return }
+                        guard let url = URL(string: "https://elixxir.io/privategrity-corporation-terms-of-use/") else { return }
                         UIApplication.shared.open(url, options: [:])
                     }
             }.store(in: &cancellables)
diff --git a/Sources/Shared/AutoGenerated/Strings.swift b/Sources/Shared/AutoGenerated/Strings.swift
index 03ada00df323371aae8d22c05b50903f9da768d6..afcdff24e5e7789bae262515880be622e5a14954 100644
--- a/Sources/Shared/AutoGenerated/Strings.swift
+++ b/Sources/Shared/AutoGenerated/Strings.swift
@@ -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 also won’t see any new group chat messages 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")
     }
   }
 
@@ -483,11 +497,11 @@ public enum Localized {
         public static func description(_ p1: Any) -> String {
           return Localized.tr("Localizable", "contact.delete.drawer.description", String(describing: p1))
         }
-        /// Delete Connection?
+        /// Delete and block connection?
         public static let title = Localized.tr("Localizable", "contact.delete.drawer.title")
       }
       public enum Info {
-        /// Delete Connection
+        /// Delete and block connection
         public static let title = Localized.tr("Localizable", "contact.delete.info.title")
       }
     }
@@ -1284,9 +1298,9 @@ public enum Localized {
         return Localized.tr("Localizable", "ud.search.input", String(describing: p1))
       }
       public enum Placeholder {
-        /// Your searches are anonymous. Search information is never linked to your account or personally identifiable.
+        /// Your searches are private. Search information is never linked to your account or personally identifiable.
         public static let subtitle = Localized.tr("Localizable", "ud.search.placeholder.subtitle")
-        /// Search for #friends# anonymously, add them to your #connections# to start a completely private messaging channel.
+        /// Search for #friends# privately, add them to your #connections# to start a completely private messaging channel.
         public static let title = Localized.tr("Localizable", "ud.search.placeholder.title")
       }
     }
diff --git a/Sources/Shared/Resources/en.lproj/Localizable.strings b/Sources/Shared/Resources/en.lproj/Localizable.strings
index 033e7a6f2ff8bd973d4eaf2cec0755295c8936b5..6b61f43701841cb1f2793045fa1cdd4c3e19de6b 100644
--- a/Sources/Shared/Resources/en.lproj/Localizable.strings
+++ b/Sources/Shared/Resources/en.lproj/Localizable.strings
@@ -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 also won’t see any new group chat messages from this user";
+"chat.report.action"
+= "Confirm and Report";
+"chat.report.cancel"
+= "Cancel";
+
 // ScanFeature
 
 "scan.status.reading"
@@ -243,9 +258,9 @@
 // ContactFeature - Delete
 
 "contact.delete.info.title"
-= "Delete Connection";
+= "Delete and block connection";
 "contact.delete.drawer.title"
-= "Delete Connection?";
+= "Delete and block connection?";
 "contact.delete.drawer.description"
 = "This is a silent deletion, %@ will not know you deleted them. This action will remove all information on your phone about this user, including your communications. You #cannot undo this step, and cannot re-add them unless they delete you as a connection as well.#";
 
@@ -1014,9 +1029,9 @@
 = "Cancel search";
 
 "ud.search.placeholder.title"
-= "Search for #friends# anonymously, add them to your #connections# to start a completely private messaging channel.";
+= "Search for #friends# privately, add them to your #connections# to start a completely private messaging channel.";
 "ud.search.placeholder.subtitle"
-= "Your searches are anonymous. Search information is never linked to your account or personally identifiable.";
+= "Your searches are private. Search information is never linked to your account or personally identifiable.";
 
 // LaunchFeature
 
diff --git a/Sources/Shared/Views/CapsuleButton.swift b/Sources/Shared/Views/CapsuleButton.swift
index 96ef2e0963ec5ee20cc2b470f9a58c194847f936..4d681dc36b0f5c8ca9d214ac2ce026c940eadf01 100644
--- a/Sources/Shared/Views/CapsuleButton.swift
+++ b/Sources/Shared/Views/CapsuleButton.swift
@@ -30,7 +30,7 @@ public extension CapsuleButtonStyle {
         borderWidth: 0,
         borderColor: nil,
         titleColor: Asset.brandPrimary.color,
-        disabledTitleColor: Asset.neutralWhite.color
+        disabledTitleColor: Asset.neutralWhite.color.withAlphaComponent(0.5)
     )
 
     static let brandColored = CapsuleButtonStyle(
diff --git a/Sources/TermsFeature/RadioButton.swift b/Sources/TermsFeature/RadioButton.swift
index 43b873ac9939ac8297e466ef4494cf9c78d9512a..201aa3b9c19b2325a06168c4e9abfba4cecf0cd5 100644
--- a/Sources/TermsFeature/RadioButton.swift
+++ b/Sources/TermsFeature/RadioButton.swift
@@ -11,12 +11,12 @@ final class RadioButton: UIControl {
         containerView.layer.borderWidth = 1
         containerView.layer.cornerRadius = 15
         containerView.layer.masksToBounds = true
-        containerView.layer.borderColor = UIColor.gray.cgColor
+        containerView.layer.borderColor = Asset.neutralWhite.color.cgColor
 
         filledView.isHidden = true
         filledView.layer.cornerRadius = 10
         filledView.layer.masksToBounds = true
-        filledView.backgroundColor = Asset.brandPrimary.color
+        filledView.backgroundColor = Asset.neutralWhite.color
 
         containerView.isUserInteractionEnabled = false
         filledView.isUserInteractionEnabled = false
diff --git a/Sources/TermsFeature/RadioTextComponent.swift b/Sources/TermsFeature/RadioTextComponent.swift
index 64c7b1cfb3d80ccc85284f7544e286666faeb639..8f6509f21562ebc8d3a0941cff1a5db53b997908 100644
--- a/Sources/TermsFeature/RadioTextComponent.swift
+++ b/Sources/TermsFeature/RadioTextComponent.swift
@@ -13,7 +13,7 @@ final class RadioTextComponent: UIView {
         super.init(frame: .zero)
 
         titleLabel.numberOfLines = 0
-        titleLabel.textColor = Asset.neutralBody.color
+        titleLabel.textColor = Asset.neutralWhite.color
         titleLabel.font = Fonts.Mulish.regular.font(size: 13.0)
 
         addSubview(titleLabel)
diff --git a/Sources/TermsFeature/TermsConditionsController.swift b/Sources/TermsFeature/TermsConditionsController.swift
index 27ad6cb78e28b5eca4ee7de044afa317229e941f..b11ef15215945e388c8043ed98e3bc6b5001e142 100644
--- a/Sources/TermsFeature/TermsConditionsController.swift
+++ b/Sources/TermsFeature/TermsConditionsController.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Theme
+import WebKit
 import Shared
 import Combine
 import Defaults
@@ -30,8 +31,28 @@ public final class TermsConditionsController: UIViewController {
     public override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
         navigationItem.backButtonTitle = ""
-        statusBarController.style.send(.darkContent)
-        navigationController?.navigationBar.customize(translucent: true)
+        navigationController?.navigationBar.customize(
+            translucent: true,
+            tint: Asset.neutralWhite.color
+        )
+    }
+
+    public override func viewDidLayoutSubviews() {
+        super.viewDidLayoutSubviews()
+
+        let gradient = CAGradientLayer()
+        gradient.colors = [
+            UIColor(red: 122/255, green: 235/255, blue: 239/255, alpha: 1).cgColor,
+            UIColor(red: 56/255, green: 204/255, blue: 232/255, alpha: 1).cgColor,
+            UIColor(red: 63/255, green: 186/255, blue: 253/255, alpha: 1).cgColor,
+            UIColor(red: 98/255, green: 163/255, blue: 255/255, alpha: 1).cgColor
+        ]
+
+        gradient.startPoint = CGPoint(x: 0, y: 0)
+        gradient.endPoint = CGPoint(x: 1, y: 1)
+
+        gradient.frame = screenView.bounds
+        screenView.layer.insertSublayer(gradient, at: 0)
     }
 
     public override func viewDidLoad() {
@@ -43,6 +64,7 @@ public final class TermsConditionsController: UIViewController {
             .sink { [unowned self] in
                 screenView.radioComponent.isEnabled.toggle()
                 screenView.nextButton.isEnabled = screenView.radioComponent.isEnabled
+                UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
             }.store(in: &cancellables)
 
         screenView.nextButton
@@ -59,8 +81,13 @@ public final class TermsConditionsController: UIViewController {
 
         screenView.showTermsButton
             .publisher(for: .touchUpInside)
-            .sink { _ in
-                // TODO
+            .sink { [unowned self] _ in
+                let webView = WKWebView()
+                let webController = UIViewController()
+                webController.view.addSubview(webView)
+                webView.snp.makeConstraints { $0.edges.equalToSuperview() }
+                webView.load(URLRequest(url: URL(string: "https://elixxir.io/eula")!))
+                present(webController, animated: true)
             }.store(in: &cancellables)
     }
 }
diff --git a/Sources/TermsFeature/TermsConditionsView.swift b/Sources/TermsFeature/TermsConditionsView.swift
index fb7d1198f586cd12ecc3e1b0e1479c5cfba84a41..2f3ff8fa327956951be5299f32b6537d7ad4824d 100644
--- a/Sources/TermsFeature/TermsConditionsView.swift
+++ b/Sources/TermsFeature/TermsConditionsView.swift
@@ -2,8 +2,8 @@ import UIKit
 import Shared
 
 final class TermsConditionsView: UIView {
-    let titleLabel = UILabel()
     let nextButton = CapsuleButton()
+    let logoImageView = UIImageView()
     let showTermsButton = CapsuleButton()
     let radioComponent = RadioTextComponent()
 
@@ -11,30 +11,15 @@ final class TermsConditionsView: UIView {
         super.init(frame: .zero)
         backgroundColor = Asset.neutralWhite.color
 
-        let attString = NSMutableAttributedString(string: Localized.Terms.title)
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.15
-
-        attString.addAttribute(.paragraphStyle, value: paragraph)
-        attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
-        attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
-
-        attString.addAttributes(attributes: [
-            .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
-            .foregroundColor: Asset.brandPrimary.color
-        ], betweenCharacters: "#")
-
-        titleLabel.numberOfLines = 0
-        titleLabel.attributedText = attString
-
+        logoImageView.contentMode = .center
+        logoImageView.image = Asset.onboardingLogoStart.image
         radioComponent.titleLabel.text = Localized.Terms.radio
 
         nextButton.isEnabled = false
-        nextButton.set(style: .brandColored, title: Localized.Terms.accept)
-        showTermsButton.set(style: .seeThrough, title: Localized.Terms.show)
+        nextButton.set(style: .white, title: Localized.Terms.accept)
+        showTermsButton.set(style: .seeThroughWhite, title: Localized.Terms.show)
 
-        addSubview(titleLabel)
+        addSubview(logoImageView)
         addSubview(nextButton)
         addSubview(radioComponent)
         addSubview(showTermsButton)
@@ -45,10 +30,9 @@ final class TermsConditionsView: UIView {
     required init?(coder: NSCoder) { nil }
 
     private func setupConstraints() {
-        titleLabel.snp.makeConstraints {
+        logoImageView.snp.makeConstraints {
             $0.top.equalTo(safeAreaLayoutGuide).offset(30)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-44)
+            $0.centerX.equalToSuperview()
         }
 
         radioComponent.snp.makeConstraints {