diff --git a/Package.swift b/Package.swift
index 5a8a8e1d8fdc7f2e8887e4caa5d72b89b2449201..9f27f5c4c29eb483e7136b6a798f568d8a2854f3 100644
--- a/Package.swift
+++ b/Package.swift
@@ -9,7 +9,6 @@ let package = Package(
   ],
   products: [
     .library(name: "App", targets: ["App"]),
-    .library(name: "HUD", targets: ["HUD"]),
     .library(name: "Shared", targets: ["Shared"]),
     .library(name: "Models", targets: ["Models"]),
     .library(name: "XXLogger", targets: ["XXLogger"]),
@@ -266,13 +265,6 @@ let package = Package(
         .product(name: "ScrollViewController", package: "ScrollViewController"),
       ]
     ),
-    .target(
-      name: "HUD",
-      dependencies: [
-        .target(name: "Shared"),
-        .product(name: "SnapKit", package: "SnapKit"),
-      ]
-    ),
     .target(
       name: "XXLogger",
       dependencies: [
@@ -318,7 +310,6 @@ let package = Package(
     .target(
       name: "RestoreFeature",
       dependencies: [
-        .target(name: "HUD"),
         .target(name: "Shared"),
         .target(name: "Presentation"),
         .target(name: "DependencyInjection"),
@@ -353,7 +344,6 @@ let package = Package(
     .target(
       name: "ChatFeature",
       dependencies: [
-        .target(name: "HUD"),
         .target(name: "Shared"),
         .target(name: "Defaults"),
         .target(name: "Keychain"),
@@ -384,7 +374,6 @@ let package = Package(
     .target(
       name: "SearchFeature",
       dependencies: [
-        .target(name: "HUD"),
         .target(name: "Shared"),
         .target(name: "Countries"),
         .target(name: "PushFeature"),
@@ -408,7 +397,6 @@ let package = Package(
     .target(
       name: "LaunchFeature",
       dependencies: [
-        .target(name: "HUD"),
         .target(name: "Shared"),
         .target(name: "Defaults"),
         .target(name: "PushFeature"),
@@ -454,7 +442,6 @@ let package = Package(
     .target(
       name: "ProfileFeature",
       dependencies: [
-        .target(name: "HUD"),
         .target(name: "Shared"),
         .target(name: "Keychain"),
         .target(name: "Defaults"),
@@ -507,7 +494,6 @@ let package = Package(
     .target(
       name: "OnboardingFeature",
       dependencies: [
-        .target(name: "HUD"),
         .target(name: "Shared"),
         .target(name: "Defaults"),
         .target(name: "Keychain"),
@@ -547,7 +533,6 @@ let package = Package(
     .target(
       name: "BackupFeature",
       dependencies: [
-        .target(name: "HUD"),
         .target(name: "Shared"),
         .target(name: "Models"),
         .target(name: "InputField"),
@@ -607,7 +592,6 @@ let package = Package(
     .target(
       name: "SettingsFeature",
       dependencies: [
-        .target(name: "HUD"),
         .target(name: "Shared"),
         .target(name: "Defaults"),
         .target(name: "Keychain"),
diff --git a/Sources/App/DependencyRegistrator.swift b/Sources/App/DependencyRegistrator.swift
index 824b2ef6de0378d6cf41e14f566694469f6e9ca1..5f3d0418f49bf1eb249ca680a54b8fd21c043224 100644
--- a/Sources/App/DependencyRegistrator.swift
+++ b/Sources/App/DependencyRegistrator.swift
@@ -7,7 +7,6 @@ import MobileCoreServices
 
 // MARK: Isolated features
 
-import HUD
 import Bindings
 import XXLogger
 import Keychain
@@ -110,7 +109,7 @@ struct DependencyRegistrator {
 
     // MARK: Isolated
 
-    container.register(HUD())
+    container.register(HUDController())
     container.register(ToastController())
     container.register(StatusBarStylist())
 
diff --git a/Sources/BackupFeature/Controllers/BackupController.swift b/Sources/BackupFeature/Controllers/BackupController.swift
index 822ebbc77c3fa46acd0d842579382101aac5b3f9..780efb8f5a18c448210e76943a1172085bac77c6 100644
--- a/Sources/BackupFeature/Controllers/BackupController.swift
+++ b/Sources/BackupFeature/Controllers/BackupController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Models
@@ -6,8 +5,6 @@ import Combine
 import DependencyInjection
 
 public final class BackupController: UIViewController {
-    @Dependency var hud: HUD
-
     private let viewModel = BackupViewModel.live()
     private var cancellables = Set<AnyCancellable>()
 
@@ -21,8 +18,6 @@ public final class BackupController: UIViewController {
     public override func viewDidLoad() {
         super.viewDidLoad()
         view.backgroundColor = Asset.neutralWhite.color
-        hud.update(with: .on)
-
         setupNavigationBar()
         setupBindings()
     }
@@ -41,8 +36,6 @@ public final class BackupController: UIViewController {
             .receive(on: DispatchQueue.main)
             .removeDuplicates()
             .sink { [unowned self] in
-                hud.update(with: .none)
-
                 switch $0 {
                 case .setup:
                     contentViewController = BackupSetupController(viewModel.setupViewModel())
diff --git a/Sources/BackupFeature/Controllers/BackupSFTPController.swift b/Sources/BackupFeature/Controllers/BackupSFTPController.swift
index 7c5c9c7cf372c5837ded9979478c25a8e568cc45..3fa7f9c396b5412f9465e43e99c6e3a2953a5004 100644
--- a/Sources/BackupFeature/Controllers/BackupSFTPController.swift
+++ b/Sources/BackupFeature/Controllers/BackupSFTPController.swift
@@ -1,12 +1,8 @@
-import HUD
 import UIKit
 import Combine
-import DependencyInjection
 import ScrollViewController
 
 public final class BackupSFTPController: UIViewController {
-  @Dependency private var hud: HUD
-
   lazy private var screenView = BackupSFTPView()
   lazy private var scrollViewController = ScrollViewController()
 
@@ -44,11 +40,6 @@ public final class BackupSFTPController: UIViewController {
   }
 
   private func setupBindings() {
-    viewModel.hudPublisher
-      .receive(on: DispatchQueue.main)
-      .sink { [hud] in hud.update(with: $0) }
-      .store(in: &cancellables)
-
     viewModel.authPublisher
       .receive(on: DispatchQueue.main)
       .sink { [unowned self] params in
diff --git a/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift b/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift
index 569a65de8bd272ccb06cbd661e1e5bc1f7015d2a..1057d966d5dd2e584db74cb730127a3b6a6831ac 100644
--- a/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift
+++ b/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Models
 import Shared
@@ -35,8 +34,8 @@ struct BackupConfigViewModel {
 extension BackupConfigViewModel {
   static func live() -> Self {
     class Context {
-      @Dependency var hud: HUD
       @Dependency var service: BackupService
+      @Dependency var hudController: HUDController
       @Dependency var coordinator: BackupCoordinating
     }
 
@@ -45,9 +44,9 @@ extension BackupConfigViewModel {
     return .init(
       didTapBackupNow: {
         context.service.didForceBackup()
-        context.hud.update(with: .on)
+        context.hudController.show()
         DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
-          context.hud.update(with: .none)
+          context.hudController.dismiss()
         }
       },
       didChooseWifiOnly: context.service.didSetWiFiOnly(enabled:),
@@ -61,10 +60,12 @@ extension BackupConfigViewModel {
         context.coordinator.toPassphrase(from: controller, cancelClosure: {
           context.service.toggle(service: service, enabling: false)
         }, passphraseClosure: { passphrase in
-          context.hud.update(with: .onTitle("Initializing and securing your backup file will take few seconds, please keep the app open."))
+          context.hudController.show(.init(
+            content: "Initializing and securing your backup file will take few seconds, please keep the app open."
+          ))
           context.service.toggle(service: service, enabling: enabling)
           context.service.initializeBackup(passphrase: passphrase)
-          context.hud.update(with: .none)
+          context.hudController.dismiss()
         })
       },
       didTapService: { service, controller in
diff --git a/Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift b/Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift
index 9b113c9f175c712fd4715b749238e73d21ad54b1..109bc6ef897924e5e9075111630b3ee95bbf0863 100644
--- a/Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift
+++ b/Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift
@@ -1,12 +1,12 @@
 import UIKit
-
-import HUD
 import Shout
 import Socket
+import Shared
 import Combine
 import Foundation
 import CloudFiles
 import CloudFilesSFTP
+import DependencyInjection
 
 struct SFTPViewState {
   var host: String = ""
@@ -16,9 +16,7 @@ struct SFTPViewState {
 }
 
 final class BackupSFTPViewModel {
-  var hudPublisher: AnyPublisher<HUDStatus, Never> {
-    hudSubject.eraseToAnyPublisher()
-  }
+  @Dependency var hudController: HUDController
 
   var statePublisher: AnyPublisher<SFTPViewState, Never> {
     stateSubject.eraseToAnyPublisher()
@@ -28,7 +26,6 @@ final class BackupSFTPViewModel {
     authSubject.eraseToAnyPublisher()
   }
 
-  private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
   private let stateSubject = CurrentValueSubject<SFTPViewState, Never>(.init())
   private let authSubject = PassthroughSubject<(String, String, String), Never>()
 
@@ -48,7 +45,7 @@ final class BackupSFTPViewModel {
   }
 
   func didTapLogin() {
-    hudSubject.send(.on)
+    hudController.show()
 
     let host = stateSubject.value.host
     let username = stateSubject.value.username
@@ -67,7 +64,7 @@ final class BackupSFTPViewModel {
         ).link(anyController) {
           switch $0 {
           case .success:
-            self.hudSubject.send(.none)
+            self.hudController.dismiss()
             self.authSubject.send((host, username, password))
           case .failure(let error):
             var message = "An error occurred while trying to link SFTP: "
@@ -84,11 +81,11 @@ final class BackupSFTPViewModel {
               message.append(error.localizedDescription)
             }
 
-            self.hudSubject.send(.error(.init(content: message)))
+            self.hudController.show(.init(content: message))
           }
         }
       } catch {
-        self.hudSubject.send(.error(.init(with: error)))
+        self.hudController.show(.init(error: error))
       }
     }
   }
diff --git a/Sources/ChatFeature/Controllers/GroupChatController.swift b/Sources/ChatFeature/Controllers/GroupChatController.swift
index 5bd1504ec4f4a91b30cc2303f0ebcfd097756dee..9f8b464a831ee4b05a8df465e520f51031307a8c 100644
--- a/Sources/ChatFeature/Controllers/GroupChatController.swift
+++ b/Sources/ChatFeature/Controllers/GroupChatController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Models
 import Shared
@@ -20,7 +19,6 @@ typealias OutgoingFailedGroupTextCell = CollectionCell<FlexibleSpace, StackMessa
 typealias OutgoingFailedGroupReplyCell = CollectionCell<FlexibleSpace, ReplyStackMessageView>
 
 public final class GroupChatController: UIViewController {
-  @Dependency var hud: HUD
   @Dependency var database: Database
   @Dependency var barStylist: StatusBarStylist
   @Dependency var coordinator: ChatCoordinating
@@ -181,11 +179,6 @@ 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
diff --git a/Sources/ChatFeature/Controllers/SingleChatController.swift b/Sources/ChatFeature/Controllers/SingleChatController.swift
index 5ad2e83d43be534ca156dcf58020818ef222ebf1..a356dfa257152b0a8339f801255e6f6353a5e2c2 100644
--- a/Sources/ChatFeature/Controllers/SingleChatController.swift
+++ b/Sources/ChatFeature/Controllers/SingleChatController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Models
 import Shared
@@ -24,7 +23,6 @@ extension Message: Differentiable {
 }
 
 public final class SingleChatController: UIViewController {
-  @Dependency var hud: HUD
   @Dependency var logger: XXLogger
   @Dependency var voxophone: Voxophone
   @Dependency var barStylist: StatusBarStylist
@@ -240,11 +238,6 @@ public final class SingleChatController: UIViewController {
   }
 
   private func setupBindings() {
-    viewModel.hud
-      .receive(on: DispatchQueue.main)
-      .sink { [hud] in hud.update(with: $0) }
-      .store(in: &cancellables)
-
     sheet.actionPublisher
       .receive(on: DispatchQueue.main)
       .sink { [unowned self] in
diff --git a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
index 2265930fde064d351972af38bebd8c85ba9ab31e..4a7c0e9ed8fca41b15155f9b2773d4a57867c2ad 100644
--- a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
+++ b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Models
 import Shared
@@ -15,317 +14,313 @@ import struct XXModels.Message
 import XXClient
 
 enum GroupChatNavigationRoutes: Equatable {
-    case waitingRound
-    case webview(String)
+  case waitingRound
+  case webview(String)
 }
 
 final class GroupChatViewModel {
-    @Dependency var database: Database
-    @Dependency var sendReport: SendReport
-    @Dependency var groupManager: GroupChat
-    @Dependency var messenger: Messenger
-    @Dependency var reportingStatus: ReportingStatus
-    @Dependency var toastController: ToastController
-
-    @KeyObject(.username, defaultValue: nil) var username: String?
-
-    var myId: Data {
-        try! messenger.e2e.get()!.getContact().getId()
-    }
-
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
-
-    var reportPopupPublisher: AnyPublisher<XXModels.Contact, Never> {
-        reportPopupSubject.eraseToAnyPublisher()
-    }
-
-    var replyPublisher: AnyPublisher<(String, String), Never> {
-        replySubject.eraseToAnyPublisher()
-    }
+  @Dependency var database: Database
+  @Dependency var messenger: Messenger
+  @Dependency var sendReport: SendReport
+  @Dependency var groupManager: GroupChat
+  @Dependency var hudController: HUDController
+  @Dependency var reportingStatus: ReportingStatus
+  @Dependency var toastController: ToastController
+
+  @KeyObject(.username, defaultValue: nil) var username: String?
+
+  var myId: Data {
+    try! messenger.e2e.get()!.getContact().getId()
+  }
+
+  var reportPopupPublisher: AnyPublisher<XXModels.Contact, Never> {
+    reportPopupSubject.eraseToAnyPublisher()
+  }
+
+  var replyPublisher: AnyPublisher<(String, String), Never> {
+    replySubject.eraseToAnyPublisher()
+  }
+
+  var routesPublisher: AnyPublisher<GroupChatNavigationRoutes, Never> {
+    routesSubject.eraseToAnyPublisher()
+  }
+
+  let info: GroupInfo
+  private var stagedReply: Reply?
+  private var cancellables = Set<AnyCancellable>()
+  private let reportPopupSubject = PassthroughSubject<XXModels.Contact, Never>()
+  private let replySubject = PassthroughSubject<(String, String), Never>()
+  private let routesSubject = PassthroughSubject<GroupChatNavigationRoutes, Never>()
+
+  var messages: AnyPublisher<[ArraySection<ChatSection, Message>], Never> {
+    database.fetchMessagesPublisher(.init(chat: .group(info.group.id)))
+      .replaceError(with: [])
+      .map { messages -> [ArraySection<ChatSection, Message>] in
+        let groupedByDate = Dictionary(grouping: messages) { domainModel -> Date in
+          let components = Calendar.current.dateComponents([.day, .month, .year], from: domainModel.date)
+          return Calendar.current.date(from: components)!
+        }
 
-    var routesPublisher: AnyPublisher<GroupChatNavigationRoutes, Never> {
-        routesSubject.eraseToAnyPublisher()
+        return groupedByDate
+          .map { .init(model: ChatSection(date: $0.key), elements: $0.value) }
+          .sorted(by: { $0.model.date < $1.model.date })
+      }
+      .map { sections -> [ArraySection<ChatSection, Message>] in
+        var snapshot = [ArraySection<ChatSection, Message>]()
+        sections.forEach { snapshot.append(.init(model: $0.model, elements: $0.elements)) }
+        return snapshot
+      }.eraseToAnyPublisher()
+  }
+
+  init(_ info: GroupInfo) {
+    self.info = info
+  }
+
+  func readAll() {
+    let assignment = Message.Assignments(isUnread: false)
+    let query = Message.Query(chat: .group(info.group.id))
+    _ = try? database.bulkUpdateMessages(query, assignment)
+  }
+
+  func didRequestDelete(_ messages: [Message]) {
+    _ = try? database.deleteMessages(.init(id: Set(messages.map(\.id))))
+  }
+
+  func didRequestReport(_ message: Message) {
+    if let contact = try? database.fetchContacts(.init(id: [message.senderId])).first {
+      reportPopupSubject.send(contact)
     }
-
-    let info: GroupInfo
-    private var stagedReply: Reply?
-    private var cancellables = Set<AnyCancellable>()
-    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
-    private let reportPopupSubject = PassthroughSubject<XXModels.Contact, Never>()
-    private let replySubject = PassthroughSubject<(String, String), Never>()
-    private let routesSubject = PassthroughSubject<GroupChatNavigationRoutes, Never>()
-
-    var messages: AnyPublisher<[ArraySection<ChatSection, Message>], Never> {
-        database.fetchMessagesPublisher(.init(chat: .group(info.group.id)))
-        .replaceError(with: [])
-            .map { messages -> [ArraySection<ChatSection, Message>] in
-                let groupedByDate = Dictionary(grouping: messages) { domainModel -> Date in
-                    let components = Calendar.current.dateComponents([.day, .month, .year], from: domainModel.date)
-                    return Calendar.current.date(from: components)!
-                }
-
-                return groupedByDate
-                    .map { .init(model: ChatSection(date: $0.key), elements: $0.value) }
-                    .sorted(by: { $0.model.date < $1.model.date })
+  }
+
+  func send(_ text: String) {
+    var message = Message(
+      senderId: myId,
+      recipientId: nil,
+      groupId: info.group.id,
+      date: Date(),
+      status: .sending,
+      isUnread: false,
+      text: text.trimmingCharacters(in: .whitespacesAndNewlines),
+      replyMessageId: stagedReply?.messageId
+    )
+
+    print("")
+    print("Outgoing GroupMessage:")
+    print("- groupId: \(info.group.id.base64EncodedString().prefix(10))...")
+    print("- senderId: \(myId.base64EncodedString().prefix(10))...")
+    print("- payload.text: \(message.text)")
+
+    do {
+      message = try database.saveMessage(message)
+
+      let payload = Payload(
+        text: text.trimmingCharacters(in: .whitespacesAndNewlines),
+        reply: stagedReply
+      ).asData()
+
+      let report = try groupManager.send(
+        groupId: info.group.id,
+        message: payload
+      )
+
+      print("- messageId: \(report.messageId.base64EncodedString().prefix(10))...")
+
+      if let foo = stagedReply {
+        print("- payload.reply.messageId: \(foo.messageId.base64EncodedString().prefix(10))...")
+        print("- payload.reply.senderId: \(foo.senderId.base64EncodedString().prefix(10))...")
+      } else {
+        print("- payload.reply: ∅")
+      }
+
+      message.networkId = report.messageId
+
+      try messenger.cMix.get()!.waitForRoundResult(
+        roundList: try report.encode(),
+        timeoutMS: 15_000,
+        callback: .init(handle: {
+          switch $0 {
+          case .delivered:
+            message.status = .sent
+            if let foo = try? self.database.saveMessage(message) {
+              message = foo
             }
-            .map { sections -> [ArraySection<ChatSection, Message>] in
-            var snapshot = [ArraySection<ChatSection, Message>]()
-            sections.forEach { snapshot.append(.init(model: $0.model, elements: $0.elements)) }
-            return snapshot
-        }.eraseToAnyPublisher()
-    }
-
-    init(_ info: GroupInfo) {
-        self.info = info
-    }
-
-    func readAll() {
-        let assignment = Message.Assignments(isUnread: false)
-        let query = Message.Query(chat: .group(info.group.id))
-        _ = try? database.bulkUpdateMessages(query, assignment)
-    }
-
-    func didRequestDelete(_ messages: [Message]) {
-        _ = try? database.deleteMessages(.init(id: Set(messages.map(\.id))))
-    }
-
-    func didRequestReport(_ message: Message) {
-        if let contact = try? database.fetchContacts(.init(id: [message.senderId])).first {
-            reportPopupSubject.send(contact)
-        }
-    }
-
-    func send(_ text: String) {
-        var message = Message(
-            senderId: myId,
-            recipientId: nil,
-            groupId: info.group.id,
-            date: Date(),
-            status: .sending,
-            isUnread: false,
-            text: text.trimmingCharacters(in: .whitespacesAndNewlines),
-            replyMessageId: stagedReply?.messageId
-        )
-
-        print("")
-        print("Outgoing GroupMessage:")
-        print("- groupId: \(info.group.id.base64EncodedString().prefix(10))...")
-        print("- senderId: \(myId.base64EncodedString().prefix(10))...")
-        print("- payload.text: \(message.text)")
-
-        do {
-            message = try database.saveMessage(message)
-
-            let payload = Payload(
-                text: text.trimmingCharacters(in: .whitespacesAndNewlines),
-                reply: stagedReply
-            ).asData()
-
-            let report = try groupManager.send(
-                groupId: info.group.id,
-                message: payload
-            )
-
-            print("- messageId: \(report.messageId.base64EncodedString().prefix(10))...")
-
-            if let foo = stagedReply {
-                print("- payload.reply.messageId: \(foo.messageId.base64EncodedString().prefix(10))...")
-                print("- payload.reply.senderId: \(foo.senderId.base64EncodedString().prefix(10))...")
+          case .notDelivered(timedOut: let timedOut):
+            if timedOut {
+              message.status = .sendingTimedOut
             } else {
-                print("- payload.reply: ∅")
+              message.status = .sendingFailed
             }
 
-            message.networkId = report.messageId
-
-            try messenger.cMix.get()!.waitForRoundResult(
-                roundList: try report.encode(),
-                timeoutMS: 15_000,
-                callback: .init(handle: {
-                    switch $0 {
-                    case .delivered:
-                        message.status = .sent
-                        if let foo = try? self.database.saveMessage(message) {
-                            message = foo
-                        }
-                    case .notDelivered(timedOut: let timedOut):
-                        if timedOut {
-                            message.status = .sendingTimedOut
-                        } else {
-                            message.status = .sendingFailed
-                        }
-
-                        if let foo = try? self.database.saveMessage(message) {
-                            message = foo
-                        }
-                    }
-                })
-            )
-
-            print("")
-
-            message.roundURL = report.roundURL
-            message.date = Date.fromTimestamp(Int(report.timestamp))
-            message = try database.saveMessage(message)
-        } catch {
-            message.status = .sendingFailed
-            if let foo = try? database.saveMessage(message) {
-                message = foo
+            if let foo = try? self.database.saveMessage(message) {
+              message = foo
             }
-        }
-
-        stagedReply = nil
+          }
+        })
+      )
+
+      print("")
+
+      message.roundURL = report.roundURL
+      message.date = Date.fromTimestamp(Int(report.timestamp))
+      message = try database.saveMessage(message)
+    } catch {
+      message.status = .sendingFailed
+      if let foo = try? database.saveMessage(message) {
+        message = foo
+      }
     }
 
-    func retry(_ message: Message) {
-        var message = message
-
-        do {
-            message.status = .sending
-            message = try database.saveMessage(message)
-
-            var reply: Reply?
-
-            if let replyId = message.replyMessageId {
-                reply = Reply(messageId: replyId, senderId: myId)
+    stagedReply = nil
+  }
+
+  func retry(_ message: Message) {
+    var message = message
+
+    do {
+      message.status = .sending
+      message = try database.saveMessage(message)
+
+      var reply: Reply?
+
+      if let replyId = message.replyMessageId {
+        reply = Reply(messageId: replyId, senderId: myId)
+      }
+
+      let report = try groupManager.send(
+        groupId: message.groupId!,
+        message: Payload(
+          text: message.text,
+          reply: reply
+        ).asData()
+      )
+
+      try messenger.cMix.get()!.waitForRoundResult(
+        roundList: try report.encode(),
+        timeoutMS: 15_000,
+        callback: .init(handle: {
+          switch $0 {
+          case .delivered:
+            message.status = .sent
+            if let foo = try? self.database.saveMessage(message) {
+              message = foo
             }
-
-            let report = try groupManager.send(
-                groupId: message.groupId!,
-                message: Payload(
-                    text: message.text,
-                    reply: reply
-                ).asData()
-            )
-
-            try messenger.cMix.get()!.waitForRoundResult(
-                roundList: try report.encode(),
-                timeoutMS: 15_000,
-                callback: .init(handle: {
-                    switch $0 {
-                    case .delivered:
-                        message.status = .sent
-                        if let foo = try? self.database.saveMessage(message) {
-                            message = foo
-                        }
-                    case .notDelivered(timedOut: let timedOut):
-                        if timedOut {
-                            message.status = .sendingTimedOut
-                        } else {
-                            message.status = .sendingFailed
-                        }
-
-                        if let foo = try? self.database.saveMessage(message) {
-                            message = foo
-                        }
-                    }
-                })
-            )
-
-            message.networkId = report.messageId
-            message.date = Date.fromTimestamp(Int(report.timestamp))
-            message = try database.saveMessage(message)
-        } catch {
-            message.status = .sendingFailed
-            if let foo = try? database.saveMessage(message) {
-                message = foo
+          case .notDelivered(timedOut: let timedOut):
+            if timedOut {
+              message.status = .sendingTimedOut
+            } else {
+              message.status = .sendingFailed
             }
-        }
-    }
 
-    func showRoundFrom(_ roundURL: String?) {
-        if let urlString = roundURL, !urlString.isEmpty {
-            routesSubject.send(.webview(urlString))
-        } else {
-            routesSubject.send(.waitingRound)
-        }
+            if let foo = try? self.database.saveMessage(message) {
+              message = foo
+            }
+          }
+        })
+      )
+
+      message.networkId = report.messageId
+      message.date = Date.fromTimestamp(Int(report.timestamp))
+      message = try database.saveMessage(message)
+    } catch {
+      message.status = .sendingFailed
+      if let foo = try? database.saveMessage(message) {
+        message = foo
+      }
     }
+  }
 
-    func abortReply() {
-        stagedReply = nil
+  func showRoundFrom(_ roundURL: String?) {
+    if let urlString = roundURL, !urlString.isEmpty {
+      routesSubject.send(.webview(urlString))
+    } else {
+      routesSubject.send(.waitingRound)
     }
+  }
 
-    func getReplyContent(for messageId: Data) -> (String, String) {
-        guard let message = try? database.fetchMessages(.init(networkId: messageId)).first else {
-            return ("[DELETED]", "[DELETED]")
-        }
+  func abortReply() {
+    stagedReply = nil
+  }
 
-        return (getName(from: message.senderId), message.text)
+  func getReplyContent(for messageId: Data) -> (String, String) {
+    guard let message = try? database.fetchMessages(.init(networkId: messageId)).first else {
+      return ("[DELETED]", "[DELETED]")
     }
 
-    func getName(from senderId: Data) -> String {
-        guard senderId != myId else { return "You" }
+    return (getName(from: message.senderId), message.text)
+  }
 
-        guard let contact = try? database.fetchContacts(.init(id: [senderId])).first else {
-            return "[DELETED]"
-        }
-
-        var name = (contact.nickname ?? contact.username) ?? "Fetching username..."
-
-        if contact.isBlocked, reportingStatus.isEnabled() {
-            name = "\(name) (Blocked)"
-        }
+  func getName(from senderId: Data) -> String {
+    guard senderId != myId else { return "You" }
 
-        return name
+    guard let contact = try? database.fetchContacts(.init(id: [senderId])).first else {
+      return "[DELETED]"
     }
 
-    func didRequestReply(_ message: Message) {
-        guard let networkId = message.networkId else { return }
-        stagedReply = Reply(messageId: networkId, senderId: message.senderId)
-        replySubject.send(getReplyContent(for: networkId))
-    }
+    var name = (contact.nickname ?? contact.username) ?? "Fetching username..."
 
-    func report(contact: XXModels.Contact, screenshot: UIImage, completion: @escaping () -> Void) {
-        let report = Report(
-            sender: .init(
-                userId: contact.id.base64EncodedString(),
-                username: contact.username!
-            ),
-            recipient: .init(
-                userId: 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()
-                }
-            }
-        }
+    if contact.isBlocked, reportingStatus.isEnabled() {
+      name = "\(name) (Blocked)"
     }
 
-    private func blockContact(_ contact: XXModels.Contact) {
-        var contact = contact
-        contact.isBlocked = true
-        _ = try? database.saveContact(contact)
-    }
+    return name
+  }
+
+  func didRequestReply(_ message: Message) {
+    guard let networkId = message.networkId else { return }
+    stagedReply = Reply(messageId: networkId, senderId: message.senderId)
+    replySubject.send(getReplyContent(for: networkId))
+  }
+
+  func report(contact: XXModels.Contact, screenshot: UIImage, completion: @escaping () -> Void) {
+    let report = Report(
+      sender: .init(
+        userId: contact.id.base64EncodedString(),
+        username: contact.username!
+      ),
+      recipient: .init(
+        userId: 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 ?? "")
+      }
+    )
+
+    hudController.show()
+    sendReport(report) { result in
+      switch result {
+      case .failure(let error):
+        DispatchQueue.main.async {
+          self.hudController.show(.init(error: error))
+        }
 
-    private func presentReportConfirmation(contact: XXModels.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
-        ))
+      case .success(_):
+        self.blockContact(contact)
+        DispatchQueue.main.async {
+          self.hudController.dismiss()
+          self.presentReportConfirmation(contact: contact)
+          completion()
+        }
+      }
     }
+  }
+
+  private func blockContact(_ contact: XXModels.Contact) {
+    var contact = contact
+    contact.isBlocked = true
+    _ = try? database.saveContact(contact)
+  }
+
+  private func presentReportConfirmation(contact: XXModels.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 58757d2f8b83412fd9a7482de7d7275b98bb16d2..cd1a6f4b65431b2c51f8e47901bc950fff5c29e8 100644
--- a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
+++ b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Models
 import Shared
@@ -20,503 +19,501 @@ import struct XXModels.FileTransfer
 import NetworkMonitor
 
 enum SingleChatNavigationRoutes: Equatable {
-    case none
-    case camera
-    case library
-    case waitingRound
-    case cameraPermission
-    case libraryPermission
-    case microphonePermission
-    case webview(String)
+  case none
+  case camera
+  case library
+  case waitingRound
+  case cameraPermission
+  case libraryPermission
+  case microphonePermission
+  case webview(String)
 }
 
 final class SingleChatViewModel: NSObject {
-    @Dependency var logger: XXLogger
-    @Dependency var database: Database
-    @Dependency var sendReport: SendReport
-    @Dependency var messenger: Messenger
-    @Dependency var permissions: PermissionHandling
-    @Dependency var toastController: ToastController
-    @Dependency var networkMonitor: NetworkMonitoring
-    @Dependency var transferManager: XXClient.FileTransfer
-
-    @KeyObject(.username, defaultValue: nil) var username: String?
-
-    var contact: XXModels.Contact { contactSubject.value }
-    private var stagedReply: Reply?
-    private var cancellables = Set<AnyCancellable>()
-    private let contactSubject: CurrentValueSubject<XXModels.Contact, Never>
-    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>()
-
-    private var healthCancellable: XXClient.Cancellable?
-
-    var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-
-    var isOnline: AnyPublisher<Bool, Never> {
-        networkMonitor
-            .statusPublisher
-            .map { $0 == .available }
-            .eraseToAnyPublisher()
-    }
-
-
-    var myId: Data {
-        try! messenger.e2e.get()!.getContact().getId()
-    }
-
-    var contactPublisher: AnyPublisher<XXModels.Contact, Never> { contactSubject.eraseToAnyPublisher() }
-    var replyPublisher: AnyPublisher<(String, String), Never> { replySubject.eraseToAnyPublisher() }
-    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>]()
-            sections.forEach { snapshot.append(.init(model: $0.model, elements: $0.elements)) }
-            return snapshot
-        }.eraseToAnyPublisher()
-    }
-
-    private func updateRecentState(_ contact: XXModels.Contact) {
-        if contact.isRecent == true {
-            var contact = contact
-            contact.isRecent = false
-            _ = try? database.saveContact(contact)
-        }
-    }
-
-    func viewDidAppear() {
-        updateRecentState(contact)
-    }
-
-    init(_ contact: XXModels.Contact) {
-        self.contactSubject = .init(contact)
-        super.init()
-
-        updateRecentState(contact)
-
-        database.fetchContactsPublisher(Contact.Query(id: [contact.id]))
-        .replaceError(with: [])
-            .compactMap { $0.first }
-            .sink { [unowned self] in contactSubject.send($0) }
-            .store(in: &cancellables)
-
-        database.fetchMessagesPublisher(.init(chat: .direct(myId, contact.id)))
-        .replaceError(with: [])
-            .map {
-                let groupedByDate = Dictionary(grouping: $0) { domainModel -> Date in
-                    let components = Calendar.current.dateComponents([.day, .month, .year], from: domainModel.date)
-                    return Calendar.current.date(from: components)!
-                }
-
-                return groupedByDate
-                    .map { .init(model: ChatSection(date: $0.key), elements: $0.value) }
-                    .sorted(by: { $0.model.date < $1.model.date })
-            }.receive(on: DispatchQueue.main)
-            .sink { [unowned self] in sectionsRelay.send($0) }
-            .store(in: &cancellables)
-
-        healthCancellable = messenger.cMix.get()!.addHealthCallback(.init(handle: { [weak self] in
-            guard let self = self else { return }
-            self.networkMonitor.update($0)
-        }))
-    }
-
-    // MARK: Public
-
-    func getFileTransferWith(id: Data) -> FileTransfer {
-        guard let transfer = try? database.fetchFileTransfers(.init(id: [id])).first else {
-            fatalError()
-        }
-
-        return transfer
+  @Dependency var logger: XXLogger
+  @Dependency var database: Database
+  @Dependency var messenger: Messenger
+  @Dependency var sendReport: SendReport
+  @Dependency var hudController: HUDController
+  @Dependency var permissions: PermissionHandling
+  @Dependency var toastController: ToastController
+  @Dependency var networkMonitor: NetworkMonitoring
+  @Dependency var transferManager: XXClient.FileTransfer
+  
+  @KeyObject(.username, defaultValue: nil) var username: String?
+  
+  var contact: XXModels.Contact { contactSubject.value }
+  private var stagedReply: Reply?
+  private var cancellables = Set<AnyCancellable>()
+  private let contactSubject: CurrentValueSubject<XXModels.Contact, Never>
+  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>()
+  
+  private var healthCancellable: XXClient.Cancellable?
+  
+  var isOnline: AnyPublisher<Bool, Never> {
+    networkMonitor
+      .statusPublisher
+      .map { $0 == .available }
+      .eraseToAnyPublisher()
+  }
+  
+  
+  var myId: Data {
+    try! messenger.e2e.get()!.getContact().getId()
+  }
+  
+  var contactPublisher: AnyPublisher<XXModels.Contact, Never> { contactSubject.eraseToAnyPublisher() }
+  var replyPublisher: AnyPublisher<(String, String), Never> { replySubject.eraseToAnyPublisher() }
+  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>]()
+      sections.forEach { snapshot.append(.init(model: $0.model, elements: $0.elements)) }
+      return snapshot
+    }.eraseToAnyPublisher()
+  }
+  
+  private func updateRecentState(_ contact: XXModels.Contact) {
+    if contact.isRecent == true {
+      var contact = contact
+      contact.isRecent = false
+      _ = try? database.saveContact(contact)
     }
-
-    func didSendAudio(url: URL) {
-        do {
-            let _ = try transferManager.send(
-                params: .init(
-                    payload: .init(
-                        name: "",
-                        type: "",
-                        preview: Data(),
-                        contents: Data()
-                    ),
-                    recipientId: contact.id,
-                    retry: 1,
-                    period: 1_000
-                ),
-                callback: .init(handle: {
-                    switch $0 {
-                    case .success(let progressCallback):
-                        print(progressCallback.progress.total)
-                    case .failure(let error):
-                        print(error.localizedDescription)
-                    }
-                })
-            )
-
-            // transferId
-        } catch {
-
-        }
-    }
-
-    func didSend(image: UIImage) {
-        guard let imageData = image.orientedUp().jpegData(compressionQuality: 1.0) else { return }
-        hudRelay.send(.on)
-
-        let transferName = UUID().uuidString
-
-        do {
-            let tid = try transferManager.send(
-                params: .init(
-                    payload: .init(
-                        name: transferName,
-                        type: "jpeg",
-                        preview: Data(),
-                        contents: imageData
-                    ),
-                    recipientId: contact.id,
-                    retry: 10,
-                    period: 1_000
-                ),
-                callback: .init(handle: {
-                    switch $0 {
-                    case .success(let progressCallback):
-
-                        if progressCallback.progress.completed {
-                            print(">>> Outgoing transfer finished successfully")
-                        } else {
-                            print(">>> Outgoing transfer. (\(progressCallback.progress.transmitted)/\(progressCallback.progress.total))")
-                        }
-
-                        /// THIS IS TOO COMPLEX, NEEDS HELP FROM DARIUSZ
-
-                    case .failure(let error):
-                        print(">>> Transfer.error: \(error.localizedDescription)")
-                    }
-                })
-            )
-
-            let transferModel = FileTransfer(
-                id: tid,
-                contactId: contact.id,
-                name: transferName,
-                type: "jpeg",
-                data: imageData,
-                progress: 0.0,
-                isIncoming: false,
-                createdAt: Date()
-            )
-
-            let transferMessage = Message(
-                senderId: myId,
-                recipientId: contact.id,
-                groupId: nil,
-                date: Date(),
-                status: .sending,
-                isUnread: false,
-                text: "",
-                replyMessageId: nil,
-                roundURL: nil,
-                fileTransferId: tid
-            )
-
-            try database.saveFileTransfer(transferModel)
-            try database.saveMessage(transferMessage)
-
-            hudRelay.send(.none)
-        } catch {
-            hudRelay.send(.error(.init(with: error)))
+  }
+  
+  func viewDidAppear() {
+    updateRecentState(contact)
+  }
+  
+  init(_ contact: XXModels.Contact) {
+    self.contactSubject = .init(contact)
+    super.init()
+    
+    updateRecentState(contact)
+    
+    database.fetchContactsPublisher(Contact.Query(id: [contact.id]))
+      .replaceError(with: [])
+      .compactMap { $0.first }
+      .sink { [unowned self] in contactSubject.send($0) }
+      .store(in: &cancellables)
+    
+    database.fetchMessagesPublisher(.init(chat: .direct(myId, contact.id)))
+      .replaceError(with: [])
+      .map {
+        let groupedByDate = Dictionary(grouping: $0) { domainModel -> Date in
+          let components = Calendar.current.dateComponents([.day, .month, .year], from: domainModel.date)
+          return Calendar.current.date(from: components)!
         }
+        
+        return groupedByDate
+          .map { .init(model: ChatSection(date: $0.key), elements: $0.value) }
+          .sorted(by: { $0.model.date < $1.model.date })
+      }.receive(on: DispatchQueue.main)
+      .sink { [unowned self] in sectionsRelay.send($0) }
+      .store(in: &cancellables)
+    
+    healthCancellable = messenger.cMix.get()!.addHealthCallback(.init(handle: { [weak self] in
+      guard let self = self else { return }
+      self.networkMonitor.update($0)
+    }))
+  }
+  
+  // MARK: Public
+  
+  func getFileTransferWith(id: Data) -> FileTransfer {
+    guard let transfer = try? database.fetchFileTransfers(.init(id: [id])).first else {
+      fatalError()
     }
-
-    func readAll() {
-        let assignment = Message.Assignments(isUnread: false)
-        let query = Message.Query(chat: .direct(myId, contact.id))
-        _ = try? database.bulkUpdateMessages(query, assignment)
-    }
-
-    func didRequestDeleteAll() {
-        _ = try? database.deleteMessages(.init(chat: .direct(myId, contact.id)))
-    }
-
-    func didRequestRetry(_ message: Message) {
-        var message = message
-
-        do {
-            message.status = .sending
-            message = try database.saveMessage(message)
-
-            var reply: Reply?
-
-            if let replyId = message.replyMessageId {
-                reply = Reply(messageId: replyId, senderId: myId)
-            }
-
-            let report = try messenger.e2e.get()!.send(
-                messageType: 2,
-                recipientId: contact.id,
-                payload: Payload(
-                    text: message.text,
-                    reply: reply
-                ).asData(),
-                e2eParams: GetE2EParams.liveDefault()
-            )
-
-            try messenger.cMix.get()!.waitForRoundResult(
-                roundList: try report.encode(),
-                timeoutMS: 15_000,
-                callback: .init(handle: {
-                    switch $0 {
-                    case .delivered:
-                        message.status = .sent
-                        _ = try? self.database.saveMessage(message)
-
-                    case .notDelivered(timedOut: let timedOut):
-                        if timedOut {
-                            message.status = .sendingTimedOut
-                        } else {
-                            message.status = .sendingFailed
-                        }
-
-                        _ = try? self.database.saveMessage(message)
-                    }
-                })
-            )
-
-            message.roundURL = report.roundURL
-            message.networkId = report.messageId
-            if let timestamp = report.timestamp {
-                message.date = Date.fromTimestamp(Int(timestamp))
-            }
-
-            message = try database.saveMessage(message)
-        } catch {
+    
+    return transfer
+  }
+  
+  func didSendAudio(url: URL) {
+    do {
+      let _ = try transferManager.send(
+        params: .init(
+          payload: .init(
+            name: "",
+            type: "",
+            preview: Data(),
+            contents: Data()
+          ),
+          recipientId: contact.id,
+          retry: 1,
+          period: 1_000
+        ),
+        callback: .init(handle: {
+          switch $0 {
+          case .success(let progressCallback):
+            print(progressCallback.progress.total)
+          case .failure(let error):
             print(error.localizedDescription)
-            message.status = .sendingFailed
-            _ = try? database.saveMessage(message)
-        }
-    }
-
-    func didNavigateSomewhere() {
-        navigationRoutes.send(.none)
+          }
+        })
+      )
+      
+      // transferId
+    } catch {
+      
     }
-
-    @discardableResult
-    func didTest(permission: PermissionType) -> Bool {
-        switch permission {
-        case .camera:
-            if permissions.isCameraAllowed {
-                navigationRoutes.send(.camera)
-            } else {
-                navigationRoutes.send(.cameraPermission)
-            }
-        case .library:
-            if permissions.isPhotosAllowed {
-                navigationRoutes.send(.library)
+  }
+  
+  func didSend(image: UIImage) {
+    guard let imageData = image.orientedUp().jpegData(compressionQuality: 1.0) else { return }
+    hudController.show()
+    
+    let transferName = UUID().uuidString
+    
+    do {
+      let tid = try transferManager.send(
+        params: .init(
+          payload: .init(
+            name: transferName,
+            type: "jpeg",
+            preview: Data(),
+            contents: imageData
+          ),
+          recipientId: contact.id,
+          retry: 10,
+          period: 1_000
+        ),
+        callback: .init(handle: {
+          switch $0 {
+          case .success(let progressCallback):
+            
+            if progressCallback.progress.completed {
+              print(">>> Outgoing transfer finished successfully")
             } else {
-                navigationRoutes.send(.libraryPermission)
+              print(">>> Outgoing transfer. (\(progressCallback.progress.transmitted)/\(progressCallback.progress.total))")
             }
-        case .microphone:
-            if permissions.isMicrophoneAllowed {
-                return true
+            
+            /// THIS IS TOO COMPLEX, NEEDS HELP FROM DARIUSZ
+            
+          case .failure(let error):
+            print(">>> Transfer.error: \(error.localizedDescription)")
+          }
+        })
+      )
+      
+      let transferModel = FileTransfer(
+        id: tid,
+        contactId: contact.id,
+        name: transferName,
+        type: "jpeg",
+        data: imageData,
+        progress: 0.0,
+        isIncoming: false,
+        createdAt: Date()
+      )
+      
+      let transferMessage = Message(
+        senderId: myId,
+        recipientId: contact.id,
+        groupId: nil,
+        date: Date(),
+        status: .sending,
+        isUnread: false,
+        text: "",
+        replyMessageId: nil,
+        roundURL: nil,
+        fileTransferId: tid
+      )
+      
+      try database.saveFileTransfer(transferModel)
+      try database.saveMessage(transferMessage)
+      
+      hudController.dismiss()
+    } catch {
+      hudController.show(.init(error: error))
+    }
+  }
+  
+  func readAll() {
+    let assignment = Message.Assignments(isUnread: false)
+    let query = Message.Query(chat: .direct(myId, contact.id))
+    _ = try? database.bulkUpdateMessages(query, assignment)
+  }
+  
+  func didRequestDeleteAll() {
+    _ = try? database.deleteMessages(.init(chat: .direct(myId, contact.id)))
+  }
+  
+  func didRequestRetry(_ message: Message) {
+    var message = message
+    
+    do {
+      message.status = .sending
+      message = try database.saveMessage(message)
+      
+      var reply: Reply?
+      
+      if let replyId = message.replyMessageId {
+        reply = Reply(messageId: replyId, senderId: myId)
+      }
+      
+      let report = try messenger.e2e.get()!.send(
+        messageType: 2,
+        recipientId: contact.id,
+        payload: Payload(
+          text: message.text,
+          reply: reply
+        ).asData(),
+        e2eParams: GetE2EParams.liveDefault()
+      )
+      
+      try messenger.cMix.get()!.waitForRoundResult(
+        roundList: try report.encode(),
+        timeoutMS: 15_000,
+        callback: .init(handle: {
+          switch $0 {
+          case .delivered:
+            message.status = .sent
+            _ = try? self.database.saveMessage(message)
+            
+          case .notDelivered(timedOut: let timedOut):
+            if timedOut {
+              message.status = .sendingTimedOut
             } else {
-                navigationRoutes.send(.microphonePermission)
+              message.status = .sendingFailed
             }
-        }
-
-        return false
-    }
-
-    func didRequestCopy(_ model: Message) {
-        UIPasteboard.general.string = model.text
-    }
-
-    func didRequestDeleteSingle(_ model: Message) {
-        didRequestDelete([model])
-    }
-
-    func didRequestReport(_: Message) {
-        reportPopupSubject.send()
+            
+            _ = try? self.database.saveMessage(message)
+          }
+        })
+      )
+      
+      message.roundURL = report.roundURL
+      message.networkId = report.messageId
+      if let timestamp = report.timestamp {
+        message.date = Date.fromTimestamp(Int(timestamp))
+      }
+      
+      message = try database.saveMessage(message)
+    } catch {
+      print(error.localizedDescription)
+      message.status = .sendingFailed
+      _ = try? database.saveMessage(message)
     }
-
-    func abortReply() {
-        stagedReply = nil
+  }
+  
+  func didNavigateSomewhere() {
+    navigationRoutes.send(.none)
+  }
+  
+  @discardableResult
+  func didTest(permission: PermissionType) -> Bool {
+    switch permission {
+    case .camera:
+      if permissions.isCameraAllowed {
+        navigationRoutes.send(.camera)
+      } else {
+        navigationRoutes.send(.cameraPermission)
+      }
+    case .library:
+      if permissions.isPhotosAllowed {
+        navigationRoutes.send(.library)
+      } else {
+        navigationRoutes.send(.libraryPermission)
+      }
+    case .microphone:
+      if permissions.isMicrophoneAllowed {
+        return true
+      } else {
+        navigationRoutes.send(.microphonePermission)
+      }
     }
-
-    func send(_ string: String) {
-        var message: Message = .init(
-            senderId: myId,
-            recipientId: contact.id,
-            groupId: nil,
-            date: Date(),
-            status: .sending,
-            isUnread: false,
-            text: string.trimmingCharacters(in: .whitespacesAndNewlines),
-            replyMessageId: stagedReply?.messageId
+    
+    return false
+  }
+  
+  func didRequestCopy(_ model: Message) {
+    UIPasteboard.general.string = model.text
+  }
+  
+  func didRequestDeleteSingle(_ model: Message) {
+    didRequestDelete([model])
+  }
+  
+  func didRequestReport(_: Message) {
+    reportPopupSubject.send()
+  }
+  
+  func abortReply() {
+    stagedReply = nil
+  }
+  
+  func send(_ string: String) {
+    var message: Message = .init(
+      senderId: myId,
+      recipientId: contact.id,
+      groupId: nil,
+      date: Date(),
+      status: .sending,
+      isUnread: false,
+      text: string.trimmingCharacters(in: .whitespacesAndNewlines),
+      replyMessageId: stagedReply?.messageId
+    )
+    
+    DispatchQueue.global().async { [weak self] in
+      guard let self = self else { return }
+      
+      do {
+        message = try self.database.saveMessage(message)
+        
+        let report = try self.messenger.e2e.get()!.send(
+          messageType: 2,
+          recipientId: self.contact.id,
+          payload: Payload(text: message.text, reply: self.stagedReply).asData(),
+          e2eParams: GetE2EParams.liveDefault()
         )
-
-        DispatchQueue.global().async { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                message = try self.database.saveMessage(message)
-
-                let report = try self.messenger.e2e.get()!.send(
-                    messageType: 2,
-                    recipientId: self.contact.id,
-                    payload: Payload(text: message.text, reply: self.stagedReply).asData(),
-                    e2eParams: GetE2EParams.liveDefault()
-                )
-
-                try self.messenger.cMix.get()!.waitForRoundResult(
-                    roundList: try report.encode(),
-                    timeoutMS: 15_000,
-                    callback: .init(handle: {
-                        switch $0 {
-                        case .delivered:
-                            message.status = .sent
-                            _ = try? self.database.saveMessage(message)
-
-                        case .notDelivered(timedOut: let timedOut):
-                            if timedOut {
-                                message.status = .sendingTimedOut
-                            } else {
-                                message.status = .sendingFailed
-                            }
-
-                            _ = try? self.database.saveMessage(message)
-                        }
-                    })
-                )
-
-                message.roundURL = report.roundURL
-                message.networkId = report.messageId
-                if let timestamp = report.timestamp {
-                    message.date = Date.fromTimestamp(Int(timestamp))
-                }
-
-                message = try self.database.saveMessage(message)
-            } catch {
-                print(error.localizedDescription)
+        
+        try self.messenger.cMix.get()!.waitForRoundResult(
+          roundList: try report.encode(),
+          timeoutMS: 15_000,
+          callback: .init(handle: {
+            switch $0 {
+            case .delivered:
+              message.status = .sent
+              _ = try? self.database.saveMessage(message)
+              
+            case .notDelivered(timedOut: let timedOut):
+              if timedOut {
+                message.status = .sendingTimedOut
+              } else {
                 message.status = .sendingFailed
-                _ = try? self.database.saveMessage(message)
+              }
+              
+              _ = try? self.database.saveMessage(message)
             }
-
-            self.stagedReply = nil
-        }
-    }
-
-    func didRequestReply(_ message: Message) {
-        guard let networkId = message.networkId else { return }
-
-        let senderTitle: String = {
-            if message.senderId == myId {
-                return "You"
-            } else {
-                return (contact.nickname ?? contact.username) ?? "Fetching username..."
-            }
-        }()
-
-        replySubject.send((senderTitle, message.text))
-        stagedReply = Reply(messageId: networkId, senderId: message.senderId)
-    }
-
-    func getReplyContent(for messageId: Data) -> (String, String) {
-        guard let message = try? database.fetchMessages(.init(networkId: messageId)).first else {
-            return ("[DELETED]", "[DELETED]")
-        }
-
-        guard let contact = try? database.fetchContacts(.init(id: [message.senderId])).first else {
-            fatalError()
+          })
+        )
+        
+        message.roundURL = report.roundURL
+        message.networkId = report.messageId
+        if let timestamp = report.timestamp {
+          message.date = Date.fromTimestamp(Int(timestamp))
         }
-
-        let contactTitle = (contact.nickname ?? contact.username) ?? "You"
-        return (contactTitle, message.text)
+        
+        message = try self.database.saveMessage(message)
+      } catch {
+        print(error.localizedDescription)
+        message.status = .sendingFailed
+        _ = try? self.database.saveMessage(message)
+      }
+      
+      self.stagedReply = nil
     }
-
-    func showRoundFrom(_ roundURL: String?) {
-        if let urlString = roundURL, !urlString.isEmpty {
-            navigationRoutes.send(.webview(urlString))
-        } else {
-            navigationRoutes.send(.waitingRound)
-        }
+  }
+  
+  func didRequestReply(_ message: Message) {
+    guard let networkId = message.networkId else { return }
+    
+    let senderTitle: String = {
+      if message.senderId == myId {
+        return "You"
+      } else {
+        return (contact.nickname ?? contact.username) ?? "Fetching username..."
+      }
+    }()
+    
+    replySubject.send((senderTitle, message.text))
+    stagedReply = Reply(messageId: networkId, senderId: message.senderId)
+  }
+  
+  func getReplyContent(for messageId: Data) -> (String, String) {
+    guard let message = try? database.fetchMessages(.init(networkId: messageId)).first else {
+      return ("[DELETED]", "[DELETED]")
     }
-
-    func didRequestDelete(_ items: [Message]) {
-        _ = try? database.deleteMessages(.init(id: Set(items.compactMap(\.id))))
+    
+    guard let contact = try? database.fetchContacts(.init(id: [message.senderId])).first else {
+      fatalError()
     }
-
-    func itemWith(id: Int64) -> Message? {
-        sectionsRelay.value.flatMap(\.elements).first(where: { $0.id == id })
+    
+    let contactTitle = (contact.nickname ?? contact.username) ?? "You"
+    return (contactTitle, message.text)
+  }
+  
+  func showRoundFrom(_ roundURL: String?) {
+    if let urlString = roundURL, !urlString.isEmpty {
+      navigationRoutes.send(.webview(urlString))
+    } else {
+      navigationRoutes.send(.waitingRound)
     }
-
-    func itemAt(indexPath: IndexPath) -> Message? {
-        guard sectionsRelay.value.count > indexPath.section else { return nil }
-
-        let items = sectionsRelay.value[indexPath.section].elements
-        return items.count > indexPath.row ? items[indexPath.row] : nil
-    }
-
-    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: 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)
-                }
-            }
+  }
+  
+  func didRequestDelete(_ items: [Message]) {
+    _ = try? database.deleteMessages(.init(id: Set(items.compactMap(\.id))))
+  }
+  
+  func itemWith(id: Int64) -> Message? {
+    sectionsRelay.value.flatMap(\.elements).first(where: { $0.id == id })
+  }
+  
+  func itemAt(indexPath: IndexPath) -> Message? {
+    guard sectionsRelay.value.count > indexPath.section else { return nil }
+    
+    let items = sectionsRelay.value[indexPath.section].elements
+    return items.count > indexPath.row ? items[indexPath.row] : nil
+  }
+  
+  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: myId.base64EncodedString(),
+        username: username!
+      ),
+      type: .dm,
+      screenshot: screenshot.pngData()!
+    )
+    
+    hudController.show()
+    sendReport(report) { result in
+      switch result {
+      case .failure(let error):
+        DispatchQueue.main.async {
+          self.hudController.show(.init(error: error))
+          completion(false)
         }
+        
+      case .success(_):
+        self.blockContact()
+        DispatchQueue.main.async {
+          self.hudController.dismiss()
+          self.presentReportConfirmation()
+          completion(true)
+        }
+      }
     }
-
-    private func blockContact() {
-        var contact = contact
-        contact.isBlocked = true
-        _ = try? database.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
-        ))
-    }
+  }
+  
+  private func blockContact() {
+    var contact = contact
+    contact.isBlocked = true
+    _ = try? database.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/ChatListFeature/ViewModel/ChatListViewModel.swift b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
index 32b7e99a945568919a6b5bbde947515944490f87..f61aef1e7e33b55fd857cfae60899b3ad71dd6bf 100644
--- a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
+++ b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Models
@@ -13,195 +12,191 @@ import struct XXModels.Group
 import XXClient
 
 enum SearchSection {
-    case chats
-    case connections
+  case chats
+  case connections
 }
 
 enum SearchItem: Equatable, Hashable {
-    case chat(ChatInfo)
-    case connection(XXModels.Contact)
+  case chat(ChatInfo)
+  case connection(XXModels.Contact)
 }
 
 typealias RecentsSnapshot = NSDiffableDataSourceSnapshot<SectionId, XXModels.Contact>
 typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem>
 
 final class ChatListViewModel {
-    @Dependency var database: Database
-    @Dependency var messenger: Messenger
-    @Dependency var groupManager: GroupChat
-    @Dependency var reportingStatus: ReportingStatus
-
-    // TO REFACTOR:
-    var isOnline: AnyPublisher<Bool, Never> {
-        Just(.init(true)).eraseToAnyPublisher()
-    }
-
-    var myId: Data {
-        try! messenger.e2e.get()!.getContact().getId()
-    }
-
-    var chatsPublisher: AnyPublisher<[ChatInfo], Never> {
-        chatsSubject.eraseToAnyPublisher()
-    }
-
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
-
-    var recentsPublisher: AnyPublisher<RecentsSnapshot, Never> {
-        let query = Contact.Query(
-            authStatus: [.friend],
-            isRecent: true,
-            isBlocked: reportingStatus.isEnabled() ? false : nil,
-            isBanned: reportingStatus.isEnabled() ? false : nil
-        )
-
-        return database.fetchContactsPublisher(query)
+  @Dependency var database: Database
+  @Dependency var messenger: Messenger
+  @Dependency var groupManager: GroupChat
+  @Dependency var hudController: HUDController
+  @Dependency var reportingStatus: ReportingStatus
+
+  // TO REFACTOR:
+  var isOnline: AnyPublisher<Bool, Never> {
+    Just(.init(true)).eraseToAnyPublisher()
+  }
+
+  var myId: Data {
+    try! messenger.e2e.get()!.getContact().getId()
+  }
+
+  var chatsPublisher: AnyPublisher<[ChatInfo], Never> {
+    chatsSubject.eraseToAnyPublisher()
+  }
+
+  var recentsPublisher: AnyPublisher<RecentsSnapshot, Never> {
+    let query = Contact.Query(
+      authStatus: [.friend],
+      isRecent: true,
+      isBlocked: reportingStatus.isEnabled() ? false : nil,
+      isBanned: reportingStatus.isEnabled() ? false : nil
+    )
+
+    return database.fetchContactsPublisher(query)
+      .replaceError(with: [])
+      .map {
+        let section = SectionId()
+        var snapshot = RecentsSnapshot()
+        snapshot.appendSections([section])
+        snapshot.appendItems($0, toSection: section)
+        return snapshot
+      }.eraseToAnyPublisher()
+  }
+
+  var searchPublisher: AnyPublisher<SearchSnapshot, Never> {
+    let contactsQuery = Contact.Query(
+      isBlocked: reportingStatus.isEnabled() ? false : nil,
+      isBanned: reportingStatus.isEnabled() ? false : nil
+    )
+
+    return Publishers.CombineLatest3(
+      database.fetchContactsPublisher(contactsQuery)
         .replaceError(with: [])
-            .map {
-                let section = SectionId()
-                var snapshot = RecentsSnapshot()
-                snapshot.appendSections([section])
-                snapshot.appendItems($0, toSection: section)
-                return snapshot
-            }.eraseToAnyPublisher()
-    }
-
-    var searchPublisher: AnyPublisher<SearchSnapshot, Never> {
-        let contactsQuery = Contact.Query(
-            isBlocked: reportingStatus.isEnabled() ? false : nil,
-            isBanned: reportingStatus.isEnabled() ? false : nil
-        )
-
-        return Publishers.CombineLatest3(
-            database.fetchContactsPublisher(contactsQuery)
-              .replaceError(with: [])
-                .map { $0.filter { $0.id != self.myId }},
-            chatsPublisher,
-            searchSubject
-                .removeDuplicates()
-                .debounce(for: .milliseconds(100), scheduler: DispatchQueue.main)
-                .eraseToAnyPublisher()
-        )
-        .map { (contacts, chats, query) in
-            let connectionItems = contacts.filter {
-                let username = $0.username?.lowercased().contains(query.lowercased()) ?? false
-                let nickname = $0.nickname?.lowercased().contains(query.lowercased()) ?? false
-                return username || nickname
-            }.map(SearchItem.connection)
-
-            let chatItems = chats.filter {
-                switch $0 {
-                case .group(let group):
-                    return group.name.lowercased().contains(query.lowercased())
-
-                case .groupChat(let info):
-                    let name = info.group.name.lowercased().contains(query.lowercased())
-                    let last = info.lastMessage.text.lowercased().contains(query.lowercased())
-                    return name || last
-
-                case .contactChat(let info):
-                    let username = info.contact.username?.lowercased().contains(query.lowercased()) ?? false
-                    let nickname = info.contact.nickname?.lowercased().contains(query.lowercased()) ?? false
-                    let lastMessage = info.lastMessage.text.lowercased().contains(query.lowercased())
-                    return username || nickname || lastMessage
-
-                }
-            }.map(SearchItem.chat)
-
-            var snapshot = SearchSnapshot()
-
-            if connectionItems.count > 0 {
-                snapshot.appendSections([.connections])
-                snapshot.appendItems(connectionItems, toSection: .connections)
-            }
-
-            if chatItems.count > 0 {
-                snapshot.appendSections([.chats])
-                snapshot.appendItems(chatItems, toSection: .chats)
-            }
-
-            return snapshot
-        }.eraseToAnyPublisher()
-    }
-
-    var badgeCountPublisher: AnyPublisher<Int, Never> {
-        let groupQuery = Group.Query(authStatus: [.pending])
-        let contactsQuery = Contact.Query(
-            authStatus: [
-                .verified,
-                .confirming,
-                .confirmationFailed,
-                .verificationFailed,
-                .verificationInProgress
-            ],
-            isBlocked: reportingStatus.isEnabled() ? false : nil,
-            isBanned: reportingStatus.isEnabled() ? false : nil
-        )
-
-        return Publishers.CombineLatest(
-            database.fetchContactsPublisher(contactsQuery).replaceError(with: []),
-            database.fetchGroupsPublisher(groupQuery).replaceError(with: [])
-        )
-        .map { $0.0.count + $0.1.count }
+        .map { $0.filter { $0.id != self.myId }},
+      chatsPublisher,
+      searchSubject
+        .removeDuplicates()
+        .debounce(for: .milliseconds(100), scheduler: DispatchQueue.main)
         .eraseToAnyPublisher()
-    }
-
-    private var cancellables = Set<AnyCancellable>()
-    private let searchSubject = CurrentValueSubject<String, Never>("")
-    private let chatsSubject = CurrentValueSubject<[ChatInfo], Never>([])
-    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
-
-    init() {
-        database.fetchChatInfosPublisher(
-            ChatInfo.Query(
-                contactChatInfoQuery: .init(
-                    userId: myId,
-                    authStatus: [.friend],
-                    isBlocked: reportingStatus.isEnabled() ? false : nil,
-                    isBanned: reportingStatus.isEnabled() ? false : nil
-                ),
-                groupChatInfoQuery: GroupChatInfo.Query(
-                    authStatus: [.participating],
-                    excludeBannedContactsMessages: reportingStatus.isEnabled()
-                ),
-                groupQuery: Group.Query(
-                    withMessages: false,
-                    authStatus: [.participating]
-                )
-            ))
-        .replaceError(with: [])
-        .sink { [unowned self] in chatsSubject.send($0) }
-        .store(in: &cancellables)
-    }
+    )
+    .map { (contacts, chats, query) in
+      let connectionItems = contacts.filter {
+        let username = $0.username?.lowercased().contains(query.lowercased()) ?? false
+        let nickname = $0.nickname?.lowercased().contains(query.lowercased()) ?? false
+        return username || nickname
+      }.map(SearchItem.connection)
+
+      let chatItems = chats.filter {
+        switch $0 {
+        case .group(let group):
+          return group.name.lowercased().contains(query.lowercased())
+
+        case .groupChat(let info):
+          let name = info.group.name.lowercased().contains(query.lowercased())
+          let last = info.lastMessage.text.lowercased().contains(query.lowercased())
+          return name || last
+
+        case .contactChat(let info):
+          let username = info.contact.username?.lowercased().contains(query.lowercased()) ?? false
+          let nickname = info.contact.nickname?.lowercased().contains(query.lowercased()) ?? false
+          let lastMessage = info.lastMessage.text.lowercased().contains(query.lowercased())
+          return username || nickname || lastMessage
 
-    func updateSearch(query: String) {
-        searchSubject.send(query)
-    }
-
-    func leave(_ group: Group) {
-        hudSubject.send(.on)
-
-        do {
-            try groupManager.leaveGroup(groupId: group.id)
-            try database.deleteMessages(.init(chat: .group(group.id)))
-            try database.deleteGroup(group)
-            hudSubject.send(.none)
-        } catch {
-            hudSubject.send(.error(.init(with: error)))
         }
+      }.map(SearchItem.chat)
+
+      var snapshot = SearchSnapshot()
+
+      if connectionItems.count > 0 {
+        snapshot.appendSections([.connections])
+        snapshot.appendItems(connectionItems, toSection: .connections)
+      }
+
+      if chatItems.count > 0 {
+        snapshot.appendSections([.chats])
+        snapshot.appendItems(chatItems, toSection: .chats)
+      }
+
+      return snapshot
+    }.eraseToAnyPublisher()
+  }
+
+  var badgeCountPublisher: AnyPublisher<Int, Never> {
+    let groupQuery = Group.Query(authStatus: [.pending])
+    let contactsQuery = Contact.Query(
+      authStatus: [
+        .verified,
+        .confirming,
+        .confirmationFailed,
+        .verificationFailed,
+        .verificationInProgress
+      ],
+      isBlocked: reportingStatus.isEnabled() ? false : nil,
+      isBanned: reportingStatus.isEnabled() ? false : nil
+    )
+
+    return Publishers.CombineLatest(
+      database.fetchContactsPublisher(contactsQuery).replaceError(with: []),
+      database.fetchGroupsPublisher(groupQuery).replaceError(with: [])
+    )
+    .map { $0.0.count + $0.1.count }
+    .eraseToAnyPublisher()
+  }
+
+  private var cancellables = Set<AnyCancellable>()
+  private let searchSubject = CurrentValueSubject<String, Never>("")
+  private let chatsSubject = CurrentValueSubject<[ChatInfo], Never>([])
+
+  init() {
+    database.fetchChatInfosPublisher(
+      ChatInfo.Query(
+        contactChatInfoQuery: .init(
+          userId: myId,
+          authStatus: [.friend],
+          isBlocked: reportingStatus.isEnabled() ? false : nil,
+          isBanned: reportingStatus.isEnabled() ? false : nil
+        ),
+        groupChatInfoQuery: GroupChatInfo.Query(
+          authStatus: [.participating],
+          excludeBannedContactsMessages: reportingStatus.isEnabled()
+        ),
+        groupQuery: Group.Query(
+          withMessages: false,
+          authStatus: [.participating]
+        )
+      ))
+    .replaceError(with: [])
+    .sink { [unowned self] in chatsSubject.send($0) }
+    .store(in: &cancellables)
+  }
+
+  func updateSearch(query: String) {
+    searchSubject.send(query)
+  }
+
+  func leave(_ group: Group) {
+    hudController.show()
+
+    do {
+      try groupManager.leaveGroup(groupId: group.id)
+      try database.deleteMessages(.init(chat: .group(group.id)))
+      try database.deleteGroup(group)
+      hudController.dismiss()
+    } catch {
+      hudController.show(.init(error: error))
     }
+  }
 
-    func clear(_ contact: XXModels.Contact) {
-        _ = try? database.deleteMessages(.init(chat: .direct(myId, contact.id)))
-    }
-
-    func groupInfo(from group: Group) -> GroupInfo? {
-        let query = GroupInfo.Query(groupId: group.id)
-        guard let info = try? database.fetchGroupInfos(query).first else {
-            return nil
-        }
+  func clear(_ contact: XXModels.Contact) {
+    _ = try? database.deleteMessages(.init(chat: .direct(myId, contact.id)))
+  }
 
-        return info
+  func groupInfo(from group: Group) -> GroupInfo? {
+    let query = GroupInfo.Query(groupId: group.id)
+    guard let info = try? database.fetchGroupInfos(query).first else {
+      return nil
     }
+
+    return info
+  }
 }
diff --git a/Sources/ContactFeature/Controllers/ContactController.swift b/Sources/ContactFeature/Controllers/ContactController.swift
index d9c7f68fc87b0b4970ea138aa63dbf08e1d660dd..1fa130ce1a3bc80b88a280fd575ae4f4f73e75da 100644
--- a/Sources/ContactFeature/Controllers/ContactController.swift
+++ b/Sources/ContactFeature/Controllers/ContactController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Models
@@ -9,7 +8,6 @@ import DependencyInjection
 import ScrollViewController
 
 public final class ContactController: UIViewController {
-  @Dependency var hud: HUD
   @Dependency var barStylist: StatusBarStylist
   @Dependency var coordinator: ContactCoordinating
 
@@ -75,11 +73,6 @@ public final class ContactController: UIViewController {
   }
 
   private func setupBindings() {
-    viewModel.hudPublisher
-      .receive(on: DispatchQueue.main)
-      .sink { [hud] in hud.update(with: $0) }
-      .store(in: &cancellables)
-
     screenView.cardComponent.avatarView.editButton
       .publisher(for: .touchUpInside)
       .receive(on: DispatchQueue.main)
diff --git a/Sources/ContactFeature/ViewModels/ContactViewModel.swift b/Sources/ContactFeature/ViewModels/ContactViewModel.swift
index 5e4b3d717d600e2479f1e242211ddc87f570fe7a..ee0e2676bcc7fd665c32f05a5fb45a6aae4b387b 100644
--- a/Sources/ContactFeature/ViewModels/ContactViewModel.swift
+++ b/Sources/ContactFeature/ViewModels/ContactViewModel.swift
@@ -1,218 +1,216 @@
-import HUD
 import UIKit
+import Shared
 import Models
 import Combine
 import XXModels
 import Defaults
+import XXClient
 import CombineSchedulers
-import DependencyInjection
 import XXMessengerClient
-
-import XXClient
+import DependencyInjection
 
 struct ContactViewState: Equatable {
-    var title: String?
-    var email: String?
-    var phone: String?
-    var photo: UIImage?
-    var username: String?
-    var nickname: String?
+  var title: String?
+  var email: String?
+  var phone: String?
+  var photo: UIImage?
+  var username: String?
+  var nickname: String?
 }
 
 final class ContactViewModel {
-    @Dependency var database: Database
-    @Dependency var messenger: Messenger
-    @Dependency var getFactsFromContact: GetFactsFromContact
-
-    @KeyObject(.username, defaultValue: nil) var username: String?
-    @KeyObject(.sharingEmail, defaultValue: false) var sharingEmail: Bool
-    @KeyObject(.sharingPhone, defaultValue: false) var sharingPhone: Bool
-
-    var contact: XXModels.Contact
-
-    var popToRootPublisher: AnyPublisher<Void, Never> { popToRootRelay.eraseToAnyPublisher() }
-    var popPublisher: AnyPublisher<Void, Never> { popRelay.eraseToAnyPublisher() }
-    var hudPublisher: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-    var successPublisher: AnyPublisher<Void, Never> { successRelay.eraseToAnyPublisher() }
-    var statePublisher: AnyPublisher<ContactViewState, Never> { stateRelay.eraseToAnyPublisher() }
-
-    private let popRelay = PassthroughSubject<Void, Never>()
-    private let popToRootRelay = PassthroughSubject<Void, Never>()
-    private let successRelay = PassthroughSubject<Void, Never>()
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-    private let stateRelay = CurrentValueSubject<ContactViewState, Never>(.init())
-
-    var myId: Data {
-        try! messenger.e2e.get()!.getContact().getId()
+  @Dependency var database: Database
+  @Dependency var messenger: Messenger
+  @Dependency var hudController: HUDController
+  @Dependency var getFactsFromContact: GetFactsFromContact
+  
+  @KeyObject(.username, defaultValue: nil) var username: String?
+  @KeyObject(.sharingEmail, defaultValue: false) var sharingEmail: Bool
+  @KeyObject(.sharingPhone, defaultValue: false) var sharingPhone: Bool
+  
+  var contact: XXModels.Contact
+  
+  var popPublisher: AnyPublisher<Void, Never> { popRelay.eraseToAnyPublisher() }
+  var successPublisher: AnyPublisher<Void, Never> { successRelay.eraseToAnyPublisher() }
+  var popToRootPublisher: AnyPublisher<Void, Never> { popToRootRelay.eraseToAnyPublisher() }
+  var statePublisher: AnyPublisher<ContactViewState, Never> { stateRelay.eraseToAnyPublisher() }
+  
+  private let popRelay = PassthroughSubject<Void, Never>()
+  private let popToRootRelay = PassthroughSubject<Void, Never>()
+  private let successRelay = PassthroughSubject<Void, Never>()
+  private let stateRelay = CurrentValueSubject<ContactViewState, Never>(.init())
+  
+  var myId: Data {
+    try! messenger.e2e.get()!.getContact().getId()
+  }
+  
+  var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
+  
+  init(_ contact: XXModels.Contact) {
+    self.contact = contact
+    
+    let facts = try? getFactsFromContact(contact.marshaled!)
+    let email = facts?.first(where: { $0.type == .email })?.value
+    let phone = facts?.first(where: { $0.type == .phone })?.value
+    
+    stateRelay.value = .init(
+      title: contact.nickname ?? contact.username,
+      email: email,
+      phone: phone,
+      photo: contact.photo != nil ? UIImage(data: contact.photo!) : nil,
+      username: contact.username,
+      nickname: contact.nickname
+    )
+  }
+  
+  func didChoosePhoto(_ photo: UIImage) {
+    stateRelay.value.photo = photo
+    contact.photo = photo.jpegData(compressionQuality: 0.0)
+    _ = try? database.saveContact(contact)
+  }
+  
+  func didTapDelete() {
+    hudController.show()
+    
+    do {
+      try messenger.e2e.get()!.deleteRequest.partnerId(contact.id)
+      try database.deleteContact(contact)
+      
+      hudController.dismiss()
+      popToRootRelay.send()
+    } catch {
+      hudController.show(.init(error: error))
     }
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
-
-    init(_ contact: XXModels.Contact) {
-        self.contact = contact
-
-        let facts = try? getFactsFromContact(contact.marshaled!)
-        let email = facts?.first(where: { $0.type == .email })?.value
-        let phone = facts?.first(where: { $0.type == .phone })?.value
-
-        stateRelay.value = .init(
-            title: contact.nickname ?? contact.username,
-            email: email,
-            phone: phone,
-            photo: contact.photo != nil ? UIImage(data: contact.photo!) : nil,
-            username: contact.username,
-            nickname: contact.nickname
+  }
+  
+  func didTapReject() {
+    // TODO: Reject function on the API?
+    _ = try? database.deleteContact(contact)
+    popRelay.send()
+  }
+  
+  func didTapClear() {
+    _ = try? database.deleteMessages(.init(chat: .direct(myId, contact.id)))
+  }
+  
+  func didUpdateNickname(_ string: String) {
+    contact.nickname = string.isEmpty ? nil : string
+    stateRelay.value.title = string.isEmpty ? contact.username : string
+    _ = try? database.saveContact(contact)
+    
+    stateRelay.value.nickname = contact.nickname
+  }
+  
+  func didTapResend() {
+    hudController.show()
+    contact.authStatus = .requesting
+    
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
+      
+      do {
+        try self.database.saveContact(self.contact)
+        
+        var includedFacts: [Fact] = []
+        let myFacts = try self.messenger.ud.get()!.getFacts()
+        
+        if let fact = myFacts.get(.username) {
+          includedFacts.append(fact)
+        }
+        
+        if self.sharingEmail, let fact = myFacts.get(.email) {
+          includedFacts.append(fact)
+        }
+        
+        if self.sharingPhone, let fact = myFacts.get(.phone) {
+          includedFacts.append(fact)
+        }
+        
+        let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
+          partner: .live(self.contact.marshaled!),
+          myFacts: includedFacts
         )
+        
+        self.contact.authStatus = .requested
+        try self.database.saveContact(self.contact)
+        
+        self.hudController.dismiss()
+        self.popRelay.send()
+      } catch {
+        self.contact.authStatus = .requestFailed
+        _ = try? self.database.saveContact(self.contact)
+        self.hudController.show(.init(error: error))
+      }
     }
-
-    func didChoosePhoto(_ photo: UIImage) {
-        stateRelay.value.photo = photo
-        contact.photo = photo.jpegData(compressionQuality: 0.0)
-        _ = try? database.saveContact(contact)
-    }
-
-    func didTapDelete() {
-        hudRelay.send(.on)
-
-        do {
-            try messenger.e2e.get()!.deleteRequest.partnerId(contact.id)
-            try database.deleteContact(contact)
-
-            hudRelay.send(.none)
-            popToRootRelay.send()
-        } catch {
-            hudRelay.send(.error(.init(with: error)))
+  }
+  
+  func didTapRequest(with nickname: String) {
+    hudController.show()
+    contact.nickname = nickname
+    contact.authStatus = .requesting
+    
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
+      
+      do {
+        try self.database.saveContact(self.contact)
+        
+        var includedFacts: [Fact] = []
+        let myFacts = try self.messenger.ud.get()!.getFacts()
+        
+        if let fact = myFacts.get(.username) {
+          includedFacts.append(fact)
         }
-    }
-
-    func didTapReject() {
-        // TODO: Reject function on the API?
-        _ = try? database.deleteContact(contact)
-        popRelay.send()
-    }
-
-    func didTapClear() {
-        _ = try? database.deleteMessages(.init(chat: .direct(myId, contact.id)))
-    }
-
-    func didUpdateNickname(_ string: String) {
-        contact.nickname = string.isEmpty ? nil : string
-        stateRelay.value.title = string.isEmpty ? contact.username : string
-        _ = try? database.saveContact(contact)
-
-        stateRelay.value.nickname = contact.nickname
-    }
-
-    func didTapResend() {
-        hudRelay.send(.on)
-        contact.authStatus = .requesting
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.database.saveContact(self.contact)
-
-                var includedFacts: [Fact] = []
-                let myFacts = try self.messenger.ud.get()!.getFacts()
-
-                if let fact = myFacts.get(.username) {
-                    includedFacts.append(fact)
-                }
-
-                if self.sharingEmail, let fact = myFacts.get(.email) {
-                    includedFacts.append(fact)
-                }
-
-                if self.sharingPhone, let fact = myFacts.get(.phone) {
-                    includedFacts.append(fact)
-                }
-
-                let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
-                    partner: .live(self.contact.marshaled!),
-                    myFacts: includedFacts
-                )
-
-                self.contact.authStatus = .requested
-                try self.database.saveContact(self.contact)
-
-                self.hudRelay.send(.none)
-                self.popRelay.send()
-            } catch {
-                self.contact.authStatus = .requestFailed
-                _ = try? self.database.saveContact(self.contact)
-                self.hudRelay.send(.error(.init(with: error)))
-            }
+        
+        if self.sharingEmail, let fact = myFacts.get(.email) {
+          includedFacts.append(fact)
         }
-    }
-
-    func didTapRequest(with nickname: String) {
-        hudRelay.send(.on)
-        contact.nickname = nickname
-        contact.authStatus = .requesting
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.database.saveContact(self.contact)
-
-                var includedFacts: [Fact] = []
-                let myFacts = try self.messenger.ud.get()!.getFacts()
-
-                if let fact = myFacts.get(.username) {
-                    includedFacts.append(fact)
-                }
-
-                if self.sharingEmail, let fact = myFacts.get(.email) {
-                    includedFacts.append(fact)
-                }
-
-                if self.sharingPhone, let fact = myFacts.get(.phone) {
-                    includedFacts.append(fact)
-                }
-
-                let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
-                    partner: .live(self.contact.marshaled!),
-                    myFacts: includedFacts
-                )
-
-                self.contact.authStatus = .requested
-                try self.database.saveContact(self.contact)
-
-                self.hudRelay.send(.none)
-                self.successRelay.send()
-            } catch {
-                self.contact.authStatus = .requestFailed
-                _ = try? self.database.saveContact(self.contact)
-                self.hudRelay.send(.error(.init(with: error)))
-            }
+        
+        if self.sharingPhone, let fact = myFacts.get(.phone) {
+          includedFacts.append(fact)
         }
+        
+        let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
+          partner: .live(self.contact.marshaled!),
+          myFacts: includedFacts
+        )
+        
+        self.contact.authStatus = .requested
+        try self.database.saveContact(self.contact)
+        
+        self.hudController.dismiss()
+        self.successRelay.send()
+      } catch {
+        self.contact.authStatus = .requestFailed
+        _ = try? self.database.saveContact(self.contact)
+        self.hudController.show(.init(error: error))
+      }
     }
-
-    func didTapAccept(_ nickname: String) {
-        hudRelay.send(.on)
-        contact.nickname = nickname
-        contact.authStatus = .confirming
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.database.saveContact(self.contact)
-
-                let _ = try self.messenger.e2e.get()!.confirmReceivedRequest(partner: XXClient.Contact.live(self.contact.marshaled!))
-
-                self.contact.authStatus = .friend
-                try self.database.saveContact(self.contact)
-
-                self.hudRelay.send(.none)
-                self.popRelay.send()
-            } catch {
-                self.contact.authStatus = .confirmationFailed
-                _ = try? self.database.saveContact(self.contact)
-                self.hudRelay.send(.error(.init(with: error)))
-            }
-        }
+  }
+  
+  func didTapAccept(_ nickname: String) {
+    hudController.show()
+    contact.nickname = nickname
+    contact.authStatus = .confirming
+    
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
+      
+      do {
+        try self.database.saveContact(self.contact)
+        
+        let _ = try self.messenger.e2e.get()!.confirmReceivedRequest(partner: XXClient.Contact.live(self.contact.marshaled!))
+        
+        self.contact.authStatus = .friend
+        try self.database.saveContact(self.contact)
+        
+        self.hudController.dismiss()
+        self.popRelay.send()
+      } catch {
+        self.contact.authStatus = .confirmationFailed
+        _ = try? self.database.saveContact(self.contact)
+        self.hudController.show(.init(error: error))
+      }
     }
+  }
 }
diff --git a/Sources/ContactListFeature/Controllers/CreateGroupController.swift b/Sources/ContactListFeature/Controllers/CreateGroupController.swift
index bc4efdef058f92889fc04d2b6421610368e065d8..d6df303a0771dfc7e01b4efc83c9ef099ece1002 100644
--- a/Sources/ContactListFeature/Controllers/CreateGroupController.swift
+++ b/Sources/ContactListFeature/Controllers/CreateGroupController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Models
 import Shared
@@ -7,7 +6,6 @@ import XXModels
 import DependencyInjection
 
 public final class CreateGroupController: UIViewController {
-    @Dependency private var hud: HUD
     @Dependency private var coordinator: ContactListCoordinating
 
     lazy private var titleLabel = UILabel()
@@ -111,11 +109,6 @@ public final class CreateGroupController: UIViewController {
     }
 
     private func setupBindings() {
-        viewModel.hud
-            .receive(on: DispatchQueue.main)
-            .sink { [hud] in hud.update(with: $0) }
-            .store(in: &cancellables)
-
         let selected = viewModel.selected.share()
 
         selected
diff --git a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
index 08314f15ff9c16729e88e4ada4270ceef6ad3277..2277d915c4a50b1673c30879ed32b17d712fcec4 100644
--- a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
+++ b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
@@ -1,5 +1,5 @@
-import HUD
 import UIKit
+import Shared
 import Models
 import Combine
 import XXModels
@@ -7,146 +7,136 @@ import Defaults
 import XXClient
 import ReportingFeature
 import CombineSchedulers
-import DependencyInjection
 import XXMessengerClient
+import DependencyInjection
 
 final class CreateGroupViewModel {
-    @KeyObject(.username, defaultValue: "") var username: String
-
-    // MARK: Injected
-
-    @Dependency var database: Database
-    @Dependency var groupManager: GroupChat
-    @Dependency var messenger: Messenger
-    @Dependency var reportingStatus: ReportingStatus
-
-    // MARK: Properties
-
-    var myId: Data {
-        try! messenger.e2e.get()!.getContact().getId()
-    }
-
-    var selected: AnyPublisher<[XXModels.Contact], Never> {
-        selectedContactsRelay.eraseToAnyPublisher()
-    }
-
-    var contacts: AnyPublisher<[XXModels.Contact], Never> {
-        contactsRelay.eraseToAnyPublisher()
+  @KeyObject(.username, defaultValue: "") var username: String
+
+  @Dependency var database: Database
+  @Dependency var messenger: Messenger
+  @Dependency var groupManager: GroupChat
+  @Dependency var hudController: HUDController
+  @Dependency var reportingStatus: ReportingStatus
+
+  var myId: Data {
+    try! messenger.e2e.get()!.getContact().getId()
+  }
+
+  var selected: AnyPublisher<[XXModels.Contact], Never> {
+    selectedContactsRelay.eraseToAnyPublisher()
+  }
+
+  var contacts: AnyPublisher<[XXModels.Contact], Never> {
+    contactsRelay.eraseToAnyPublisher()
+  }
+
+  var info: AnyPublisher<GroupInfo, Never> {
+    infoRelay.eraseToAnyPublisher()
+  }
+
+  var backgroundScheduler: AnySchedulerOf<DispatchQueue>
+  = DispatchQueue.global().eraseToAnyScheduler()
+
+  private var allContacts = [XXModels.Contact]()
+  private var cancellables = Set<AnyCancellable>()
+  private let infoRelay = PassthroughSubject<GroupInfo, Never>()
+  private let contactsRelay = CurrentValueSubject<[XXModels.Contact], Never>([])
+  private let selectedContactsRelay = CurrentValueSubject<[XXModels.Contact], Never>([])
+
+  init() {
+    let query = Contact.Query(
+      authStatus: [.friend],
+      isBlocked: reportingStatus.isEnabled() ? false : nil,
+      isBanned: reportingStatus.isEnabled() ? false : nil
+    )
+
+    database.fetchContactsPublisher(query)
+      .replaceError(with: [])
+      .map { $0.filter { $0.id != self.myId }}
+      .map { $0.sorted(by: { $0.username! < $1.username! })}
+      .sink { [unowned self] in
+        allContacts = $0
+        contactsRelay.send($0)
+      }.store(in: &cancellables)
+  }
+
+  // MARK: Public
+
+  func didSelect(contact: XXModels.Contact) {
+    if selectedContactsRelay.value.contains(contact) {
+      selectedContactsRelay.value.removeAll { $0.username == contact.username }
+    } else {
+      selectedContactsRelay.value.append(contact)
     }
+  }
 
-    var hud: AnyPublisher<HUDStatus, Never> {
-        hudRelay.eraseToAnyPublisher()
+  func filter(_ text: String) {
+    guard text.isEmpty == false else {
+      contactsRelay.send(allContacts)
+      return
     }
 
-    var info: AnyPublisher<GroupInfo, Never> {
-        infoRelay.eraseToAnyPublisher()
-    }
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue>
-    = DispatchQueue.global().eraseToAnyScheduler()
+    contactsRelay.send(
+      allContacts.filter {
+        ($0.username ?? "").contains(text.lowercased())
+      }
+    )
+  }
 
-    private var allContacts = [XXModels.Contact]()
-    private var cancellables = Set<AnyCancellable>()
-    private let infoRelay = PassthroughSubject<GroupInfo, Never>()
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-    private let contactsRelay = CurrentValueSubject<[XXModels.Contact], Never>([])
-    private let selectedContactsRelay = CurrentValueSubject<[XXModels.Contact], Never>([])
+  func create(name: String, welcome: String?, members: [XXModels.Contact]) {
+    hudController.show()
 
-    // MARK: Lifecycle
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
 
-    init() {
-        let query = Contact.Query(
-            authStatus: [.friend],
-            isBlocked: reportingStatus.isEnabled() ? false : nil,
-            isBanned: reportingStatus.isEnabled() ? false : nil
+      do {
+        let report = try self.groupManager.makeGroup(
+          membership: members.map(\.id),
+          message: welcome?.data(using: .utf8),
+          name: name.data(using: .utf8)
         )
 
-        database.fetchContactsPublisher(query)
-        .replaceError(with: [])
-            .map { $0.filter { $0.id != self.myId }}
-            .map { $0.sorted(by: { $0.username! < $1.username! })}
-            .sink { [unowned self] in
-                allContacts = $0
-                contactsRelay.send($0)
-            }.store(in: &cancellables)
-    }
-
-    // MARK: Public
+        let group = Group(
+          id: report.id,
+          name: name,
+          leaderId: self.myId,
+          createdAt: Date(),
+          authStatus: .participating,
+          serialized: try report.encode() // ?
+        )
 
-    func didSelect(contact: XXModels.Contact) {
-        if selectedContactsRelay.value.contains(contact) {
-            selectedContactsRelay.value.removeAll { $0.username == contact.username }
-        } else {
-            selectedContactsRelay.value.append(contact)
+        _ = try self.database.saveGroup(group)
+
+        if let welcomeMessage = welcome {
+          try self.database.saveMessage(
+            Message(
+              senderId: self.myId,
+              recipientId: nil,
+              groupId: group.id,
+              date: group.createdAt,
+              status: .sent,
+              isUnread: false,
+              text: welcomeMessage,
+              replyMessageId: nil,
+              roundURL: nil,
+              fileTransferId: nil
+            )
+          )
         }
-    }
 
-    func filter(_ text: String) {
-        guard text.isEmpty == false else {
-            contactsRelay.send(allContacts)
-            return
-        }
+        try members
+          .map { GroupMember(groupId: group.id, contactId: $0.id) }
+          .forEach { try self.database.saveGroupMember($0) }
 
-        contactsRelay.send(
-            allContacts.filter {
-                ($0.username ?? "").contains(text.lowercased())
-            }
-        )
-    }
+        let query = GroupInfo.Query(groupId: group.id)
+        let info = try self.database.fetchGroupInfos(query).first
 
-    func create(name: String, welcome: String?, members: [XXModels.Contact]) {
-        hudRelay.send(.on)
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                let report = try self.groupManager.makeGroup(
-                    membership: members.map(\.id),
-                    message: welcome?.data(using: .utf8),
-                    name: name.data(using: .utf8)
-                )
-
-                let group = Group(
-                    id: report.id,
-                    name: name,
-                    leaderId: self.myId,
-                    createdAt: Date(),
-                    authStatus: .participating,
-                    serialized: try report.encode() // ?
-                )
-
-                _ = try self.database.saveGroup(group)
-
-                if let welcomeMessage = welcome {
-                    try self.database.saveMessage(
-                        Message(
-                            senderId: self.myId,
-                            recipientId: nil,
-                            groupId: group.id,
-                            date: group.createdAt,
-                            status: .sent,
-                            isUnread: false,
-                            text: welcomeMessage,
-                            replyMessageId: nil,
-                            roundURL: nil,
-                            fileTransferId: nil
-                        )
-                    )
-                }
-
-                try members
-                    .map { GroupMember(groupId: group.id, contactId: $0.id) }
-                    .forEach { try self.database.saveGroupMember($0) }
-
-                let query = GroupInfo.Query(groupId: group.id)
-                let info = try self.database.fetchGroupInfos(query).first
-
-                self.infoRelay.send(info!)
-                self.hudRelay.send(.none)
-            } catch {
-                self.hudRelay.send(.error(.init(with: error)))
-            }
-        }
+        self.infoRelay.send(info!)
+        self.hudController.dismiss()
+      } catch {
+        self.hudController.show(.init(error: error))
+      }
     }
+  }
 }
diff --git a/Sources/HUD/DotAnimation.swift b/Sources/HUD/DotAnimation.swift
deleted file mode 100644
index f7bfae046b167ba1a0974723b10c359189ec5de0..0000000000000000000000000000000000000000
--- a/Sources/HUD/DotAnimation.swift
+++ /dev/null
@@ -1,94 +0,0 @@
-import UIKit
-
-final class DotAnimation: UIView {
-    let leftDot = UIView()
-    let middleDot = UIView()
-    let rightDot = UIView()
-
-    var leftInvert = false
-    var middleInvert = false
-    var rightInvert = false
-
-    var leftValue: CGFloat = 20
-    var middleValue: CGFloat = 45
-    var rightValue: CGFloat = 70
-
-    var displayLink: CADisplayLink?
-
-    init() {
-        super.init(frame: .zero)
-        setup()
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    func setColor(
-        _ color: UIColor = UIColor(
-            red: 0,
-            green: 188/255,
-            blue: 206/255,
-            alpha: 1.0
-        )
-    ) {
-        leftDot.backgroundColor = color
-        middleDot.backgroundColor = color
-        rightDot.backgroundColor = color
-    }
-
-    private func setup() {
-        setupCornerRadius()
-        setColor()
-        addSubviews()
-        setupConstraints()
-
-        displayLink = CADisplayLink(target: self, selector: #selector(handleAnimations))
-        displayLink!.add(to: RunLoop.main, forMode: .default)
-    }
-
-    private func setupCornerRadius() {
-        leftDot.layer.cornerRadius = 7.5
-        middleDot.layer.cornerRadius = 7.5
-        rightDot.layer.cornerRadius = 7.5
-    }
-
-    private func addSubviews() {
-        addSubview(leftDot)
-        addSubview(middleDot)
-        addSubview(rightDot)
-    }
-
-    private func setupConstraints() {
-        leftDot.snp.makeConstraints { make in
-            make.centerY.equalTo(middleDot)
-            make.right.equalTo(middleDot.snp.left).offset(-5)
-            make.width.height.equalTo(15)
-        }
-
-        middleDot.snp.makeConstraints { make in
-            make.center.equalToSuperview()
-            make.width.height.equalTo(15)
-        }
-
-        rightDot.snp.makeConstraints { make in
-            make.centerY.equalTo(middleDot)
-            make.left.equalTo(middleDot.snp.right).offset(5)
-            make.width.height.equalTo(15)
-        }
-    }
-
-    @objc private func handleAnimations() {
-        let factor: CGFloat = 70
-
-        leftInvert ? (leftValue -= 1) : (leftValue += 1)
-        middleInvert ? (middleValue -= 1) : (middleValue += 1)
-        rightInvert ? (rightValue -= 1) : (rightValue += 1)
-
-        leftDot.layer.transform = CATransform3DMakeScale(leftValue/factor, leftValue/factor, 1)
-        middleDot.layer.transform = CATransform3DMakeScale(middleValue/factor, middleValue/factor, 1)
-        rightDot.layer.transform = CATransform3DMakeScale(rightValue/factor, rightValue/factor, 1)
-
-        if leftValue > factor || leftValue < 10 { leftInvert.toggle() }
-        if middleValue > factor || middleValue < 10 { middleInvert.toggle() }
-        if rightValue > factor || rightValue < 10 { rightInvert.toggle() }
-    }
-}
diff --git a/Sources/HUD/ErrorView.swift b/Sources/HUD/ErrorView.swift
deleted file mode 100644
index 2692ab2a53274cbb2e3d63303ad830885f4a8116..0000000000000000000000000000000000000000
--- a/Sources/HUD/ErrorView.swift
+++ /dev/null
@@ -1,58 +0,0 @@
-import UIKit
-import Shared
-import SnapKit
-
-final class ErrorView: UIView {
-    let title = UILabel()
-    let content = UILabel()
-    let stack = UIStackView()
-    let button = CapsuleButton()
-
-    init(with model: HUDError) {
-        super.init(frame: .zero)
-        setup(with: model)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    private func setup(with model: HUDError) {
-        layer.cornerRadius = 6
-        backgroundColor = Asset.neutralWhite.color
-
-        title.text = model.title
-        title.textColor = Asset.neutralBody.color
-        title.font = Fonts.Mulish.bold.font(size: 35.0)
-        title.textAlignment = .center
-        title.numberOfLines = 0
-
-        content.text = model.content
-        content.textColor = Asset.neutralBody.color
-        content.numberOfLines = 0
-        content.font = Fonts.Mulish.regular.font(size: 14.0)
-        content.textAlignment = .center
-
-        button.setTitle(model.buttonTitle, for: .normal)
-        button.setStyle(.brandColored)
-
-        stack.axis = .vertical
-
-        stack.addArrangedSubview(title)
-        stack.addArrangedSubview(content)
-
-        if model.dismissable {
-            stack.addArrangedSubview(button)
-        }
-
-        stack.setCustomSpacing(25, after: title)
-        stack.setCustomSpacing(59, after: content)
-
-        addSubview(stack)
-
-        stack.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(60)
-            make.left.equalToSuperview().offset(57)
-            make.right.equalToSuperview().offset(-57)
-            make.bottom.equalToSuperview().offset(-35)
-        }
-    }
-}
diff --git a/Sources/HUD/HUD.swift b/Sources/HUD/HUD.swift
deleted file mode 100644
index 8850d8a2ee7a2e7be7fa7e20f36d0f8070973cb4..0000000000000000000000000000000000000000
--- a/Sources/HUD/HUD.swift
+++ /dev/null
@@ -1,194 +0,0 @@
-import UIKit
-import Shared
-import Combine
-import SnapKit
-
-private enum Constants {
-  static let title = Localized.Hud.Error.title
-  static let action = Localized.Hud.Error.action
-}
-
-public enum HUDStatus: Equatable {
-  case none
-  case on
-  case onTitle(String)
-  case onAction(String)
-  case error(HUDError)
-
-  var isPresented: Bool {
-    switch self {
-    case .none:
-      return false
-    case .on, .error, .onTitle, .onAction:
-      return true
-    }
-  }
-}
-
-public struct HUDError: Equatable {
-  var title: String
-  var content: String
-  var buttonTitle: String
-  var dismissable: Bool
-
-  public init(
-    content: String,
-    title: String? = nil,
-    buttonTitle: String? = nil,
-    dismissable: Bool = true
-  ) {
-    self.content = content
-    self.title = title ?? Constants.title
-    self.buttonTitle = buttonTitle ?? Constants.action
-    self.dismissable = dismissable
-  }
-
-  public init(with error: Error) {
-    self.title = Constants.title
-    self.buttonTitle = Constants.action
-    self.content = error.localizedDescription
-    self.dismissable = true
-  }
-}
-
-public final class HUD {
-  private(set) var window: UIWindow?
-  private(set) var errorView: ErrorView?
-  private(set) var titleLabel: UILabel?
-  private(set) var animation: DotAnimation?
-  public var actionButton: CapsuleButton?
-  private var cancellables = Set<AnyCancellable>()
-
-  private var status: HUDStatus = .none {
-    didSet {
-      if oldValue.isPresented == true && status.isPresented == true {
-        self.errorView = nil
-        self.animation = nil
-        self.window = nil
-        self.actionButton = nil
-        self.titleLabel = nil
-
-        switch status {
-        case .on:
-          animation = DotAnimation()
-
-        case .onTitle(let text):
-          animation = DotAnimation()
-          titleLabel = UILabel()
-          titleLabel!.text = text
-
-        case .onAction(let title):
-          animation = DotAnimation()
-          actionButton = CapsuleButton()
-          actionButton!.set(style: .seeThroughWhite, title: title)
-
-        case .error(let error):
-          errorView = ErrorView(with: error)
-        case .none:
-          break
-        }
-
-        showWindow()
-      }
-
-      if oldValue.isPresented == false && status.isPresented == true {
-        switch status {
-        case .on:
-          animation = DotAnimation()
-
-        case .onTitle(let text):
-          animation = DotAnimation()
-          titleLabel = UILabel()
-          titleLabel!.text = text
-
-        case .onAction(let title):
-          animation = DotAnimation()
-          actionButton = CapsuleButton()
-          actionButton!.set(style: .seeThroughWhite, title: title)
-
-        case .error(let error):
-          errorView = ErrorView(with: error)
-        case .none:
-          break
-        }
-
-        showWindow()
-      }
-
-      if oldValue.isPresented == true && status.isPresented == false {
-        hideWindow()
-      }
-    }
-  }
-
-  public init() {}
-
-  public func update(with status: HUDStatus) {
-    self.status = status
-  }
-
-  private func showWindow() {
-    window = UIWindow(frame: UIScreen.main.bounds)
-    window?.backgroundColor = UIColor.black.withAlphaComponent(0.8)
-    window?.rootViewController = RootViewController(nil)
-
-    if let animation = animation {
-      window?.addSubview(animation)
-      animation.setColor(.white)
-      animation.snp.makeConstraints { $0.center.equalToSuperview() }
-    }
-
-    if let titleLabel = titleLabel {
-      window?.addSubview(titleLabel)
-      titleLabel.textAlignment = .center
-      titleLabel.numberOfLines = 0
-      titleLabel.snp.makeConstraints { make in
-        make.left.equalToSuperview().offset(18)
-        make.center.equalToSuperview().offset(50)
-        make.right.equalToSuperview().offset(-18)
-      }
-    }
-
-    if let actionButton = actionButton {
-      window?.addSubview(actionButton)
-      actionButton.snp.makeConstraints {
-        $0.left.equalToSuperview().offset(18)
-        $0.right.equalToSuperview().offset(-18)
-        $0.bottom.equalToSuperview().offset(-50)
-      }
-    }
-
-    if let errorView = errorView {
-      window?.addSubview(errorView)
-      errorView.snp.makeConstraints { make in
-        make.left.equalToSuperview().offset(18)
-        make.center.equalToSuperview()
-        make.right.equalToSuperview().offset(-18)
-      }
-
-      errorView.button
-        .publisher(for: .touchUpInside)
-        .receive(on: DispatchQueue.main)
-        .sink { [unowned self] in hideWindow() }
-        .store(in: &cancellables)
-    }
-
-    window?.alpha = 0.0
-    window?.makeKeyAndVisible()
-
-    UIView.animate(withDuration: 0.3) { self.window?.alpha = 1.0 }
-  }
-
-  private func hideWindow() {
-    UIView.animate(withDuration: 0.3) {
-      self.window?.alpha = 0.0
-    } completion: { _ in
-      self.cancellables.removeAll()
-      self.errorView = nil
-      self.animation = nil
-      self.actionButton = nil
-      self.titleLabel = nil
-      self.window = nil
-    }
-  }
-}
diff --git a/Sources/LaunchFeature/LaunchController.swift b/Sources/LaunchFeature/LaunchController.swift
index fe5d10cbead95e6ca6433a600325cbceca1f13d7..4c533ee1f9fa7c2357300e75191af99aa9463fec 100644
--- a/Sources/LaunchFeature/LaunchController.swift
+++ b/Sources/LaunchFeature/LaunchController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Combine
@@ -7,7 +6,6 @@ import PushFeature
 import DependencyInjection
 
 public final class LaunchController: UIViewController {
-  @Dependency var hud: HUD
   @Dependency var coordinator: LaunchCoordinating
 
   @KeyObject(.acceptedTerms, defaultValue: false) var didAcceptTerms: Bool
@@ -41,12 +39,7 @@ public final class LaunchController: UIViewController {
 
   public override func viewDidLoad() {
     super.viewDidLoad()
-
-    viewModel.hudPublisher
-      .receive(on: DispatchQueue.main)
-      .sink { [hud] in hud.update(with: $0) }
-      .store(in: &cancellables)
-
+  
     viewModel.routePublisher
       .receive(on: DispatchQueue.main)
       .sink { [unowned self] in
@@ -136,26 +129,26 @@ public final class LaunchController: UIViewController {
       title: model.positiveActionTitle
     )
 
-    if let negativeTitle = model.negativeActionTitle {
-      let negativeButton = CapsuleButton()
-      negativeButton.set(style: .simplestColoredRed, title: negativeTitle)
-
-      negativeButton.publisher(for: .touchUpInside)
-        .sink { [unowned self] in
-          blocker.hideWindow()
-          viewModel.continueWithInitialization()
-        }.store(in: &cancellables)
-
-      vStack.addArrangedSubview(negativeButton)
-    }
-
-    blocker.window?.addSubview(drawerView)
-    drawerView.snp.makeConstraints {
-      $0.left.equalToSuperview().offset(18)
-      $0.center.equalToSuperview()
-      $0.right.equalToSuperview().offset(-18)
-    }
-
-    blocker.showWindow()
+//    if let negativeTitle = model.negativeActionTitle {
+//      let negativeButton = CapsuleButton()
+//      negativeButton.set(style: .simplestColoredRed, title: negativeTitle)
+//
+//      negativeButton.publisher(for: .touchUpInside)
+//        .sink { [unowned self] in
+//          blocker.hideWindow()
+//          viewModel.continueWithInitialization()
+//        }.store(in: &cancellables)
+//
+//      vStack.addArrangedSubview(negativeButton)
+//    }
+//
+//    blocker.window?.addSubview(drawerView)
+//    drawerView.snp.makeConstraints {
+//      $0.left.equalToSuperview().offset(18)
+//      $0.center.equalToSuperview()
+//      $0.right.equalToSuperview().offset(-18)
+//    }
+//
+//    blocker.showWindow()
   }
 }
diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift
index 8eda0596d6817bb246d86620f22c7190a18ab908..d1472aa558d1ebfe6b12ecb833a85a44a0d7ea34 100644
--- a/Sources/LaunchFeature/LaunchViewModel.swift
+++ b/Sources/LaunchFeature/LaunchViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import Shared
 import Models
 import Combine
@@ -43,6 +42,7 @@ enum LaunchRoute {
 
 final class LaunchViewModel {
   @Dependency var database: Database
+  @Dependency var hudController: HUDController
   @Dependency var backupService: BackupService
   @Dependency var versionChecker: VersionChecker
   @Dependency var fetchBannedList: FetchBannedList
@@ -57,10 +57,6 @@ final class LaunchViewModel {
   @KeyObject(.biometrics, defaultValue: false) var isBiometricsOn: Bool
   @KeyObject(.dummyTrafficOn, defaultValue: false) var dummyTrafficOn: Bool
 
-  var hudPublisher: AnyPublisher<HUDStatus, Never> {
-    hudSubject.eraseToAnyPublisher()
-  }
-
   var authCallbacksCancellable: Cancellable?
   var backupCallbackCancellable: Cancellable?
   var networkCallbacksCancellable: Cancellable?
@@ -88,13 +84,12 @@ final class LaunchViewModel {
 
   private var cancellables = Set<AnyCancellable>()
   private let routeSubject = PassthroughSubject<LaunchRoute, Never>()
-  private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
 
   func viewDidAppear() {
     backgroundScheduler.schedule(after: .init(.now() + 1)) { [weak self] in
       guard let self = self else { return }
 
-      self.hudSubject.send(.on)
+      self.hudController.show()
 
       self.versionChecker().sink { [unowned self] in
         switch $0 {
@@ -150,16 +145,16 @@ final class LaunchViewModel {
       if messenger.isLoggedIn() == false {
         if try messenger.isRegistered() {
           try messenger.logIn()
-          hudSubject.send(.none)
+          hudController.dismiss()
           routeSubject.send(.chats)
         } else {
           try? sftpManager.unlink()
           try? dropboxManager.unlink()
-          hudSubject.send(.none)
+          hudController.dismiss()
           routeSubject.send(.onboarding)
         }
       } else {
-        hudSubject.send(.none)
+        hudController.dismiss()
         routeSubject.send(.chats)
       }
 
@@ -171,7 +166,7 @@ final class LaunchViewModel {
 
     } catch {
       let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
-      hudSubject.send(.error(.init(content: xxError)))
+      hudController.show(.init(content: xxError))
     }
   }
 
@@ -181,7 +176,7 @@ final class LaunchViewModel {
   }
 
   private func presentOnboardingFlow() {
-    hudSubject.send(.none)
+    hudController.dismiss()
     routeSubject.send(.onboarding)
   }
 
@@ -254,15 +249,15 @@ final class LaunchViewModel {
   }
 
   private func versionFailed(error: Error) {
-    let title = Localized.Launch.Version.failed
-    let content = error.localizedDescription
-    let hudError = HUDError(content: content, title: title, dismissable: false)
-
-    hudSubject.send(.error(hudError))
+    hudController.show(.init(
+      title: Localized.Launch.Version.failed,
+      content: error.localizedDescription,
+      isDismissable: false
+    ))
   }
 
   private func versionUpdateRequired(_ info: DappVersionInformation) {
-    hudSubject.send(.none)
+    hudController.dismiss()
 
     let model = Update(
       content: info.minimumMessage,
@@ -276,7 +271,7 @@ final class LaunchViewModel {
   }
 
   private func versionUpdateRecommended(_ info: DappVersionInformation) {
-    hudSubject.send(.none)
+    hudController.dismiss()
 
     let model = Update(
       content: Localized.Launch.Version.Recommended.title,
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingEmailConfirmationController.swift b/Sources/OnboardingFeature/Controllers/OnboardingEmailConfirmationController.swift
index b45a26fcc8e08e24c1232444434b632914edda51..4ae996adb6822770888832baf95b95ee7bf627d9 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingEmailConfirmationController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingEmailConfirmationController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Models
@@ -8,7 +7,6 @@ import DependencyInjection
 import ScrollViewController
 
 public final class OnboardingEmailConfirmationController: UIViewController {
-  @Dependency var hud: HUD
   @Dependency var barStylist: StatusBarStylist
   @Dependency var coordinator: OnboardingCoordinating
 
@@ -65,10 +63,6 @@ public final class OnboardingEmailConfirmationController: UIViewController {
   }
 
   private func setupBindings() {
-    viewModel.hud.receive(on: DispatchQueue.main)
-      .sink { [hud] in hud.update(with: $0) }
-      .store(in: &cancellables)
-
     screenView.inputField.textPublisher
       .sink { [unowned self] in viewModel.didInput($0) }
       .store(in: &cancellables)
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingEmailController.swift b/Sources/OnboardingFeature/Controllers/OnboardingEmailController.swift
index b3ff6046b302a2ccb6eb1bc8d263fffe16030d17..2f65daf755a9d65f796c9e3bd0ed352cc73f40fe 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingEmailController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingEmailController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Combine
@@ -7,7 +6,6 @@ import DependencyInjection
 import ScrollViewController
 
 public final class OnboardingEmailController: UIViewController {
-  @Dependency var hud: HUD
   @Dependency var barStylist: StatusBarStylist
   @Dependency var coordinator: OnboardingCoordinating
 
@@ -50,10 +48,6 @@ public final class OnboardingEmailController: UIViewController {
   }
 
   private func setupBindings() {
-    viewModel.hud.receive(on: DispatchQueue.main)
-      .sink { [hud] in hud.update(with: $0) }
-      .store(in: &cancellables)
-
     screenView.inputField.textPublisher
       .sink { [unowned self] in viewModel.didInput($0) }
       .store(in: &cancellables)
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingPhoneConfirmationController.swift b/Sources/OnboardingFeature/Controllers/OnboardingPhoneConfirmationController.swift
index 1fb93ad4f023b0e18fa8c1a52b8ffd3c30d759cc..ab9cbca2292a8ebe29407080ecbb13a858fa7635 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingPhoneConfirmationController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingPhoneConfirmationController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Models
@@ -8,7 +7,6 @@ import DependencyInjection
 import ScrollViewController
 
 public final class OnboardingPhoneConfirmationController: UIViewController {
-  @Dependency var hud: HUD
   @Dependency var barStylist: StatusBarStylist
   @Dependency var coordinator: OnboardingCoordinating
 
@@ -65,10 +63,6 @@ public final class OnboardingPhoneConfirmationController: UIViewController {
   }
 
   private func setupBindings() {
-    viewModel.hud.receive(on: DispatchQueue.main)
-      .sink { [hud] in hud.update(with: $0) }
-      .store(in: &cancellables)
-
     screenView.inputField.textPublisher
       .sink { [unowned self] in viewModel.didInput($0) }
       .store(in: &cancellables)
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift b/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift
index e25481b405aabf394245fde4598a44d4a548cd3c..e593fbd207286f08bd1ad44aebbd61d5c3f70750 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Combine
@@ -7,7 +6,6 @@ import DependencyInjection
 import ScrollViewController
 
 public final class OnboardingPhoneController: UIViewController {
-  @Dependency var hud: HUD
   @Dependency var barStylist: StatusBarStylist
   @Dependency var coordinator: OnboardingCoordinating
 
@@ -50,11 +48,6 @@ public final class OnboardingPhoneController: UIViewController {
   }
 
   private func setupBindings() {
-    viewModel.hud
-      .receive(on: DispatchQueue.main)
-      .sink { [hud] in hud.update(with: $0) }
-      .store(in: &cancellables)
-
     screenView.inputField.textPublisher
       .sink { [unowned self] in viewModel.didInput($0) }
       .store(in: &cancellables)
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift b/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift
index a902a8d2e68656aa4b920a55e8c11c0012abc87b..23ff5f77896757c5e087e154f5ef4745657a238e 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift
@@ -1,11 +1,8 @@
-import HUD
 import UIKit
-import Shared
 import Combine
 import DependencyInjection
 
 public final class OnboardingStartController: UIViewController {
-    @Dependency private var hud: HUD
     @Dependency private var coordinator: OnboardingCoordinating
 
     lazy private var screenView = OnboardingStartView()
@@ -43,7 +40,9 @@ public final class OnboardingStartController: UIViewController {
     public override func viewDidLoad() {
         super.viewDidLoad()
 
-        screenView.startButton.publisher(for: .touchUpInside)
+        screenView
+        .startButton
+        .publisher(for: .touchUpInside)
             .sink { [unowned self] in coordinator.toTerms(from: self) }
             .store(in: &cancellables)
     }
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift b/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift
index 2b139fad43b2f00f23a2834289952673efbbfb0b..566cc2ed94afb1f61776a07f5254c15de1381f5d 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Combine
@@ -7,7 +6,6 @@ import DependencyInjection
 import ScrollViewController
 
 public final class OnboardingUsernameController: UIViewController {
-  @Dependency var hud: HUD
   @Dependency var barStylist: StatusBarStylist
   @Dependency var coordinator: OnboardingCoordinating
 
@@ -50,11 +48,6 @@ public final class OnboardingUsernameController: UIViewController {
   }
 
   private func setupBindings() {
-    viewModel.hud
-      .receive(on: DispatchQueue.main)
-      .sink { [hud] in hud.update(with: $0) }
-      .store(in: &cancellables)
-
     screenView.inputField.textPublisher
       .removeDuplicates()
       .compactMap { $0 }
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift
index 756479e25a56b544766ab694596bccc688216828..d008997d4a83b18a415100b2ec9b20250dcf9032 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Models
 import Shared
@@ -11,85 +10,83 @@ import DependencyInjection
 import XXMessengerClient
 
 struct OnboardingEmailConfirmationViewState: Equatable {
-    var input: String = ""
-    var status: InputField.ValidationStatus = .unknown(nil)
-    var resendDebouncer: Int = 0
+  var input: String = ""
+  var status: InputField.ValidationStatus = .unknown(nil)
+  var resendDebouncer: Int = 0
 }
 
 final class OnboardingEmailConfirmationViewModel {
-    @Dependency var messenger: Messenger
-
-    @KeyObject(.email, defaultValue: nil) var email: String?
-
-    var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-
-    var completionPublisher: AnyPublisher<AttributeConfirmation, Never> { completionRelay.eraseToAnyPublisher() }
-    private let completionRelay = PassthroughSubject<AttributeConfirmation, Never>()
-
-    var timer: Timer?
-    let confirmation: AttributeConfirmation
-
-    var state: AnyPublisher<OnboardingEmailConfirmationViewState, Never> { stateRelay.eraseToAnyPublisher() }
-    private let stateRelay = CurrentValueSubject<OnboardingEmailConfirmationViewState, Never>(.init())
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
-
-    init(_ confirmation: AttributeConfirmation) {
-        self.confirmation = confirmation
-        didTapResend()
-    }
-
-    func didInput(_ string: String) {
-        stateRelay.value.input = string
-        validate()
-    }
-
-    func didTapResend() {
-        guard stateRelay.value.resendDebouncer == 0 else { return }
-
-        stateRelay.value.resendDebouncer = 60
-
-        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {  [weak self] in
-            guard let self = self, self.stateRelay.value.resendDebouncer > 0 else {
-                $0.invalidate()
-                return
-            }
-
-            self.stateRelay.value.resendDebouncer -= 1
-        }
+  @Dependency var messenger: Messenger
+  @Dependency var hudController: HUDController
+  
+  @KeyObject(.email, defaultValue: nil) var email: String?
+  
+  var completionPublisher: AnyPublisher<AttributeConfirmation, Never> { completionRelay.eraseToAnyPublisher() }
+  private let completionRelay = PassthroughSubject<AttributeConfirmation, Never>()
+  
+  var timer: Timer?
+  let confirmation: AttributeConfirmation
+  
+  var state: AnyPublisher<OnboardingEmailConfirmationViewState, Never> { stateRelay.eraseToAnyPublisher() }
+  private let stateRelay = CurrentValueSubject<OnboardingEmailConfirmationViewState, Never>(.init())
+  
+  var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
+  
+  init(_ confirmation: AttributeConfirmation) {
+    self.confirmation = confirmation
+    didTapResend()
+  }
+  
+  func didInput(_ string: String) {
+    stateRelay.value.input = string
+    validate()
+  }
+  
+  func didTapResend() {
+    guard stateRelay.value.resendDebouncer == 0 else { return }
+    
+    stateRelay.value.resendDebouncer = 60
+    
+    timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {  [weak self] in
+      guard let self = self, self.stateRelay.value.resendDebouncer > 0 else {
+        $0.invalidate()
+        return
+      }
+      
+      self.stateRelay.value.resendDebouncer -= 1
     }
-
-    func didTapNext() {
-        hudRelay.send(.on)
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.messenger.ud.get()!.confirmFact(
-                    confirmationId: self.confirmation.confirmationId!,
-                    code: self.stateRelay.value.input
-                )
-
-                self.email = self.confirmation.content
-
-                self.timer?.invalidate()
-                self.hudRelay.send(.none)
-                self.completionRelay.send(self.confirmation)
-            } catch {
-                let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
-                self.hudRelay.send(.error(.init(content: xxError)))
-            }
-        }
+  }
+  
+  func didTapNext() {
+    hudController.show()
+    
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
+      
+      do {
+        try self.messenger.ud.get()!.confirmFact(
+          confirmationId: self.confirmation.confirmationId!,
+          code: self.stateRelay.value.input
+        )
+        
+        self.email = self.confirmation.content
+        
+        self.timer?.invalidate()
+        self.hudController.dismiss()
+        self.completionRelay.send(self.confirmation)
+      } catch {
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.hudController.show(.init(content: xxError))
+      }
     }
-
-    private func validate() {
-        switch Validator.code.validate(stateRelay.value.input) {
-        case .success:
-            stateRelay.value.status = .valid(nil)
-        case .failure(let error):
-            stateRelay.value.status = .invalid(error)
-        }
+  }
+  
+  private func validate() {
+    switch Validator.code.validate(stateRelay.value.input) {
+    case .success:
+      stateRelay.value.status = .valid(nil)
+    case .failure(let error):
+      stateRelay.value.status = .invalid(error)
     }
+  }
 }
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift
index 8215204dd75749bf4ca4f6f243453d04d2eed1e5..6e1159ae8b7cb74b66ada962cd8d480a613142cb 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Models
 import Shared
@@ -7,67 +6,65 @@ import Defaults
 import XXClient
 import InputField
 import CombineSchedulers
-import DependencyInjection
 import XXMessengerClient
+import DependencyInjection
 
 struct OnboardingEmailViewState: Equatable {
-    var input: String = ""
-    var confirmation: AttributeConfirmation? = nil
-    var status: InputField.ValidationStatus = .unknown(nil)
+  var input: String = ""
+  var confirmation: AttributeConfirmation? = nil
+  var status: InputField.ValidationStatus = .unknown(nil)
 }
 
 final class OnboardingEmailViewModel {
-    @Dependency var messenger: Messenger
-
-    @KeyObject(.pushNotifications, defaultValue: false) private var pushNotifications
-
-    var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-
-    var state: AnyPublisher<OnboardingEmailViewState, Never> { stateRelay.eraseToAnyPublisher() }
-    private let stateRelay = CurrentValueSubject<OnboardingEmailViewState, Never>(.init())
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
-
-    func clearUp() {
-        stateRelay.value.confirmation = nil
+  @Dependency var messenger: Messenger
+  @Dependency var hudController: HUDController
+  
+  @KeyObject(.pushNotifications, defaultValue: false) private var pushNotifications
+  
+  var state: AnyPublisher<OnboardingEmailViewState, Never> { stateRelay.eraseToAnyPublisher() }
+  private let stateRelay = CurrentValueSubject<OnboardingEmailViewState, Never>(.init())
+  
+  var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
+  
+  func clearUp() {
+    stateRelay.value.confirmation = nil
+  }
+  
+  func didInput(_ string: String) {
+    stateRelay.value.input = string
+    validate()
+  }
+  
+  func didTapNext() {
+    hudController.show()
+    
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
+      
+      do {
+        let confirmationId = try self.messenger.ud.get()!.sendRegisterFact(
+          .init(type: .email, value: self.stateRelay.value.input)
+        )
+        
+        self.hudController.dismiss()
+        self.stateRelay.value.confirmation = .init(
+          content: self.stateRelay.value.input,
+          isEmail: true,
+          confirmationId: confirmationId
+        )
+      } catch {
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.hudController.show(.init(content: xxError))
+      }
     }
-
-    func didInput(_ string: String) {
-        stateRelay.value.input = string
-        validate()
-    }
-
-    func didTapNext() {
-        hudRelay.send(.on)
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                let confirmationId = try self.messenger.ud.get()!.sendRegisterFact(
-                    .init(type: .email, value: self.stateRelay.value.input)
-                )
-
-                self.hudRelay.send(.none)
-                self.stateRelay.value.confirmation = .init(
-                    content: self.stateRelay.value.input,
-                    isEmail: true,
-                    confirmationId: confirmationId
-                )
-            } catch {
-                let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
-                self.hudRelay.send(.error(.init(content: xxError)))
-            }
-        }
-    }
-
-    private func validate() {
-        switch Validator.email.validate(stateRelay.value.input) {
-        case .success:
-            stateRelay.value.status = .valid(nil)
-        case .failure(let error):
-            stateRelay.value.status = .invalid(error)
-        }
+  }
+  
+  private func validate() {
+    switch Validator.email.validate(stateRelay.value.input) {
+    case .success:
+      stateRelay.value.status = .valid(nil)
+    case .failure(let error):
+      stateRelay.value.status = .invalid(error)
     }
+  }
 }
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift
index ba335ba8092e751dae7e1092947bb48ce31b9348..387993d54ad136ef2ec01bd397eb035d2ea0dd45 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Models
 import Shared
@@ -7,89 +6,87 @@ import Defaults
 import InputField
 import XXClient
 import CombineSchedulers
-import DependencyInjection
 import XXMessengerClient
+import DependencyInjection
 
 struct OnboardingPhoneConfirmationViewState: Equatable {
-    var input: String = ""
-    var status: InputField.ValidationStatus = .unknown(nil)
-    var resendDebouncer: Int = 0
+  var input: String = ""
+  var status: InputField.ValidationStatus = .unknown(nil)
+  var resendDebouncer: Int = 0
 }
 
 final class OnboardingPhoneConfirmationViewModel {
-    @Dependency var messenger: Messenger
-
-    @KeyObject(.phone, defaultValue: nil) var phone: String?
-
-    var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-
-    var completionPublisher: AnyPublisher<AttributeConfirmation, Never> { completionRelay.eraseToAnyPublisher() }
-    private let completionRelay = PassthroughSubject<AttributeConfirmation, Never>()
-
-    var timer: Timer?
-    let confirmation: AttributeConfirmation
-
-    var state: AnyPublisher<OnboardingPhoneConfirmationViewState, Never> { stateRelay.eraseToAnyPublisher() }
-    private let stateRelay = CurrentValueSubject<OnboardingPhoneConfirmationViewState, Never>(.init())
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
-
-    init(_ confirmation: AttributeConfirmation) {
-        self.confirmation = confirmation
-        didTapResend()
-    }
-
-    func didInput(_ string: String) {
-        stateRelay.value.input = string
-        validate()
+  @Dependency var messenger: Messenger
+  @Dependency var hudController: HUDController
+  
+  @KeyObject(.phone, defaultValue: nil) var phone: String?
+  
+  var completionPublisher: AnyPublisher<AttributeConfirmation, Never> { completionRelay.eraseToAnyPublisher() }
+  private let completionRelay = PassthroughSubject<AttributeConfirmation, Never>()
+  
+  var timer: Timer?
+  let confirmation: AttributeConfirmation
+  
+  var state: AnyPublisher<OnboardingPhoneConfirmationViewState, Never> { stateRelay.eraseToAnyPublisher() }
+  private let stateRelay = CurrentValueSubject<OnboardingPhoneConfirmationViewState, Never>(.init())
+  
+  var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
+  
+  init(_ confirmation: AttributeConfirmation) {
+    self.confirmation = confirmation
+    didTapResend()
+  }
+  
+  func didInput(_ string: String) {
+    stateRelay.value.input = string
+    validate()
+  }
+  
+  func didTapResend() {
+    guard stateRelay.value.resendDebouncer == 0 else { return }
+    
+    stateRelay.value.resendDebouncer = 60
+    
+    timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {  [weak self] in
+      guard let self = self, self.stateRelay.value.resendDebouncer > 0 else {
+        $0.invalidate()
+        return
+      }
+      
+      self.stateRelay.value.resendDebouncer -= 1
     }
-
-    func didTapResend() {
-        guard stateRelay.value.resendDebouncer == 0 else { return }
-
-        stateRelay.value.resendDebouncer = 60
-
-        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {  [weak self] in
-            guard let self = self, self.stateRelay.value.resendDebouncer > 0 else {
-                $0.invalidate()
-                return
-            }
-
-            self.stateRelay.value.resendDebouncer -= 1
-        }
+  }
+  
+  func didTapNext() {
+    hudController.show()
+    
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
+      
+      do {
+        try self.messenger.ud.get()!.confirmFact(
+          confirmationId: self.confirmation.confirmationId!,
+          code: self.stateRelay.value.input
+        )
+        
+        self.phone = self.confirmation.content
+        
+        self.timer?.invalidate()
+        self.hudController.dismiss()
+        self.completionRelay.send(self.confirmation)
+      } catch {
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.hudController.show(.init(content: xxError))
+      }
     }
-
-    func didTapNext() {
-        hudRelay.send(.on)
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.messenger.ud.get()!.confirmFact(
-                    confirmationId: self.confirmation.confirmationId!,
-                    code: self.stateRelay.value.input
-                )
-
-                self.phone = self.confirmation.content
-
-                self.timer?.invalidate()
-                self.hudRelay.send(.none)
-                self.completionRelay.send(self.confirmation)
-            } catch {
-                let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
-                self.hudRelay.send(.error(.init(content: xxError)))
-            }
-        }
-    }
-
-    private func validate() {
-        switch Validator.code.validate(stateRelay.value.input) {
-        case .success:
-            stateRelay.value.status = .valid(nil)
-        case .failure(let error):
-            stateRelay.value.status = .invalid(error)
-        }
+  }
+  
+  private func validate() {
+    switch Validator.code.validate(stateRelay.value.input) {
+    case .success:
+      stateRelay.value.status = .valid(nil)
+    case .failure(let error):
+      stateRelay.value.status = .invalid(error)
     }
+  }
 }
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift
index 1644c795d0d3b23c5b17b0316daa2b39a7e9dfc9..346a7bf15f5a75e7136f2be1f08ff734869a9b51 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import Shared
 import Models
 import Combine
@@ -7,78 +6,74 @@ import Countries
 import InputField
 import Foundation
 import CombineSchedulers
-import DependencyInjection
 import XXMessengerClient
+import DependencyInjection
 
 struct OnboardingPhoneViewState: Equatable {
-    var input: String = ""
-    var confirmation: AttributeConfirmation?
-    var status: InputField.ValidationStatus = .unknown(nil)
-    var country: Country = .fromMyPhone()
+  var input: String = ""
+  var confirmation: AttributeConfirmation?
+  var status: InputField.ValidationStatus = .unknown(nil)
+  var country: Country = .fromMyPhone()
 }
 
 final class OnboardingPhoneViewModel {
-    @Dependency var messenger: Messenger
-
-    var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-
-    var state: AnyPublisher<OnboardingPhoneViewState, Never> { stateRelay.eraseToAnyPublisher() }
-    private let stateRelay = CurrentValueSubject<OnboardingPhoneViewState, Never>(.init())
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
-
-    // MARK: Public
-
-    func clearUp() {
-        stateRelay.value.confirmation = nil
-    }
-
-    func didInput(_ string: String) {
-        stateRelay.value.input = string
-        validate()
-    }
-
-    func didChooseCountry(_ country: Country) {
-        stateRelay.value.country = country
-        validate()
-    }
-
-    func didGoForward() {
-        stateRelay.value.confirmation = nil
-    }
-
-    func didTapNext() {
-        hudRelay.send(.on)
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            let content = "\(self.stateRelay.value.input)\(self.stateRelay.value.country.code)"
-
-            do {
-                let confirmationId = try self.messenger.ud.get()!.sendRegisterFact(
-                    .init(type: .phone, value: content)
-                )
-
-                self.hudRelay.send(.none)
-                self.stateRelay.value.confirmation = .init(
-                    content: content,
-                    confirmationId: confirmationId
-                )
-            } catch {
-                let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
-                self.hudRelay.send(.error(.init(content: xxError)))
-            }
-        }
+  @Dependency var messenger: Messenger
+  @Dependency var hudController: HUDController
+  
+  var state: AnyPublisher<OnboardingPhoneViewState, Never> { stateRelay.eraseToAnyPublisher() }
+  private let stateRelay = CurrentValueSubject<OnboardingPhoneViewState, Never>(.init())
+  
+  var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
+
+  func clearUp() {
+    stateRelay.value.confirmation = nil
+  }
+  
+  func didInput(_ string: String) {
+    stateRelay.value.input = string
+    validate()
+  }
+  
+  func didChooseCountry(_ country: Country) {
+    stateRelay.value.country = country
+    validate()
+  }
+  
+  func didGoForward() {
+    stateRelay.value.confirmation = nil
+  }
+  
+  func didTapNext() {
+    hudController.show()
+    
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
+      
+      let content = "\(self.stateRelay.value.input)\(self.stateRelay.value.country.code)"
+      
+      do {
+        let confirmationId = try self.messenger.ud.get()!.sendRegisterFact(
+          .init(type: .phone, value: content)
+        )
+        
+        self.hudController.dismiss()
+        self.stateRelay.value.confirmation = .init(
+          content: content,
+          confirmationId: confirmationId
+        )
+      } catch {
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.hudController.show(.init(content: xxError))
+      }
     }
-
-    private func validate() {
-        switch Validator.phone.validate((stateRelay.value.country.regex, stateRelay.value.input)) {
-        case .success:
-            stateRelay.value.status = .valid(nil)
-        case .failure(let error):
-            stateRelay.value.status = .invalid(error)
-        }
+  }
+  
+  private func validate() {
+    switch Validator.phone.validate((stateRelay.value.country.regex, stateRelay.value.input)) {
+    case .success:
+      stateRelay.value.status = .valid(nil)
+    case .failure(let error):
+      stateRelay.value.status = .invalid(error)
     }
+  }
 }
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift
index b042341ce209a880374a9fcba1841f7e67e4cfe2..1483525b62f65afd5f7a8a01aea97cdc46cc3ed4 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import Shared
 import Models
 import Combine
@@ -12,72 +11,70 @@ import CombineSchedulers
 import DependencyInjection
 
 struct OnboardingUsernameViewState: Equatable {
-    var input: String = ""
-    var status: InputField.ValidationStatus = .unknown(nil)
+  var input: String = ""
+  var status: InputField.ValidationStatus = .unknown(nil)
 }
 
 final class OnboardingUsernameViewModel {
-    @Dependency var database: Database
-    @Dependency var messenger: Messenger
+  @Dependency var database: Database
+  @Dependency var messenger: Messenger
+  @Dependency var hudController: HUDController
+  
+  @KeyObject(.username, defaultValue: "") var username: String
+  
+  var backgroundScheduler: AnySchedulerOf<DispatchQueue>
+  = DispatchQueue.global().eraseToAnyScheduler()
+  
+  var greenPublisher: AnyPublisher<Void, Never> { greenRelay.eraseToAnyPublisher() }
+  private let greenRelay = PassthroughSubject<Void, Never>()
 
-    @KeyObject(.username, defaultValue: "") var username: String
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue>
-    = DispatchQueue.global().eraseToAnyScheduler()
-
-    var greenPublisher: AnyPublisher<Void, Never> { greenRelay.eraseToAnyPublisher() }
-    private let greenRelay = PassthroughSubject<Void, Never>()
-
-    var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-
-    var state: AnyPublisher<OnboardingUsernameViewState, Never> { stateRelay.eraseToAnyPublisher() }
-    private let stateRelay = CurrentValueSubject<OnboardingUsernameViewState, Never>(.init())
-
-    func didInput(_ string: String) {
-        stateRelay.value.input = string.trimmingCharacters(in: .whitespacesAndNewlines)
-
-        switch Validator.username.validate(stateRelay.value.input) {
-        case .success(let text):
-            stateRelay.value.status = .valid(text)
-        case .failure(let error):
-            stateRelay.value.status = .invalid(error)
-        }
+  var state: AnyPublisher<OnboardingUsernameViewState, Never> { stateRelay.eraseToAnyPublisher() }
+  private let stateRelay = CurrentValueSubject<OnboardingUsernameViewState, Never>(.init())
+  
+  func didInput(_ string: String) {
+    stateRelay.value.input = string.trimmingCharacters(in: .whitespacesAndNewlines)
+    
+    switch Validator.username.validate(stateRelay.value.input) {
+    case .success(let text):
+      stateRelay.value.status = .valid(text)
+    case .failure(let error):
+      stateRelay.value.status = .invalid(error)
     }
-
-    func didTapRegister() {
-        hudRelay.send(.on)
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.messenger.register(
-                    username: self.stateRelay.value.input
-                )
-
-                try self.database.saveContact(.init(
-                    id: self.messenger.e2e.get()!.getContact().getId(),
-                    marshaled: self.messenger.e2e.get()!.getContact().data,
-                    username: self.stateRelay.value.input,
-                    email: nil,
-                    phone: nil,
-                    nickname: nil,
-                    photo: nil,
-                    authStatus: .friend,
-                    isRecent: false,
-                    isBlocked: false,
-                    isBanned: false,
-                    createdAt: Date()
-                ))
-
-                self.username = self.stateRelay.value.input
-                self.hudRelay.send(.none)
-                self.greenRelay.send()
-            } catch {
-                self.hudRelay.send(.none)
-                self.stateRelay.value.status = .invalid(CreateUserFriendlyErrorMessage.live(error.localizedDescription))
-            }
-        }
+  }
+  
+  func didTapRegister() {
+    hudController.show()
+    
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
+      
+      do {
+        try self.messenger.register(
+          username: self.stateRelay.value.input
+        )
+        
+        try self.database.saveContact(.init(
+          id: self.messenger.e2e.get()!.getContact().getId(),
+          marshaled: self.messenger.e2e.get()!.getContact().data,
+          username: self.stateRelay.value.input,
+          email: nil,
+          phone: nil,
+          nickname: nil,
+          photo: nil,
+          authStatus: .friend,
+          isRecent: false,
+          isBlocked: false,
+          isBanned: false,
+          createdAt: Date()
+        ))
+        
+        self.username = self.stateRelay.value.input
+        self.hudController.dismiss()
+        self.greenRelay.send()
+      } catch {
+        self.hudController.dismiss()
+        self.stateRelay.value.status = .invalid(CreateUserFriendlyErrorMessage.live(error.localizedDescription))
+      }
     }
+  }
 }
diff --git a/Sources/ProfileFeature/Controllers/ProfileCodeController.swift b/Sources/ProfileFeature/Controllers/ProfileCodeController.swift
index d612b9e9e79c4dbb362b5a0c7782eb1463dc4fe6..037ac0413123cdccdd1d1fb7d4fdbc19a96b59d3 100644
--- a/Sources/ProfileFeature/Controllers/ProfileCodeController.swift
+++ b/Sources/ProfileFeature/Controllers/ProfileCodeController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Models
 import Shared
@@ -10,8 +9,6 @@ import ScrollViewController
 public typealias ControllerClosure = (UIViewController, AttributeConfirmation) -> Void
 
 public final class ProfileCodeController: UIViewController {
-    @Dependency private var hud: HUD
-
     lazy private var screenView = ProfileCodeView()
     lazy private var scrollViewController = ScrollViewController()
 
@@ -55,11 +52,6 @@ public final class ProfileCodeController: UIViewController {
     }
 
     private func setupBindings() {
-        viewModel.hud
-            .receive(on: DispatchQueue.main)
-            .sink { [hud] in hud.update(with: $0) }
-            .store(in: &cancellables)
-
         screenView.inputField.textPublisher
             .sink { [unowned self] in viewModel.didInput($0) }
             .store(in: &cancellables)
diff --git a/Sources/ProfileFeature/Controllers/ProfileController.swift b/Sources/ProfileFeature/Controllers/ProfileController.swift
index f6b04009f92f47c91d586ff848b55e835f0e837b..154996066df64c037906593484f4db56129c543f 100644
--- a/Sources/ProfileFeature/Controllers/ProfileController.swift
+++ b/Sources/ProfileFeature/Controllers/ProfileController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Combine
@@ -6,7 +5,6 @@ import DrawerFeature
 import DependencyInjection
 
 public final class ProfileController: UIViewController {
-  @Dependency var hud: HUD
   @Dependency var barStylist: StatusBarStylist
   @Dependency var coordinator: ProfileCoordinating
 
@@ -48,11 +46,6 @@ public final class ProfileController: UIViewController {
   }
 
   private func setupBindings() {
-    viewModel.hud
-      .receive(on: DispatchQueue.main)
-      .sink { [hud] in hud.update(with: $0) }
-      .store(in: &cancellables)
-
     screenView.emailView.actionButton
       .publisher(for: .touchUpInside)
       .receive(on: DispatchQueue.main)
diff --git a/Sources/ProfileFeature/Controllers/ProfileEmailController.swift b/Sources/ProfileFeature/Controllers/ProfileEmailController.swift
index f06b1f30d4b756ac5b9403753aa8e6876ba0e0fa..39396d0a2a48ba94338f283bff3016d06d4d1cc7 100644
--- a/Sources/ProfileFeature/Controllers/ProfileEmailController.swift
+++ b/Sources/ProfileFeature/Controllers/ProfileEmailController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Combine
@@ -6,7 +5,6 @@ import DependencyInjection
 import ScrollViewController
 
 public final class ProfileEmailController: UIViewController {
-  @Dependency var hud: HUD
   @Dependency var barStylist: StatusBarStylist
   @Dependency var coordinator: ProfileCoordinating
 
@@ -40,11 +38,6 @@ public final class ProfileEmailController: UIViewController {
   }
 
   private func setupBindings() {
-    viewModel.hud
-      .receive(on: DispatchQueue.main)
-      .sink { [hud] in hud.update(with: $0) }
-      .store(in: &cancellables)
-
     screenView.inputField.textPublisher
       .sink { [unowned self] in viewModel.didInput($0) }
       .store(in: &cancellables)
diff --git a/Sources/ProfileFeature/Controllers/ProfilePhoneController.swift b/Sources/ProfileFeature/Controllers/ProfilePhoneController.swift
index 0b1264850c2049efbbffc11fe8f33b09d88028d8..6b220c37a4a935119abfa5172149dcf80d76fb24 100644
--- a/Sources/ProfileFeature/Controllers/ProfilePhoneController.swift
+++ b/Sources/ProfileFeature/Controllers/ProfilePhoneController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Combine
@@ -6,7 +5,6 @@ import DependencyInjection
 import ScrollViewController
 
 public final class ProfilePhoneController: UIViewController {
-  @Dependency var hud: HUD
   @Dependency var barStylist: StatusBarStylist
   @Dependency var coordinator: ProfileCoordinating
 
@@ -40,11 +38,6 @@ public final class ProfilePhoneController: UIViewController {
   }
 
   private func setupBindings() {
-    viewModel.hud
-      .receive(on: DispatchQueue.main)
-      .sink { [hud] in hud.update(with: $0) }
-      .store(in: &cancellables)
-
     screenView.inputField.textPublisher
       .sink { [unowned self] in viewModel.didInput($0) }
       .store(in: &cancellables)
diff --git a/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift
index 40c7abb500cf46d2e7805470fac3a6ca95c33543..926c6d49a4f01eb1088c0431edcdf00a7dec7e1b 100644
--- a/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import Shared
 import Models
 import Combine
@@ -19,6 +18,7 @@ struct ProfileCodeViewState: Equatable {
 
 final class ProfileCodeViewModel {
   @Dependency var messenger: Messenger
+  @Dependency var hudController: HUDController
   @Dependency var backupService: BackupService
 
   @KeyObject(.email, defaultValue: nil) var email: String?
@@ -31,9 +31,6 @@ final class ProfileCodeViewModel {
   var completionPublisher: AnyPublisher<AttributeConfirmation, Never> { completionRelay.eraseToAnyPublisher() }
   private let completionRelay = PassthroughSubject<AttributeConfirmation, Never>()
 
-  var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-  private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-
   var state: AnyPublisher<ProfileCodeViewState, Never> { stateRelay.eraseToAnyPublisher() }
   private let stateRelay = CurrentValueSubject<ProfileCodeViewState, Never>(.init())
 
@@ -65,7 +62,7 @@ final class ProfileCodeViewModel {
   }
 
   func didTapNext() {
-    hudRelay.send(.on)
+    hudController.show()
 
     backgroundScheduler.schedule { [weak self] in
       guard let self = self else { return }
@@ -83,12 +80,12 @@ final class ProfileCodeViewModel {
         }
 
         self.timer?.invalidate()
-        self.hudRelay.send(.none)
+        self.hudController.dismiss()
         self.completionRelay.send(self.confirmation)
 
         self.backupService.didUpdateFacts()
       } catch {
-        self.hudRelay.send(.error(.init(with: error)))
+        self.hudController.show(.init(error: error))
       }
     }
   }
diff --git a/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift
index 4f7b61c3fdda42c084739cecc97fd048303f2d74..79b36b2f15b638ecffd3dc17177c08caf7c13055 100644
--- a/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import Models
 import Shared
 import Combine
@@ -6,73 +5,63 @@ import XXClient
 import Foundation
 import InputField
 import CombineSchedulers
-import DependencyInjection
 import XXMessengerClient
+import DependencyInjection
 
 struct ProfileEmailViewState: Equatable {
-    var input: String = ""
-    var confirmation: AttributeConfirmation? = nil
-    var status: InputField.ValidationStatus = .unknown(nil)
+  var input: String = ""
+  var confirmation: AttributeConfirmation? = nil
+  var status: InputField.ValidationStatus = .unknown(nil)
 }
 
 final class ProfileEmailViewModel {
-    // MARK: Injected
-
-    @Dependency var messenger: Messenger
-
-    // MARK: Properties
-
-    var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-
-    var state: AnyPublisher<ProfileEmailViewState, Never> { stateRelay.eraseToAnyPublisher() }
-    private let stateRelay = CurrentValueSubject<ProfileEmailViewState, Never>(.init())
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
-
-    // MARK: Public
-
-    func didInput(_ string: String) {
-        stateRelay.value.input = string
-        validate()
+  @Dependency var messenger: Messenger
+  @Dependency var hudController: HUDController
+
+  var state: AnyPublisher<ProfileEmailViewState, Never> { stateRelay.eraseToAnyPublisher() }
+  private let stateRelay = CurrentValueSubject<ProfileEmailViewState, Never>(.init())
+
+  var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
+
+  func didInput(_ string: String) {
+    stateRelay.value.input = string
+    validate()
+  }
+
+  func clearUp() {
+    stateRelay.value.confirmation = nil
+  }
+
+  func didTapNext() {
+    hudController.show()
+
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
+
+      do {
+        let confirmationId = try self.messenger.ud.get()!.sendRegisterFact(
+          .init(type: .email, value: self.stateRelay.value.input)
+        )
+
+        self.hudController.dismiss()
+        self.stateRelay.value.confirmation = .init(
+          content: self.stateRelay.value.input,
+          isEmail: true,
+          confirmationId: confirmationId
+        )
+      } catch {
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.hudController.show(.init(content: xxError))
+      }
     }
-
-    func clearUp() {
-        stateRelay.value.confirmation = nil
-    }
-
-    func didTapNext() {
-        hudRelay.send(.on)
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                let confirmationId = try self.messenger.ud.get()!.sendRegisterFact(
-                    .init(type: .email, value: self.stateRelay.value.input)
-                )
-
-                self.hudRelay.send(.none)
-                self.stateRelay.value.confirmation = .init(
-                    content: self.stateRelay.value.input,
-                    isEmail: true,
-                    confirmationId: confirmationId
-                )
-            } catch {
-                let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
-                self.hudRelay.send(.error(.init(content: xxError)))
-            }
-        }
-    }
-
-    // MARK: Private
-
-    private func validate() {
-        switch Validator.email.validate(stateRelay.value.input) {
-        case .success:
-            stateRelay.value.status = .valid(nil)
-        case .failure(let error):
-            stateRelay.value.status = .invalid(error)
-        }
+  }
+
+  private func validate() {
+    switch Validator.email.validate(stateRelay.value.input) {
+    case .success:
+      stateRelay.value.status = .valid(nil)
+    case .failure(let error):
+      stateRelay.value.status = .invalid(error)
     }
+  }
 }
diff --git a/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift
index 560f78cc0b7b9a94e356872fedff46005acf54cf..b2b3e34db5b6acd8520fba1d812459b41a5ce1d3 100644
--- a/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import Shared
 import Models
 import Combine
@@ -11,77 +10,67 @@ import DependencyInjection
 import XXMessengerClient
 
 struct ProfilePhoneViewState: Equatable {
-    var input: String = ""
-    var confirmation: AttributeConfirmation? = nil
-    var status: InputField.ValidationStatus = .unknown(nil)
-    var country: Country = .fromMyPhone()
+  var input: String = ""
+  var confirmation: AttributeConfirmation? = nil
+  var status: InputField.ValidationStatus = .unknown(nil)
+  var country: Country = .fromMyPhone()
 }
 
 final class ProfilePhoneViewModel {
-    // MARK: Injected
+  @Dependency var messenger: Messenger
+  @Dependency var hudController: HUDController
 
-    @Dependency var messenger: Messenger
+  var state: AnyPublisher<ProfilePhoneViewState, Never> { stateRelay.eraseToAnyPublisher() }
+  private let stateRelay = CurrentValueSubject<ProfilePhoneViewState, Never>(.init())
 
-    // MARK: Properties
+  var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
 
-    var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
+  func didInput(_ string: String) {
+    stateRelay.value.input = string
+    validate()
+  }
 
-    var state: AnyPublisher<ProfilePhoneViewState, Never> { stateRelay.eraseToAnyPublisher() }
-    private let stateRelay = CurrentValueSubject<ProfilePhoneViewState, Never>(.init())
+  func clearUp() {
+    stateRelay.value.confirmation = nil
+  }
 
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
+  func didChooseCountry(_ country: Country) {
+    stateRelay.value.country = country
+    validate()
+  }
 
-    // MARK: Public
+  func didTapNext() {
+    hudController.show()
 
-    func didInput(_ string: String) {
-        stateRelay.value.input = string
-        validate()
-    }
-
-    func clearUp() {
-        stateRelay.value.confirmation = nil
-    }
-
-    func didChooseCountry(_ country: Country) {
-        stateRelay.value.country = country
-        validate()
-    }
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
 
-    func didTapNext() {
-        hudRelay.send(.on)
+      let content = "\(self.stateRelay.value.input)\(self.stateRelay.value.country.code)"
 
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
+      do {
+        let confirmationId = try self.messenger.ud.get()!.sendRegisterFact(
+          .init(type: .phone, value: content)
+        )
 
-            let content = "\(self.stateRelay.value.input)\(self.stateRelay.value.country.code)"
+        self.hudController.dismiss()
+        self.stateRelay.value.confirmation = .init(
+          content: content,
+          confirmationId: confirmationId
+        )
 
-            do {
-                let confirmationId = try self.messenger.ud.get()!.sendRegisterFact(
-                    .init(type: .phone, value: content)
-                )
-
-                self.hudRelay.send(.none)
-                self.stateRelay.value.confirmation = .init(
-                    content: content,
-                    confirmationId: confirmationId
-                )
-
-            } catch {
-                let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
-                self.hudRelay.send(.error(.init(content: xxError)))
-            }
-        }
+      } catch {
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.hudController.show(.init(content: xxError))
+      }
     }
-
-    // MARK: Private
-
-    private func validate() {
-        switch Validator.phone.validate((stateRelay.value.country.regex, stateRelay.value.input)) {
-        case .success:
-            stateRelay.value.status = .valid(nil)
-        case .failure(let error):
-            stateRelay.value.status = .invalid(error)
-        }
+  }
+
+  private func validate() {
+    switch Validator.phone.validate((stateRelay.value.country.regex, stateRelay.value.input)) {
+    case .success:
+      stateRelay.value.status = .valid(nil)
+    case .failure(let error):
+      stateRelay.value.status = .invalid(error)
     }
+  }
 }
diff --git a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift
index d700516ef7f2bf1df7f917cf91c306f518e607fd..d8aa77527d7bbf10fe4c1a6728e145e574822587 100644
--- a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Models
@@ -35,6 +34,7 @@ final class ProfileViewModel {
 
   @Dependency var messenger: Messenger
   @Dependency var backupService: BackupService
+  @Dependency var hudController: HUDController
   @Dependency var permissions: PermissionHandling
 
   var name: String { username! }
@@ -42,9 +42,6 @@ final class ProfileViewModel {
   var state: AnyPublisher<ProfileViewState, Never> { stateRelay.eraseToAnyPublisher() }
   private let stateRelay = CurrentValueSubject<ProfileViewState, Never>(.init())
 
-  var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-  private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-
   var navigation: AnyPublisher<ProfileNavigationRoutes, Never> { navigationRoutes.eraseToAnyPublisher() }
   private let navigationRoutes = PassthroughSubject<ProfileNavigationRoutes, Never>()
 
@@ -87,7 +84,7 @@ final class ProfileViewModel {
   }
 
   func didTapDelete(isEmail: Bool) {
-    hudRelay.send(.on)
+    hudController.show()
 
     backgroundScheduler.schedule { [weak self] in
       guard let self = self else { return }
@@ -109,11 +106,11 @@ final class ProfileViewModel {
         }
 
         self.backupService.didUpdateFacts()
-        self.hudRelay.send(.none)
+        self.hudController.dismiss()
         self.refresh()
       } catch {
         let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
-        self.hudRelay.send(.error(.init(content: xxError)))
+        self.hudController.show(.init(content: xxError))
       }
     }
   }
diff --git a/Sources/RequestsFeature/Controllers/RequestsFailedController.swift b/Sources/RequestsFeature/Controllers/RequestsFailedController.swift
index c0af65d5f83a4bc1528065eef1551e597a52f6c9..40d6b4cd068bcf0928ffa6c8519f2a7452530e85 100644
--- a/Sources/RequestsFeature/Controllers/RequestsFailedController.swift
+++ b/Sources/RequestsFeature/Controllers/RequestsFailedController.swift
@@ -1,12 +1,8 @@
-import HUD
 import UIKit
-import Shared
 import Combine
 import DependencyInjection
 
 final class RequestsFailedController: UIViewController {
-    @Dependency private var hud: HUD
-
     lazy private var screenView = RequestsFailedView()
     private var cancellables = Set<AnyCancellable>()
     private let viewModel = RequestsFailedViewModel()
@@ -39,10 +35,5 @@ final class RequestsFailedController: UIViewController {
                 dataSource?.apply($0, animatingDifferences: false)
                 screenView.collectionView.isHidden = $0.numberOfItems == 0
             }.store(in: &cancellables)
-
-        viewModel.hudPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [hud] in hud.update(with: $0) }
-            .store(in: &cancellables)
     }
 }
diff --git a/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift b/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift
index c5a9118966df113885bae7de0d6d4538470d4bff..b6fc65c14009e4911a32cccc4ad39d62700f0d06 100644
--- a/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift
+++ b/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Models
 import Shared
@@ -9,9 +8,8 @@ import DrawerFeature
 import DependencyInjection
 
 final class RequestsReceivedController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var toaster: ToastController
-    @Dependency private var coordinator: RequestsCoordinating
+    @Dependency var toaster: ToastController
+    @Dependency var coordinator: RequestsCoordinating
 
     lazy private var screenView = RequestsReceivedView()
     private var cancellables = Set<AnyCancellable>()
@@ -77,11 +75,6 @@ final class RequestsReceivedController: UIViewController {
             return cell
         }
 
-        viewModel.hudPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [hud] in hud.update(with: $0) }
-            .store(in: &cancellables)
-
         viewModel.verifyingPublisher
             .receive(on: DispatchQueue.main)
             .sink { [unowned self] in presentVerifyingDrawer() }
diff --git a/Sources/RequestsFeature/Controllers/RequestsSentController.swift b/Sources/RequestsFeature/Controllers/RequestsSentController.swift
index ceed2cc28989b51d72d0191e63e81ade2f930bdd..b632e8216dda25340fa1c263aa62f7c2f275ce1a 100644
--- a/Sources/RequestsFeature/Controllers/RequestsSentController.swift
+++ b/Sources/RequestsFeature/Controllers/RequestsSentController.swift
@@ -1,12 +1,8 @@
-import HUD
 import UIKit
-import Shared
 import Combine
 import DependencyInjection
 
 final class RequestsSentController: UIViewController {
-    @Dependency private var hud: HUD
-
     var connectionsPublisher: AnyPublisher<Void, Never> {
         connectionSubject.eraseToAnyPublisher()
     }
@@ -46,11 +42,6 @@ final class RequestsSentController: UIViewController {
                 screenView.collectionView.isHidden = $0.numberOfItems == 0
             }.store(in: &cancellables)
 
-        viewModel.hudPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [hud] in hud.update(with: $0) }
-            .store(in: &cancellables)
-
         screenView.connectionsButton
             .publisher(for: .touchUpInside)
             .sink { [unowned self] in connectionSubject.send() }
diff --git a/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift
index e570e286c30e54f136873acf4feba2923f792748..97159b02162bb1f069045060895c9f6c21c0feb8 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift
@@ -1,6 +1,6 @@
-import HUD
 import UIKit
 import Models
+import Shared
 import Combine
 import XXModels
 import Defaults
@@ -10,88 +10,84 @@ import DependencyInjection
 import XXMessengerClient
 
 final class RequestsFailedViewModel {
-    @Dependency var database: Database
-    @Dependency var messenger: Messenger
-
-    @KeyObject(.username, defaultValue: nil) var username: String?
-    @KeyObject(.sharingEmail, defaultValue: false) var sharingEmail: Bool
-    @KeyObject(.sharingPhone, defaultValue: false) var sharingPhone: Bool
-
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
-
-    var itemsPublisher: AnyPublisher<NSDiffableDataSourceSnapshot<Section, Request>, Never> {
-        itemsSubject.eraseToAnyPublisher()
-    }
-
-    private var cancellables = Set<AnyCancellable>()
-    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
-    private let itemsSubject = CurrentValueSubject<NSDiffableDataSourceSnapshot<Section, Request>, Never>(.init())
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
-
-    init() {
-        database.fetchContactsPublisher(.init(authStatus: [.requestFailed, .confirmationFailed]))
-        .replaceError(with: [])
-            .map { data -> NSDiffableDataSourceSnapshot<Section, Request> in
-                var snapshot = NSDiffableDataSourceSnapshot<Section, Request>()
-                snapshot.appendSections([.appearing])
-                snapshot.appendItems(data.map { Request.contact($0) }, toSection: .appearing)
-                return snapshot
-            }.sink { [unowned self] in itemsSubject.send($0) }
-            .store(in: &cancellables)
+  @Dependency var database: Database
+  @Dependency var messenger: Messenger
+  @Dependency var hudController: HUDController
+  
+  @KeyObject(.username, defaultValue: nil) var username: String?
+  @KeyObject(.sharingEmail, defaultValue: false) var sharingEmail: Bool
+  @KeyObject(.sharingPhone, defaultValue: false) var sharingPhone: Bool
+
+  var itemsPublisher: AnyPublisher<NSDiffableDataSourceSnapshot<Section, Request>, Never> {
+    itemsSubject.eraseToAnyPublisher()
+  }
+  
+  private var cancellables = Set<AnyCancellable>()
+  private let itemsSubject = CurrentValueSubject<NSDiffableDataSourceSnapshot<Section, Request>, Never>(.init())
+  
+  var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
+  
+  init() {
+    database.fetchContactsPublisher(.init(authStatus: [.requestFailed, .confirmationFailed]))
+      .replaceError(with: [])
+      .map { data -> NSDiffableDataSourceSnapshot<Section, Request> in
+        var snapshot = NSDiffableDataSourceSnapshot<Section, Request>()
+        snapshot.appendSections([.appearing])
+        snapshot.appendItems(data.map { Request.contact($0) }, toSection: .appearing)
+        return snapshot
+      }.sink { [unowned self] in itemsSubject.send($0) }
+      .store(in: &cancellables)
+  }
+  
+  func didTapStateButtonFor(request: Request) {
+    guard case var .contact(contact) = request,
+          request.status == .failedToRequest ||
+            request.status == .failedToConfirm else {
+      return
     }
-
-    func didTapStateButtonFor(request: Request) {
-        guard case var .contact(contact) = request,
-                request.status == .failedToRequest ||
-                request.status == .failedToConfirm else {
-            return
-        }
-
-        hudSubject.send(.on)
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                if request.status == .failedToRequest {
-
-                    var includedFacts: [Fact] = []
-                    let myFacts = try self.messenger.ud.get()!.getFacts()
-
-                    if let fact = myFacts.get(.username) {
-                        includedFacts.append(fact)
-                    }
-
-                    if self.sharingEmail, let fact = myFacts.get(.email) {
-                        includedFacts.append(fact)
-                    }
-
-                    if self.sharingPhone, let fact = myFacts.get(.phone) {
-                        includedFacts.append(fact)
-                    }
-
-                    let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
-                        partner: .live(contact.marshaled!),
-                        myFacts: includedFacts
-                    )
-
-                    contact.authStatus = .requested
-                } else {
-                    let _ = try self.messenger.e2e.get()!.confirmReceivedRequest(
-                        partner: XXClient.Contact.live(contact.marshaled!)
-                    )
-
-                    contact.authStatus = .friend
-                }
-
-                try self.database.saveContact(contact)
-                self.hudSubject.send(.none)
-            } catch {
-                let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
-                self.hudSubject.send(.error(.init(content: xxError)))
-            }
+    
+    hudController.show()
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
+      
+      do {
+        if request.status == .failedToRequest {
+          
+          var includedFacts: [Fact] = []
+          let myFacts = try self.messenger.ud.get()!.getFacts()
+          
+          if let fact = myFacts.get(.username) {
+            includedFacts.append(fact)
+          }
+          
+          if self.sharingEmail, let fact = myFacts.get(.email) {
+            includedFacts.append(fact)
+          }
+          
+          if self.sharingPhone, let fact = myFacts.get(.phone) {
+            includedFacts.append(fact)
+          }
+          
+          let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
+            partner: .live(contact.marshaled!),
+            myFacts: includedFacts
+          )
+          
+          contact.authStatus = .requested
+        } else {
+          let _ = try self.messenger.e2e.get()!.confirmReceivedRequest(
+            partner: XXClient.Contact.live(contact.marshaled!)
+          )
+          
+          contact.authStatus = .friend
         }
+        
+        try self.database.saveContact(contact)
+        self.hudController.dismiss()
+      } catch {
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.hudController.show(.init(content: xxError))
+      }
     }
+  }
 }
diff --git a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
index fd1cc9b51dbf39625d22e709fb17c815586e131b..c459bf79b1649595882956830a443a646a1d6e28 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Models
 import Shared
@@ -15,278 +14,274 @@ import XXMessengerClient
 import struct XXModels.Group
 
 struct RequestReceived: Hashable, Equatable {
-    var request: Request?
-    var isHidden: Bool
-    var leader: String?
+  var request: Request?
+  var isHidden: Bool
+  var leader: String?
 }
 
 final class RequestsReceivedViewModel {
-    @Dependency var database: Database
-    @Dependency var groupManager: GroupChat
-    @Dependency var messenger: Messenger
-    @Dependency var reportingStatus: ReportingStatus
-
-    @KeyObject(.isShowingHiddenRequests, defaultValue: false) var isShowingHiddenRequests: Bool
-
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
-
-    var verifyingPublisher: AnyPublisher<Void, Never> {
-        verifyingSubject.eraseToAnyPublisher()
+  @Dependency var database: Database
+  @Dependency var messenger: Messenger
+  @Dependency var groupManager: GroupChat
+  @Dependency var hudController: HUDController
+  @Dependency var reportingStatus: ReportingStatus
+  
+  @KeyObject(.isShowingHiddenRequests, defaultValue: false) var isShowingHiddenRequests: Bool
+
+  var verifyingPublisher: AnyPublisher<Void, Never> {
+    verifyingSubject.eraseToAnyPublisher()
+  }
+  
+  var itemsPublisher: AnyPublisher<NSDiffableDataSourceSnapshot<Section, RequestReceived>, Never> {
+    itemsSubject.eraseToAnyPublisher()
+  }
+  
+  var groupConfirmationPublisher: AnyPublisher<Group, Never> {
+    groupConfirmationSubject.eraseToAnyPublisher()
+  }
+  
+  var contactConfirmationPublisher: AnyPublisher<XXModels.Contact, Never> {
+    contactConfirmationSubject.eraseToAnyPublisher()
+  }
+  
+  private var cancellables = Set<AnyCancellable>()
+  private let updateSubject = CurrentValueSubject<Void, Never>(())
+  private let verifyingSubject = PassthroughSubject<Void, Never>()
+  private let groupConfirmationSubject = PassthroughSubject<Group, Never>()
+  private let contactConfirmationSubject = PassthroughSubject<XXModels.Contact, Never>()
+  private let itemsSubject = CurrentValueSubject<NSDiffableDataSourceSnapshot<Section, RequestReceived>, Never>(.init())
+  
+  var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
+  
+  init() {
+    let groupsQuery = Group.Query(
+      authStatus: [
+        .hidden,
+        .pending
+      ],
+      isLeaderBlocked: reportingStatus.isEnabled() ? false : nil,
+      isLeaderBanned: reportingStatus.isEnabled() ? false : nil
+    )
+    
+    let contactsQuery = Contact.Query(
+      authStatus: [
+        .friend,
+        .hidden,
+        .verified,
+        .verificationFailed,
+        .verificationInProgress
+      ],
+      isBlocked: reportingStatus.isEnabled() ? false : nil,
+      isBanned: reportingStatus.isEnabled() ? false : nil
+    )
+    
+    let groupStream = database
+      .fetchGroupsPublisher(groupsQuery)
+      .replaceError(with: [])
+    
+    let contactsStream = database
+      .fetchContactsPublisher(contactsQuery)
+      .replaceError(with: [])
+    
+    Publishers.CombineLatest3(
+      groupStream,
+      contactsStream,
+      updateSubject.eraseToAnyPublisher()
+    )
+    .subscribe(on: DispatchQueue.main)
+    .receive(on: DispatchQueue.global())
+    .map { [unowned self] data -> NSDiffableDataSourceSnapshot<Section, RequestReceived> in
+      var snapshot = NSDiffableDataSourceSnapshot<Section, RequestReceived>()
+      snapshot.appendSections([.appearing, .hidden])
+      
+      let contactsFilteringFriends = data.1.filter { $0.authStatus != .friend }
+      let requests = data.0.map(Request.group) + contactsFilteringFriends.map(Request.contact)
+      let receivedRequests = requests.map { request -> RequestReceived in
+        switch request {
+        case let .group(group):
+          func leaderName() -> String {
+            if let leader = data.1.first(where: { $0.id == group.leaderId }) {
+              return (leader.nickname ?? leader.username) ?? "Leader is not a friend"
+            } else {
+              return "[Error retrieving leader]"
+            }
+          }
+          
+          return RequestReceived(
+            request: request,
+            isHidden: group.authStatus == .hidden,
+            leader: leaderName()
+          )
+        case let .contact(contact):
+          return RequestReceived(
+            request: request,
+            isHidden: contact.authStatus == .hidden,
+            leader: nil
+          )
+        }
+      }
+      
+      if self.isShowingHiddenRequests {
+        snapshot.appendItems(receivedRequests.filter(\.isHidden), toSection: .hidden)
+      }
+      
+      guard receivedRequests.filter({ $0.isHidden == false }).count > 0 else {
+        snapshot.appendItems([RequestReceived(isHidden: false)], toSection: .appearing)
+        return snapshot
+      }
+      
+      snapshot.appendItems(receivedRequests.filter { $0.isHidden == false }, toSection: .appearing)
+      return snapshot
+    }.sink(
+      receiveCompletion: { _ in },
+      receiveValue: { [unowned self] in itemsSubject.send($0) }
+    ).store(in: &cancellables)
+  }
+  
+  func didToggleHiddenRequestsSwitcher() {
+    isShowingHiddenRequests.toggle()
+    updateSubject.send()
+  }
+  
+  func didTapStateButtonFor(request: Request) {
+    guard case var .contact(contact) = request else { return }
+    
+    if request.status == .failedToVerify {
+      backgroundScheduler.schedule { [weak self] in
+        guard let self = self else { return }
+        
+        do {
+          contact.authStatus = .verificationInProgress
+          try self.database.saveContact(contact)
+          
+          print(">>> [messenger.verifyContact] will start")
+          
+          if try self.messenger.verifyContact(XXClient.Contact.live(contact.marshaled!)) {
+            print(">>> [messenger.verifyContact] verified")
+            
+            contact.authStatus = .verified
+            contact = try self.database.saveContact(contact)
+          } else {
+            print(">>> [messenger.verifyContact] is fake")
+            
+            try self.database.deleteContact(contact)
+          }
+        } catch {
+          print(">>> [messenger.verifyContact] thrown an exception: \(error.localizedDescription)")
+          
+          contact.authStatus = .verificationFailed
+          _ = try? self.database.saveContact(contact)
+        }
+      }
+    } else if request.status == .verifying {
+      verifyingSubject.send()
     }
-
-    var itemsPublisher: AnyPublisher<NSDiffableDataSourceSnapshot<Section, RequestReceived>, Never> {
-        itemsSubject.eraseToAnyPublisher()
+  }
+  
+  func didRequestHide(group: Group) {
+    if var group = try? database.fetchGroups(.init(id: [group.id])).first {
+      group.authStatus = .hidden
+      _ = try? database.saveGroup(group)
     }
-
-    var groupConfirmationPublisher: AnyPublisher<Group, Never> {
-        groupConfirmationSubject.eraseToAnyPublisher()
+  }
+  
+  func didRequestAccept(group: Group) {
+    hudController.show()
+    
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
+      
+      do {
+        try self.groupManager.joinGroup(serializedGroupData: group.serialized)
+        
+        var group = group
+        group.authStatus = .participating
+        try self.database.saveGroup(group)
+        
+        self.hudController.dismiss()
+        self.groupConfirmationSubject.send(group)
+      } catch {
+        self.hudController.show(.init(error: error))
+      }
     }
-
-    var contactConfirmationPublisher: AnyPublisher<XXModels.Contact, Never> {
-        contactConfirmationSubject.eraseToAnyPublisher()
-    }
-
-    private var cancellables = Set<AnyCancellable>()
-    private let updateSubject = CurrentValueSubject<Void, Never>(())
-    private let verifyingSubject = PassthroughSubject<Void, Never>()
-    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
-    private let groupConfirmationSubject = PassthroughSubject<Group, Never>()
-    private let contactConfirmationSubject = PassthroughSubject<XXModels.Contact, Never>()
-    private let itemsSubject = CurrentValueSubject<NSDiffableDataSourceSnapshot<Section, RequestReceived>, Never>(.init())
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
-
-    init() {
-        let groupsQuery = Group.Query(
-            authStatus: [
-                .hidden,
-                .pending
-            ],
-            isLeaderBlocked: reportingStatus.isEnabled() ? false : nil,
-            isLeaderBanned: reportingStatus.isEnabled() ? false : nil
-        )
-
-        let contactsQuery = Contact.Query(
-            authStatus: [
-                .friend,
-                .hidden,
-                .verified,
-                .verificationFailed,
-                .verificationInProgress
-            ],
-            isBlocked: reportingStatus.isEnabled() ? false : nil,
-            isBanned: reportingStatus.isEnabled() ? false : nil
-        )
-
-        let groupStream = database
-        .fetchGroupsPublisher(groupsQuery)
-        .replaceError(with: [])
-
-      let contactsStream = database
-        .fetchContactsPublisher(contactsQuery)
+  }
+  
+  func fetchMembers(
+    _ group: Group,
+    _ completion: @escaping (Result<[DrawerTableCellModel], Error>) -> Void
+  ) {
+    if let info = try? database.fetchGroupInfos(.init(groupId: group.id)).first {
+      database.fetchContactsPublisher(.init(id: Set(info.members.map(\.id))))
         .replaceError(with: [])
-
-        Publishers.CombineLatest3(
-            groupStream,
-            contactsStream,
-            updateSubject.eraseToAnyPublisher()
-        )
-        .subscribe(on: DispatchQueue.main)
-        .receive(on: DispatchQueue.global())
-        .map { [unowned self] data -> NSDiffableDataSourceSnapshot<Section, RequestReceived> in
-            var snapshot = NSDiffableDataSourceSnapshot<Section, RequestReceived>()
-            snapshot.appendSections([.appearing, .hidden])
-
-            let contactsFilteringFriends = data.1.filter { $0.authStatus != .friend }
-            let requests = data.0.map(Request.group) + contactsFilteringFriends.map(Request.contact)
-            let receivedRequests = requests.map { request -> RequestReceived in
-                switch request {
-                case let .group(group):
-                    func leaderName() -> String {
-                        if let leader = data.1.first(where: { $0.id == group.leaderId }) {
-                            return (leader.nickname ?? leader.username) ?? "Leader is not a friend"
-                        } else {
-                            return "[Error retrieving leader]"
-                        }
-                    }
-
-                    return RequestReceived(
-                        request: request,
-                        isHidden: group.authStatus == .hidden,
-                        leader: leaderName()
-                    )
-                case let .contact(contact):
-                    return RequestReceived(
-                        request: request,
-                        isHidden: contact.authStatus == .hidden,
-                        leader: nil
-                    )
-                }
+        .sink { members in
+          let withUsername = members
+            .filter { $0.username != nil }
+            .map {
+              DrawerTableCellModel(
+                id: $0.id,
+                title: $0.nickname ?? $0.username!,
+                image: $0.photo,
+                isCreator: $0.id == group.leaderId,
+                isConnection: $0.authStatus == .friend
+              )
             }
-
-            if self.isShowingHiddenRequests {
-                snapshot.appendItems(receivedRequests.filter(\.isHidden), toSection: .hidden)
+          
+          let withoutUsername = members
+            .filter { $0.username == nil }
+            .map {
+              DrawerTableCellModel(
+                id: $0.id,
+                title: "Fetching username...",
+                image: $0.photo,
+                isCreator: $0.id == group.leaderId,
+                isConnection: $0.authStatus == .friend
+              )
             }
-
-            guard receivedRequests.filter({ $0.isHidden == false }).count > 0 else {
-                snapshot.appendItems([RequestReceived(isHidden: false)], toSection: .appearing)
-                return snapshot
-            }
-
-            snapshot.appendItems(receivedRequests.filter { $0.isHidden == false }, toSection: .appearing)
-            return snapshot
-        }.sink(
-            receiveCompletion: { _ in },
-            receiveValue: { [unowned self] in itemsSubject.send($0) }
-        ).store(in: &cancellables)
+          
+          completion(.success(withUsername + withoutUsername))
+        }.store(in: &cancellables)
     }
-
-    func didToggleHiddenRequestsSwitcher() {
-        isShowingHiddenRequests.toggle()
-        updateSubject.send()
+  }
+  
+  func didRequestHide(contact: XXModels.Contact) {
+    if var contact = try? database.fetchContacts(.init(id: [contact.id])).first {
+      contact.authStatus = .hidden
+      _ = try? database.saveContact(contact)
     }
-
-    func didTapStateButtonFor(request: Request) {
-        guard case var .contact(contact) = request else { return }
-
-        if request.status == .failedToVerify {
-            backgroundScheduler.schedule { [weak self] in
-                guard let self = self else { return }
-
-                do {
-                    contact.authStatus = .verificationInProgress
-                    try self.database.saveContact(contact)
-
-                    print(">>> [messenger.verifyContact] will start")
-
-                    if try self.messenger.verifyContact(XXClient.Contact.live(contact.marshaled!)) {
-                        print(">>> [messenger.verifyContact] verified")
-
-                        contact.authStatus = .verified
-                        contact = try self.database.saveContact(contact)
-                    } else {
-                        print(">>> [messenger.verifyContact] is fake")
-
-                        try self.database.deleteContact(contact)
-                    }
-                } catch {
-                    print(">>> [messenger.verifyContact] thrown an exception: \(error.localizedDescription)")
-
-                    contact.authStatus = .verificationFailed
-                    _ = try? self.database.saveContact(contact)
-                }
-            }
-        } else if request.status == .verifying {
-            verifyingSubject.send()
-        }
-    }
-
-    func didRequestHide(group: Group) {
-        if var group = try? database.fetchGroups(.init(id: [group.id])).first {
-            group.authStatus = .hidden
-            _ = try? database.saveGroup(group)
-        }
+  }
+  
+  func didRequestAccept(contact: XXModels.Contact, nickname: String? = nil) {
+    hudController.show()
+    
+    var contact = contact
+    contact.authStatus = .confirming
+    contact.nickname = nickname ?? contact.username
+    
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
+      
+      do {
+        try self.database.saveContact(contact)
+        
+        let _ = try self.messenger.e2e.get()!.confirmReceivedRequest(partner: .live(contact.marshaled!))
+        contact.authStatus = .friend
+        try self.database.saveContact(contact)
+        
+        self.hudController.dismiss()
+        self.contactConfirmationSubject.send(contact)
+      } catch {
+        contact.authStatus = .confirmationFailed
+        _ = try? self.database.saveContact(contact)
+        self.hudController.show(.init(error: error))
+      }
     }
-
-    func didRequestAccept(group: Group) {
-        hudSubject.send(.on)
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.groupManager.joinGroup(serializedGroupData: group.serialized)
-
-                var group = group
-                group.authStatus = .participating
-                try self.database.saveGroup(group)
-
-                self.hudSubject.send(.none)
-                self.groupConfirmationSubject.send(group)
-            } catch {
-                self.hudSubject.send(.error(.init(with: error)))
-            }
-        }
-    }
-
-    func fetchMembers(
-        _ group: Group,
-        _ completion: @escaping (Result<[DrawerTableCellModel], Error>) -> Void
-    ) {
-        if let info = try? database.fetchGroupInfos(.init(groupId: group.id)).first {
-            database.fetchContactsPublisher(.init(id: Set(info.members.map(\.id))))
-            .replaceError(with: [])
-                .sink { members in
-                    let withUsername = members
-                        .filter { $0.username != nil }
-                        .map {
-                            DrawerTableCellModel(
-                                id: $0.id,
-                                title: $0.nickname ?? $0.username!,
-                                image: $0.photo,
-                                isCreator: $0.id == group.leaderId,
-                                isConnection: $0.authStatus == .friend
-                            )
-                        }
-
-                    let withoutUsername = members
-                        .filter { $0.username == nil }
-                        .map {
-                            DrawerTableCellModel(
-                                id: $0.id,
-                                title: "Fetching username...",
-                                image: $0.photo,
-                                isCreator: $0.id == group.leaderId,
-                                isConnection: $0.authStatus == .friend
-                            )
-                        }
-
-                    completion(.success(withUsername + withoutUsername))
-                }.store(in: &cancellables)
-        }
-    }
-
-    func didRequestHide(contact: XXModels.Contact) {
-        if var contact = try? database.fetchContacts(.init(id: [contact.id])).first {
-            contact.authStatus = .hidden
-            _ = try? database.saveContact(contact)
-        }
-    }
-
-    func didRequestAccept(contact: XXModels.Contact, nickname: String? = nil) {
-        hudSubject.send(.on)
-
-        var contact = contact
-        contact.authStatus = .confirming
-        contact.nickname = nickname ?? contact.username
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.database.saveContact(contact)
-
-                let _ = try self.messenger.e2e.get()!.confirmReceivedRequest(partner: .live(contact.marshaled!))
-                contact.authStatus = .friend
-                try self.database.saveContact(contact)
-
-                self.hudSubject.send(.none)
-                self.contactConfirmationSubject.send(contact)
-            } catch {
-                contact.authStatus = .confirmationFailed
-                _ = try? self.database.saveContact(contact)
-                self.hudSubject.send(.error(.init(with: error)))
-            }
-        }
-    }
-
-    func groupChatWith(group: Group) -> GroupInfo {
-        guard let info = try? database.fetchGroupInfos(.init(groupId: group.id)).first else {
-            fatalError()
-        }
-
-        return info
+  }
+  
+  func groupChatWith(group: Group) -> GroupInfo {
+    guard let info = try? database.fetchGroupInfos(.init(groupId: group.id)).first else {
+      fatalError()
     }
+    
+    return info
+  }
 }
diff --git a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
index 86dd95914ac5871a15497b22529901f39bfe4e49..9faa49547ab7766986f238ecdf1dab8902d3079b 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Models
 import Shared
@@ -13,121 +12,117 @@ import DependencyInjection
 import XXMessengerClient
 
 struct RequestSent: Hashable, Equatable {
-    var request: Request
-    var isResent: Bool = false
+  var request: Request
+  var isResent: Bool = false
 }
 
 final class RequestsSentViewModel {
-    @Dependency var database: Database
-    @Dependency var messenger: Messenger
-    @Dependency var reportingStatus: ReportingStatus
-    @Dependency var toastController: ToastController
-
-    @KeyObject(.username, defaultValue: nil) var username: String?
-    @KeyObject(.sharingEmail, defaultValue: false) var sharingEmail: Bool
-    @KeyObject(.sharingPhone, defaultValue: false) var sharingPhone: Bool
-
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
-
-    var itemsPublisher: AnyPublisher<NSDiffableDataSourceSnapshot<Section, RequestSent>, Never> {
-        itemsSubject.eraseToAnyPublisher()
-    }
-
-    private var cancellables = Set<AnyCancellable>()
-    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
-    private let itemsSubject = CurrentValueSubject<NSDiffableDataSourceSnapshot<Section, RequestSent>, Never>(.init())
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
-
-    init() {
-        let query = Contact.Query(
-            authStatus: [
-                .requested,
-                .requesting
-            ],
-            isBlocked: reportingStatus.isEnabled() ? false : nil,
-            isBanned: reportingStatus.isEnabled() ? false : nil
-        )
-
-        database.fetchContactsPublisher(query)
-        .replaceError(with: [])
-            .removeDuplicates()
-            .map { data -> NSDiffableDataSourceSnapshot<Section, RequestSent> in
-                var snapshot = NSDiffableDataSourceSnapshot<Section, RequestSent>()
-                snapshot.appendSections([.appearing])
-                snapshot.appendItems(data.map { RequestSent(request: .contact($0)) }, toSection: .appearing)
-                return snapshot
-            }.sink { [unowned self] in itemsSubject.send($0) }
-            .store(in: &cancellables)
+  @Dependency var database: Database
+  @Dependency var messenger: Messenger
+  @Dependency var hudController: HUDController
+  @Dependency var reportingStatus: ReportingStatus
+  @Dependency var toastController: ToastController
+  
+  @KeyObject(.username, defaultValue: nil) var username: String?
+  @KeyObject(.sharingEmail, defaultValue: false) var sharingEmail: Bool
+  @KeyObject(.sharingPhone, defaultValue: false) var sharingPhone: Bool
+
+  var itemsPublisher: AnyPublisher<NSDiffableDataSourceSnapshot<Section, RequestSent>, Never> {
+    itemsSubject.eraseToAnyPublisher()
+  }
+  
+  private var cancellables = Set<AnyCancellable>()
+  private let itemsSubject = CurrentValueSubject<NSDiffableDataSourceSnapshot<Section, RequestSent>, Never>(.init())
+  
+  var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
+  
+  init() {
+    let query = Contact.Query(
+      authStatus: [
+        .requested,
+        .requesting
+      ],
+      isBlocked: reportingStatus.isEnabled() ? false : nil,
+      isBanned: reportingStatus.isEnabled() ? false : nil
+    )
+    
+    database.fetchContactsPublisher(query)
+      .replaceError(with: [])
+      .removeDuplicates()
+      .map { data -> NSDiffableDataSourceSnapshot<Section, RequestSent> in
+        var snapshot = NSDiffableDataSourceSnapshot<Section, RequestSent>()
+        snapshot.appendSections([.appearing])
+        snapshot.appendItems(data.map { RequestSent(request: .contact($0)) }, toSection: .appearing)
+        return snapshot
+      }.sink { [unowned self] in itemsSubject.send($0) }
+      .store(in: &cancellables)
+  }
+  
+  func didTapStateButtonFor(request item: RequestSent) {
+    guard case let .contact(contact) = item.request,
+          item.request.status == .requested ||
+            item.request.status == .requesting ||
+            item.request.status == .failedToRequest else {
+      return
     }
-
-    func didTapStateButtonFor(request item: RequestSent) {
-        guard case let .contact(contact) = item.request,
-              item.request.status == .requested ||
-                item.request.status == .requesting ||
-                item.request.status == .failedToRequest else {
-            return
+    
+    let name = (contact.nickname ?? contact.username) ?? ""
+    
+    hudController.show()
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
+      
+      do {
+        var includedFacts: [Fact] = []
+        let myFacts = try self.messenger.ud.get()!.getFacts()
+        
+        if let fact = myFacts.get(.username) {
+          includedFacts.append(fact)
         }
-
-        let name = (contact.nickname ?? contact.username) ?? ""
-
-        hudSubject.send(.on)
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                var includedFacts: [Fact] = []
-                let myFacts = try self.messenger.ud.get()!.getFacts()
-
-                if let fact = myFacts.get(.username) {
-                    includedFacts.append(fact)
-                }
-
-                if self.sharingEmail, let fact = myFacts.get(.email) {
-                    includedFacts.append(fact)
-                }
-
-                if self.sharingPhone, let fact = myFacts.get(.phone) {
-                    includedFacts.append(fact)
-                }
-
-                let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
-                    partner: .live(contact.marshaled!),
-                    myFacts: includedFacts
-                )
-
-                self.hudSubject.send(.none)
-
-                var item = item
-                var allRequests = self.itemsSubject.value.itemIdentifiers
-
-                if let indexOfRequest = allRequests.firstIndex(of: item) {
-                    allRequests.remove(at: indexOfRequest)
-                }
-
-                item.isResent = true
-                allRequests.append(item)
-
-                self.toastController.enqueueToast(model: .init(
-                    title: Localized.Requests.Sent.Toast.resent(name),
-                    leftImage: Asset.requestSentToaster.image
-                ))
-
-                var snapshot = NSDiffableDataSourceSnapshot<Section, RequestSent>()
-                snapshot.appendSections([.appearing])
-                snapshot.appendItems(allRequests, toSection: .appearing)
-                self.itemsSubject.send(snapshot)
-            } catch {
-                self.toastController.enqueueToast(model: .init(
-                    title: Localized.Requests.Sent.Toast.resentFailed(name),
-                    leftImage: Asset.requestFailedToaster.image
-                ))
-
-                let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
-                self.hudSubject.send(.error(.init(content: xxError)))
-            }
+        
+        if self.sharingEmail, let fact = myFacts.get(.email) {
+          includedFacts.append(fact)
+        }
+        
+        if self.sharingPhone, let fact = myFacts.get(.phone) {
+          includedFacts.append(fact)
+        }
+        
+        let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
+          partner: .live(contact.marshaled!),
+          myFacts: includedFacts
+        )
+        
+        self.hudController.dismiss()
+        
+        var item = item
+        var allRequests = self.itemsSubject.value.itemIdentifiers
+        
+        if let indexOfRequest = allRequests.firstIndex(of: item) {
+          allRequests.remove(at: indexOfRequest)
         }
+        
+        item.isResent = true
+        allRequests.append(item)
+        
+        self.toastController.enqueueToast(model: .init(
+          title: Localized.Requests.Sent.Toast.resent(name),
+          leftImage: Asset.requestSentToaster.image
+        ))
+        
+        var snapshot = NSDiffableDataSourceSnapshot<Section, RequestSent>()
+        snapshot.appendSections([.appearing])
+        snapshot.appendItems(allRequests, toSection: .appearing)
+        self.itemsSubject.send(snapshot)
+      } catch {
+        self.toastController.enqueueToast(model: .init(
+          title: Localized.Requests.Sent.Toast.resentFailed(name),
+          leftImage: Asset.requestFailedToaster.image
+        ))
+        
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.hudController.show(.init(content: xxError))
+      }
     }
+  }
 }
diff --git a/Sources/RestoreFeature/Controllers/RestoreListController.swift b/Sources/RestoreFeature/Controllers/RestoreListController.swift
index 15acce20539862d69c36ab73b483a97fa356dc2f..bfea5df338a13928ee2fb68fa3aa989c5f9ce7e0 100644
--- a/Sources/RestoreFeature/Controllers/RestoreListController.swift
+++ b/Sources/RestoreFeature/Controllers/RestoreListController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Combine
@@ -6,7 +5,6 @@ import DrawerFeature
 import DependencyInjection
 
 public final class RestoreListController: UIViewController {
-  @Dependency var hud: HUD
   @Dependency var coordinator: RestoreCoordinating
 
   lazy private var screenView = RestoreListView()
@@ -42,11 +40,6 @@ public final class RestoreListController: UIViewController {
         }
       }.store(in: &cancellables)
 
-    viewModel.hudPublisher
-      .receive(on: DispatchQueue.main)
-      .sink { [hud] in hud.update(with: $0) }
-      .store(in: &cancellables)
-
     viewModel.detailsPublisher
       .receive(on: DispatchQueue.main)
       .sink { [unowned self] in
diff --git a/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift b/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift
index c60b41d9e19d3540dd3090abdd9ea550a21b3906..2ddf4ab1160f6c09aa7644eb857f46f6e2fffaa3 100644
--- a/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift
+++ b/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift
@@ -1,12 +1,9 @@
-import HUD
 import UIKit
 import Combine
 import DependencyInjection
 import ScrollViewController
 
 public final class RestoreSFTPController: UIViewController {
-  @Dependency private var hud: HUD
-
   lazy private var screenView = RestoreSFTPView()
   lazy private var scrollViewController = ScrollViewController()
 
@@ -44,11 +41,6 @@ public final class RestoreSFTPController: UIViewController {
   }
 
   private func setupBindings() {
-    viewModel.hudPublisher
-      .receive(on: DispatchQueue.main)
-      .sink { [hud] in hud.update(with: $0) }
-      .store(in: &cancellables)
-
     viewModel.authPublisher
       .receive(on: DispatchQueue.main)
       .sink { [unowned self] params in
diff --git a/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
index 2b992439f004284dae958827025882ab0640c9b2..249ab05d20cc164ef5930f8767747e520ed6d219 100644
--- a/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
+++ b/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
@@ -1,5 +1,5 @@
-import HUD
 import UIKit
+import Shared
 import Combine
 import CloudFiles
 import CloudFilesSFTP
@@ -11,20 +11,17 @@ public struct RestorationDetails {
 }
 
 final class RestoreListViewModel {
+  @Dependency var hudController: HUDController
+
   var sftpPublisher: AnyPublisher<Void, Never> {
     sftpSubject.eraseToAnyPublisher()
   }
 
-  var hudPublisher: AnyPublisher<HUDStatus, Never> {
-    hudSubject.eraseToAnyPublisher()
-  }
-
   var detailsPublisher: AnyPublisher<RestorationDetails, Never> {
     detailsSubject.eraseToAnyPublisher()
   }
 
   private let sftpSubject = PassthroughSubject<Void, Never>()
-  private let hudSubject = PassthroughSubject<HUDStatus, Never>()
   private let detailsSubject = PassthroughSubject<RestorationDetails, Never>()
 
   func setupSFTP(host: String, username: String, password: String) {
@@ -54,33 +51,33 @@ final class RestoreListViewModel {
         case .success:
           onSuccess()
         case .failure(let error):
-          self.hudSubject.send(.error(.init(with: error)))
+          self.hudController.show(.init(error: error))
         }
       }
     } catch {
-      hudSubject.send(.error(.init(with: error)))
+      hudController.show(.init(error: error))
     }
   }
 
   func fetch(provider: CloudService) {
-    hudSubject.send(.on)
+    hudController.show()
     do {
       try CloudFilesManager.all[provider]!.fetch { [weak self] in
         guard let self else { return }
 
         switch $0 {
         case .success(let metadata):
-          self.hudSubject.send(.none)
+          self.hudController.dismiss()
           self.detailsSubject.send(.init(
             provider: provider,
             metadata: metadata
           ))
         case .failure(let error):
-          self.hudSubject.send(.error(.init(with: error)))
+          self.hudController.show(.init(error: error))
         }
       }
     } catch {
-      hudSubject.send(.error(.init(with: error)))
+      hudController.show(.init(error: error))
     }
   }
 }
diff --git a/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift
index 6c509cbbf1ee17116a5c5424615485ac10935b16..bb81db9e12002ae7cf205eed0c7c728bc2b0d564 100644
--- a/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift
+++ b/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift
@@ -1,9 +1,10 @@
-import HUD
 import UIKit
+import Shared
 import Combine
 import Foundation
 import CloudFiles
 import CloudFilesSFTP
+import DependencyInjection
 
 struct SFTPViewState {
   var host: String = ""
@@ -13,9 +14,7 @@ struct SFTPViewState {
 }
 
 final class RestoreSFTPViewModel {
-  var hudPublisher: AnyPublisher<HUDStatus, Never> {
-    hudSubject.eraseToAnyPublisher()
-  }
+  @Dependency var hudController: HUDController
 
   var statePublisher: AnyPublisher<SFTPViewState, Never> {
     stateSubject.eraseToAnyPublisher()
@@ -25,7 +24,6 @@ final class RestoreSFTPViewModel {
     authSubject.eraseToAnyPublisher()
   }
 
-  private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
   private let stateSubject = CurrentValueSubject<SFTPViewState, Never>(.init())
   private let authSubject = PassthroughSubject<(String, String, String), Never>()
 
@@ -45,7 +43,7 @@ final class RestoreSFTPViewModel {
   }
 
   func didTapLogin() {
-    hudSubject.send(.on)
+    hudController.show()
 
     let host = stateSubject.value.host
     let username = stateSubject.value.username
@@ -64,14 +62,14 @@ final class RestoreSFTPViewModel {
         ).link(anyController) {
           switch $0 {
           case .success:
-            self.hudSubject.send(.none)
+            self.hudController.dismiss()
             self.authSubject.send((host, username, password))
           case .failure(let error):
-            self.hudSubject.send(.error(.init(with: error)))
+            self.hudController.show(.init(error: error))
           }
         }
       } catch {
-        self.hudSubject.send(.error(.init(with: error)))
+        self.hudController.show(.init(error: error))
       }
     }
   }
diff --git a/Sources/SearchFeature/Controllers/SearchLeftController.swift b/Sources/SearchFeature/Controllers/SearchLeftController.swift
index cc46ad60cf83f8baa03aa3ac7bb34562fbccaea8..e850dfde12532dc74c85f7bc44d8e546e9543ecf 100644
--- a/Sources/SearchFeature/Controllers/SearchLeftController.swift
+++ b/Sources/SearchFeature/Controllers/SearchLeftController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Combine
@@ -9,8 +8,7 @@ import DrawerFeature
 import DependencyInjection
 
 final class SearchLeftController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: SearchCoordinating
+    @Dependency var coordinator: SearchCoordinating
 
     @KeyObject(.email, defaultValue: nil) var email: String?
     @KeyObject(.phone, defaultValue: nil) var phone: String?
@@ -110,24 +108,23 @@ final class SearchLeftController: UIViewController {
     }
 
     private func setupBindings() {
-        viewModel.hudPublisher
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                hud.update(with: $0)
-
-                if case .onAction = $0, let hudBtn = hud.actionButton {
-                    hudBtn.publisher(for: .touchUpInside)
-                        .receive(on: DispatchQueue.main)
-                        .sink { [unowned self] in viewModel.didTapCancelSearch() }
-                        .store(in: &self.hudCancellables)
-                } else {
-                    hudCancellables.forEach { $0.cancel() }
-                    hudCancellables.removeAll()
-                }
-            }
-            .store(in: &cancellables)
-
+//        viewModel.hudPublisher
+//            .removeDuplicates()
+//            .receive(on: DispatchQueue.main)
+//            .sink { [unowned self] in
+//                hud.update(with: $0)
+//
+//                if case .onAction = $0, let hudBtn = hud.actionButton {
+//                    hudBtn.publisher(for: .touchUpInside)
+//                        .receive(on: DispatchQueue.main)
+//                        .sink { [unowned self] in viewModel.didTapCancelSearch() }
+//                        .store(in: &self.hudCancellables)
+//                } else {
+//                    hudCancellables.forEach { $0.cancel() }
+//                    hudCancellables.removeAll()
+//                }
+//            }
+//            .store(in: &cancellables)
 
         viewModel.statePublisher
             .map(\.item)
diff --git a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
index e7a0921a149c6cb95f288a68912fe970cc4d93e6..5fd6243a1c177e489172231c549f969c3a4dc55d 100644
--- a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
+++ b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import Retry
 import UIKit
 import Models
@@ -18,350 +17,346 @@ import DependencyInjection
 typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem>
 
 struct SearchLeftViewState {
-    var input = ""
-    var snapshot: SearchSnapshot?
-    var country: Country = .fromMyPhone()
-    var item: SearchSegmentedControl.Item = .username
+  var input = ""
+  var snapshot: SearchSnapshot?
+  var country: Country = .fromMyPhone()
+  var item: SearchSegmentedControl.Item = .username
 }
 
 final class SearchLeftViewModel {
-    @Dependency var database: Database
-    @Dependency var messenger: Messenger
-    @Dependency var reportingStatus: ReportingStatus
-    @Dependency var toastController: ToastController
-    @Dependency var networkMonitor: NetworkMonitoring
-
-    @KeyObject(.username, defaultValue: nil) var username: String?
-    @KeyObject(.sharingEmail, defaultValue: false) var sharingEmail: Bool
-    @KeyObject(.sharingPhone, defaultValue: false) var sharingPhone: Bool
-
-    var myId: Data {
-        try! messenger.e2e.get()!.getContact().getId()
+  @Dependency var database: Database
+  @Dependency var messenger: Messenger
+  @Dependency var hudController: HUDController
+  @Dependency var reportingStatus: ReportingStatus
+  @Dependency var toastController: ToastController
+  @Dependency var networkMonitor: NetworkMonitoring
+
+  @KeyObject(.username, defaultValue: nil) var username: String?
+  @KeyObject(.sharingEmail, defaultValue: false) var sharingEmail: Bool
+  @KeyObject(.sharingPhone, defaultValue: false) var sharingPhone: Bool
+
+  var myId: Data {
+    try! messenger.e2e.get()!.getContact().getId()
+  }
+
+  var successPublisher: AnyPublisher<XXModels.Contact, Never> {
+    successSubject.eraseToAnyPublisher()
+  }
+
+  var statePublisher: AnyPublisher<SearchLeftViewState, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
+
+  var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
+
+  var invitation: String?
+  private var searchCancellables = Set<AnyCancellable>()
+  private let successSubject = PassthroughSubject<XXModels.Contact, Never>()
+  private let stateSubject = CurrentValueSubject<SearchLeftViewState, Never>(.init())
+  private var networkCancellable = Set<AnyCancellable>()
+
+  init(_ invitation: String? = nil) {
+    self.invitation = invitation
+  }
+
+  func viewDidAppear() {
+    if let pendingInvitation = invitation {
+      invitation = nil
+      stateSubject.value.input = pendingInvitation
+      hudController.show(.init(actionTitle: Localized.Ud.Search.cancel))
+
+      networkCancellable.removeAll()
+
+      networkMonitor.statusPublisher
+        .first { $0 == .available }
+        .eraseToAnyPublisher()
+        .flatMap { _ in
+          self.waitForNodes(timeout: 5)
+        }.sink(receiveCompletion: {
+          if case .failure(let error) = $0 {
+            self.hudController.show(.init(error: error))
+          }
+        }, receiveValue: {
+          self.didStartSearching()
+        }).store(in: &networkCancellable)
     }
+  }
 
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
+  func didEnterInput(_ string: String) {
+    stateSubject.value.input = string
+  }
 
-    var successPublisher: AnyPublisher<XXModels.Contact, Never> {
-        successSubject.eraseToAnyPublisher()
-    }
+  func didPick(country: Country) {
+    stateSubject.value.country = country
+  }
 
-    var statePublisher: AnyPublisher<SearchLeftViewState, Never> {
-        stateSubject.eraseToAnyPublisher()
-    }
+  func didSelectItem(_ item: SearchSegmentedControl.Item) {
+    stateSubject.value.item = item
+  }
 
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
+  func didTapCancelSearch() {
+    searchCancellables.forEach { $0.cancel() }
+    searchCancellables.removeAll()
+    hudController.dismiss()
+  }
 
-    var invitation: String?
-    private var searchCancellables = Set<AnyCancellable>()
-    private let successSubject = PassthroughSubject<XXModels.Contact, Never>()
-    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
-    private let stateSubject = CurrentValueSubject<SearchLeftViewState, Never>(.init())
-    private var networkCancellable = Set<AnyCancellable>()
+  func didStartSearching() {
+    guard stateSubject.value.input.isEmpty == false else { return }
 
-    init(_ invitation: String? = nil) {
-        self.invitation = invitation
-    }
-
-    func viewDidAppear() {
-        if let pendingInvitation = invitation {
-            invitation = nil
-            stateSubject.value.input = pendingInvitation
-            hudSubject.send(.onAction(Localized.Ud.Search.cancel))
-
-            networkCancellable.removeAll()
-
-            networkMonitor.statusPublisher
-                .first { $0 == .available }
-                .eraseToAnyPublisher()
-                .flatMap { _ in
-                    self.waitForNodes(timeout: 5)
-                }.sink(receiveCompletion: {
-                    if case .failure(let error) = $0 {
-                        self.hudSubject.send(.error(.init(with: error)))
-                    }
-                }, receiveValue: {
-                    self.didStartSearching()
-                }).store(in: &networkCancellable)
-        }
-    }
+    hudController.show(.init(actionTitle: Localized.Ud.Search.cancel))
 
-    func didEnterInput(_ string: String) {
-        stateSubject.value.input = string
-    }
+    var content = stateSubject.value.input
 
-    func didPick(country: Country) {
-        stateSubject.value.country = country
+    if stateSubject.value.item == .phone {
+      content += stateSubject.value.country.code
     }
 
-    func didSelectItem(_ item: SearchSegmentedControl.Item) {
-        stateSubject.value.item = item
+    enum NodeRegistrationError: Error {
+      case unhealthyNet
+      case belowMinimum
     }
 
-    func didTapCancelSearch() {
-        searchCancellables.forEach { $0.cancel() }
-        searchCancellables.removeAll()
-        hudSubject.send(.none)
+    retry(max: 5, retryStrategy: .delay(seconds: 2)) { [weak self] in
+      guard let self = self else { return }
+
+      do {
+        let nrr = try self.messenger.cMix.get()!.getNodeRegistrationStatus()
+        if nrr.ratio < 0.8 { throw NodeRegistrationError.belowMinimum }
+      } catch {
+        throw NodeRegistrationError.unhealthyNet
+      }
+    }.finalCatch { [weak self] in
+      guard let self = self else { return }
+
+      if case .unhealthyNet = $0 as? NodeRegistrationError {
+        self.hudController.show(.init(content: "Network is not healthy yet, try again within the next minute or so."))
+      } else if case .belowMinimum = $0 as? NodeRegistrationError {
+        self.hudController.show(.init(content:"Node registration ratio is still below 80%, try again within the next minute or so."))
+      } else {
+        self.hudController.show(.init(error: $0))
+      }
+
+      return
     }
 
-    func didStartSearching() {
-        guard stateSubject.value.input.isEmpty == false else { return }
-
-        hudSubject.send(.onAction(Localized.Ud.Search.cancel))
-
-        var content = stateSubject.value.input
-
-        if stateSubject.value.item == .phone {
-            content += stateSubject.value.country.code
-        }
-
-        enum NodeRegistrationError: Error {
-            case unhealthyNet
-            case belowMinimum
-        }
+    var factType: FactType = .username
 
-        retry(max: 5, retryStrategy: .delay(seconds: 2)) { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                let nrr = try self.messenger.cMix.get()!.getNodeRegistrationStatus()
-                if nrr.ratio < 0.8 { throw NodeRegistrationError.belowMinimum }
-            } catch {
-                throw NodeRegistrationError.unhealthyNet
-            }
-        }.finalCatch { [weak self] in
-            guard let self = self else { return }
-
-            if case .unhealthyNet = $0 as? NodeRegistrationError {
-                self.hudSubject.send(.error(.init(content: "Network is not healthy yet, try again within the next minute or so.")))
-            } else if case .belowMinimum = $0 as? NodeRegistrationError {
-                self.hudSubject.send(.error(.init(content: "Node registration ratio is still below 80%, try again within the next minute or so.")))
-            } else {
-                self.hudSubject.send(.error(.init(with: $0)))
-            }
-
-            return
-        }
-
-        var factType: FactType = .username
-
-        if stateSubject.value.item == .phone {
-            factType = .phone
-        } else if stateSubject.value.item == .email {
-            factType = .email
-        }
+    if stateSubject.value.item == .phone {
+      factType = .phone
+    } else if stateSubject.value.item == .email {
+      factType = .email
+    }
 
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                let report = try SearchUD.live(
-                    params: .init(
-                        e2eId: self.messenger.e2e.get()!.getId(),
-                        udContact: self.messenger.ud.get()!.getContact(),
-                        facts: [.init(type: factType, value: content)]
-                    ),
-                    callback: .init(handle: {
-                        switch $0 {
-                        case .success(let results):
-                            self.hudSubject.send(.none)
-                            self.appendToLocalSearch(
-                                XXModels.Contact(
-                                    id: try! results.first!.getId(),
-                                    marshaled: results.first!.data,
-                                    username: try! results.first?.getFacts().first(where: { $0.type == .username })?.value,
-                                    email: try? results.first?.getFacts().first(where: { $0.type == .email })?.value,
-                                    phone: try? results.first?.getFacts().first(where: { $0.type == .phone })?.value,
-                                    nickname: nil,
-                                    photo: nil,
-                                    authStatus: .stranger,
-                                    isRecent: true,
-                                    isBlocked: false,
-                                    isBanned: false,
-                                    createdAt: Date()
-                                )
-                            )
-                        case .failure(let error):
-                            print(">>> SearchUD error: \(error.localizedDescription)")
-
-                            self.appendToLocalSearch(nil)
-                            self.hudSubject.send(.error(.init(with: error)))
-                        }
-                    })
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
+
+      do {
+        let report = try SearchUD.live(
+          params: .init(
+            e2eId: self.messenger.e2e.get()!.getId(),
+            udContact: self.messenger.ud.get()!.getContact(),
+            facts: [.init(type: factType, value: content)]
+          ),
+          callback: .init(handle: {
+            switch $0 {
+            case .success(let results):
+              self.hudController.dismiss()
+              self.appendToLocalSearch(
+                XXModels.Contact(
+                  id: try! results.first!.getId(),
+                  marshaled: results.first!.data,
+                  username: try! results.first?.getFacts().first(where: { $0.type == .username })?.value,
+                  email: try? results.first?.getFacts().first(where: { $0.type == .email })?.value,
+                  phone: try? results.first?.getFacts().first(where: { $0.type == .phone })?.value,
+                  nickname: nil,
+                  photo: nil,
+                  authStatus: .stranger,
+                  isRecent: true,
+                  isBlocked: false,
+                  isBanned: false,
+                  createdAt: Date()
                 )
+              )
+            case .failure(let error):
+              print(">>> SearchUD error: \(error.localizedDescription)")
 
-                print(">>> UDSearch.Report: \(report))")
-            } catch {
-                print(">>> UDSearch.Exception: \(error.localizedDescription)")
+              self.appendToLocalSearch(nil)
+              self.hudController.show(.init(error: error))
             }
-        }
-    }
-
-    func didTapResend(contact: XXModels.Contact) {
-        hudSubject.send(.on)
-
-        var contact = contact
-        contact.authStatus = .requesting
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
+          })
+        )
 
-            do {
-                try self.database.saveContact(contact)
+        print(">>> UDSearch.Report: \(report))")
+      } catch {
+        print(">>> UDSearch.Exception: \(error.localizedDescription)")
+      }
+    }
+  }
 
-                var includedFacts: [Fact] = []
-                let myFacts = try self.messenger.ud.get()!.getFacts()
+  func didTapResend(contact: XXModels.Contact) {
+    hudController.show()
 
-                if let fact = myFacts.get(.username) {
-                    includedFacts.append(fact)
-                }
+    var contact = contact
+    contact.authStatus = .requesting
 
-                if self.sharingEmail, let fact = myFacts.get(.email) {
-                    includedFacts.append(fact)
-                }
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
 
-                if self.sharingPhone, let fact = myFacts.get(.phone) {
-                    includedFacts.append(fact)
-                }
+      do {
+        try self.database.saveContact(contact)
 
-                let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
-                    partner: .live(contact.marshaled!),
-                    myFacts: includedFacts
-                )
+        var includedFacts: [Fact] = []
+        let myFacts = try self.messenger.ud.get()!.getFacts()
 
-                contact.authStatus = .requested
-                contact = try self.database.saveContact(contact)
-
-                self.hudSubject.send(.none)
-                self.presentSuccessToast(for: contact, resent: true)
-            } catch {
-                contact.authStatus = .requestFailed
-                _ = try? self.database.saveContact(contact)
-                self.hudSubject.send(.error(.init(with: error)))
-            }
+        if let fact = myFacts.get(.username) {
+          includedFacts.append(fact)
         }
-    }
 
-    func didTapRequest(contact: XXModels.Contact) {
-        hudSubject.send(.on)
+        if self.sharingEmail, let fact = myFacts.get(.email) {
+          includedFacts.append(fact)
+        }
 
-        var contact = contact
-        contact.nickname = contact.username
-        contact.authStatus = .requesting
+        if self.sharingPhone, let fact = myFacts.get(.phone) {
+          includedFacts.append(fact)
+        }
 
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
+        let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
+          partner: .live(contact.marshaled!),
+          myFacts: includedFacts
+        )
 
-            do {
-                try self.database.saveContact(contact)
+        contact.authStatus = .requested
+        contact = try self.database.saveContact(contact)
 
-                var includedFacts: [Fact] = []
-                let myFacts = try self.messenger.ud.get()!.getFacts()
+        self.hudController.dismiss()
+        self.presentSuccessToast(for: contact, resent: true)
+      } catch {
+        contact.authStatus = .requestFailed
+        _ = try? self.database.saveContact(contact)
+        self.hudController.show(.init(error: error))
+      }
+    }
+  }
 
-                if let fact = myFacts.get(.username) {
-                    includedFacts.append(fact)
-                }
+  func didTapRequest(contact: XXModels.Contact) {
+    hudController.show()
 
-                if self.sharingEmail, let fact = myFacts.get(.email) {
-                    includedFacts.append(fact)
-                }
+    var contact = contact
+    contact.nickname = contact.username
+    contact.authStatus = .requesting
 
-                if self.sharingPhone, let fact = myFacts.get(.phone) {
-                    includedFacts.append(fact)
-                }
+    backgroundScheduler.schedule { [weak self] in
+      guard let self = self else { return }
 
-                let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
-                    partner: .live(contact.marshaled!),
-                    myFacts: includedFacts
-                )
+      do {
+        try self.database.saveContact(contact)
 
-                contact.authStatus = .requested
-                contact = try self.database.saveContact(contact)
+        var includedFacts: [Fact] = []
+        let myFacts = try self.messenger.ud.get()!.getFacts()
 
-                self.hudSubject.send(.none)
-                self.successSubject.send(contact)
-                self.presentSuccessToast(for: contact, resent: false)
-            } catch {
-                contact.authStatus = .requestFailed
-                _ = try? self.database.saveContact(contact)
-                self.hudSubject.send(.error(.init(with: error)))
-            }
+        if let fact = myFacts.get(.username) {
+          includedFacts.append(fact)
         }
-    }
 
-    func didSet(nickname: String, for contact: XXModels.Contact) {
-        if var contact = try? database.fetchContacts(.init(id: [contact.id])).first {
-            contact.nickname = nickname
-            _ = try? database.saveContact(contact)
+        if self.sharingEmail, let fact = myFacts.get(.email) {
+          includedFacts.append(fact)
         }
-    }
 
-    private func appendToLocalSearch(_ user: XXModels.Contact?) {
-        var snapshot = SearchSnapshot()
-
-        if var user = user {
-            if let contact = try? database.fetchContacts(.init(id: [user.id])).first {
-                user.isBanned = contact.isBanned
-                user.isBlocked = contact.isBlocked
-                user.authStatus = contact.authStatus
-            }
-
-            if user.authStatus != .friend, !reportingStatus.isEnabled() {
-                snapshot.appendSections([.stranger])
-                snapshot.appendItems([.stranger(user)], toSection: .stranger)
-            } else if user.authStatus != .friend, reportingStatus.isEnabled(), !user.isBanned, !user.isBlocked {
-                snapshot.appendSections([.stranger])
-                snapshot.appendItems([.stranger(user)], toSection: .stranger)
-            }
+        if self.sharingPhone, let fact = myFacts.get(.phone) {
+          includedFacts.append(fact)
         }
 
-        let localsQuery = Contact.Query(
-            text: stateSubject.value.input,
-            authStatus: [.friend],
-            isBlocked: reportingStatus.isEnabled() ? false : nil,
-            isBanned: reportingStatus.isEnabled() ? false : nil
+        let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
+          partner: .live(contact.marshaled!),
+          myFacts: includedFacts
         )
 
-        if let locals = try? database.fetchContacts(localsQuery),
-           let localsWithoutMe = removeMyself(from: locals),
-           localsWithoutMe.isEmpty == false {
-            snapshot.appendSections([.connections])
-            snapshot.appendItems(
-                localsWithoutMe.map(SearchItem.connection),
-                toSection: .connections
-            )
-        }
-
-        stateSubject.value.snapshot = snapshot
+        contact.authStatus = .requested
+        contact = try self.database.saveContact(contact)
+
+        self.hudController.dismiss()
+        self.successSubject.send(contact)
+        self.presentSuccessToast(for: contact, resent: false)
+      } catch {
+        contact.authStatus = .requestFailed
+        _ = try? self.database.saveContact(contact)
+        self.hudController.show(.init(error: error))
+      }
     }
+  }
 
-    private func removeMyself(from collection: [XXModels.Contact]) -> [XXModels.Contact]? {
-        collection.filter { $0.id != myId }
+  func didSet(nickname: String, for contact: XXModels.Contact) {
+    if var contact = try? database.fetchContacts(.init(id: [contact.id])).first {
+      contact.nickname = nickname
+      _ = try? database.saveContact(contact)
     }
-
-    private func presentSuccessToast(for contact: XXModels.Contact, resent: Bool) {
-        let name = contact.nickname ?? contact.username
-        let sentTitle = Localized.Requests.Sent.Toast.sent(name ?? "")
-        let resentTitle = Localized.Requests.Sent.Toast.resent(name ?? "")
-
-        toastController.enqueueToast(model: .init(
-            title: resent ? resentTitle : sentTitle,
-            leftImage: Asset.sharedSuccess.image
-        ))
+  }
+
+  private func appendToLocalSearch(_ user: XXModels.Contact?) {
+    var snapshot = SearchSnapshot()
+
+    if var user = user {
+      if let contact = try? database.fetchContacts(.init(id: [user.id])).first {
+        user.isBanned = contact.isBanned
+        user.isBlocked = contact.isBlocked
+        user.authStatus = contact.authStatus
+      }
+
+      if user.authStatus != .friend, !reportingStatus.isEnabled() {
+        snapshot.appendSections([.stranger])
+        snapshot.appendItems([.stranger(user)], toSection: .stranger)
+      } else if user.authStatus != .friend, reportingStatus.isEnabled(), !user.isBanned, !user.isBlocked {
+        snapshot.appendSections([.stranger])
+        snapshot.appendItems([.stranger(user)], toSection: .stranger)
+      }
     }
 
-    private func waitForNodes(timeout: Int) -> AnyPublisher<Void, Error> {
-        Deferred {
-            Future { promise in
-                retry(max: timeout, retryStrategy: .delay(seconds: 1)) { [weak self] in
-                    guard let self = self else { return }
-                    _ = try self.messenger.cMix.get()!.getNodeRegistrationStatus()
-                    promise(.success(()))
-                }.finalCatch {
-                    promise(.failure($0))
-                }
-            }
-        }.eraseToAnyPublisher()
+    let localsQuery = Contact.Query(
+      text: stateSubject.value.input,
+      authStatus: [.friend],
+      isBlocked: reportingStatus.isEnabled() ? false : nil,
+      isBanned: reportingStatus.isEnabled() ? false : nil
+    )
+
+    if let locals = try? database.fetchContacts(localsQuery),
+       let localsWithoutMe = removeMyself(from: locals),
+       localsWithoutMe.isEmpty == false {
+      snapshot.appendSections([.connections])
+      snapshot.appendItems(
+        localsWithoutMe.map(SearchItem.connection),
+        toSection: .connections
+      )
     }
+
+    stateSubject.value.snapshot = snapshot
+  }
+
+  private func removeMyself(from collection: [XXModels.Contact]) -> [XXModels.Contact]? {
+    collection.filter { $0.id != myId }
+  }
+
+  private func presentSuccessToast(for contact: XXModels.Contact, resent: Bool) {
+    let name = contact.nickname ?? contact.username
+    let sentTitle = Localized.Requests.Sent.Toast.sent(name ?? "")
+    let resentTitle = Localized.Requests.Sent.Toast.resent(name ?? "")
+
+    toastController.enqueueToast(model: .init(
+      title: resent ? resentTitle : sentTitle,
+      leftImage: Asset.sharedSuccess.image
+    ))
+  }
+
+  private func waitForNodes(timeout: Int) -> AnyPublisher<Void, Error> {
+    Deferred {
+      Future { promise in
+        retry(max: timeout, retryStrategy: .delay(seconds: 1)) { [weak self] in
+          guard let self = self else { return }
+          _ = try self.messenger.cMix.get()!.getNodeRegistrationStatus()
+          promise(.success(()))
+        }.finalCatch {
+          promise(.failure($0))
+        }
+      }
+    }.eraseToAnyPublisher()
+  }
 }
diff --git a/Sources/SettingsFeature/Controllers/AccountDeleteController.swift b/Sources/SettingsFeature/Controllers/AccountDeleteController.swift
index 0f583b42aae6e0cc7fccbe2312cccd00e3e10403..7b23ea8008ed06973c602b2dd4e40653e38e3f4c 100644
--- a/Sources/SettingsFeature/Controllers/AccountDeleteController.swift
+++ b/Sources/SettingsFeature/Controllers/AccountDeleteController.swift
@@ -1,17 +1,15 @@
-import HUD
 import UIKit
-import DrawerFeature
 import Shared
 import Combine
 import Defaults
+import DrawerFeature
 import ScrollViewController
 import DependencyInjection
 
 public final class AccountDeleteController: UIViewController {
     @KeyObject(.username, defaultValue: "") var username: String
 
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: SettingsCoordinating
+    @Dependency var coordinator: SettingsCoordinating
 
     lazy private var screenView = AccountDeleteView()
     lazy private var scrollViewController = ScrollViewController()
@@ -51,11 +49,6 @@ public final class AccountDeleteController: UIViewController {
     }
 
     private func setupBindings() {
-        viewModel.hudPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [hud] in hud.update(with: $0) }
-            .store(in: &cancellables)
-
         screenView.cancelButton.publisher(for: .touchUpInside)
             .receive(on: DispatchQueue.main)
             .sink { [unowned self] in dismiss(animated: true) }
diff --git a/Sources/SettingsFeature/Controllers/SettingsController.swift b/Sources/SettingsFeature/Controllers/SettingsController.swift
index af68cf87bf8803e6282b23bc9340aa25404c63cd..de85af7cfb09a459cc8805e35a784cf035035c10 100644
--- a/Sources/SettingsFeature/Controllers/SettingsController.swift
+++ b/Sources/SettingsFeature/Controllers/SettingsController.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Combine
@@ -7,7 +6,6 @@ import DependencyInjection
 import ScrollViewController
 
 public final class SettingsController: UIViewController {
-  @Dependency var hud: HUD
   @Dependency var barStylist: StatusBarStylist
   @Dependency var coordinator: SettingsCoordinating
 
@@ -92,11 +90,6 @@ public final class SettingsController: UIViewController {
   }
 
   private func setupBindings() {
-    viewModel.hud
-      .receive(on: DispatchQueue.main)
-      .sink { [hud] in hud.update(with: $0) }
-      .store(in: &cancellables)
-
     screenView.inAppNotifications.switcherView
       .publisher(for: .valueChanged)
       .sink { [weak viewModel] in viewModel?.didToggleInAppNotifications() }
diff --git a/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift b/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift
index fa7592a96e4bc61a26b4ba011f88e19331d33f74..9065c744e6275cb46317db77e5f93a23e34301bd 100644
--- a/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift
+++ b/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift
@@ -1,81 +1,77 @@
-import HUD
+import Shared
+import Retry
 import Models
 import Combine
 import Defaults
 import Keychain
+import XXModels
 import XXClient
 import Foundation
 import XXMessengerClient
 import DependencyInjection
-import Retry
-import XXModels
 
 final class AccountDeleteViewModel {
-    @Dependency var messenger: Messenger
-    @Dependency var keychain: KeychainHandling
-    @Dependency var database: Database
-
-    @KeyObject(.username, defaultValue: nil) var username: String?
-
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
-
-    private var isCurrentlyDeleting = false
-    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
-
-    func didTapDelete() {
-        guard isCurrentlyDeleting == false else { return }
-        isCurrentlyDeleting = true
-
-        hudSubject.send(.on)
-
-        do {
-            print(">>> try self.cleanUD()")
-            try cleanUD()
-
-            print(">>> try self.messenger.destroy()")
-            try messenger.destroy()
-
-            print(">>> try self.keychain.clear()")
-            try keychain.clear()
-
-            print(">>> try database.drop()")
-            try database.drop()
-
-            print(">>> try self.deleteDatabase()")
-            try deleteDatabase()
-
-            UserDefaults.resetStandardUserDefaults()
-            UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
-            UserDefaults.standard.synchronize()
-
-            hudSubject.send(.error(.init(
-                content: "Now kill the app and re-open",
-                title: "Account deleted",
-                dismissable: false
-            )))
-        } catch {
-            DispatchQueue.main.async { [weak self] in
-                guard let self = self else { return }
-                self.hudSubject.send(.error(.init(with: error)))
-            }
-        }
-    }
-
-    private func cleanUD() throws {
-        print(">>> Deleting my username (\(username ?? "NO_USERNAME")) from ud")
-        try messenger.ud.get()!.permanentDeleteAccount(username: .init(type: .username, value: username!))
-    }
-
-    private func deleteDatabase() throws {
-        print(">>> Deleting database...")
-
-        let dbPath = FileManager.default
-            .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
-            .appendingPathComponent("xxm_database")
-            .appendingPathExtension("sqlite").path
-
-        try FileManager.default.removeItem(atPath: dbPath)
+  @Dependency var database: Database
+  @Dependency var messenger: Messenger
+  @Dependency var keychain: KeychainHandling
+  @Dependency var hudController: HUDController
+  
+  @KeyObject(.username, defaultValue: nil) var username: String?
+
+  private var isCurrentlyDeleting = false
+  
+  func didTapDelete() {
+    guard isCurrentlyDeleting == false else { return }
+    isCurrentlyDeleting = true
+
+    hudController.show()
+    
+    do {
+      print(">>> try self.cleanUD()")
+      try cleanUD()
+      
+      print(">>> try self.messenger.destroy()")
+      try messenger.destroy()
+      
+      print(">>> try self.keychain.clear()")
+      try keychain.clear()
+      
+      print(">>> try database.drop()")
+      try database.drop()
+      
+      print(">>> try self.deleteDatabase()")
+      try deleteDatabase()
+      
+      UserDefaults.resetStandardUserDefaults()
+      UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
+      UserDefaults.standard.synchronize()
+      
+      hudController.show(.init(
+        title: "Account deleted",
+        content: "Now kill the app and re-open",
+        isDismissable: false
+      ))
+    } catch {
+      DispatchQueue.main.async { [weak self] in
+        guard let self = self else { return }
+        self.hudController.show(.init(error: error))
+      }
     }
+  }
+  
+  private func cleanUD() throws {
+    print(">>> Deleting my username (\(username ?? "NO_USERNAME")) from ud")
+    try messenger.ud.get()!.permanentDeleteAccount(username: .init(type: .username, value: username!))
+  }
+  
+  private func deleteDatabase() throws {
+    print(">>> Deleting database...")
+    
+    let dbPath = FileManager.default
+      .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
+      .appendingPathComponent("xxm_database")
+      .appendingPathExtension("sqlite").path
+    
+    try FileManager.default.removeItem(atPath: dbPath)
+  }
 }
diff --git a/Sources/SettingsFeature/ViewModels/SettingsViewModel.swift b/Sources/SettingsFeature/ViewModels/SettingsViewModel.swift
index e157bce23751995b42b915fd45e965fed835bf97..3bea57bea7fd39113ad41e087d41883b639a427f 100644
--- a/Sources/SettingsFeature/ViewModels/SettingsViewModel.swift
+++ b/Sources/SettingsFeature/ViewModels/SettingsViewModel.swift
@@ -1,4 +1,3 @@
-import HUD
 import UIKit
 import Shared
 import Combine
@@ -12,144 +11,140 @@ import CombineSchedulers
 import DependencyInjection
 
 struct SettingsViewState: Equatable {
-    var isHideActiveApps: Bool = false
-    var isPushNotification: Bool = false
-    var isIcognitoKeyboard: Bool = false
-    var isInAppNotification: Bool = false
-    var isBiometricsEnabled: Bool = false
-    var isBiometricsPossible: Bool = false
-    var isDummyTrafficOn = false
+  var isHideActiveApps: Bool = false
+  var isPushNotification: Bool = false
+  var isIcognitoKeyboard: Bool = false
+  var isInAppNotification: Bool = false
+  var isBiometricsEnabled: Bool = false
+  var isBiometricsPossible: Bool = false
+  var isDummyTrafficOn = false
 }
 
 final class SettingsViewModel {
-    @Dependency var messenger: Messenger
-    @Dependency var pushHandler: PushHandling
-    @Dependency var permissions: PermissionHandling
-    @Dependency var dummyTrafficManager: DummyTraffic
-
-    @KeyObject(.biometrics, defaultValue: false) var biometrics
-    @KeyObject(.hideAppList, defaultValue: false) var hideAppList
-    @KeyObject(.dummyTrafficOn, defaultValue: false) var dummyTrafficOn
-    @KeyObject(.icognitoKeyboard, defaultValue: false) var icognitoKeyboard
-    @KeyObject(.pushNotifications, defaultValue: false) var pushNotifications
-    @KeyObject(.inappnotifications, defaultValue: true) var inAppNotifications
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
-
-    var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-
-    var state: AnyPublisher<SettingsViewState, Never> { stateRelay.eraseToAnyPublisher() }
-    private let stateRelay = CurrentValueSubject<SettingsViewState, Never>(.init())
-
-    func loadCachedSettings() {
-        stateRelay.value.isHideActiveApps = hideAppList
-        stateRelay.value.isBiometricsEnabled = biometrics
-        stateRelay.value.isIcognitoKeyboard = icognitoKeyboard
-        stateRelay.value.isPushNotification = pushNotifications
-        stateRelay.value.isInAppNotification = inAppNotifications
-        stateRelay.value.isBiometricsPossible = permissions.isBiometricsAvailable
-        stateRelay.value.isDummyTrafficOn = dummyTrafficManager.getStatus()
-    }
-
-    func didToggleBiometrics() {
-        biometricAuthentication(enable: !biometrics)
-    }
-
-    func didToggleInAppNotifications() {
-        inAppNotifications.toggle()
-        stateRelay.value.isInAppNotification.toggle()
-    }
-
-    func didTogglePushNotifications() {
-        pushNotifications(enable: !pushNotifications)
-    }
-
-    func didToggleDummyTraffic() {
-        let currently = dummyTrafficManager.getStatus()
-        try! dummyTrafficManager.setStatus(!currently)
-        stateRelay.value.isDummyTrafficOn = !currently
-        dummyTrafficOn = stateRelay.value.isDummyTrafficOn
-    }
-
-    func didToggleHideActiveApps() {
-        hideAppList.toggle()
-        stateRelay.value.isHideActiveApps.toggle()
-    }
-
-    func didToggleIcognitoKeyboard() {
-        icognitoKeyboard.toggle()
-        stateRelay.value.isIcognitoKeyboard.toggle()
+  @Dependency var messenger: Messenger
+  @Dependency var pushHandler: PushHandling
+  @Dependency var hudController: HUDController
+  @Dependency var permissions: PermissionHandling
+  @Dependency var dummyTrafficManager: DummyTraffic
+  
+  @KeyObject(.biometrics, defaultValue: false) var biometrics
+  @KeyObject(.hideAppList, defaultValue: false) var hideAppList
+  @KeyObject(.dummyTrafficOn, defaultValue: false) var dummyTrafficOn
+  @KeyObject(.icognitoKeyboard, defaultValue: false) var icognitoKeyboard
+  @KeyObject(.pushNotifications, defaultValue: false) var pushNotifications
+  @KeyObject(.inappnotifications, defaultValue: true) var inAppNotifications
+  
+  var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
+  
+  var state: AnyPublisher<SettingsViewState, Never> { stateRelay.eraseToAnyPublisher() }
+  private let stateRelay = CurrentValueSubject<SettingsViewState, Never>(.init())
+  
+  func loadCachedSettings() {
+    stateRelay.value.isHideActiveApps = hideAppList
+    stateRelay.value.isBiometricsEnabled = biometrics
+    stateRelay.value.isIcognitoKeyboard = icognitoKeyboard
+    stateRelay.value.isPushNotification = pushNotifications
+    stateRelay.value.isInAppNotification = inAppNotifications
+    stateRelay.value.isBiometricsPossible = permissions.isBiometricsAvailable
+    stateRelay.value.isDummyTrafficOn = dummyTrafficManager.getStatus()
+  }
+  
+  func didToggleBiometrics() {
+    biometricAuthentication(enable: !biometrics)
+  }
+  
+  func didToggleInAppNotifications() {
+    inAppNotifications.toggle()
+    stateRelay.value.isInAppNotification.toggle()
+  }
+  
+  func didTogglePushNotifications() {
+    pushNotifications(enable: !pushNotifications)
+  }
+  
+  func didToggleDummyTraffic() {
+    let currently = dummyTrafficManager.getStatus()
+    try! dummyTrafficManager.setStatus(!currently)
+    stateRelay.value.isDummyTrafficOn = !currently
+    dummyTrafficOn = stateRelay.value.isDummyTrafficOn
+  }
+  
+  func didToggleHideActiveApps() {
+    hideAppList.toggle()
+    stateRelay.value.isHideActiveApps.toggle()
+  }
+  
+  func didToggleIcognitoKeyboard() {
+    icognitoKeyboard.toggle()
+    stateRelay.value.isIcognitoKeyboard.toggle()
+  }
+
+  private func biometricAuthentication(enable: Bool) {
+    stateRelay.value.isBiometricsEnabled = enable
+    
+    guard enable == true else {
+      biometrics = false
+      stateRelay.value.isBiometricsEnabled = false
+      return
     }
-
-    // MARK: Private
-
-    private func biometricAuthentication(enable: Bool) {
-        stateRelay.value.isBiometricsEnabled = enable
-
-        guard enable == true else {
-            biometrics = false
-            stateRelay.value.isBiometricsEnabled = false
-            return
-        }
-
-        permissions.requestBiometrics { [weak self] result in
-            guard let self = self else { return }
-
-            switch result {
-            case .success(let granted):
-                if granted {
-                    self.biometrics = true
-                    self.stateRelay.value.isBiometricsEnabled = true
-                } else {
-                    self.biometrics = false
-                    self.stateRelay.value.isBiometricsEnabled = false
-                }
-            case .failure:
-                self.biometrics = false
-                self.stateRelay.value.isBiometricsEnabled = false
-            }
+    
+    permissions.requestBiometrics { [weak self] result in
+      guard let self = self else { return }
+      
+      switch result {
+      case .success(let granted):
+        if granted {
+          self.biometrics = true
+          self.stateRelay.value.isBiometricsEnabled = true
+        } else {
+          self.biometrics = false
+          self.stateRelay.value.isBiometricsEnabled = false
         }
+      case .failure:
+        self.biometrics = false
+        self.stateRelay.value.isBiometricsEnabled = false
+      }
     }
-
-    private func pushNotifications(enable: Bool) {
-        hudRelay.send(.on)
-
-        if enable == true {
-            pushHandler.requestAuthorization { [weak self] result in
-                guard let self = self else { return }
-
-                switch result {
-                case .success(let granted):
-                    self.pushNotifications = granted
-                    self.stateRelay.value.isPushNotification = granted
-                    if granted { DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() }}
-                    self.hudRelay.send(.none)
-
-                case .failure(let error):
-                    self.hudRelay.send(.error(.init(with: error)))
-                    self.pushNotifications = false
-                    self.stateRelay.value.isPushNotification = false
-                }
-            }
-        } else {
-            backgroundScheduler.schedule { [weak self] in
-                guard let self = self else { return }
-
-                do {
-                    try UnregisterForNotifications.live(
-                        e2eId: self.messenger.e2e.get()!.getId()
-                    )
-
-                    self.hudRelay.send(.none)
-                } catch {
-                    let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
-                    self.hudRelay.send(.error(.init(content: xxError)))
-                }
-
-                self.pushNotifications = false
-                self.stateRelay.value.isPushNotification = false
-            }
+  }
+  
+  private func pushNotifications(enable: Bool) {
+    hudController.show()
+    
+    if enable == true {
+      pushHandler.requestAuthorization { [weak self] result in
+        guard let self = self else { return }
+        
+        switch result {
+        case .success(let granted):
+          self.pushNotifications = granted
+          self.stateRelay.value.isPushNotification = granted
+          if granted { DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() }}
+          self.hudController.dismiss()
+          
+        case .failure(let error):
+          self.hudController.show(.init(error: error))
+          self.pushNotifications = false
+          self.stateRelay.value.isPushNotification = false
+        }
+      }
+    } else {
+      backgroundScheduler.schedule { [weak self] in
+        guard let self = self else { return }
+        
+        do {
+          try UnregisterForNotifications.live(
+            e2eId: self.messenger.e2e.get()!.getId()
+          )
+          
+          self.hudController.dismiss()
+        } catch {
+          let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+          self.hudController.show(.init(content: xxError))
         }
+        
+        self.pushNotifications = false
+        self.stateRelay.value.isPushNotification = false
+      }
     }
+  }
 }
diff --git a/Sources/Shared/Controllers/HUDController.swift b/Sources/Shared/Controllers/HUDController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..1d281a39ac906fc4f54a32076a99d41909e31ab0
--- /dev/null
+++ b/Sources/Shared/Controllers/HUDController.swift
@@ -0,0 +1,19 @@
+import Combine
+
+public final class HUDController {
+  var modelPublisher: AnyPublisher<HUDModel?, Never> {
+    modelSubject.eraseToAnyPublisher()
+  }
+
+  private let modelSubject = PassthroughSubject<HUDModel?, Never>()
+
+  public init() {}
+
+  public func dismiss() {
+    modelSubject.send(nil)
+  }
+
+  public func show(_ model: HUDModel? = nil) {
+    modelSubject.send(model)
+  }
+}
diff --git a/Sources/Shared/Controllers/RootViewController.swift b/Sources/Shared/Controllers/RootViewController.swift
index 3e1fcac17b6a17905d8450bf5a5d264326e9a990..383ef01a2afb32dea0aebdf64fbfa1fe4b79d387 100644
--- a/Sources/Shared/Controllers/RootViewController.swift
+++ b/Sources/Shared/Controllers/RootViewController.swift
@@ -4,12 +4,14 @@ import DependencyInjection
 
 public final class RootViewController: UIViewController {
   @Dependency var barStylist: StatusBarStylist
+  @Dependency var hudDispatcher: HUDController
   @Dependency var toastDispatcher: ToastController
 
-  var toastTimer: Timer?
   let content: UIViewController?
-  let toastTopPadding: CGFloat = 10
   var cancellables = Set<AnyCancellable>()
+
+  var toastTimer: Timer?
+  let toastTopPadding: CGFloat = 10
   var topToastConstraint: NSLayoutConstraint?
 
   public init(_ content: UIViewController?) {
@@ -45,15 +47,31 @@ public final class RootViewController: UIViewController {
         }
       }.store(in: &cancellables)
 
-    toastDispatcher.currentToast
+    toastDispatcher
+      .currentToast
       .receive(on: DispatchQueue.main)
       .sink { [unowned self] model in
         let toastView = ToastView(model: model)
         add(toastView: toastView)
         present(toastView: toastView)
       }.store(in: &cancellables)
+
+    hudDispatcher
+      .modelPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] model in
+        guard model != nil else {
+          // REMOVE FROM SUPERVIEW
+          return
+        }
+        // ADD TO SUPERVIEW
+      }.store(in: &cancellables)
   }
+}
+
+// MARK: - Toaster
 
+extension RootViewController {
   @objc private func didPanToast(_ sender: UIPanGestureRecognizer) {
     guard let toastView = sender.view else { return }
 
@@ -144,3 +162,127 @@ public final class RootViewController: UIViewController {
     }
   }
 }
+
+// MARK: - HUD
+
+extension RootViewController {
+  //  private func showWindow() {
+  //    if let animation = animation {
+  //      window?.addSubview(animation)
+  //      animation.setColor(.white)
+  //      animation.snp.makeConstraints { $0.center.equalToSuperview() }
+  //    }
+  //
+  //    if let titleLabel = titleLabel {
+  //      window?.addSubview(titleLabel)
+  //      titleLabel.textAlignment = .center
+  //      titleLabel.numberOfLines = 0
+  //      titleLabel.snp.makeConstraints { make in
+  //        make.left.equalToSuperview().offset(18)
+  //        make.center.equalToSuperview().offset(50)
+  //        make.right.equalToSuperview().offset(-18)
+  //      }
+  //    }
+  //
+  //    if let actionButton = actionButton {
+  //      window?.addSubview(actionButton)
+  //      actionButton.snp.makeConstraints {
+  //        $0.left.equalToSuperview().offset(18)
+  //        $0.right.equalToSuperview().offset(-18)
+  //        $0.bottom.equalToSuperview().offset(-50)
+  //      }
+  //    }
+  //
+  //    if let errorView = errorView {
+  //      window?.addSubview(errorView)
+  //      errorView.snp.makeConstraints { make in
+  //        make.left.equalToSuperview().offset(18)
+  //        make.center.equalToSuperview()
+  //        make.right.equalToSuperview().offset(-18)
+  //      }
+  //
+  //      errorView.button
+  //        .publisher(for: .touchUpInside)
+  //        .receive(on: DispatchQueue.main)
+  //        .sink { [unowned self] in hideWindow() }
+  //        .store(in: &cancellables)
+  //    }
+  //
+  //    window?.alpha = 0.0
+  //    window?.makeKeyAndVisible()
+  //
+  //    UIView.animate(withDuration: 0.3) { self.window?.alpha = 1.0 }
+  //  }
+  //
+  //  private func hideWindow() {
+  //    UIView.animate(withDuration: 0.3) {
+  //      self.window?.alpha = 0.0
+  //    } completion: { _ in
+  //      self.cancellables.removeAll()
+  //      self.errorView = nil
+  //      self.animation = nil
+  //      self.actionButton = nil
+  //      self.titleLabel = nil
+  //      self.window = nil
+  //    }
+  //  }
+
+
+  //    if statusSubject.value.isPresented == true && status.isPresented == true {
+  //      self.errorView = nil
+  //      self.animation = nil
+  //      self.window = nil
+  //      self.actionButton = nil
+  //      self.titleLabel = nil
+  //
+  //      switch status {
+  //      case .on:
+  //        animation = DotAnimation()
+  //
+  //      case .onTitle(let text):
+  //        animation = DotAnimation()
+  //        titleLabel = UILabel()
+  //        titleLabel!.text = text
+  //
+  //      case .onAction(let title):
+  //        animation = DotAnimation()
+  //        actionButton = CapsuleButton()
+  //        actionButton!.set(style: .seeThroughWhite, title: title)
+  //
+  //      case .error(let error):
+  //        errorView = ErrorView(with: error)
+  //      case .none:
+  //        break
+  //      }
+  //
+  //      showWindow()
+  //    }
+
+  //    if statusSubject.value.isPresented == false && status.isPresented == true {
+  //        switch status {
+  //        case .on:
+  //          animation = DotAnimation()
+  //
+  //        case .onTitle(let text):
+  //          animation = DotAnimation()
+  //          titleLabel = UILabel()
+  //          titleLabel!.text = text
+  //
+  //        case .onAction(let title):
+  //          animation = DotAnimation()
+  //          actionButton = CapsuleButton()
+  //          actionButton!.set(style: .seeThroughWhite, title: title)
+  //
+  //        case .error(let error):
+  //          errorView = ErrorView(with: error)
+  //        case .none:
+  //          break
+  //        }
+  //
+  //        showWindow()
+  //    }
+
+  //    if statusSubject.value.isPresented == true && status.isPresented == false {
+  //        hideWindow()
+  //    }
+}
diff --git a/Sources/Shared/Models/HUDModel.swift b/Sources/Shared/Models/HUDModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d9e58e4d2b1170fa20dd224ffca265f72264aa8b
--- /dev/null
+++ b/Sources/Shared/Models/HUDModel.swift
@@ -0,0 +1,62 @@
+import UIKit
+
+public struct HUDModel {
+  var title: String?
+  var content: String?
+  var actionTitle: String?
+  var isDismissable: Bool
+  var animationColor: UIColor?
+  var onTapClosure: (() -> Void)?
+
+  public init(
+    title: String? = nil,
+    content: String? = nil,
+    actionTitle: String? = nil,
+    isDismissable: Bool = true,
+    animationColor: UIColor? = nil,
+    onTapClosure: (() -> Void)? = nil
+  ) {
+    self.title = title
+    self.content = content
+    self.actionTitle = actionTitle
+    self.isDismissable = isDismissable
+    self.onTapClosure = onTapClosure
+    self.animationColor = animationColor
+  }
+
+  public init(
+    error: Error,
+    isDismissable: Bool = true
+  ) {
+    self.isDismissable = isDismissable
+    self.title = Localized.Hud.Error.title
+    self.content = error.localizedDescription
+    self.actionTitle = Localized.Hud.Error.action
+  }
+}
+
+//public struct HUDError: Equatable {
+//  var title: String
+//  var content: String
+//  var buttonTitle: String
+//  var dismissable: Bool
+//
+//  public init(
+//    content: String,
+//    title: String = Localized.Hud.Error.title,
+//    buttonTitle: String = Localized.Hud.Error.action,
+//    dismissable: Bool = true
+//  ) {
+//    self.title = title
+//    self.content = content
+//    self.buttonTitle = buttonTitle
+//    self.dismissable = dismissable
+//  }
+//
+//  public init(with error: Error) {
+//    self.title = Localized.Hud.Error.title
+//    self.buttonTitle = Localized.Hud.Error.action
+//    self.content = error.localizedDescription
+//    self.dismissable = true
+//  }
+//}
diff --git a/Sources/Shared/Controllers/ToastModel.swift b/Sources/Shared/Models/ToastModel.swift
similarity index 100%
rename from Sources/Shared/Controllers/ToastModel.swift
rename to Sources/Shared/Models/ToastModel.swift
diff --git a/Sources/Shared/Views/DotAnimation.swift b/Sources/Shared/Views/DotAnimation.swift
index bfefe3c41d1e3ffdef01e6446e52b2ab20b175f9..bcf182b9180b7d4cfc5ef4e511083ca97727a7e6 100644
--- a/Sources/Shared/Views/DotAnimation.swift
+++ b/Sources/Shared/Views/DotAnimation.swift
@@ -1,100 +1,72 @@
 import UIKit
-import SnapKit
 
 public final class DotAnimation: UIView {
-    // MARK: UI
-
-    let leftDot = UIView()
-    let middleDot = UIView()
-    let rightDot = UIView()
-
-    // MARK: Properties
-
-    var leftInvert = false
-    var middleInvert = false
-    var rightInvert = false
-
-    var leftValue: CGFloat = 20
-    var middleValue: CGFloat = 45
-    var rightValue: CGFloat = 70
-
-    var displayLink: CADisplayLink?
-
-    // MARK: Lifecycle
-
-    public init() {
-        super.init(frame: .zero)
-        setup()
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    // MARK: Public
-
-    func setColor(_ color: UIColor = Asset.brandPrimary.color) {
-        leftDot.backgroundColor = color
-        middleDot.backgroundColor = color
-        rightDot.backgroundColor = color
+  let leftDot = UIView()
+  let rightDot = UIView()
+  let middleDot = UIView()
+  var displayLink: CADisplayLink?
+
+  var leftInvert = false
+  var rightInvert = false
+  var middleInvert = false
+  var leftValue: CGFloat = 20
+  var rightValue: CGFloat = 70
+  var middleValue: CGFloat = 45
+
+  public init() {
+    super.init(frame: .zero)
+    leftDot.layer.cornerRadius = 7.5
+    middleDot.layer.cornerRadius = 7.5
+    rightDot.layer.cornerRadius = 7.5
+
+    setColor()
+
+    addSubview(leftDot)
+    addSubview(middleDot)
+    addSubview(rightDot)
+
+    leftDot.snp.makeConstraints {
+      $0.centerY.equalTo(middleDot)
+      $0.right.equalTo(middleDot.snp.left).offset(-5)
+      $0.width.height.equalTo(15)
     }
 
-    // MARK: Private
-
-    private func setup() {
-        setupCornerRadius()
-        setColor()
-        addSubviews()
-        setupConstraints()
-
-        displayLink = CADisplayLink(target: self, selector: #selector(handleAnimations))
-        displayLink!.add(to: RunLoop.main, forMode: .default)
+    middleDot.snp.makeConstraints {
+      $0.center.equalToSuperview()
+      $0.width.height.equalTo(15)
     }
 
-    private func setupCornerRadius() {
-        leftDot.layer.cornerRadius = 4.5
-        middleDot.layer.cornerRadius = 4.5
-        rightDot.layer.cornerRadius = 4.5
+    rightDot.snp.makeConstraints {
+      $0.centerY.equalTo(middleDot)
+      $0.left.equalTo(middleDot.snp.right).offset(5)
+      $0.width.height.equalTo(15)
     }
 
-    private func addSubviews() {
-        addSubview(leftDot)
-        addSubview(middleDot)
-        addSubview(rightDot)
-    }
+    displayLink = CADisplayLink(target: self, selector: #selector(handleAnimations))
+    displayLink!.add(to: RunLoop.main, forMode: .default)
+  }
 
-    private func setupConstraints() {
-        leftDot.snp.makeConstraints { make in
-            make.centerY.equalTo(middleDot)
-            make.right.equalTo(middleDot.snp.left).offset(-2)
-            make.width.height.equalTo(9)
-        }
+  required init?(coder: NSCoder) { nil }
 
-        middleDot.snp.makeConstraints { make in
-            make.center.equalToSuperview()
-            make.width.height.equalTo(9)
-        }
+  func setColor(_ color: UIColor = Asset.brandPrimary.color) {
+    leftDot.backgroundColor = color
+    middleDot.backgroundColor = color
+    rightDot.backgroundColor = color
+  }
 
-        rightDot.snp.makeConstraints { make in
-            make.centerY.equalTo(middleDot)
-            make.left.equalTo(middleDot.snp.right).offset(2)
-            make.width.height.equalTo(9)
-        }
-    }
-
-    // MARK: Selectors
+  @objc private func handleAnimations() {
+    let factor: CGFloat = 70
 
-    @objc private func handleAnimations() {
-        let factor: CGFloat = 70
+    leftInvert ? (leftValue -= 1) : (leftValue += 1)
+    middleInvert ? (middleValue -= 1) : (middleValue += 1)
+    rightInvert ? (rightValue -= 1) : (rightValue += 1)
 
-        leftInvert ? (leftValue -= 1) : (leftValue += 1)
-        middleInvert ? (middleValue -= 1) : (middleValue += 1)
-        rightInvert ? (rightValue -= 1) : (rightValue += 1)
+    leftDot.layer.transform = CATransform3DMakeScale(leftValue/factor, leftValue/factor, 1)
+    middleDot.layer.transform = CATransform3DMakeScale(middleValue/factor, middleValue/factor, 1)
+    rightDot.layer.transform = CATransform3DMakeScale(rightValue/factor, rightValue/factor, 1)
 
-        leftDot.layer.transform = CATransform3DMakeScale(leftValue/factor, leftValue/factor, 1)
-        middleDot.layer.transform = CATransform3DMakeScale(middleValue/factor, middleValue/factor, 1)
-        rightDot.layer.transform = CATransform3DMakeScale(rightValue/factor, rightValue/factor, 1)
-
-        if leftValue > factor || leftValue < 10 { leftInvert.toggle() }
-        if middleValue > factor || middleValue < 10 { middleInvert.toggle() }
-        if rightValue > factor || rightValue < 10 { rightInvert.toggle() }
-    }
+    if leftValue > factor || leftValue < 10 { leftInvert.toggle() }
+    if middleValue > factor || middleValue < 10 { middleInvert.toggle() }
+    if rightValue > factor || rightValue < 10 { rightInvert.toggle() }
+  }
 }
diff --git a/Sources/Shared/Views/ErrorView.swift b/Sources/Shared/Views/ErrorView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..e0206ac765d80ce542fbbdd4c821ab45a2189376
--- /dev/null
+++ b/Sources/Shared/Views/ErrorView.swift
@@ -0,0 +1,57 @@
+//import UIKit
+//import SnapKit
+//
+//final class ErrorView: UIView {
+//    let title = UILabel()
+//    let content = UILabel()
+//    let stack = UIStackView()
+//    let button = CapsuleButton()
+//
+//    init(with model: HUDError) {
+//        super.init(frame: .zero)
+//        setup(with: model)
+//    }
+//
+//    required init?(coder: NSCoder) { nil }
+//
+//    private func setup(with model: HUDError) {
+//        layer.cornerRadius = 6
+//        backgroundColor = Asset.neutralWhite.color
+//
+//        title.text = model.title
+//        title.textColor = Asset.neutralBody.color
+//        title.font = Fonts.Mulish.bold.font(size: 35.0)
+//        title.textAlignment = .center
+//        title.numberOfLines = 0
+//
+//        content.text = model.content
+//        content.textColor = Asset.neutralBody.color
+//        content.numberOfLines = 0
+//        content.font = Fonts.Mulish.regular.font(size: 14.0)
+//        content.textAlignment = .center
+//
+//        button.setTitle(model.buttonTitle, for: .normal)
+//        button.setStyle(.brandColored)
+//
+//        stack.axis = .vertical
+//
+//        stack.addArrangedSubview(title)
+//        stack.addArrangedSubview(content)
+//
+//        if model.dismissable {
+//            stack.addArrangedSubview(button)
+//        }
+//
+//        stack.setCustomSpacing(25, after: title)
+//        stack.setCustomSpacing(59, after: content)
+//
+//        addSubview(stack)
+//
+//        stack.snp.makeConstraints { make in
+//            make.top.equalToSuperview().offset(60)
+//            make.left.equalToSuperview().offset(57)
+//            make.right.equalToSuperview().offset(-57)
+//            make.bottom.equalToSuperview().offset(-35)
+//        }
+//    }
+//}
diff --git a/Sources/Shared/Views/HUDView.swift b/Sources/Shared/Views/HUDView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..df54b7d55b770ae39c727710e7deed05e038e5a7
--- /dev/null
+++ b/Sources/Shared/Views/HUDView.swift
@@ -0,0 +1,33 @@
+import UIKit
+import Combine
+
+final class HUDView: UIView {
+  let titleLabel = UILabel()
+  let actionButton = CapsuleButton()
+  let animationView = DotAnimation()
+  var cancellables = Set<AnyCancellable>()
+
+  init(model: HUDModel) {
+    super.init(frame: .zero)
+
+    titleLabel.textColor = Asset.neutralWhite.color
+    backgroundColor =  Asset.neutralDark.color.withAlphaComponent(0.8)
+
+    if let color = model.animationColor {
+      animationView.setColor(color)
+    }
+
+    if let actionTitle = model.actionTitle {
+      actionButton.set(
+        style: .seeThroughWhite,
+        title: actionTitle
+      )
+      actionButton
+        .publisher(for: .touchUpInside)
+        .sink { model.onTapClosure?() }
+        .store(in: &cancellables)
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+}