diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/AppCore.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/AppCore.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..b5c156a91b7402c846b359ae58ee5a2841f263a7
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/AppCore.xcscheme
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1410"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "AppCore"
+               BuildableName = "AppCore"
+               BlueprintName = "AppCore"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AppCore"
+            BuildableName = "AppCore"
+            BlueprintName = "AppCore"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/AppFeature.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/AppFeature.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..f456abbc6723aaeea5b0855be66dfd2b2bcc16b8
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/AppFeature.xcscheme
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1410"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "AppFeature"
+               BuildableName = "AppFeature"
+               BlueprintName = "AppFeature"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AppFeature"
+            BuildableName = "AppFeature"
+            BlueprintName = "AppFeature"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/AppNavigation.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/AppNavigation.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..15cbf312c024090afcf8a19dc2a95dc46942b6ae
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/AppNavigation.xcscheme
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1410"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "AppNavigation"
+               BuildableName = "AppNavigation"
+               BlueprintName = "AppNavigation"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "AppNavigation"
+            BuildableName = "AppNavigation"
+            BlueprintName = "AppNavigation"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/BackupFeature.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/BackupFeature.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..c1eeaf920bb20f7f66ef27f50c0bdfc24728e417
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/BackupFeature.xcscheme
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1410"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "BackupFeature"
+               BuildableName = "BackupFeature"
+               BlueprintName = "BackupFeature"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "BackupFeature"
+            BuildableName = "BackupFeature"
+            BlueprintName = "BackupFeature"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Countries.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Countries.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..4c7d001db37cb451452f23627813acfe6b17b4d1
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/Countries.xcscheme
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1410"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "Countries"
+               BuildableName = "Countries"
+               BlueprintName = "Countries"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "Countries"
+            BuildableName = "Countries"
+            BlueprintName = "Countries"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Defaults.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Defaults.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..8d32953525d76ceaaeafe999fcdec2c2b6a64c44
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/Defaults.xcscheme
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1410"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "Defaults"
+               BuildableName = "Defaults"
+               BlueprintName = "Defaults"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "Defaults"
+            BuildableName = "Defaults"
+            BlueprintName = "Defaults"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Keychain.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Keychain.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..e1efe3f4ab10b6ff34298955c87ae081d23eafea
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/Keychain.xcscheme
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1410"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "Keychain"
+               BuildableName = "Keychain"
+               BlueprintName = "Keychain"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "Keychain"
+            BuildableName = "Keychain"
+            BlueprintName = "Keychain"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/LaunchFeature 1.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/LaunchFeature 1.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..550e1ea22c75a628d02430c276f3fe8f14053252
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/LaunchFeature 1.xcscheme	
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1410"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "LaunchFeature"
+               BuildableName = "LaunchFeature"
+               BlueprintName = "LaunchFeature"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "LaunchFeature"
+            BuildableName = "LaunchFeature"
+            BlueprintName = "LaunchFeature"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/OnboardingFeature.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/OnboardingFeature.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..16cd9172dfb7808e9dfad0db2323fbeda4c764c3
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/OnboardingFeature.xcscheme
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1410"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "OnboardingFeature"
+               BuildableName = "OnboardingFeature"
+               BlueprintName = "OnboardingFeature"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "OnboardingFeature"
+            BuildableName = "OnboardingFeature"
+            BlueprintName = "OnboardingFeature"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/SettingsFeature.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/SettingsFeature.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..244b1a0ce2d97ea860b30062ec59ec322e831e1a
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/SettingsFeature.xcscheme
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1410"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "SettingsFeature"
+               BuildableName = "SettingsFeature"
+               BlueprintName = "SettingsFeature"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "SettingsFeature"
+            BuildableName = "SettingsFeature"
+            BlueprintName = "SettingsFeature"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/StatusBarFeature.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/StatusBarFeature.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..34f2d75bb6289d7283a321a3574f369a72da8605
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/StatusBarFeature.xcscheme
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1410"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "StatusBarFeature"
+               BuildableName = "StatusBarFeature"
+               BlueprintName = "StatusBarFeature"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "StatusBarFeature"
+            BuildableName = "StatusBarFeature"
+            BlueprintName = "StatusBarFeature"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/UpdateErrors.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/UpdateErrors.xcscheme
new file mode 100644
index 0000000000000000000000000000000000000000..1998517fa48f8d42029ba164bc4429012ad24652
--- /dev/null
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/UpdateErrors.xcscheme
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1410"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "UpdateErrors"
+               BuildableName = "UpdateErrors"
+               BlueprintName = "UpdateErrors"
+               ReferencedContainer = "container:">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "UpdateErrors"
+            BuildableName = "UpdateErrors"
+            BlueprintName = "UpdateErrors"
+            ReferencedContainer = "container:">
+         </BuildableReference>
+      </MacroExpansion>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/App/NotificationExtension/NotificationService.swift b/App/NotificationExtension/NotificationService.swift
index 495958b9ab1e9b26981c9996f088b17b5fa1053d..d9657fb8cd68d0cd4dcdd2d41d0cfd4800d5d6ed 100644
--- a/App/NotificationExtension/NotificationService.swift
+++ b/App/NotificationExtension/NotificationService.swift
@@ -1,13 +1,97 @@
-import PushFeature
+import XXModels
+import XXClient
+import XXDatabase
+import ReportingFeature
+import XXMessengerClient
 import UserNotifications
 
 final class NotificationService: UNNotificationServiceExtension {
-    private let pushHandler = PushHandler()
+  override func didReceive(
+    _ request: UNNotificationRequest,
+    withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
+  ) {
+    guard let defaults = UserDefaults(suiteName: "group.elixxir.messenger") else { return }
 
-    override func didReceive(
-        _ request: UNNotificationRequest,
-        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
-    ) {
-        pushHandler.handlePush(request, contentHandler)
+    var environment = MessengerEnvironment.live()
+    environment.serviceList = .userDefaults(key: "preImage", userDefaults: defaults)
+    let messenger = Messenger.live(environment)
+    let userInfo = request.content.userInfo
+    let dbPath = FileManager.default
+      .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
+      .appendingPathComponent("xxm_databasse")
+      .appendingPathExtension("sqlite").path
+
+    guard let csv = userInfo["notificationData"] as? String,
+          let reports = try? messenger.getNotificationReports(notificationCSV: csv) else { return }
+    reports
+      .filter { $0.forMe }
+      .filter { $0.type != .silent }
+      .filter { $0.type != .default }
+      .compactMap {
+        let content = UNMutableNotificationContent()
+        content.badge = 1
+        content.sound = .default
+        content.threadIdentifier = "new_message_identifier"
+        content.userInfo["type"] = $0.type.rawValue
+        content.userInfo["source"] = $0.source
+        content.body = getBodyForUnknownWith(type: $0.type)
+
+        guard let db = try? Database.onDisk(path: dbPath),
+              let contact = try? db.fetchContacts(.init(id: [$0.source])).first else {
+          return content
+        }
+        if ReportingStatus.live().isEnabled(), (contact.isBlocked || contact.isBanned) {
+          return nil
+        }
+        if let showSender = defaults.value(forKey: "isShowingUsernames") as? Bool, showSender == true {
+          let name = (contact.nickname ?? contact.username) ?? ""
+          content.body = getBodyFor(name: name, with: $0.type)
+        }
+        return content
+      }.forEach {
+        contentHandler($0)
+      }
+  }
+
+  private func getBodyForUnknownWith(type: NotificationReport.ReportType) -> String {
+    switch type {
+    case .`default`, .silent:
+      fatalError()
+    case .request:
+      return "Request received"
+    case .reset:
+      return "One of your contacts has restored their account"
+    case .confirm:
+      return "Request accepted"
+    case .e2e:
+      return "New private message"
+    case .group:
+      return "New group message"
+    case .endFT:
+      return "New media received"
+    case .groupRQ:
+      return "Group request received"
+    }
+  }
+
+  private func getBodyFor(name: String, with type: NotificationReport.ReportType) -> String {
+    switch type {
+    case .silent, .`default`:
+      fatalError()
+    case .e2e:
+      return String(format: "%@ sent you a private message", name)
+    case .reset:
+      return String(format: "%@ restored their account", name)
+    case .endFT:
+      return String(format: "%@ sent you a file", name)
+    case .group:
+      return String(format: "%@ sent you a group message", name)
+    case .groupRQ:
+      return String(format: "%@ sent you a group request", name)
+    case .confirm:
+      return String(format: "%@ confirmed your contact request", name)
+    case .request:
+      return String(format: "%@ sent you a contact request", name)
     }
+  }
 }
diff --git a/App/client-ios.xcodeproj/project.pbxproj b/App/client-ios.xcodeproj/project.pbxproj
index 3e09d58a41130302b5a9477bbef829531567752b..0f9849c8e5b29c44bd270caee0cc7ba2c25f0a8d 100644
--- a/App/client-ios.xcodeproj/project.pbxproj
+++ b/App/client-ios.xcodeproj/project.pbxproj
@@ -13,10 +13,10 @@
 		02FDD07021EDA39B000F1286 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 02FDD06E21EDA39B000F1286 /* LaunchScreen.storyboard */; };
 		32179BA826410149008B26EC /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32179BA726410149008B26EC /* NotificationService.swift */; };
 		32179BAC26410149008B26EC /* NotificationExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 32179BA526410149008B26EC /* NotificationExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
-		3273327126C7391D0027D79D /* App in Frameworks */ = {isa = PBXBuildFile; productRef = 3273327026C7391D0027D79D /* App */; };
+		3242BD412921DC950045E647 /* AppFeature in Frameworks */ = {isa = PBXBuildFile; productRef = 3242BD402921DC950045E647 /* AppFeature */; };
+		3242BD432921DC9E0045E647 /* AppFeature in Frameworks */ = {isa = PBXBuildFile; productRef = 3242BD422921DC9E0045E647 /* AppFeature */; };
 		32C194E02808C65500876917 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 32C194DF2808C65500876917 /* GoogleService-Info.plist */; };
 		32C194E12808C65500876917 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 32C194DF2808C65500876917 /* GoogleService-Info.plist */; };
-		32CAAFAE2845836100446BB9 /* App in Frameworks */ = {isa = PBXBuildFile; productRef = 32CAAFAD2845836100446BB9 /* App */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -64,8 +64,8 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				3273327126C7391D0027D79D /* App in Frameworks */,
 				026135B321F2729900038B5E /* libsqlite3.tbd in Frameworks */,
+				3242BD432921DC9E0045E647 /* AppFeature in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -73,7 +73,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				32CAAFAE2845836100446BB9 /* App in Frameworks */,
+				3242BD412921DC950045E647 /* AppFeature in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -159,7 +159,7 @@
 			);
 			name = "client-ios";
 			packageProductDependencies = (
-				3273327026C7391D0027D79D /* App */,
+				3242BD422921DC9E0045E647 /* AppFeature */,
 			);
 			productName = "client-ios";
 			productReference = 02FDD06221EDA39A000F1286 /* client-ios.app */;
@@ -179,7 +179,7 @@
 			);
 			name = NotificationExtension;
 			packageProductDependencies = (
-				32CAAFAD2845836100446BB9 /* App */,
+				3242BD402921DC950045E647 /* AppFeature */,
 			);
 			productName = NotificationExtension;
 			productReference = 32179BA526410149008B26EC /* NotificationExtension.appex */;
@@ -448,7 +448,7 @@
 				CODE_SIGN_ENTITLEMENTS = "client-ios/Resources/client-ios.entitlements";
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 167;
+				CURRENT_PROJECT_VERSION = 294;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DEVELOPMENT_TEAM = S6JDM2WW29;
 				ENABLE_BITCODE = NO;
@@ -463,7 +463,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 1.1.2;
+				MARKETING_VERSION = 1.1.8;
 				OTHER_SWIFT_FLAGS = "$(inherited)";
 				PRODUCT_BUNDLE_IDENTIFIER = xx.messenger.mock;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -487,7 +487,7 @@
 				CODE_SIGN_ENTITLEMENTS = "client-ios/Resources/client-ios.entitlements";
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 167;
+				CURRENT_PROJECT_VERSION = 294;
 				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
 				DEVELOPMENT_TEAM = S6JDM2WW29;
 				ENABLE_BITCODE = NO;
@@ -503,7 +503,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 1.1.2;
+				MARKETING_VERSION = 1.1.8;
 				OTHER_SWIFT_FLAGS = "";
 				PRODUCT_BUNDLE_IDENTIFIER = xx.messenger;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -522,7 +522,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationExtension/NotificationExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 167;
+				CURRENT_PROJECT_VERSION = 294;
 				DEVELOPMENT_TEAM = S6JDM2WW29;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
@@ -536,7 +536,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 1.1.3;
+				MARKETING_VERSION = 1.1.8;
 				PRODUCT_BUNDLE_IDENTIFIER = xx.messenger.mock.notifications;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -553,7 +553,7 @@
 				CODE_SIGN_ENTITLEMENTS = NotificationExtension/NotificationExtension.entitlements;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 167;
+				CURRENT_PROJECT_VERSION = 294;
 				DEVELOPMENT_TEAM = S6JDM2WW29;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
@@ -567,7 +567,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 1.1.3;
+				MARKETING_VERSION = 1.1.8;
 				PRODUCT_BUNDLE_IDENTIFIER = xx.messenger.notifications;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -610,13 +610,13 @@
 /* End XCConfigurationList section */
 
 /* Begin XCSwiftPackageProductDependency section */
-		3273327026C7391D0027D79D /* App */ = {
+		3242BD402921DC950045E647 /* AppFeature */ = {
 			isa = XCSwiftPackageProductDependency;
-			productName = App;
+			productName = AppFeature;
 		};
-		32CAAFAD2845836100446BB9 /* App */ = {
+		3242BD422921DC9E0045E647 /* AppFeature */ = {
 			isa = XCSwiftPackageProductDependency;
-			productName = App;
+			productName = AppFeature;
 		};
 /* End XCSwiftPackageProductDependency section */
 	};
diff --git a/App/client-ios.xcodeproj/xcshareddata/xcschemes/Release.xcscheme b/App/client-ios.xcodeproj/xcshareddata/xcschemes/Release.xcscheme
index 800c200df2b4ad94d75e5144e7951bdcd91611f2..b47f627ae04ed4310d32bd7d0d0f1e5176aa6b0b 100644
--- a/App/client-ios.xcodeproj/xcshareddata/xcschemes/Release.xcscheme
+++ b/App/client-ios.xcodeproj/xcshareddata/xcschemes/Release.xcscheme
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
    LastUpgradeVersion = "1200"
-   version = "1.7">
+   version = "1.8">
    <BuildAction
       parallelizeBuildables = "YES"
       buildImplicitDependencies = "YES">
diff --git a/App/client-ios/Resources/GoogleService-Info.plist b/App/client-ios/Resources/GoogleService-Info.plist
index 03e09469daae0502a5202f6e63aca9db140d1d77..10566b5ece72bfa2c34ad23c1d289d327d757327 100644
--- a/App/client-ios/Resources/GoogleService-Info.plist
+++ b/App/client-ios/Resources/GoogleService-Info.plist
@@ -3,34 +3,34 @@
 <plist version="1.0">
 <dict>
 	<key>CLIENT_ID</key>
-	<string></string>
+	<string>662236151640-r1lrlppqdcmhb4p8urq32fo7784cdoal.apps.googleusercontent.com</string>
 	<key>REVERSED_CLIENT_ID</key>
-	<string></string>
+	<string>com.googleusercontent.apps.662236151640-r1lrlppqdcmhb4p8urq32fo7784cdoal</string>
 	<key>ANDROID_CLIENT_ID</key>
-	<string></string>
+	<string>662236151640-2ughgo2dvc59dm4o39b45lbdungp2mct.apps.googleusercontent.com</string>
 	<key>API_KEY</key>
-	<string></string>
+	<string>AIzaSyCbI2yQ7pbuVSRvraqanjGcS9CDrjD7lNU</string>
 	<key>GCM_SENDER_ID</key>
-	<string></string>
+	<string>662236151640</string>
 	<key>PLIST_VERSION</key>
-	<string></string>
+	<string>1</string>
 	<key>BUNDLE_ID</key>
-	<string></string>
+	<string>io.xxlabs.messenger</string>
 	<key>PROJECT_ID</key>
-	<string></string>
+	<string>xx-messenger-6e03e</string>
 	<key>STORAGE_BUCKET</key>
-	<string></string>
+	<string>xx-messenger-6e03e.appspot.com</string>
 	<key>IS_ADS_ENABLED</key>
 	<false/>
 	<key>IS_ANALYTICS_ENABLED</key>
 	<false/>
 	<key>IS_APPINVITE_ENABLED</key>
-	<false/>
+	<true/>
 	<key>IS_GCM_ENABLED</key>
-	<false/>
+	<true/>
 	<key>IS_SIGNIN_ENABLED</key>
-	<false/>
+	<true/>
 	<key>GOOGLE_APP_ID</key>
-	<string></string>
+	<string>1:662236151640:ios:24badb58ab07515d8cef2d</string>
 </dict>
 </plist>
diff --git a/App/client-ios/Resources/Info.plist b/App/client-ios/Resources/Info.plist
index d8cb845b40392068f453251c9c0aa72cd00979db..76cc3739f247885e2336b18a22b9965a16fe9965 100644
--- a/App/client-ios/Resources/Info.plist
+++ b/App/client-ios/Resources/Info.plist
@@ -67,6 +67,10 @@
 		<key>NSAllowsArbitraryLoadsInWebContent</key>
 		<true/>
 	</dict>
+	<key>NSBonjourServices</key>
+	<array>
+		<string>_pulse._tcp</string>
+	</array>
 	<key>NSCameraUsageDescription</key>
 	<string>This permission is required for scanning qr codes</string>
 	<key>NSFaceIDUsageDescription</key>
@@ -105,6 +109,6 @@
 	<key>UIViewControllerBasedStatusBarAppearance</key>
 	<true/>
 	<key>isReportingOptional</key>
-	<false/>
+	<true/>
 </dict>
 </plist>
diff --git a/App/client-ios/main.swift b/App/client-ios/main.swift
index 00c22b5303b5134db6e20ca68475518f8105efc7..3d2972992b1778100134c827e9c22617f3837ad1 100644
--- a/App/client-ios/main.swift
+++ b/App/client-ios/main.swift
@@ -1,5 +1,5 @@
-import App
 import UIKit
+import AppFeature
 
 let appDelegate: String? =
     NSClassFromString("XCTestCase") == nil
diff --git a/Package.swift b/Package.swift
index f5678619dec83e0af6ca965e92d6303b0bc6f373..ef4dc8f0544a8d56f504f00192f82458c4032dc0 100644
--- a/Package.swift
+++ b/Package.swift
@@ -2,758 +2,615 @@
 import PackageDescription
 
 let package = Package(
-    name: "client-ios",
-    defaultLocalization: "en",
-    platforms: [
-        .iOS(.v14),
-    ],
-    products: [
-        .library(name: "App", targets: ["App"]),
-        .library(name: "HUD", targets: ["HUD"]),
-        .library(name: "Theme", targets: ["Theme"]),
-        .library(name: "Shared", targets: ["Shared"]),
-        .library(name: "Models", targets: ["Models"]),
-        .library(name: "XXLogger", targets: ["XXLogger"]),
-        .library(name: "Defaults", targets: ["Defaults"]),
-        .library(name: "Keychain", targets: ["Keychain"]),
-        .library(name: "Voxophone", targets: ["Voxophone"]),
-        .library(name: "Countries", targets: ["Countries"]),
-        .library(name: "InputField", targets: ["InputField"]),
-        .library(name: "TestHelpers", targets: ["TestHelpers"]),
-        .library(name: "ScanFeature", targets: ["ScanFeature"]),
-        .library(name: "Permissions", targets: ["Permissions"]),
-        .library(name: "MenuFeature", targets: ["MenuFeature"]),
-        .library(name: "Integration", targets: ["Integration"]),
-        .library(name: "ChatFeature", targets: ["ChatFeature"]),
-        .library(name: "PushFeature", targets: ["PushFeature"]),
-        .library(name: "SFTPFeature", targets: ["SFTPFeature"]),
-        .library(name: "CrashService", targets: ["CrashService"]),
-        .library(name: "TermsFeature", targets: ["TermsFeature"]),
-        .library(name: "Presentation", targets: ["Presentation"]),
-        .library(name: "ToastFeature", targets: ["ToastFeature"]),
-        .library(name: "BackupFeature", targets: ["BackupFeature"]),
-        .library(name: "LaunchFeature", targets: ["LaunchFeature"]),
-        .library(name: "iCloudFeature", targets: ["iCloudFeature"]),
-        .library(name: "SearchFeature", targets: ["SearchFeature"]),
-        .library(name: "DrawerFeature", targets: ["DrawerFeature"]),
-        .library(name: "CollectionView", targets: ["CollectionView"]),
-        .library(name: "RestoreFeature", targets: ["RestoreFeature"]),
-        .library(name: "CrashReporting", targets: ["CrashReporting"]),
-        .library(name: "ProfileFeature", targets: ["ProfileFeature"]),
-        .library(name: "ContactFeature", targets: ["ContactFeature"]),
-        .library(name: "NetworkMonitor", targets: ["NetworkMonitor"]),
-        .library(name: "DropboxFeature", targets: ["DropboxFeature"]),
-        .library(name: "VersionChecking", targets: ["VersionChecking"]),
-        .library(name: "SettingsFeature", targets: ["SettingsFeature"]),
-        .library(name: "ChatListFeature", targets: ["ChatListFeature"]),
-        .library(name: "RequestsFeature", targets: ["RequestsFeature"]),
-        .library(name: "ChatInputFeature", targets: ["ChatInputFeature"]),
-        .library(name: "OnboardingFeature", targets: ["OnboardingFeature"]),
-        .library(name: "GoogleDriveFeature", targets: ["GoogleDriveFeature"]),
-        .library(name: "ContactListFeature", targets: ["ContactListFeature"]),
-        .library(name: "DependencyInjection", targets: ["DependencyInjection"]),
-        .library(name: "ReportingFeature", targets: ["ReportingFeature"]),
-    ],
-    dependencies: [
-        .package(
-            url: "https://github.com/Quick/Quick",
-            .upToNextMajor(from: "3.0.0")
-        ),
-        .package(
-            url: "https://github.com/Quick/Nimble",
-            .upToNextMajor(from: "9.0.0")
-        ),
-        .package(
-            url: "https://github.com/SnapKit/SnapKit",
-            .upToNextMajor(from: "5.0.1")
-        ),
-        .package(
-            url: "https://github.com/icanzilb/Retry.git",
-            .upToNextMajor(from: "0.6.3")
-        ),
-        .package(
-            url: "https://github.com/ekazaev/ChatLayout",
-            .upToNextMajor(from: "1.1.14")
-        ),
-        .package(
-            url: "https://github.com/ra1028/DifferenceKit",
-            .upToNextMajor(from: "1.2.0")
-        ),
-        .package(
-            url: "https://github.com/apple/swift-protobuf",
-            .upToNextMajor(from: "1.14.0")
-        ),
-        .package(
-            url: "https://github.com/google/GoogleSignIn-iOS",
-            .upToNextMajor(from: "6.1.0")
-        ),
-        .package(
-            url: "https://github.com/dropbox/SwiftyDropbox.git",
-            .upToNextMajor(from: "8.2.1")
-        ),
-        .package(
-            url: "https://github.com/amosavian/FileProvider.git",
-            .upToNextMajor(from: "0.26.0")
-        ),
-        .package(
-            url: "https://github.com/SwiftyBeaver/SwiftyBeaver.git",
-            .upToNextMajor(from: "1.9.5")
-        ),
-        .package(
-            url: "https://github.com/darrarski/ScrollViewController",
-            .upToNextMajor(from: "1.2.0")
-        ),
-        .package(
-            url: "https://github.com/pointfreeco/combine-schedulers",
-            .upToNextMajor(from: "0.5.0")
-        ),
-        .package(
-            url: "https://github.com/kishikawakatsumi/KeychainAccess",
-            .upToNextMajor(from: "4.2.1")
-        ),
-        .package(
-            url: "https://github.com/google/google-api-objectivec-client-for-rest",
-            .upToNextMajor(from: "1.6.0")
-        ),
-        .package(
-            url: "https://git.xx.network/elixxir/client-ios-db.git",
-            .upToNextMajor(from: "1.1.0")
-        ),
-        .package(
-            url: "https://github.com/firebase/firebase-ios-sdk.git",
-            .upToNextMajor(from: "8.10.0")
-        ),
-        .package(
-            url: "https://github.com/darrarski/Shout.git",
-            revision: "df5a662293f0ac15eeb4f2fd3ffd0c07b73d0de0"
-        ),
-        .package(
-            url: "https://github.com/pointfreeco/swift-composable-architecture.git",
-            .upToNextMajor(from: "0.32.0")
-        ),
-        .package(
-            url: "https://github.com/pointfreeco/swift-custom-dump.git",
-            .upToNextMajor(from: "0.5.0")
-        ),
-        .package(
-            url: "https://github.com/swiftcsv/SwiftCSV.git",
-            from: "0.8.0"
-        ),
-        .package(
-            url: "https://github.com/pointfreeco/xctest-dynamic-overlay.git",
-            .upToNextMajor(from: "0.3.3")
-        ),
-    ],
-    targets: [
-        .target(
-            name: "App",
-            dependencies: [
-                .target(name: "Keychain"),
-                .target(name: "Voxophone"),
-                .target(name: "Permissions"),
-                .target(name: "ScanFeature"),
-                .target(name: "ChatFeature"),
-                .target(name: "MenuFeature"),
-                .target(name: "PushFeature"),
-                .target(name: "SFTPFeature"),
-                .target(name: "TermsFeature"),
-                .target(name: "ToastFeature"),
-                .target(name: "CrashService"),
-                .target(name: "BackupFeature"),
-                .target(name: "SearchFeature"),
-                .target(name: "LaunchFeature"),
-                .target(name: "iCloudFeature"),
-                .target(name: "DropboxFeature"),
-                .target(name: "ContactFeature"),
-                .target(name: "RestoreFeature"),
-                .target(name: "ProfileFeature"),
-                .target(name: "CrashReporting"),
-                .target(name: "ChatListFeature"),
-                .target(name: "SettingsFeature"),
-                .target(name: "RequestsFeature"),
-                .target(name: "ReportingFeature"),
-                .target(name: "OnboardingFeature"),
-                .target(name: "GoogleDriveFeature"),
-                .target(name: "ContactListFeature"),
-            ]
-        ),
-        .testTarget(
-            name: "AppTests",
-            dependencies: [
-                .target(name: "App"),
-            ]
-        ),
-        .target(
-            name: "CrashReporting"
-        ),
-        .target(
-            name: "NetworkMonitor"
-        ),
-        .target(
-            name: "VersionChecking"
-        ),
-        .target(
-            name: "DependencyInjection"
-        ),
-        .testTarget(
-            name: "DependencyInjectionTests",
-            dependencies: [
-                .target(name: "DependencyInjection"),
-            ]
-        ),
-        .target(
-            name: "InputField",
-            dependencies: [
-                .target(name: "Shared"),
-            ]
-        ),
-        .binaryTarget(
-            name: "Bindings",
-            path: "XCFrameworks/Bindings.xcframework"
-        ),
-        .target(
-            name: "Permissions",
-            dependencies: [
-                .target(name: "Theme"),
-                .target(name: "Shared"),
-                .target(name: "DependencyInjection"),
-            ]
-        ),
-        .target(
-            name: "PushFeature",
-            dependencies: [
-                .target(name: "Models"),
-                .target(name: "Defaults"),
-                .target(name: "Integration"),
-                .target(name: "DependencyInjection"),
-            ]
-        ),
-        .target(
-            name: "TestHelpers",
-            dependencies: [
-                .target(name: "Models"),
-                .target(name: "Presentation"),
-            ]
-        ),
-        .target(
-            name: "Keychain",
-            dependencies: [
-                .product(name: "KeychainAccess", package: "KeychainAccess"),
-            ]
-        ),
-        .target(
-            name: "Voxophone",
-            dependencies: [
-                .target(name: "Shared"),
-            ]
-        ),
-        .target(
-            name: "Models",
-            dependencies: [
-                .product(name: "DifferenceKit", package: "DifferenceKit"),
-                .product(name: "SwiftProtobuf", package: "swift-protobuf"),
-            ]
-        ),
-        .target(
-            name: "Defaults",
-            dependencies: [
-                .target(name: "DependencyInjection"),
-            ]
-        ),
-        .target(
-            name: "ToastFeature",
-            dependencies: [
-                .target(name: "Shared"),
-            ]
-        ),
-        .target(
-            name: "CrashService",
-            dependencies: [
-                .target(name: "CrashReporting"),
-                .product(name: "FirebaseCrashlytics", package: "firebase-ios-sdk"),
-            ]
-        ),
-        .target(
-            name: "SFTPFeature",
-            dependencies: [
-                .target(name: "HUD"),
-                .target(name: "Models"),
-                .target(name: "Shared"),
-                .target(name: "Keychain"),
-                .target(name: "InputField"),
-                .target(name: "Presentation"),
-                .target(name: "DependencyInjection"),
-                .product(name: "Shout", package: "Shout"),
-            ]
-        ),
-        .target(
-            name: "GoogleDriveFeature",
-            dependencies: [
-                .product(name: "GoogleSignIn", package: "GoogleSignIn-iOS"),
-                .product(name: "GoogleAPIClientForREST_Drive", package: "google-api-objectivec-client-for-rest"),
-            ],
-            resources: [
-                .process("Resources"),
-            ]
-        ),
-        .target(
-            name: "iCloudFeature",
-            dependencies: [
-                .product(name: "FilesProvider", package: "FileProvider"),
-            ]
-        ),
-        .target(
-            name: "DropboxFeature",
-            dependencies: [
-                .product(name: "SwiftyDropbox", package: "SwiftyDropbox"),
-            ],
-            resources: [
-                .process("Resources"),
-            ]
-        ),
-        .target(
-            name: "Countries",
-            dependencies: [
-                .target(name: "Theme"),
-                .target(name: "Shared"),
-                .target(name: "DependencyInjection"),
-            ],
-            resources: [
-                .process("Resources"),
-            ]
-        ),
-        .target(
-            name: "Theme",
-            dependencies: [
-                .target(name: "Defaults"),
-                .target(name: "DependencyInjection"),
-            ]
-        ),
-        .testTarget(
-            name: "ThemeTests",
-            dependencies: [
-                .target(name: "Theme"),
-                .product(name: "Quick", package: "Quick"),
-                .product(name: "Nimble", package: "Nimble"),
-            ]
-        ),
-        .target(
-            name: "DrawerFeature",
-            dependencies: [
-                .target(name: "Shared"),
-                .target(name: "InputField"),
-                .product(name: "ScrollViewController", package: "ScrollViewController"),
-            ]
-        ),
-        .target(
-            name: "HUD",
-            dependencies: [
-                .target(name: "Theme"),
-                .target(name: "Shared"),
-                .product(name: "SnapKit", package: "SnapKit"),
-            ]
-        ),
-        .target(
-            name: "XXLogger",
-            dependencies: [
-                .product(name: "SwiftyBeaver", package: "SwiftyBeaver"),
-            ]
-        ),
-        .target(
-            name: "Shared",
-            dependencies: [
-                .product(name: "SnapKit", package: "SnapKit"),
-                .product(name: "ChatLayout", package: "ChatLayout"),
-                .product(name: "DifferenceKit", package: "DifferenceKit"),
-            ],
-            exclude: [
-                "swiftgen.yml",
-            ],
-            resources: [
-                .process("Resources"),
-            ]
-        ),
-        .target(
-            name: "Integration",
-            dependencies: [
-                .target(name: "Shared"),
-                .target(name: "Bindings"),
-                .target(name: "XXLogger"),
-                .target(name: "Keychain"),
-                .target(name: "ToastFeature"),
-                .target(name: "BackupFeature"),
-                .target(name: "CrashReporting"),
-                .target(name: "NetworkMonitor"),
-                .target(name: "DependencyInjection"),
-                .product(name: "Retry", package: "Retry"),
-                .product(name: "XXDatabase", package: "client-ios-db"),
-                .product(name: "XXLegacyDatabaseMigrator", package: "client-ios-db"),
-            ],
-            resources: [
-                .process("Resources"),
-            ]
-        ),
-        .target(
-            name: "Presentation",
-            dependencies: [
-                .target(name: "Theme"),
-                .target(name: "Shared"),
-                .product(name: "SnapKit", package: "SnapKit"),
-            ]
-        ),
-        .testTarget(
-            name: "PresentationTests",
-            dependencies: [
-                .target(name: "Presentation"),
-                .product(name: "Quick", package: "Quick"),
-                .product(name: "Nimble", package: "Nimble"),
-            ]
-        ),
-        .target(
-            name: "ChatInputFeature",
-            dependencies: [
-                .target(name: "Voxophone"),
-                .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
-            ]
-        ),
-        .target(
-            name: "RestoreFeature",
-            dependencies: [
-                .target(name: "HUD"),
-                .target(name: "Shared"),
-                .target(name: "SFTPFeature"),
-                .target(name: "Integration"),
-                .target(name: "Presentation"),
-                .target(name: "iCloudFeature"),
-                .target(name: "DropboxFeature"),
-                .target(name: "GoogleDriveFeature"),
-                .target(name: "DependencyInjection"),
-            ]
-        ),
-        .target(
-            name: "ContactFeature",
-            dependencies: [
-                .target(name: "Shared"),
-                .target(name: "InputField"),
-                .target(name: "ChatFeature"),
-                .target(name: "Presentation"),
-                .product(name: "CombineSchedulers", package: "combine-schedulers"),
-                .product(name: "ScrollViewController", package: "ScrollViewController"),
-            ]
-        ),
-        .testTarget(
-            name: "ContactFeatureTests",
-            dependencies: [
-                .target(name: "TestHelpers"),
-                .target(name: "ContactFeature"),
-                .product(name: "Quick", package: "Quick"),
-                .product(name: "Nimble", package: "Nimble"),
-            ]
-        ),
-        .target(
-            name: "ChatFeature",
-            dependencies: [
-                .target(name: "HUD"),
-                .target(name: "Theme"),
-                .target(name: "Shared"),
-                .target(name: "Defaults"),
-                .target(name: "Keychain"),
-                .target(name: "Voxophone"),
-                .target(name: "Integration"),
-                .target(name: "Permissions"),
-                .target(name: "Presentation"),
-                .target(name: "DrawerFeature"),
-                .target(name: "ChatInputFeature"),
-                .target(name: "ReportingFeature"),
-                .target(name: "DependencyInjection"),
-                .product(name: "ChatLayout", package: "ChatLayout"),
-                .product(name: "DifferenceKit", package: "DifferenceKit"),
-                .product(name: "ScrollViewController", package: "ScrollViewController"),
-            ]
-        ),
-        .testTarget(
-            name: "ChatFeatureTests",
-            dependencies: [
-                .target(name: "ChatFeature"),
-                .target(name: "TestHelpers"),
-                .product(name: "Quick", package: "Quick"),
-                .product(name: "Nimble", package: "Nimble"),
-            ]
-        ),
-        .target(
-            name: "SearchFeature",
-            dependencies: [
-                .target(name: "HUD"),
-                .target(name: "Shared"),
-                .target(name: "Countries"),
-                .target(name: "Integration"),
-                .target(name: "Presentation"),
-                .target(name: "ContactFeature"),
-                .target(name: "DependencyInjection"),
-            ]
-        ),
-        .testTarget(
-            name: "SearchFeatureTests",
-            dependencies: [
-                .target(name: "TestHelpers"),
-                .target(name: "SearchFeature"),
-                .product(name: "Quick", package: "Quick"),
-                .product(name: "Nimble", package: "Nimble"),
-            ]
-        ),
-        .target(
-            name: "LaunchFeature",
-            dependencies: [
-                .target(name: "HUD"),
-                .target(name: "Theme"),
-                .target(name: "Shared"),
-                .target(name: "Defaults"),
-                .target(name: "PushFeature"),
-                .target(name: "Integration"),
-                .target(name: "Permissions"),
-                .target(name: "DropboxFeature"),
-                .target(name: "VersionChecking"),
-                .target(name: "ReportingFeature"),
-                .target(name: "DependencyInjection"),
-            ]
-        ),
-        .target(
-            name: "TermsFeature",
-            dependencies: [
-                .target(name: "Theme"),
-                .target(name: "Shared"),
-                .target(name: "Defaults"),
-                .target(name: "Presentation"),
-            ]
-        ),
-        .target(
-            name: "RequestsFeature",
-            dependencies: [
-                .target(name: "Theme"),
-                .target(name: "Shared"),
-                .target(name: "Integration"),
-                .target(name: "ToastFeature"),
-                .target(name: "ContactFeature"),
-                .target(name: "DependencyInjection"),
-                .product(name: "DifferenceKit", package: "DifferenceKit"),
-            ]
-        ),
-        .testTarget(
-            name: "RequestsFeatureTests",
-            dependencies: [
-                .target(name: "TestHelpers"),
-                .target(name: "RequestsFeature"),
-                .product(name: "Quick", package: "Quick"),
-                .product(name: "Nimble", package: "Nimble"),
-            ]
-        ),
-        .target(
-            name: "ProfileFeature",
-            dependencies: [
-                .target(name: "HUD"),
-                .target(name: "Theme"),
-                .target(name: "Shared"),
-                .target(name: "Keychain"),
-                .target(name: "Defaults"),
-                .target(name: "Countries"),
-                .target(name: "InputField"),
-                .target(name: "MenuFeature"),
-                .target(name: "Permissions"),
-                .target(name: "Integration"),
-                .target(name: "Presentation"),
-                .target(name: "DrawerFeature"),
-                .target(name: "DependencyInjection"),
-                .product(name: "CombineSchedulers", package: "combine-schedulers"),
-                .product(name: "ScrollViewController", package: "ScrollViewController"),
-            ]
-        ),
-        .testTarget(
-            name: "ProfileFeatureTests",
-            dependencies: [
-                .target(name: "TestHelpers"),
-                .target(name: "ProfileFeature"),
-                .product(name: "Quick", package: "Quick"),
-                .product(name: "Nimble", package: "Nimble"),
-            ]
-        ),
-        .target(
-            name: "ChatListFeature",
-            dependencies: [
-                .target(name: "Theme"),
-                .target(name: "Shared"),
-                .target(name: "Defaults"),
-                .target(name: "MenuFeature"),
-                .target(name: "ChatFeature"),
-                .target(name: "ProfileFeature"),
-                .target(name: "SettingsFeature"),
-                .target(name: "ContactListFeature"),
-                .target(name: "DependencyInjection"),
-                .product(name: "DifferenceKit", package: "DifferenceKit"),
-            ]
-        ),
-        .testTarget(
-            name: "ChatListFeatureTests",
-            dependencies: [
-                .target(name: "TestHelpers"),
-                .target(name: "ChatListFeature"),
-                .product(name: "Quick", package: "Quick"),
-                .product(name: "Nimble", package: "Nimble"),
-            ]
-        ),
-        .target(
-            name: "OnboardingFeature",
-            dependencies: [
-                .target(name: "HUD"),
-                .target(name: "Shared"),
-                .target(name: "Defaults"),
-                .target(name: "Keychain"),
-                .target(name: "Countries"),
-                .target(name: "InputField"),
-                .target(name: "Permissions"),
-                .target(name: "PushFeature"),
-                .target(name: "Integration"),
-                .target(name: "Presentation"),
-                .target(name: "DrawerFeature"),
-                .target(name: "VersionChecking"),
-                .target(name: "DependencyInjection"),
-                .product(name: "CombineSchedulers", package: "combine-schedulers"),
-                .product(name: "ScrollViewController", package: "ScrollViewController"),
-            ]
-        ),
-        .testTarget(
-            name: "OnboardingFeatureTests",
-            dependencies: [
-                .target(name: "TestHelpers"),
-                .target(name: "OnboardingFeature"),
-                .product(name: "Quick", package: "Quick"),
-                .product(name: "Nimble", package: "Nimble"),
-            ]
-        ),
-        .target(
-            name: "MenuFeature",
-            dependencies: [
-                .target(name: "Theme"),
-                .target(name: "Shared"),
-                .target(name: "Defaults"),
-                .target(name: "Integration"),
-                .target(name: "Presentation"),
-                .target(name: "DependencyInjection"),
-            ]
-        ),
-        .target(
-            name: "BackupFeature",
-            dependencies: [
-                .target(name: "HUD"),
-                .target(name: "Shared"),
-                .target(name: "Models"),
-                .target(name: "InputField"),
-                .target(name: "SFTPFeature"),
-                .target(name: "Presentation"),
-                .target(name: "iCloudFeature"),
-                .target(name: "DrawerFeature"),
-                .target(name: "DropboxFeature"),
-                .target(name: "GoogleDriveFeature"),
-                .target(name: "DependencyInjection"),
-            ]
-        ),
-        .target(
-            name: "ScanFeature",
-            dependencies: [
-                .target(name: "Theme"),
-                .target(name: "Shared"),
-                .target(name: "Countries"),
-                .target(name: "Permissions"),
-                .target(name: "Integration"),
-                .target(name: "Presentation"),
-                .target(name: "ContactFeature"),
-                .target(name: "DependencyInjection"),
-                .product(name: "SnapKit", package: "SnapKit"),
-            ]
-        ),
-        .testTarget(
-            name: "ScanFeatureTests",
-            dependencies: [
-                .target(name: "ScanFeature"),
-                .target(name: "TestHelpers"),
-                .product(name: "Quick", package: "Quick"),
-                .product(name: "Nimble", package: "Nimble"),
-            ]
-        ),
-        .target(
-            name: "ContactListFeature",
-            dependencies: [
-                .target(name: "Theme"),
-                .target(name: "Shared"),
-                .target(name: "Integration"),
-                .target(name: "Presentation"),
-                .target(name: "ContactFeature"),
-                .target(name: "DependencyInjection"),
-                .product(name: "DifferenceKit", package: "DifferenceKit"),
-            ]
-        ),
-        .testTarget(
-            name: "ContactListFeatureTests",
-            dependencies: [
-                .target(name: "TestHelpers"),
-                .target(name: "ContactListFeature"),
-                .product(name: "Quick", package: "Quick"),
-                .product(name: "Nimble", package: "Nimble"),
-            ]
-        ),
-        .target(
-            name: "SettingsFeature",
-            dependencies: [
-                .target(name: "HUD"),
-                .target(name: "Theme"),
-                .target(name: "Shared"),
-                .target(name: "Defaults"),
-                .target(name: "Keychain"),
-                .target(name: "InputField"),
-                .target(name: "PushFeature"),
-                .target(name: "Permissions"),
-                .target(name: "MenuFeature"),
-                .target(name: "Integration"),
-                .target(name: "Presentation"),
-                .target(name: "DrawerFeature"),
-                .target(name: "DependencyInjection"),
-                .product(name: "CombineSchedulers", package: "combine-schedulers"),
-                .product(name: "ScrollViewController", package: "ScrollViewController"),
-            ]
-        ),
-        .testTarget(
-            name: "SettingsFeatureTests",
-            dependencies: [
-                .target(name: "TestHelpers"),
-                .target(name: "SettingsFeature"),
-                .product(name: "Quick", package: "Quick"),
-                .product(name: "Nimble", package: "Nimble"),
-            ]
-        ),
-        .target(
-            name: "CollectionView",
-            dependencies: [
-                .product(name: "ChatLayout", package: "ChatLayout"),
-                .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
-            ]
-        ),
-        .testTarget(
-            name: "CollectionViewTests",
-            dependencies: [
-                .target(name: "CollectionView"),
-                .product(name: "CustomDump", package: "swift-custom-dump"),
-            ]
-        ),
-        .target(
-            name: "ReportingFeature",
-            dependencies: [
-                .target(name: "DrawerFeature"),
-                .target(name: "Shared"),
-                .product(name: "SwiftCSV", package: "SwiftCSV"),
-                .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
-            ],
-            resources: [
-                .process("Resources"),
-            ]
-        ),
-    ]
+  name: "client-ios",
+  defaultLocalization: "en",
+  platforms: [
+    .iOS(.v14),
+  ],
+  products: [
+    .library(name: "Shared", targets: ["Shared"]),
+    .library(name: "AppCore", targets: ["AppCore"]),
+    .library(name: "Defaults", targets: ["Defaults"]),
+    .library(name: "Keychain", targets: ["Keychain"]),
+    .library(name: "Voxophone", targets: ["Voxophone"]),
+    .library(name: "AppFeature", targets: ["AppFeature"]),
+    .library(name: "InputField", targets: ["InputField"]),
+    .library(name: "ScanFeature", targets: ["ScanFeature"]),
+    .library(name: "MenuFeature", targets: ["MenuFeature"]),
+    .library(name: "ChatFeature", targets: ["ChatFeature"]),
+    .library(name: "CrashReport", targets: ["CrashReport"]),
+    .library(name: "UpdateErrors", targets: ["UpdateErrors"]),
+    .library(name: "CheckVersion", targets: ["CheckVersion"]),
+    .library(name: "AppResources", targets: ["AppResources"]),
+    .library(name: "TermsFeature", targets: ["TermsFeature"]),
+    .library(name: "AppNavigation", targets: ["AppNavigation"]),
+    .library(name: "BackupFeature", targets: ["BackupFeature"]),
+    .library(name: "LaunchFeature", targets: ["LaunchFeature"]),
+    .library(name: "SearchFeature", targets: ["SearchFeature"]),
+    .library(name: "DrawerFeature", targets: ["DrawerFeature"]),
+    .library(name: "WebsiteFeature", targets: ["WebsiteFeature"]),
+    .library(name: "RestoreFeature", targets: ["RestoreFeature"]),
+    .library(name: "ProfileFeature", targets: ["ProfileFeature"]),
+    .library(name: "ContactFeature", targets: ["ContactFeature"]),
+    .library(name: "FetchBannedList", targets: ["FetchBannedList"]),
+    .library(name: "SettingsFeature", targets: ["SettingsFeature"]),
+    .library(name: "ChatListFeature", targets: ["ChatListFeature"]),
+    .library(name: "RequestsFeature", targets: ["RequestsFeature"]),
+    .library(name: "ReportingFeature", targets: ["ReportingFeature"]),
+    .library(name: "ChatInputFeature", targets: ["ChatInputFeature"]),
+    .library(name: "GroupDraftFeature", targets: ["GroupDraftFeature"]),
+    .library(name: "ProcessBannedList", targets: ["ProcessBannedList"]),
+    .library(name: "OnboardingFeature", targets: ["OnboardingFeature"]),
+    .library(name: "CreateGroupFeature", targets: ["CreateGroupFeature"]),
+    .library(name: "CountryListFeature", targets: ["CountryListFeature"]),
+    .library(name: "PermissionsFeature", targets: ["PermissionsFeature"]),
+    .library(name: "ContactListFeature", targets: ["ContactListFeature"]),
+    .library(name: "RequestPermissionFeature", targets: ["RequestPermissionFeature"]),
+  ],
+  dependencies: [
+    .package(
+      url: "https://github.com/SnapKit/SnapKit",
+      .upToNextMajor(from: "5.0.1")
+    ),
+    .package(
+      url: "https://github.com/icanzilb/Retry.git",
+      .upToNextMajor(from: "0.6.3")
+    ),
+    .package(
+      url: "https://github.com/ekazaev/ChatLayout",
+      .upToNextMajor(from: "1.1.14")
+    ),
+    .package(
+      url: "https://github.com/ra1028/DifferenceKit",
+      .upToNextMajor(from: "1.2.0")
+    ),
+    .package(
+      url: "https://github.com/apple/swift-protobuf",
+      .upToNextMajor(from: "1.14.0")
+    ),
+    .package(
+      url: "https://github.com/darrarski/ScrollViewController",
+      .upToNextMajor(from: "1.2.0")
+    ),
+    .package(
+      url: "https://github.com/pointfreeco/combine-schedulers",
+      .upToNextMajor(from: "0.5.0")
+    ),
+    .package(
+      url: "https://github.com/kishikawakatsumi/KeychainAccess",
+      .upToNextMajor(from: "4.2.1")
+    ),
+    .package(
+      url: "https://git.xx.network/elixxir/elixxir-dapps-sdk-swift",
+      .upToNextMajor(from: "1.0.0")
+    ),
+    .package(
+      url: "https://git.xx.network/elixxir/client-ios-db.git",
+      .upToNextMajor(from: "1.1.0")
+    ),
+    .package(
+      url: "https://git.xx.network/elixxir/xxm-cloud-providers.git",
+      .upToNextMajor(from: "1.0.2")
+    ),
+    .package(
+      url: "https://github.com/firebase/firebase-ios-sdk.git",
+      .upToNextMajor(from: "8.10.0")
+    ),
+    .package(
+      url: "https://github.com/pointfreeco/swift-composable-architecture.git",
+      .upToNextMajor(from: "0.43.0")
+    ),
+    .package(
+      url: "https://github.com/swiftcsv/SwiftCSV.git",
+      from: "0.8.0"
+    ),
+    .package(
+      url: "https://github.com/apple/swift-log.git",
+      .upToNextMajor(from: "1.4.4")
+    ),
+    .package(
+      url: "https://github.com/kean/Pulse.git",
+      .upToNextMajor(from: "2.1.3")
+    ),
+    .package(
+      url: "https://github.com/pointfreeco/xctest-dynamic-overlay.git",
+      .upToNextMajor(from: "0.3.3")
+    ),
+  ],
+  targets: [
+    .target(
+      name: "AppFeature",
+      dependencies: [
+        .target(name: "AppCore"),
+        .target(name: "Keychain"),
+        .target(name: "ScanFeature"),
+        .target(name: "ChatFeature"),
+        .target(name: "MenuFeature"),
+        .target(name: "CrashReport"),
+        .target(name: "TermsFeature"),
+        .target(name: "BackupFeature"),
+        .target(name: "SearchFeature"),
+        .target(name: "LaunchFeature"),
+        .target(name: "ContactFeature"),
+        .target(name: "WebsiteFeature"),
+        .target(name: "RestoreFeature"),
+        .target(name: "ProfileFeature"),
+        .target(name: "ChatListFeature"),
+        .target(name: "SettingsFeature"),
+        .target(name: "RequestsFeature"),
+        .target(name: "ReportingFeature"),
+        .target(name: "GroupDraftFeature"),
+        .target(name: "OnboardingFeature"),
+        .target(name: "CreateGroupFeature"),
+        .target(name: "ContactListFeature"),
+        .target(name: "RequestPermissionFeature"),
+        .product(name: "PulseUI", package: "Pulse"), // TO REMOVE
+        .product(name: "PulseLogHandler", package: "Pulse"), // TO REMOVE
+      ]
+    ),
+    .testTarget(
+      name: "AppFeatureTests",
+      dependencies: [
+        .target(name: "AppFeature"),
+      ]
+    ),
+    .target(
+      name: "AppCore",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "AppResources"),
+        .product(name: "SnapKit", package: "SnapKit"),
+        .product(name: "Logging", package: "swift-log"),
+        .product(name: "XXModels", package: "client-ios-db"),
+        .product(name: "XXDatabase", package: "client-ios-db"),
+        .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
+        .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
+      ]
+    ),
+    .target(
+      name: "CheckVersion",
+      dependencies: [
+        .product(
+          name: "Dependencies",
+          package: "swift-composable-architecture"
+        ),
+      ]
+    ),
+    .target(
+      name: "Voxophone",
+      dependencies: [
+        .target(name: "Shared"),
+      ]
+    ),
+    .target(name: "WebsiteFeature"),
+    .target(
+      name: "CrashReport",
+      dependencies: [
+        .product(
+          name: "FirebaseCrashlytics",
+          package: "firebase-ios-sdk"
+        ),
+        .product(
+          name: "Dependencies",
+          package: "swift-composable-architecture"
+        ),
+      ]
+    ),
+    .target(
+      name: "AppNavigation",
+      dependencies: [
+        .product(
+          name: "XXModels",
+          package: "client-ios-db"
+        ),
+        .product(
+          name: "Dependencies",
+          package: "swift-composable-architecture"
+        ),
+      ]
+    ),
+    .target(
+      name: "CreateGroupFeature",
+      dependencies: [
+        .target(name: "AppCore")
+      ]
+    ),
+    .target(
+      name: "GroupDraftFeature",
+      dependencies: [
+        .target(name: "AppCore")
+      ]
+    ),
+    .target(
+      name: "PermissionsFeature",
+      dependencies: [
+        .product(
+          name: "XCTestDynamicOverlay",
+          package: "xctest-dynamic-overlay"
+        ),
+        .product(
+          name: "Dependencies",
+          package: "swift-composable-architecture"
+        ),
+      ]
+    ),
+    .target(
+      name: "AppResources",
+      exclude: [
+        "swiftgen.yml",
+      ],
+      resources: [
+        .process("Resources")
+      ]
+    ),
+    .target(
+      name: "InputField",
+      dependencies: [
+        .target(name: "Shared"),
+      ]
+    ),
+    .target(
+      name: "RequestPermissionFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "AppCore"),
+        .target(name: "AppResources"),
+        .target(name: "AppNavigation"),
+        .target(name: "PermissionsFeature"),
+        .product(
+          name: "Dependencies",
+          package: "swift-composable-architecture"
+        ),
+      ]
+    ),
+    .target(
+      name: "Keychain",
+      dependencies: [
+        .product(
+          name: "KeychainAccess",
+          package: "KeychainAccess"
+        ),
+        .product(
+          name: "Dependencies",
+          package: "swift-composable-architecture"
+        ),
+      ]
+    ),
+    .target(
+      name: "Defaults",
+      dependencies: [
+        .product(
+          name: "Dependencies",
+          package: "swift-composable-architecture"
+        ),
+      ]
+    ),
+    .target(
+      name: "CountryListFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "AppCore")
+      ]
+    ),
+    .target(
+      name: "DrawerFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "InputField"),
+        .product(name: "ScrollViewController", package: "ScrollViewController"),
+      ]
+    ),
+    .target(
+      name: "Shared",
+      dependencies: [
+        .product(name: "SnapKit", package: "SnapKit"),
+        .product(name: "ChatLayout", package: "ChatLayout"),
+        .product(name: "DifferenceKit", package: "DifferenceKit"),
+        .product(name: "SwiftProtobuf", package: "swift-protobuf"),
+      ],
+      resources: [
+        .process("Resources"),
+      ]
+    ),
+    .target(
+      name: "ChatInputFeature",
+      dependencies: [
+        .target(
+          name: "Voxophone"
+        ),
+        .product(
+          name: "ComposableArchitecture",
+          package: "swift-composable-architecture"
+        ),
+      ]
+    ),
+    .target(
+      name: "RestoreFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "AppCore"),
+        .product(name: "XXDatabase", package: "client-ios-db"),
+        .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "CloudFilesDrive", package: "xxm-cloud-providers"),
+        .product(name: "CloudFilesDropbox", package: "xxm-cloud-providers"),
+        .product(name: "CloudFilesSFTP", package: "xxm-cloud-providers"),
+        .product(name: "CloudFilesICloud", package: "xxm-cloud-providers"),
+      ]
+    ),
+    .target(
+      name: "ContactFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "InputField"),
+        .target(name: "ChatFeature"),
+        .product(name: "CombineSchedulers", package: "combine-schedulers"),
+        .product(name: "ScrollViewController", package: "ScrollViewController"),
+      ]
+    ),
+    .target(
+      name: "ChatFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "AppCore"),
+        .target(name: "Defaults"),
+        .target(name: "Keychain"),
+        .target(name: "Voxophone"),
+        .target(name: "DrawerFeature"),
+        .target(name: "ChatInputFeature"),
+        .target(name: "ReportingFeature"),
+        .target(name: "RequestPermissionFeature"),
+        .product(name: "ChatLayout", package: "ChatLayout"),
+        .product(name: "DifferenceKit", package: "DifferenceKit"),
+        .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "ScrollViewController", package: "ScrollViewController"),
+        .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
+      ]
+    ),
+    .target(
+      name: "SearchFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "ContactFeature"),
+        .target(name: "CountryListFeature"),
+        .product(name: "Retry", package: "Retry"),
+        .product(name: "XXDatabase", package: "client-ios-db"),
+      ]
+    ),
+    .target(
+      name: "LaunchFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "Defaults"),
+        .target(name: "UpdateErrors"),
+        .target(name: "CheckVersion"),
+        .target(name: "BackupFeature"),
+        .target(name: "FetchBannedList"),
+        .target(name: "ReportingFeature"),
+        .target(name: "ProcessBannedList"),
+        .target(name: "RequestPermissionFeature"),
+        .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "CloudFilesSFTP", package: "xxm-cloud-providers"),
+        .product(name: "CombineSchedulers", package: "combine-schedulers"),
+        .product(name: "CloudFilesDropbox", package: "xxm-cloud-providers"),
+        .product(name: "XXLegacyDatabaseMigrator", package: "client-ios-db"),
+        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
+      ]
+    ),
+    .target(
+      name: "TermsFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "Defaults"),
+        .target(name: "AppNavigation"),
+        .product(
+          name: "ComposableArchitecture",
+          package: "swift-composable-architecture"
+        ),
+      ]
+    ),
+    .target(
+      name: "UpdateErrors",
+      dependencies: [
+        .product(
+          name: "XXClient",
+          package: "elixxir-dapps-sdk-swift"
+        ),
+        .product(
+          name: "XCTestDynamicOverlay",
+          package: "xctest-dynamic-overlay"
+        ),
+        .product(
+          name: "Dependencies",
+          package: "swift-composable-architecture"
+        ),
+      ]
+    ),
+    .target(
+      name: "ProcessBannedList",
+      dependencies: [
+        .product(
+          name: "SwiftCSV",
+          package: "SwiftCSV"
+        ),
+        .product(
+          name: "XCTestDynamicOverlay",
+          package: "xctest-dynamic-overlay"
+        ),
+        .product(
+          name: "Dependencies",
+          package: "swift-composable-architecture"
+        ),
+      ]
+    ),
+    .target(
+      name: "FetchBannedList",
+      dependencies: [
+        .product(
+          name: "XCTestDynamicOverlay",
+          package: "xctest-dynamic-overlay"
+        ),
+        .product(
+          name: "Dependencies",
+          package: "swift-composable-architecture"
+        ),
+      ]
+    ),
+    .target(
+      name: "RequestsFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "ContactFeature"),
+        .product(
+          name: "DifferenceKit",
+          package: "DifferenceKit"
+        ),
+      ]
+    ),
+    .target(
+      name: "ProfileFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "Keychain"),
+        .target(name: "Defaults"),
+        .target(name: "InputField"),
+        .target(name: "MenuFeature"),
+        .target(name: "DrawerFeature"),
+        .target(name: "BackupFeature"),
+        .target(name: "CountryListFeature"),
+        .target(name: "RequestPermissionFeature"),
+        .product(name: "CombineSchedulers", package: "combine-schedulers"),
+        .product(name: "ScrollViewController", package: "ScrollViewController"),
+        .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
+      ]
+    ),
+    .target(
+      name: "ChatListFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "Defaults"),
+        .target(name: "MenuFeature"),
+        .target(name: "ChatFeature"),
+        .target(name: "ProfileFeature"),
+        .target(name: "SettingsFeature"),
+        .target(name: "ContactListFeature"),
+        .product(name: "DifferenceKit", package: "DifferenceKit"),
+      ]
+    ),
+    .target(
+      name: "OnboardingFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "AppCore"),
+        .target(name: "Defaults"),
+        .target(name: "Keychain"),
+        .target(name: "InputField"),
+        .target(name: "DrawerFeature"),
+        .target(name: "AppNavigation"),
+        .target(name: "CountryListFeature"),
+        .target(name: "RequestPermissionFeature"),
+        .product(name: "CombineSchedulers", package: "combine-schedulers"),
+        .product(name: "ScrollViewController", package: "ScrollViewController"),
+        .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
+      ]
+    ),
+    .target(
+      name: "MenuFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "AppCore"),
+        .target(name: "Defaults"),
+        .target(name: "DrawerFeature"),
+        .target(name: "ReportingFeature"),
+        .product(
+          name: "XXClient",
+          package: "elixxir-dapps-sdk-swift"
+        ),
+      ]
+    ),
+    .target(
+      name: "BackupFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "AppCore"),
+        .target(name: "InputField"),
+        .target(name: "DrawerFeature"),
+        .product(
+          name: "XXClient",
+          package: "elixxir-dapps-sdk-swift"
+        ),
+        .product(
+          name: "CloudFilesSFTP",
+          package: "xxm-cloud-providers"
+        ),
+        .product(
+          name: "CloudFilesDrive",
+          package: "xxm-cloud-providers"
+        ),
+        .product(
+          name: "CloudFilesICloud",
+          package: "xxm-cloud-providers"
+        ),
+        .product(
+          name: "CloudFilesDropbox",
+          package: "xxm-cloud-providers"
+        ),
+        .product(
+          name: "XXMessengerClient",
+          package: "elixxir-dapps-sdk-swift"
+        ),
+        .product(
+          name: "ComposableArchitecture",
+          package: "swift-composable-architecture"
+        ),
+      ]
+    ),
+    .target(
+      name: "ScanFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "ContactFeature"),
+        .target(name: "CountryListFeature"),
+        .target(name: "RequestPermissionFeature"),
+        .product(name: "SnapKit", package: "SnapKit"),
+      ]
+    ),
+    .target(
+      name: "ContactListFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "ContactFeature"),
+        .product(name: "DifferenceKit", package: "DifferenceKit"),
+      ]
+    ),
+    .target(
+      name: "SettingsFeature",
+      dependencies: [
+        .target(name: "Shared"),
+        .target(name: "Defaults"),
+        .target(name: "Keychain"),
+        .target(name: "InputField"),
+        .target(name: "MenuFeature"),
+        .target(name: "CrashReport"),
+        .target(name: "DrawerFeature"),
+        .target(name: "RequestPermissionFeature"),
+        .product(name: "CombineSchedulers", package: "combine-schedulers"),
+        .product(name: "ScrollViewController", package: "ScrollViewController"),
+      ]
+    ),
+    .target(
+      name: "ReportingFeature",
+      dependencies: [
+        .target(name: "DrawerFeature"),
+        .target(name: "Shared"),
+        .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
+      ],
+      resources: [
+        .process("Resources"),
+      ]
+    ),
+  ]
 )
diff --git a/Sources/App/AppDelegate.swift b/Sources/App/AppDelegate.swift
deleted file mode 100644
index 4143a5f270c7179e9f11892702bf7f7db79a6800..0000000000000000000000000000000000000000
--- a/Sources/App/AppDelegate.swift
+++ /dev/null
@@ -1,204 +0,0 @@
-import UIKit
-import BackgroundTasks
-
-import Theme
-import XXModels
-import XXLogger
-import Defaults
-import Integration
-import PushFeature
-import ToastFeature
-import SwiftyDropbox
-import LaunchFeature
-import DropboxFeature
-import CrashReporting
-import DependencyInjection
-
-public class AppDelegate: UIResponder, UIApplicationDelegate {
-    @Dependency private var pushRouter: PushRouter
-    @Dependency private var pushHandler: PushHandling
-    @Dependency private var crashReporter: CrashReporter
-    @Dependency private var dropboxService: DropboxInterface
-
-    @KeyObject(.hideAppList, defaultValue: false) var hideAppList: Bool
-    @KeyObject(.recordingLogs, defaultValue: true) var recordingLogs: Bool
-    @KeyObject(.crashReporting, defaultValue: true) var isCrashReportingEnabled: Bool
-
-    var calledStopNetwork = false
-    var forceFailedPendingMessages = false
-
-    var coverView: UIView?
-    var backgroundTimer: Timer?
-    public var window: UIWindow?
-
-    public func application(
-        _ application: UIApplication,
-        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
-    ) -> Bool {
-        #if DEBUG
-        DependencyRegistrator.registerForMock()
-        #else
-        DependencyRegistrator.registerForLive()
-        #endif
-
-        if recordingLogs {
-            XXLogger.start()
-        }
-
-        crashReporter.configure()
-        crashReporter.setEnabled(isCrashReportingEnabled)
-
-        UNUserNotificationCenter.current().delegate = self
-
-        let window = Window()
-        let navController = UINavigationController(rootViewController: LaunchController())
-        window.rootViewController = StatusBarViewController(ToastViewController(navController))
-        window.backgroundColor = UIColor.white
-        window.makeKeyAndVisible()
-        self.window = window
-
-        DependencyInjection.Container.shared.register(
-            PushRouter.live(navigationController: navController)
-        )
-
-        return true
-    }
-
-    public func application(application: UIApplication, shouldAllowExtensionPointIdentifier: String) -> Bool {
-        false
-    }
-
-    public func applicationDidEnterBackground(_ application: UIApplication) {
-        if let session = try? DependencyInjection.Container.shared.resolve() as SessionType {
-            let backgroundTask = application.beginBackgroundTask(withName: "xx.stop.network") {}
-
-            // An option here would be: create async completion closure
-
-            backgroundTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
-                guard UIApplication.shared.backgroundTimeRemaining > 8 else {
-                    if !self.calledStopNetwork {
-                        self.calledStopNetwork = true
-                        session.stop()
-                    } else {
-                        if session.hasRunningTasks == false {
-                            application.endBackgroundTask(backgroundTask)
-                            timer.invalidate()
-                        }
-                    }
-
-                    return
-                }
-
-                guard UIApplication.shared.backgroundTimeRemaining > 9 else {
-                    if !self.forceFailedPendingMessages {
-                        self.forceFailedPendingMessages = true
-
-                        let query = Message.Query(status: [.sending])
-                        let assignment = Message.Assignments(status: .sendingFailed)
-                        _ = try? session.dbManager.bulkUpdateMessages(query, assignment)
-                    }
-
-                    return
-                }
-            }
-        }
-    }
-
-    public func applicationWillResignActive(_ application: UIApplication) {
-        if hideAppList {
-            coverView?.removeFromSuperview()
-            coverView = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
-            coverView?.frame = window?.bounds ?? .zero
-            window?.addSubview(coverView!)
-        }
-    }
-    
-    public func applicationWillTerminate(_ application: UIApplication) {
-        if let session = try? DependencyInjection.Container.shared.resolve() as SessionType {
-            session.stop()
-        }
-    }
-
-    public func applicationWillEnterForeground(_ application: UIApplication) {
-        if backgroundTimer != nil {
-            backgroundTimer?.invalidate()
-            backgroundTimer = nil
-        }
-
-        if let session = try? DependencyInjection.Container.shared.resolve() as SessionType {
-            guard self.calledStopNetwork == true else { return }
-            session.start()
-            self.calledStopNetwork = false
-        }
-    }
-
-    public func applicationDidBecomeActive(_ application: UIApplication) {
-        application.applicationIconBadgeNumber = 0
-        coverView?.removeFromSuperview()
-    }
-
-    public func application(
-        _ app: UIApplication,
-        open url: URL,
-        options: [UIApplication.OpenURLOptionsKey : Any] = [:]
-    ) -> Bool {
-        dropboxService.handleOpenUrl(url)
-    }
-
-    public func application(
-        _ application: UIApplication,
-        continue userActivity: NSUserActivity,
-        restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
-    ) -> Bool {
-        guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
-              let incomingURL = userActivity.webpageURL,
-              let username = getUsernameFromInvitationDeepLink(incomingURL) else {
-            return false
-        }
-
-        let router = try! DependencyInjection.Container.shared.resolve() as PushRouter
-        router.navigateTo(.search(username: username), {})
-        return true
-    }
-}
-
-func getUsernameFromInvitationDeepLink(_ url: URL) -> String? {
-    if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
-       components.scheme == "https",
-       components.host == "elixxir.io",
-       components.path == "/connect",
-       let queryItem = components.queryItems?.first(where: { $0.name == "username" }),
-       let username = queryItem.value {
-        return username
-    }
-
-    return nil
-}
-
-// MARK: Notifications
-
-extension AppDelegate: UNUserNotificationCenterDelegate {
-    public func userNotificationCenter(
-        _ center: UNUserNotificationCenter,
-        didReceive response: UNNotificationResponse,
-        withCompletionHandler completionHandler: @escaping () -> Void
-    ) {
-        let userInfo = response.notification.request.content.userInfo
-        pushHandler.handleAction(pushRouter, userInfo, completionHandler)
-    }
-
-    public func application(
-        _ application: UIApplication,
-        didReceiveRemoteNotification notification: [AnyHashable: Any],
-        fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
-    ) {
-        pushHandler.handlePush(notification, completionHandler)
-    }
-
-    public func application(
-        _: UIApplication,
-        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
-    ) {
-        pushHandler.registerToken(deviceToken)
-    }
-}
diff --git a/Sources/App/DependencyRegistrator.swift b/Sources/App/DependencyRegistrator.swift
deleted file mode 100644
index 182ac2f5dbe6af2ee532e45b08e9b73d49a1e0a8..0000000000000000000000000000000000000000
--- a/Sources/App/DependencyRegistrator.swift
+++ /dev/null
@@ -1,272 +0,0 @@
-// MARK: SDK
-
-import UIKit
-import Network
-import QuickLook
-import MobileCoreServices
-
-// MARK: Isolated features
-
-import HUD
-import Theme
-import Bindings
-import XXLogger
-import Keychain
-import Defaults
-import Countries
-import Voxophone
-import Integration
-import Permissions
-import PushFeature
-import SFTPFeature
-import CrashService
-import ToastFeature
-import iCloudFeature
-import CrashReporting
-import NetworkMonitor
-import DropboxFeature
-import VersionChecking
-import ReportingFeature
-import GoogleDriveFeature
-import DependencyInjection
-
-// MARK: UI Features
-
-import ScanFeature
-import ChatFeature
-import MenuFeature
-import TermsFeature
-import BackupFeature
-import SearchFeature
-import LaunchFeature
-import RestoreFeature
-import ContactFeature
-import ProfileFeature
-import ChatListFeature
-import SettingsFeature
-import RequestsFeature
-import OnboardingFeature
-import ContactListFeature
-
-struct DependencyRegistrator {
-    static private let container = DependencyInjection.Container.shared
-
-    // MARK: MOCK
-
-    static func registerForMock() {
-        container.register(XXLogger.noop)
-        container.register(CrashReporter.noop)
-        container.register(VersionChecker.mock)
-        container.register(ReportingStatus.mock())
-        container.register(SendReport.mock())
-        container.register(XXNetwork<BindingsMock>() as XXNetworking)
-        container.register(MockNetworkMonitor() as NetworkMonitoring)
-        container.register(KeyObjectStore.userDefaults)
-        container.register(MockPushHandler() as PushHandling)
-        container.register(MockKeychainHandler() as KeychainHandling)
-        container.register(MockPermissionHandler() as PermissionHandling)
-
-        /// Restore / Backup
-
-        container.register(SFTPService.mock)
-        container.register(iCloudServiceMock() as iCloudInterface)
-        container.register(DropboxServiceMock() as DropboxInterface)
-        container.register(GoogleDriveServiceMock() as GoogleDriveInterface)
-
-        registerCommonDependencies()
-    }
-
-    // MARK: LIVE
-
-    static func registerForLive() {
-        container.register(KeyObjectStore.userDefaults)
-        container.register(XXLogger.live())
-        container.register(CrashReporter.live)
-        container.register(VersionChecker.live())
-        container.register(ReportingStatus.live())
-        container.register(SendReport.live)
-
-        container.register(XXNetwork<BindingsClient>() as XXNetworking)
-        container.register(NetworkMonitor() as NetworkMonitoring)
-        container.register(PushHandler() as PushHandling)
-        container.register(KeychainHandler() as KeychainHandling)
-        container.register(PermissionHandler() as PermissionHandling)
-
-        /// Restore / Backup
-
-        container.register(SFTPService.live)
-        container.register(iCloudService() as iCloudInterface)
-        container.register(DropboxService() as DropboxInterface)
-        container.register(GoogleDriveService() as GoogleDriveInterface)
-
-        registerCommonDependencies()
-    }
-
-    // MARK: COMMON
-
-    static private func registerCommonDependencies() {
-        container.register(Voxophone())
-        container.register(BackupService())
-        container.register(MakeAppScreenshot.live)
-        container.register(FetchBannedList.live)
-        container.register(ProcessBannedList.live)
-        container.register(MakeReportDrawer.live)
-
-        // MARK: Isolated
-
-        container.register(HUD())
-        container.register(ThemeController() as ThemeControlling)
-        container.register(ToastController())
-        container.register(StatusBarController() as StatusBarStyleControlling)
-
-        // MARK: Coordinators
-
-        container.register(
-            TermsCoordinator.live(
-                usernameFactory: OnboardingUsernameController.init(_:),
-                chatListFactory: ChatListController.init
-            )
-        )
-
-        container.register(
-            LaunchCoordinator(
-                termsFactory: TermsConditionsController.init(_:),
-                searchFactory: SearchContainerController.init,
-                requestsFactory: RequestsContainerController.init,
-                chatListFactory: ChatListController.init,
-                onboardingFactory: OnboardingStartController.init(_:),
-                singleChatFactory: SingleChatController.init(_:),
-                groupChatFactory: GroupChatController.init(_:)
-            ) as LaunchCoordinating)
-
-        container.register(
-            BackupCoordinator(
-                passphraseFactory: BackupPassphraseController.init(_:_:)
-            ) as BackupCoordinating)
-
-        container.register(
-            MenuCoordinator(
-                scanFactory: ScanContainerController.init,
-                chatsFactory: ChatListController.init,
-                profileFactory: ProfileController.init,
-                settingsFactory: SettingsController.init,
-                contactsFactory: ContactListController.init,
-                requestsFactory: RequestsContainerController.init
-            ) as MenuCoordinating)
-
-        container.register(
-            SearchCoordinator(
-                contactsFactory: ContactListController.init,
-                requestsFactory: RequestsContainerController.init,
-                contactFactory: ContactController.init(_:),
-                countriesFactory: CountryListController.init(_:)
-            ) as SearchCoordinating)
-
-        container.register(
-            ProfileCoordinator(
-                emailFactory: ProfileEmailController.init,
-                phoneFactory: ProfilePhoneController.init,
-                imagePickerFactory: UIImagePickerController.init,
-                permissionFactory: RequestPermissionController.init,
-                sideMenuFactory: MenuController.init(_:_:),
-                countriesFactory: CountryListController.init(_:),
-                codeFactory: ProfileCodeController.init(_:_:)
-            ) as ProfileCoordinating)
-
-        container.register(
-            SettingsCoordinator(
-                backupFactory: BackupController.init,
-                advancedFactory: SettingsAdvancedController.init,
-                accountDeleteFactory: AccountDeleteController.init,
-                sideMenuFactory: MenuController.init(_:_:)
-            ) as SettingsCoordinating)
-
-        container.register(
-            RestoreCoordinator(
-                successFactory: RestoreSuccessController.init,
-                chatListFactory: ChatListController.init,
-                restoreFactory: RestoreController.init(_:_:),
-                passphraseFactory: RestorePassphraseController.init(_:)
-            ) as RestoreCoordinating)
-
-        container.register(
-            ChatCoordinator(
-                retryFactory: RetrySheetController.init,
-                webFactory: WebScreen.init(url:),
-                previewFactory: QLPreviewController.init,
-                contactFactory: ContactController.init(_:),
-                imagePickerFactory: UIImagePickerController.init,
-                permissionFactory: RequestPermissionController.init
-            ) as ChatCoordinating)
-
-        container.register(
-            ContactCoordinator(
-                requestsFactory: RequestsContainerController.init,
-                singleChatFactory: SingleChatController.init(_:),
-                imagePickerFactory: UIImagePickerController.init,
-                nicknameFactory: NicknameController.init(_:_:)
-            ) as ContactCoordinating)
-
-        container.register(
-            RequestsCoordinator(
-                searchFactory: SearchContainerController.init,
-                contactFactory: ContactController.init(_:),
-                singleChatFactory: SingleChatController.init(_:),
-                groupChatFactory: GroupChatController.init(_:),
-                sideMenuFactory: MenuController.init(_:_:),
-                nicknameFactory: NicknameController.init(_:_:)
-            ) as RequestsCoordinating)
-
-        container.register(
-            OnboardingCoordinator(
-                emailFactory: OnboardingEmailController.init,
-                phoneFactory: OnboardingPhoneController.init,
-                searchFactory: SearchContainerController.init,
-                welcomeFactory: OnboardingWelcomeController.init,
-                chatListFactory: ChatListController.init,
-                termsFactory: TermsConditionsController.init(_:),
-                usernameFactory: OnboardingUsernameController.init(_:),
-                restoreListFactory: RestoreListController.init(_:),
-                successFactory: OnboardingSuccessController.init(_:),
-                countriesFactory: CountryListController.init(_:),
-                phoneConfirmationFactory: OnboardingPhoneConfirmationController.init(_:_:),
-                emailConfirmationFactory: OnboardingEmailConfirmationController.init(_:_:)
-            ) as OnboardingCoordinating)
-
-        container.register(
-            ContactListCoordinator(
-                scanFactory: ScanContainerController.init,
-                searchFactory: SearchContainerController.init,
-                newGroupFactory: CreateGroupController.init,
-                requestsFactory: RequestsContainerController.init,
-                contactFactory: ContactController.init(_:),
-                singleChatFactory: SingleChatController.init(_:),
-                groupChatFactory: GroupChatController.init(_:),
-                sideMenuFactory: MenuController.init(_:_:),
-                groupDrawerFactory: CreateDrawerController.init(_:_:)
-            ) as ContactListCoordinating)
-
-        container.register(
-            ScanCoordinator(
-                emailFactory: ProfileEmailController.init,
-                phoneFactory: ProfilePhoneController.init,
-                contactsFactory: ContactListController.init,
-                requestsFactory: RequestsContainerController.init,
-                contactFactory: ContactController.init(_:),
-                sideMenuFactory: MenuController.init(_:_:)
-            ) as ScanCoordinating)
-
-
-        container.register(
-            ChatListCoordinator(
-                scanFactory: ScanContainerController.init,
-                searchFactory: SearchContainerController.init,
-                newGroupFactory: CreateGroupController.init,
-                contactsFactory: ContactListController.init,
-                contactFactory: ContactController.init(_:),
-                singleChatFactory: SingleChatController.init(_:),
-                groupChatFactory: GroupChatController.init(_:),
-                sideMenuFactory: MenuController.init(_:_:)
-            ) as ChatListCoordinating)
-    }
-}
diff --git a/Sources/App/PushRouter.swift b/Sources/App/PushRouter.swift
deleted file mode 100644
index a2d2d809d54818566c1aec693ed4ad07937c6999..0000000000000000000000000000000000000000
--- a/Sources/App/PushRouter.swift
+++ /dev/null
@@ -1,52 +0,0 @@
-import UIKit
-import PushFeature
-import Integration
-import ChatFeature
-import SearchFeature
-import LaunchFeature
-import ChatListFeature
-import RequestsFeature
-import DependencyInjection
-
-extension PushRouter {
-    static func live(navigationController: UINavigationController) -> PushRouter {
-        PushRouter { route, completion in
-            if let launchController = navigationController.viewControllers.last as? LaunchController {
-                launchController.pendingPushRoute = route
-            } else {
-                switch route {
-                case .requests:
-                    if !(navigationController.viewControllers.last is RequestsContainerController) {
-                        navigationController.setViewControllers([RequestsContainerController()], animated: true)
-                    }
-                case .search(username: let username):
-                    if let _ = try? DependencyInjection.Container.shared.resolve() as SessionType,
-                       !(navigationController.viewControllers.last is SearchContainerController) {
-                        navigationController.setViewControllers([
-                            ChatListController(),
-                            SearchContainerController(username)
-                        ], animated: true)
-                    }
-                case .contactChat(id: let id):
-                    if let session = try? DependencyInjection.Container.shared.resolve() as SessionType,
-                       let contact = try? session.dbManager.fetchContacts(.init(id: [id])).first {
-                        navigationController.setViewControllers([
-                            ChatListController(),
-                            SingleChatController(contact)
-                        ], animated: true)
-                    }
-                case .groupChat(id: let id):
-                    if let session = try? DependencyInjection.Container.shared.resolve() as SessionType,
-                       let info = try? session.dbManager.fetchGroupInfos(.init(groupId: id)).first {
-                        navigationController.setViewControllers([
-                            ChatListController(),
-                            GroupChatController(info)
-                        ], animated: true)
-                    }
-                }
-            }
-
-            completion()
-        }
-    }
-}
diff --git a/Sources/AppCore/AppDependencies.swift b/Sources/AppCore/AppDependencies.swift
new file mode 100644
index 0000000000000000000000000000000000000000..9c4c392e6b47067a5ca46d226e6ce3ecf623e59e
--- /dev/null
+++ b/Sources/AppCore/AppDependencies.swift
@@ -0,0 +1,185 @@
+import XXClient
+import Foundation
+import XXMessengerClient
+import XCTestDynamicOverlay
+import ComposableArchitecture
+
+public struct AppDependencies {
+  public var networkMonitor: NetworkMonitor
+  public var toastManager: ToastManager
+  public var backupHandler: BackupCallbackHandler
+  public var hudManager: HUDManager
+  public var dbManager: DBManager
+  public var groupRequest: GroupRequestHandler
+  public var groupMessageHandler: GroupMessageHandler
+  public var statusBar: StatusBarStylist
+  public var messenger: Messenger
+  public var authHandler: AuthCallbackHandler
+  public var backupStorage: BackupStorage
+  public var mainQueue: AnySchedulerOf<DispatchQueue>
+  public var bgQueue: AnySchedulerOf<DispatchQueue>
+  public var now: () -> Date
+  public var sendMessage: SendMessage
+  public var sendImage: SendImage
+  public var messageListener: MessageListenerHandler
+  public var receiveFileHandler: ReceiveFileHandler
+  public var log: Logger
+  public var loadData: URLDataLoader
+}
+
+extension AppDependencies {
+  public static func live() -> AppDependencies {
+    let dbManager = DBManager.live(
+      url: FileManager.default.containerURL(
+        forSecurityApplicationGroupIdentifier: "group.elixxir.messenger"
+      )!
+    )
+    var messengerEnv = MessengerEnvironment.live()
+    messengerEnv.udEnvironment = .init(
+      address: Constants.address,
+      cert: Constants.cert.data(using: .utf8)!,
+      contact: Constants.contact.data(using: .utf8)!
+    )
+    messengerEnv.serviceList = .userDefaults(
+      key: "preImage",
+      userDefaults: UserDefaults(suiteName: "group.elixxir.messenger")!
+    )
+    let messenger = Messenger.live(messengerEnv)
+    let now: () -> Date = Date.init
+
+    return AppDependencies(
+      networkMonitor: .live(),
+      toastManager: .live(),
+      backupHandler: .live(
+        messenger: messenger
+      ),
+      hudManager: .live(),
+      dbManager: dbManager,
+      groupRequest: .live(
+        messenger: messenger,
+        db: dbManager.getDB
+      ),
+      groupMessageHandler: .live(
+        messenger: messenger,
+        db: dbManager.getDB
+      ),
+      statusBar: .live(),
+      messenger: messenger,
+      authHandler: .live(
+        messenger: messenger,
+        handleRequest: .live(
+          db: dbManager.getDB,
+          messenger: messenger,
+          now: now
+        ),
+        handleConfirm: .live(db: dbManager.getDB),
+        handleReset: .live(db: dbManager.getDB)
+      ),
+      backupStorage: .onDisk(),
+      mainQueue: DispatchQueue.main.eraseToAnyScheduler(),
+      bgQueue: DispatchQueue(label: "xx-messenger", qos: .userInitiated).eraseToAnyScheduler(),
+      now: now,
+      sendMessage: .live(
+        messenger: messenger,
+        db: dbManager.getDB,
+        now: now
+      ),
+      sendImage: .live(
+        messenger: messenger,
+        db: dbManager.getDB,
+        now: now
+      ),
+      messageListener: .live(
+        messenger: messenger,
+        db: dbManager.getDB
+      ),
+      receiveFileHandler: .live(
+        messenger: messenger,
+        db: dbManager.getDB,
+        now: now
+      ),
+      log: .live(),
+      loadData: .live
+    )
+  }
+
+  public static let unimplemented = AppDependencies(
+    networkMonitor: .unimplemented,
+    toastManager: .unimplemented,
+    backupHandler: .unimplemented,
+    hudManager: .unimplemented,
+    dbManager: .unimplemented,
+    groupRequest: .unimplemented,
+    groupMessageHandler: .unimplemented,
+    statusBar: .unimplemented,
+    messenger: .unimplemented,
+    authHandler: .unimplemented,
+    backupStorage: .unimplemented,
+    mainQueue: .unimplemented,
+    bgQueue: .unimplemented,
+    now: XCTestDynamicOverlay.unimplemented(
+      "\(Self.self)",
+      placeholder: Date(timeIntervalSince1970: 0)
+    ),
+    sendMessage: .unimplemented,
+    sendImage: .unimplemented,
+    messageListener: .unimplemented,
+    receiveFileHandler: .unimplemented,
+    log: .unimplemented,
+    loadData: .unimplemented
+  )
+}
+
+private enum AppDependenciesKey: DependencyKey {
+  static let liveValue: AppDependencies = .live()
+  static let testValue: AppDependencies = .unimplemented
+}
+
+extension DependencyValues {
+  public var app: AppDependencies {
+    get { self[AppDependenciesKey.self] }
+    set { self[AppDependenciesKey.self] = newValue }
+  }
+}
+
+private enum Constants {
+  static let address = "46.101.98.49:18001"
+  static let cert = """
+-----BEGIN CERTIFICATE-----
+MIIDbDCCAlSgAwIBAgIJAOUNtZneIYECMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQx
+GzAZBgNVBAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJp
+cDAeFw0xOTAzMDUxODM1NDNaFw0yOTAzMDIxODM1NDNaMGgxCzAJBgNVBAYTAlVT
+MRMwEQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQHDAlDbGFyZW1vbnQxGzAZBgNV
+BAoMElByaXZhdGVncml0eSBDb3JwLjETMBEGA1UEAwwKKi5jbWl4LnJpcDCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPP0WyVkfZA/CEd2DgKpcudn0oDh
+Dwsjmx8LBDWsUgQzyLrFiVigfUmUefknUH3dTJjmiJtGqLsayCnWdqWLHPJYvFfs
+WYW0IGF93UG/4N5UAWO4okC3CYgKSi4ekpfw2zgZq0gmbzTnXcHF9gfmQ7jJUKSE
+tJPSNzXq+PZeJTC9zJAb4Lj8QzH18rDM8DaL2y1ns0Y2Hu0edBFn/OqavBJKb/uA
+m3AEjqeOhC7EQUjVamWlTBPt40+B/6aFJX5BYm2JFkRsGBIyBVL46MvC02MgzTT9
+bJIJfwqmBaTruwemNgzGu7Jk03hqqS1TUEvSI6/x8bVoba3orcKkf9HsDjECAwEA
+AaMZMBcwFQYDVR0RBA4wDIIKKi5jbWl4LnJpcDANBgkqhkiG9w0BAQUFAAOCAQEA
+neUocN4AbcQAC1+b3To8u5UGdaGxhcGyZBlAoenRVdjXK3lTjsMdMWb4QctgNfIf
+U/zuUn2mxTmF/ekP0gCCgtleZr9+DYKU5hlXk8K10uKxGD6EvoiXZzlfeUuotgp2
+qvI3ysOm/hvCfyEkqhfHtbxjV7j7v7eQFPbvNaXbLa0yr4C4vMK/Z09Ui9JrZ/Z4
+cyIkxfC6/rOqAirSdIp09EGiw7GM8guHyggE4IiZrDslT8V3xIl985cbCxSxeW1R
+tgH4rdEXuVe9+31oJhmXOE9ux2jCop9tEJMgWg7HStrJ5plPbb+HmjoX3nBO04E5
+6m52PyzMNV+2N21IPppKwA==
+-----END CERTIFICATE-----
+"""
+  static let contact = """
+<xxc(2)7mbKFLE201WzH4SGxAOpHjjehwztIV+KGifi5L/PYPcDkAZiB9kZo+Dl3Vc7dD2SdZCFMOJVgwqGzfYRDkjc8RGEllBqNxq2sRRX09iQVef0kJQUgJCHNCOcvm6Ki0JJwvjLceyFh36iwK8oLbhLgqEZY86UScdACTyBCzBIab3ob5mBthYc3mheV88yq5PGF2DQ+dEvueUm+QhOSfwzppAJA/rpW9Wq9xzYcQzaqc3ztAGYfm2BBAHS7HVmkCbvZ/K07Xrl4EBPGHJYq12tWAN/C3mcbbBYUOQXyEzbSl/mO7sL3ORr0B4FMuqCi8EdlD6RO52pVhY+Cg6roRH1t5Ng1JxPt8Mv1yyjbifPhZ5fLKwxBz8UiFORfk0/jnhwgm25LRHqtNRRUlYXLvhv0HhqyYTUt17WNtCLATSVbqLrFGdy2EGadn8mP+kQNHp93f27d/uHgBNNe7LpuYCJMdWpoG6bOqmHEftxt0/MIQA8fTtTm3jJzv+7/QjZJDvQIv0SNdp8HFogpuwde+GuS4BcY7v5xz+ArGWcRR63ct2z83MqQEn9ODr1/gAAAgA7szRpDDQIdFUQo9mkWg8xBA==xxc>
+"""
+}
+
+private enum StoredDummyTrafficKey: DependencyKey {
+  static var liveValue = Stored<DummyTraffic?>.inMemory()
+  static var testValue = Stored<DummyTraffic?>.unimplemented()
+}
+
+extension DependencyValues {
+  public var dummyTraffic: Stored<DummyTraffic?> {
+    get { self[StoredDummyTrafficKey.self] }
+    set { self[StoredDummyTrafficKey.self] = newValue }
+  }
+}
diff --git a/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandler.swift b/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandler.swift
new file mode 100644
index 0000000000000000000000000000000000000000..be0ca1047096e8390ce9b98b03a16b8906b878f2
--- /dev/null
+++ b/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandler.swift
@@ -0,0 +1,49 @@
+import XXModels
+import XXClient
+import Foundation
+import XXMessengerClient
+import XCTestDynamicOverlay
+
+public struct AuthCallbackHandler {
+  public typealias OnError = (Error) -> Void
+
+  public var run: (@escaping OnError) -> Cancellable
+
+  public func callAsFunction(onError: @escaping OnError) -> Cancellable {
+    run(onError)
+  }
+}
+
+extension AuthCallbackHandler {
+  public static func live(
+    messenger: Messenger,
+    handleRequest: AuthCallbackHandlerRequest,
+    handleConfirm: AuthCallbackHandlerConfirm,
+    handleReset: AuthCallbackHandlerReset
+  ) -> AuthCallbackHandler {
+    AuthCallbackHandler { onError in
+      messenger.registerAuthCallbacks(.init { callback in
+        do {
+          switch callback {
+          case .request(let contact, _, _, _):
+            try handleRequest(contact)
+
+          case .confirm(let contact, _, _, _):
+            try handleConfirm(contact)
+
+          case .reset(let contact, _, _, _):
+            try handleReset(contact)
+          }
+        } catch {
+          onError(error)
+        }
+      })
+    }
+  }
+}
+
+extension AuthCallbackHandler {
+  public static let unimplemented = AuthCallbackHandler(
+    run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
+  )
+}
diff --git a/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerConfirm.swift b/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerConfirm.swift
new file mode 100644
index 0000000000000000000000000000000000000000..60d86e9a66eb83a792aaa7d01b664fd62aaa47da
--- /dev/null
+++ b/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerConfirm.swift
@@ -0,0 +1,32 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXModels
+
+public struct AuthCallbackHandlerConfirm {
+  public var run: (XXClient.Contact) throws -> Void
+
+  public func callAsFunction(_ contact: XXClient.Contact) throws {
+    try run(contact)
+  }
+}
+
+extension AuthCallbackHandlerConfirm {
+  public static func live(db: DBManagerGetDB) -> AuthCallbackHandlerConfirm {
+    AuthCallbackHandlerConfirm { xxContact in
+      let id = try xxContact.getId()
+      guard var dbContact = try db().fetchContacts(.init(id: [id])).first else {
+        return
+      }
+      dbContact.isRecent = true
+      dbContact.authStatus = .friend
+      dbContact = try db().saveContact(dbContact)
+    }
+  }
+}
+
+extension AuthCallbackHandlerConfirm {
+  public static let unimplemented = AuthCallbackHandlerConfirm(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift b/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift
new file mode 100644
index 0000000000000000000000000000000000000000..4ea820682a7c8c5f772e77fba26b05f3aba94496
--- /dev/null
+++ b/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift
@@ -0,0 +1,54 @@
+import XXModels
+import XXClient
+import Foundation
+import XXMessengerClient
+import XCTestDynamicOverlay
+
+public struct AuthCallbackHandlerRequest {
+  public var run: (XXClient.Contact) throws -> Void
+
+  public func callAsFunction(_ contact: XXClient.Contact) throws {
+    try run(contact)
+  }
+}
+
+extension AuthCallbackHandlerRequest {
+  public static func live(
+    db: DBManagerGetDB,
+    messenger: Messenger,
+    now: @escaping () -> Date
+  ) -> AuthCallbackHandlerRequest {
+    AuthCallbackHandlerRequest { xxContact in
+      let id = try xxContact.getId()
+      guard try db().fetchContacts(.init(id: [id])).isEmpty else {
+        return
+      }
+      var dbContact = XXModels.Contact(id: id)
+      dbContact.marshaled = xxContact.data
+      dbContact.username = try xxContact.getFact(.username)?.value
+      dbContact.email = try xxContact.getFact(.email)?.value
+      dbContact.phone = try xxContact.getFact(.phone)?.value
+      dbContact.authStatus = .verificationInProgress
+      dbContact.createdAt = now()
+      dbContact = try db().saveContact(dbContact)
+      do {
+        try messenger.waitForNetwork()
+        if try messenger.verifyContact(xxContact) {
+          dbContact.authStatus = .verified
+          dbContact = try db().saveContact(dbContact)
+        } else {
+          try db().deleteContact(dbContact)
+        }
+      } catch {
+        dbContact.authStatus = .verificationFailed
+        dbContact = try db().saveContact(dbContact)
+      }
+    }
+  }
+}
+
+extension AuthCallbackHandlerRequest {
+  public static let unimplemented = AuthCallbackHandlerRequest(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerReset.swift b/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerReset.swift
new file mode 100644
index 0000000000000000000000000000000000000000..a894b5e79200fb13b3986840410492050510e768
--- /dev/null
+++ b/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerReset.swift
@@ -0,0 +1,31 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXModels
+
+public struct AuthCallbackHandlerReset {
+  public var run: (XXClient.Contact) throws -> Void
+
+  public func callAsFunction(_ contact: XXClient.Contact) throws {
+    try run(contact)
+  }
+}
+
+extension AuthCallbackHandlerReset {
+  public static func live(db: DBManagerGetDB) -> AuthCallbackHandlerReset {
+    AuthCallbackHandlerReset { xxContact in
+      let id = try xxContact.getId()
+      guard var dbContact = try db().fetchContacts(.init(id: [id])).first else {
+        return
+      }
+      dbContact.authStatus = .friend
+      dbContact = try db().saveContact(dbContact)
+    }
+  }
+}
+
+extension AuthCallbackHandlerReset {
+  public static let unimplemented = AuthCallbackHandlerReset(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/BackupCallbackHandler/BackupCallbackHandler.swift b/Sources/AppCore/BackupCallbackHandler/BackupCallbackHandler.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d3ddb18feb39077fd2979b0fecc9e65dcd8b1837
--- /dev/null
+++ b/Sources/AppCore/BackupCallbackHandler/BackupCallbackHandler.swift
@@ -0,0 +1,45 @@
+import XXClient
+import Foundation
+import XXMessengerClient
+import XCTestDynamicOverlay
+
+public struct BackupCallbackHandler {
+  public typealias OnError = (Error) -> Void
+
+  public var run: (@escaping OnError) -> Cancellable
+
+  public func callAsFunction(onError: @escaping OnError) -> Cancellable {
+    run(onError)
+  }
+}
+
+extension BackupCallbackHandler {
+  public static func live(
+    messenger: Messenger
+  ) -> BackupCallbackHandler {
+    BackupCallbackHandler { onError in
+      let callback = UpdateBackupFunc { data in
+        do {
+          let url = try FileManager.default.url(
+            for: .applicationSupportDirectory,
+            in: .userDomainMask,
+            appropriateFor: nil,
+            create: true
+          )
+            .appendingPathComponent("backup")
+            .appendingPathExtension("xxm")
+          try data.write(to: url)
+        } catch {
+          onError(error)
+        }
+      }
+      return messenger.registerBackupCallback(callback)
+    }
+  }
+}
+
+extension BackupCallbackHandler {
+  public static let unimplemented = BackupCallbackHandler(
+    run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
+  )
+}
diff --git a/Sources/AppCore/DBManager/DBManager.swift b/Sources/AppCore/DBManager/DBManager.swift
new file mode 100644
index 0000000000000000000000000000000000000000..b66fb3d731f3cb53cfb463ae84b516e98927430d
--- /dev/null
+++ b/Sources/AppCore/DBManager/DBManager.swift
@@ -0,0 +1,40 @@
+import XXModels
+import Foundation
+
+public struct DBManager {
+  public var hasDB: DBManagerHasDB
+  public var makeDB: DBManagerMakeDB
+  public var getDB: DBManagerGetDB
+  public var removeDB: DBManagerRemoveDB
+}
+
+extension DBManager {
+  public static func live(
+    url: URL = FileManager.default
+      .urls(for: .applicationSupportDirectory, in: .userDomainMask)
+      .first!
+      .appendingPathComponent("database")
+  ) -> DBManager {
+    class Container {
+      var db: Database?
+    }
+
+    let container = Container()
+
+    return DBManager(
+      hasDB: .init { container.db != nil },
+      makeDB: .live(url: url, setDB: { container.db = $0 }),
+      getDB: .live(getDB: { container.db }),
+      removeDB: .live(url: url, getDB: { container.db }, unsetDB: { container.db = nil })
+    )
+  }
+}
+
+extension DBManager {
+  public static let unimplemented = DBManager(
+    hasDB: .unimplemented,
+    makeDB: .unimplemented,
+    getDB: .unimplemented,
+    removeDB: .unimplemented
+  )
+}
diff --git a/Sources/AppCore/DBManager/DBManagerGetDB.swift b/Sources/AppCore/DBManager/DBManagerGetDB.swift
new file mode 100644
index 0000000000000000000000000000000000000000..aae596a1b98cf5993181400242ab4ce11189b3e6
--- /dev/null
+++ b/Sources/AppCore/DBManager/DBManagerGetDB.swift
@@ -0,0 +1,33 @@
+import XXModels
+import XCTestDynamicOverlay
+
+public struct DBManagerGetDB {
+  public enum Error: Swift.Error, Equatable {
+    case missingDB
+  }
+
+  public var run: () throws -> Database
+
+  public func callAsFunction() throws -> Database {
+    try run()
+  }
+}
+
+extension DBManagerGetDB {
+  public static func live(
+    getDB: @escaping () -> Database?
+  ) -> DBManagerGetDB {
+    DBManagerGetDB {
+      guard let db = getDB() else {
+        throw Error.missingDB
+      }
+      return db
+    }
+  }
+}
+
+extension DBManagerGetDB {
+  public static let unimplemented = DBManagerGetDB(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/DBManager/DBManagerHasDB.swift b/Sources/AppCore/DBManager/DBManagerHasDB.swift
new file mode 100644
index 0000000000000000000000000000000000000000..12fb1bb34ca0882404bf765f640e61496cbaedd5
--- /dev/null
+++ b/Sources/AppCore/DBManager/DBManagerHasDB.swift
@@ -0,0 +1,19 @@
+import XCTestDynamicOverlay
+
+public struct DBManagerHasDB {
+  init(run: @escaping () -> Bool) {
+    self.run = run
+  }
+
+  public var run: () -> Bool
+
+  public func callAsFunction() -> Bool {
+    run()
+  }
+}
+
+extension DBManagerHasDB {
+  public static let unimplemented = DBManagerHasDB(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/DBManager/DBManagerMakeDB.swift b/Sources/AppCore/DBManager/DBManagerMakeDB.swift
new file mode 100644
index 0000000000000000000000000000000000000000..a3e514ae75ae674e9bf6a10f951fbd4957b6c72e
--- /dev/null
+++ b/Sources/AppCore/DBManager/DBManagerMakeDB.swift
@@ -0,0 +1,37 @@
+import XXModels
+import Foundation
+import XXDatabase
+import XCTestDynamicOverlay
+
+public struct DBManagerMakeDB {
+  public var run: () throws -> Void
+
+  public func callAsFunction() throws -> Void {
+    try run()
+  }
+}
+
+extension DBManagerMakeDB {
+  public static func live(
+    url: URL,
+    setDB: @escaping (Database) -> Void
+  ) -> DBManagerMakeDB {
+    DBManagerMakeDB {
+      try? FileManager.default
+        .createDirectory(at: url, withIntermediateDirectories: true)
+
+      let dbFilePath = url
+        .appendingPathComponent("xxm_database")
+        .appendingPathExtension("sqlite")
+        .path
+
+      setDB(try Database.onDisk(path: dbFilePath))
+    }
+  }
+}
+
+extension DBManagerMakeDB {
+  public static let unimplemented = DBManagerMakeDB(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/DBManager/DBManagerRemoveDB.swift b/Sources/AppCore/DBManager/DBManagerRemoveDB.swift
new file mode 100644
index 0000000000000000000000000000000000000000..9b6c14d4830d63fd70f8e54ac977fa652df28495
--- /dev/null
+++ b/Sources/AppCore/DBManager/DBManagerRemoveDB.swift
@@ -0,0 +1,36 @@
+import XXModels
+import Foundation
+import XXDatabase
+import XCTestDynamicOverlay
+
+public struct DBManagerRemoveDB {
+  public var run: () throws -> Void
+
+  public func callAsFunction() throws -> Void {
+    try run()
+  }
+}
+
+extension DBManagerRemoveDB {
+  public static func live(
+    url: URL,
+    getDB: @escaping () -> Database?,
+    unsetDB: @escaping () -> Void
+  ) -> DBManagerRemoveDB {
+    DBManagerRemoveDB {
+      let db = getDB()
+      unsetDB()
+      try db?.drop()
+      let fm = FileManager.default
+      if fm.fileExists(atPath: url.path) {
+        try fm.removeItem(atPath: url.path)
+      }
+    }
+  }
+}
+
+extension DBManagerRemoveDB {
+  public static let unimplemented = DBManagerRemoveDB(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/GroupHandlers/GroupMessageHandler.swift b/Sources/AppCore/GroupHandlers/GroupMessageHandler.swift
new file mode 100644
index 0000000000000000000000000000000000000000..262a53eb914987433dca7cb6a14a3d6f9f1cdb39
--- /dev/null
+++ b/Sources/AppCore/GroupHandlers/GroupMessageHandler.swift
@@ -0,0 +1,55 @@
+import XXModels
+import XXClient
+import Foundation
+import XXMessengerClient
+import XCTestDynamicOverlay
+
+public struct GroupMessageHandler {
+  public typealias OnError = (Error) -> Void
+
+  public var run: (@escaping OnError) -> Cancellable
+
+  public func callAsFunction(onError: @escaping OnError) -> Cancellable {
+    run(onError)
+  }
+}
+
+extension GroupMessageHandler {
+  public static func live(
+    messenger: Messenger,
+    db: DBManagerGetDB
+  ) -> GroupMessageHandler {
+    GroupMessageHandler { onError in
+      messenger.registerGroupChatProcessor(.init { result in
+        switch result {
+        case .success(let callback):
+          do {
+            let payload = try MessagePayload.decode(callback.decryptedMessage.payload)
+            try db().saveMessage(.init(
+              networkId: callback.decryptedMessage.messageId,
+              senderId: callback.decryptedMessage.senderId,
+              recipientId: nil,
+              groupId: callback.decryptedMessage.groupId,
+              date: Date.fromTimestamp(Int(callback.decryptedMessage.timestamp)),
+              status: .received,
+              isUnread: true,
+              text: payload.text,
+              replyMessageId: payload.replyingTo,
+              roundURL: callback.roundUrl
+            ))
+          } catch {
+            onError(error)
+          }
+        case .failure(let error):
+          onError(error)
+        }
+      })
+    }
+  }
+}
+
+extension GroupMessageHandler {
+  public static let unimplemented = GroupMessageHandler(
+    run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
+  )
+}
diff --git a/Sources/AppCore/GroupHandlers/GroupRequestHandler.swift b/Sources/AppCore/GroupHandlers/GroupRequestHandler.swift
new file mode 100644
index 0000000000000000000000000000000000000000..14afb9026aaf52338c7b6af7b7c5f8d5b094a268
--- /dev/null
+++ b/Sources/AppCore/GroupHandlers/GroupRequestHandler.swift
@@ -0,0 +1,99 @@
+import XXModels
+import XXClient
+import Foundation
+import XXMessengerClient
+import XCTestDynamicOverlay
+
+public struct GroupRequestHandler {
+  public typealias OnError = (Error) -> Void
+
+  public var run: (@escaping OnError) -> Cancellable
+
+  public func callAsFunction(onError: @escaping OnError) -> Cancellable {
+    run(onError)
+  }
+}
+
+extension GroupRequestHandler {
+  public static func live(
+    messenger: Messenger,
+    db: DBManagerGetDB
+  ) -> GroupRequestHandler {
+    GroupRequestHandler { onError in
+      messenger.registerGroupRequestHandler(.init { group in
+        do {
+          if let _ = try db().fetchGroups(.init(id: [group.getId()])).first {
+            return
+          }
+          guard let leader = try group.getMembership().first else {
+            return // Failed to get group membership/leader
+          }
+          try db().saveGroup(.init(
+            id: group.getId(),
+            name: String(data: group.getName(), encoding: .utf8)!,
+            leaderId: leader.id,
+            createdAt: Date.fromMSTimestamp(group.getCreatedMS()),
+            authStatus: .pending,
+            serialized: group.serialize()
+          ))
+          if let initialMessageData = group.getInitMessage(),
+             let initialMessage = String(data: initialMessageData, encoding: .utf8) {
+            try db().saveMessage(.init(
+              senderId: leader.id,
+              recipientId: nil,
+              groupId: group.getId(),
+              date: Date.fromMSTimestamp(group.getCreatedMS()),
+              status: .received,
+              isUnread: true,
+              text: initialMessage
+            ))
+          }
+          let members = try group.getMembership()
+          let friends = try db().fetchContacts(.init(id: Set(members.map(\.id)), authStatus: [
+            .friend, .hidden, .confirming,
+            .verified, .requested, .requesting,
+            .verificationInProgress, .requestFailed,
+            .verificationFailed, .confirmationFailed
+          ]))
+          let strangers = Set(members.map(\.id)).subtracting(Set(friends.map(\.id)))
+          try strangers.forEach {
+            if let stranger = try? db().fetchContacts(.init(id: [$0])).first {
+              print(stranger)
+            } else {
+              try db().saveContact(.init(
+                id: $0,
+                username: "Fetching...",
+                authStatus: .stranger,
+                isRecent: false,
+                isBlocked: false,
+                isBanned: false,
+                createdAt: Date.fromMSTimestamp(group.getCreatedMS())
+              ))
+            }
+          }
+          try members.map {
+            XXModels.GroupMember(groupId: group.getId(), contactId: $0.id)
+          }.forEach {
+            try db().saveGroupMember($0)
+          }
+          let multilookup = try messenger.lookupContacts(ids: strangers.map { $0 })
+          for user in multilookup.contacts {
+            if var foo = try? db().fetchContacts(.init(id: [user.getId()])).first,
+               let username = try? user.getFact(.username)?.value {
+              foo.username = username
+              _ = try? db().saveContact(foo)
+            }
+          }
+        } catch {
+          onError(error)
+        }
+      })
+    }
+  }
+}
+
+extension GroupRequestHandler {
+  public static let unimplemented = GroupRequestHandler(
+    run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
+  )
+}
diff --git a/Sources/AppCore/HUDManager/HUDHide.swift b/Sources/AppCore/HUDManager/HUDHide.swift
new file mode 100644
index 0000000000000000000000000000000000000000..fe96ef1faacb7a9c59d58b96407a1de8573bc77d
--- /dev/null
+++ b/Sources/AppCore/HUDManager/HUDHide.swift
@@ -0,0 +1,19 @@
+import XCTestDynamicOverlay
+
+public struct HUDHide {
+  init(run: @escaping () -> Void) {
+    self.run = run
+  }
+
+  public var run: () -> Void
+
+  public func callAsFunction() -> Void {
+    run()
+  }
+}
+
+extension HUDHide {
+  public static let unimplemented = HUDHide(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/HUDManager/HUDManager.swift b/Sources/AppCore/HUDManager/HUDManager.swift
new file mode 100644
index 0000000000000000000000000000000000000000..2c402e3b6b55677fbb3066ddb32777fd20d8224b
--- /dev/null
+++ b/Sources/AppCore/HUDManager/HUDManager.swift
@@ -0,0 +1,49 @@
+import Combine
+import Foundation
+import XCTestDynamicOverlay
+
+public struct HUDManager {
+  public var show: HUDShow
+  public var hide: HUDHide
+  public var observe: HUDObserve
+}
+
+extension HUDManager {
+  public static func live() -> HUDManager {
+    class Context {
+      var timer: Timer?
+      let modelSubject = PassthroughSubject<HUDModel?, Never>()
+    }
+
+    let context = Context()
+
+    return .init(
+      show: .init {
+        guard let model = $0 else {
+          context.modelSubject.send(.init(hasDotAnimation: true))
+          return
+        }
+        if model.isAutoDismissable {
+          DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
+            context.modelSubject.send(nil)
+          }
+        }
+        context.modelSubject.send(model)
+      },
+      hide: .init {
+        context.modelSubject.send(nil)
+      },
+      observe: .init {
+        context.modelSubject.eraseToAnyPublisher()
+      }
+    )
+  }
+}
+
+extension HUDManager {
+  public static let unimplemented = HUDManager(
+    show: .unimplemented,
+    hide: .unimplemented,
+    observe: .unimplemented
+  )
+}
diff --git a/Sources/AppCore/HUDManager/HUDObserve.swift b/Sources/AppCore/HUDManager/HUDObserve.swift
new file mode 100644
index 0000000000000000000000000000000000000000..c4257fdfc52b9d49bd199747a033cb51ccd18904
--- /dev/null
+++ b/Sources/AppCore/HUDManager/HUDObserve.swift
@@ -0,0 +1,16 @@
+import Combine
+import XCTestDynamicOverlay
+
+public struct HUDObserve {
+  public var run: () -> AnyPublisher<HUDModel?, Never>
+
+  public func callAsFunction() -> AnyPublisher<HUDModel?, Never> {
+    run()
+  }
+}
+
+extension HUDObserve {
+  public static let unimplemented = HUDObserve(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/HUDManager/HUDShow.swift b/Sources/AppCore/HUDManager/HUDShow.swift
new file mode 100644
index 0000000000000000000000000000000000000000..81038e4e2799a5681c0de3aa92ad0816ff77646b
--- /dev/null
+++ b/Sources/AppCore/HUDManager/HUDShow.swift
@@ -0,0 +1,19 @@
+import XCTestDynamicOverlay
+
+public struct HUDShow {
+  init(run: @escaping (HUDModel?) -> Void) {
+    self.run = run
+  }
+
+  public var run: (HUDModel?) -> Void
+
+  public func callAsFunction(_ model: HUDModel? = nil) -> Void {
+    run(model)
+  }
+}
+
+extension HUDShow {
+  public static let unimplemented = HUDShow(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/Logger/Logger.swift b/Sources/AppCore/Logger/Logger.swift
new file mode 100644
index 0000000000000000000000000000000000000000..60fc7bf3a56e55125324c3142dea8310e5aa027e
--- /dev/null
+++ b/Sources/AppCore/Logger/Logger.swift
@@ -0,0 +1,51 @@
+import Logging
+import Foundation
+import XCTestDynamicOverlay
+
+public struct Logger {
+  public enum Message: Equatable {
+    case info(String)
+    case error(NSError)
+  }
+
+  public var run: (Message, String, String, UInt) -> Void
+
+  public func callAsFunction(
+    _ msg: Message,
+    file: String = #file,
+    function: String = #function,
+    line: UInt = #line
+  ) {
+    run(msg, file, function, line)
+  }
+}
+
+extension Logger {
+  public static func live() -> Logger {
+    let logger = Logging.Logger(label: "xx.messenger")
+    return Logger { msg, file, function, line in
+      switch msg {
+      case .info(let text):
+        logger.info(
+          .init(stringLiteral: text),
+          file: file,
+          function: function,
+          line: line
+        )
+      case .error(let error):
+        logger.error(
+          .init(stringLiteral: error.localizedDescription),
+          file: file,
+          function: function,
+          line: line
+        )
+      }
+    }
+  }
+}
+
+extension Logger {
+  public static let unimplemented = Logger(
+    run: XCTUnimplemented("\(Self.self).error")
+  )
+}
diff --git a/Sources/AppCore/MessageListenerHandler/MessageListenerHandler.swift b/Sources/AppCore/MessageListenerHandler/MessageListenerHandler.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8103b2674704e57f682006e6e23cd4f263fa8bca
--- /dev/null
+++ b/Sources/AppCore/MessageListenerHandler/MessageListenerHandler.swift
@@ -0,0 +1,51 @@
+import XXModels
+import XXClient
+import Foundation
+import XXMessengerClient
+import XCTestDynamicOverlay
+
+public struct MessageListenerHandler {
+  public typealias OnError = (Error) -> Void
+
+  public var run: (@escaping OnError) -> Cancellable
+
+  public func callAsFunction(onError: @escaping OnError) -> Cancellable {
+    run(onError)
+  }
+}
+
+extension MessageListenerHandler {
+  public static func live(
+    messenger: Messenger,
+    db: DBManagerGetDB
+  ) -> MessageListenerHandler {
+    MessageListenerHandler { onError in
+      let listener = Listener { message in
+        do {
+          let payload = try MessagePayload.decode(message.payload)
+          try db().saveMessage(.init(
+            networkId: message.id,
+            senderId: message.sender,
+            recipientId: message.recipientId,
+            groupId: nil,
+            date: Date(timeIntervalSince1970: TimeInterval(message.timestamp) / 1_000_000_000),
+            status: .received,
+            isUnread: true,
+            text: payload.text,
+            replyMessageId: payload.replyingTo,
+            roundURL: message.roundURL
+          ))
+        } catch {
+          onError(error)
+        }
+      }
+      return messenger.registerMessageListener(listener)
+    }
+  }
+}
+
+extension MessageListenerHandler {
+  public static let unimplemented = MessageListenerHandler(
+    run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
+  )
+}
diff --git a/Sources/AppCore/Models/HUDModel.swift b/Sources/AppCore/Models/HUDModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8d2679e6e870fa7507d8593ae683ee3b76ab0fcb
--- /dev/null
+++ b/Sources/AppCore/Models/HUDModel.swift
@@ -0,0 +1,39 @@
+import UIKit
+import AppResources
+
+public struct HUDModel {
+  var title: String?
+  var content: String?
+  var actionTitle: String?
+  var hasDotAnimation: Bool
+  var isAutoDismissable: Bool
+  var onTapClosure: (() -> Void)?
+
+  public init(
+    title: String? = nil,
+    content: String? = nil,
+    actionTitle: String? = nil,
+    hasDotAnimation: Bool = false,
+    onTapClosure: (() -> Void)? = nil
+  ) {
+    self.title = title
+    self.content = content
+    self.actionTitle = actionTitle
+    self.onTapClosure = onTapClosure
+    self.hasDotAnimation = hasDotAnimation
+    self.isAutoDismissable = onTapClosure == nil && !hasDotAnimation
+  }
+
+  public init(
+    error: Error,
+    actionTitle: String? = Localized.Hud.Error.action,
+    onTapClosure: (() -> Void)? = nil
+  ) {
+    self.hasDotAnimation = false
+    self.actionTitle = actionTitle
+    self.onTapClosure = onTapClosure
+    self.title = Localized.Hud.Error.title
+    self.isAutoDismissable = onTapClosure == nil
+    self.content = error.localizedDescription
+  }
+}
diff --git a/Sources/AppCore/Models/MessagePayload.swift b/Sources/AppCore/Models/MessagePayload.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7ae774337944fdb62b7c5b862cfdd28d7a3a3295
--- /dev/null
+++ b/Sources/AppCore/Models/MessagePayload.swift
@@ -0,0 +1,29 @@
+import Foundation
+
+public struct MessagePayload: Equatable {
+  public init(
+    text: String,
+    replyingTo: Data? = nil
+  ) {
+    self.text = text
+    self.replyingTo = replyingTo
+  }
+
+  public var text: String
+  public var replyingTo: Data?
+}
+
+extension MessagePayload: Codable {
+  enum CodingKeys: String, CodingKey {
+    case text
+    case replyingTo
+  }
+
+  public static func decode(_ data: Data) throws -> Self {
+    try JSONDecoder().decode(Self.self, from: data)
+  }
+
+  public func encode() throws -> Data {
+    try JSONEncoder().encode(self)
+  }
+}
diff --git a/Sources/AppCore/Models/ToastModel.swift b/Sources/AppCore/Models/ToastModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7758f11c162fa0931b0fe344c4319a934f5f29ed
--- /dev/null
+++ b/Sources/AppCore/Models/ToastModel.swift
@@ -0,0 +1,36 @@
+import UIKit
+import AppResources
+
+public struct ToastModel {
+  let id: UUID
+  let title: String
+  let color: UIColor
+  let subtitle: String?
+  let leftImage: UIImage
+  let timeToLive: Int
+  let buttonTitle: String?
+  let autodismissable: Bool
+  let onTapClosure: (() -> Void)?
+  
+  public init(
+    id: UUID = UUID(),
+    title: String,
+    color: UIColor = Asset.neutralOverlay.color,
+    subtitle: String? = nil,
+    leftImage: UIImage,
+    timeToLive: Int = 4,
+    buttonTitle: String? = nil,
+    onTapClosure: (() -> Void)? = nil,
+    autodismissable: Bool = true
+  ) {
+    self.id = id
+    self.title = title
+    self.color = color
+    self.subtitle = subtitle
+    self.leftImage = leftImage
+    self.timeToLive = timeToLive
+    self.buttonTitle = buttonTitle
+    self.onTapClosure = onTapClosure
+    self.autodismissable = autodismissable
+  }
+}
diff --git a/Sources/AppCore/NetworkMonitor/GetNetworkConnType.swift b/Sources/AppCore/NetworkMonitor/GetNetworkConnType.swift
new file mode 100644
index 0000000000000000000000000000000000000000..99b460f76069535017a7cb730c3def9e3888bcf6
--- /dev/null
+++ b/Sources/AppCore/NetworkMonitor/GetNetworkConnType.swift
@@ -0,0 +1,19 @@
+import XCTestDynamicOverlay
+
+public struct GetNetworkConnType {
+  public init(run: @escaping () -> NetworkMonitor.ConnType) {
+    self.run = run
+  }
+
+  public var run: () -> NetworkMonitor.ConnType
+
+  public func callAsFunction() -> NetworkMonitor.ConnType {
+    run()
+  }
+}
+
+extension GetNetworkConnType {
+  public static let unimplemented = GetNetworkConnType(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/NetworkMonitor/GetNetworkStatus.swift b/Sources/AppCore/NetworkMonitor/GetNetworkStatus.swift
new file mode 100644
index 0000000000000000000000000000000000000000..e8d6cfb053ccfb6801e206d2f6beb6976d157e29
--- /dev/null
+++ b/Sources/AppCore/NetworkMonitor/GetNetworkStatus.swift
@@ -0,0 +1,19 @@
+import XCTestDynamicOverlay
+
+public struct GetNetworkStatus {
+  public init(run: @escaping () -> NetworkMonitor.Status) {
+    self.run = run
+  }
+
+  public var run: () -> NetworkMonitor.Status
+
+  public func callAsFunction() -> NetworkMonitor.Status {
+    run()
+  }
+}
+
+extension GetNetworkStatus {
+  public static let unimplemented = GetNetworkStatus(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/NetworkMonitor/NetworkMonitor.swift b/Sources/AppCore/NetworkMonitor/NetworkMonitor.swift
new file mode 100644
index 0000000000000000000000000000000000000000..db79b79d26f877fbd3b04470378a4d434f2e6cfd
--- /dev/null
+++ b/Sources/AppCore/NetworkMonitor/NetworkMonitor.swift
@@ -0,0 +1,96 @@
+import Combine
+import Network
+
+public struct NetworkMonitor {
+  public enum Status: Equatable {
+    case unknown
+    case available
+    case xxNotAvailable
+    case internetNotAvailable
+  }
+  public enum ConnType: Equatable {
+    case unknown
+    case wifi
+    case ethernet
+    case cellular
+  }
+
+  public var start: StartNetworkMonitor
+  public var update: UpdateNetworkStatus
+  public var getStatus: GetNetworkStatus
+  public var connType: GetNetworkConnType
+  public var observeStatus: ObserveNetworkStatus
+}
+
+extension NetworkMonitor {
+  public static func live() -> NetworkMonitor {
+    class Context {
+      var monitor = NWPathMonitor()
+      let xxAvailability = CurrentValueSubject<Bool?, Never>(nil)
+      let internetAvailability = CurrentValueSubject<Bool?, Never>(nil)
+      let currentConnType = CurrentValueSubject<ConnType, Never>(.unknown)
+    }
+
+    let context = Context()
+
+    return .init(
+      start: .init {
+        context.monitor.pathUpdateHandler = {
+          let currentInterface: ConnType
+
+          if $0.usesInterfaceType(.wifi) {
+            currentInterface = .wifi
+          } else if $0.usesInterfaceType(.wiredEthernet) {
+            currentInterface = .ethernet
+          } else if $0.usesInterfaceType(.cellular) {
+            currentInterface = .cellular
+          } else {
+            currentInterface = .unknown
+          }
+          context.currentConnType.send(currentInterface)
+          context.internetAvailability.send($0.status == .satisfied)
+        }
+        context.monitor.start(queue: .global())
+      },
+      update: .init {
+        context.xxAvailability.send($0)
+      },
+      getStatus: .init {
+        guard let xxAvailability = context.xxAvailability.value else {
+          return .xxNotAvailable
+        }
+        return xxAvailability ? .available : .xxNotAvailable
+      },
+      connType: .init {
+        context.currentConnType.value
+      },
+      observeStatus: .init {
+        context
+          .internetAvailability
+          .combineLatest(context.xxAvailability)
+          .map { (internet, xx) -> Status in
+            guard let internet, let xx else { return .unknown}
+            switch (internet, xx) {
+            case (true, true):
+              return .available
+            case (true, false):
+              return .xxNotAvailable
+            case (false, _):
+              return .internetNotAvailable
+            }
+          }.removeDuplicates()
+          .eraseToAnyPublisher()
+      }
+    )
+  }
+}
+
+extension NetworkMonitor {
+  public static let unimplemented = NetworkMonitor(
+    start: .unimplemented,
+    update: .unimplemented,
+    getStatus: .unimplemented,
+    connType: .unimplemented,
+    observeStatus: .unimplemented
+  )
+}
diff --git a/Sources/AppCore/NetworkMonitor/ObserveNetworkStatus.swift b/Sources/AppCore/NetworkMonitor/ObserveNetworkStatus.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f6df5d96ebc6f4b243fbd8afa84aa5f45c39bec6
--- /dev/null
+++ b/Sources/AppCore/NetworkMonitor/ObserveNetworkStatus.swift
@@ -0,0 +1,20 @@
+import Combine
+import XCTestDynamicOverlay
+
+public struct ObserveNetworkStatus {
+  public init(run: @escaping () -> AnyPublisher<NetworkMonitor.Status, Never>) {
+    self.run = run
+  }
+
+  public var run: () -> AnyPublisher<NetworkMonitor.Status, Never>
+
+  public func callAsFunction() -> AnyPublisher<NetworkMonitor.Status, Never> {
+    run()
+  }
+}
+
+extension ObserveNetworkStatus {
+  public static let unimplemented = ObserveNetworkStatus(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/NetworkMonitor/StartNetworkMonitor.swift b/Sources/AppCore/NetworkMonitor/StartNetworkMonitor.swift
new file mode 100644
index 0000000000000000000000000000000000000000..6fa6e45a7f79d810d9ae341293cb44761e03cbb5
--- /dev/null
+++ b/Sources/AppCore/NetworkMonitor/StartNetworkMonitor.swift
@@ -0,0 +1,20 @@
+import XCTestDynamicOverlay
+
+public struct StartNetworkMonitor {
+  public init(run: @escaping () -> Void) {
+    self.run = run
+  }
+
+  public var run: () -> Void
+
+  public func callAsFunction() -> Void {
+    run()
+  }
+}
+
+extension StartNetworkMonitor {
+  public static let unimplemented = StartNetworkMonitor(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
+
diff --git a/Sources/AppCore/NetworkMonitor/UpdateNetworkStatus.swift b/Sources/AppCore/NetworkMonitor/UpdateNetworkStatus.swift
new file mode 100644
index 0000000000000000000000000000000000000000..825945096def2084b6bd9c135bba04291ec60a53
--- /dev/null
+++ b/Sources/AppCore/NetworkMonitor/UpdateNetworkStatus.swift
@@ -0,0 +1,19 @@
+import XCTestDynamicOverlay
+
+public struct UpdateNetworkStatus {
+  public init(run: @escaping (Bool) -> Void) {
+    self.run = run
+  }
+
+  public var run: (Bool) -> Void
+
+  public func callAsFunction(_ status: Bool) -> Void {
+    run(status)
+  }
+}
+
+extension UpdateNetworkStatus {
+  public static let unimplemented = UpdateNetworkStatus(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/ReceiveFileHandler/ReceiveFileHandler.swift b/Sources/AppCore/ReceiveFileHandler/ReceiveFileHandler.swift
new file mode 100644
index 0000000000000000000000000000000000000000..187963707ea131e8f10de1e3dcf0ea0de4972620
--- /dev/null
+++ b/Sources/AppCore/ReceiveFileHandler/ReceiveFileHandler.swift
@@ -0,0 +1,117 @@
+import XXModels
+import XXClient
+import Foundation
+import XXMessengerClient
+import XCTestDynamicOverlay
+
+public struct ReceiveFileHandler {
+  public typealias OnError = (Error) -> Void
+
+  public var run: (@escaping OnError) -> Cancellable
+
+  public func callAsFunction(onError: @escaping OnError) -> Cancellable {
+    run(onError)
+  }
+}
+
+extension ReceiveFileHandler {
+  public static func live(
+    messenger: Messenger,
+    db: DBManagerGetDB,
+    now: @escaping () -> Date
+  ) -> ReceiveFileHandler {
+    ReceiveFileHandler { onError in
+      func receiveFile(_ file: ReceivedFile) {
+        do {
+          let date = now()
+          try db().saveFileTransfer(XXModels.FileTransfer(
+            id: file.transferId,
+            contactId: file.senderId,
+            name: file.name,
+            type: file.type,
+            data: nil,
+            progress: 0,
+            isIncoming: true,
+            createdAt: date
+          ))
+          try db().saveMessage(XXModels.Message(
+            senderId: file.senderId,
+            recipientId: try messenger.e2e.tryGet().getContact().getId(),
+            groupId: nil,
+            date: date,
+            status: .received,
+            isUnread: false,
+            text: "",
+            fileTransferId: file.transferId
+          ))
+          try messenger.receiveFile(.init(
+            transferId: file.transferId,
+            callbackIntervalMS: 500
+          )) { info in
+            switch info {
+            case .progress(let transmitted, let total):
+              updateProgress(
+                transferId: file.transferId,
+                transmitted: transmitted,
+                total: total
+              )
+
+            case .finished(let data):
+              saveData(
+                transferId: file.transferId,
+                data: data
+              )
+
+            case .failed(.receive(let error)):
+              onError(error)
+
+            case .failed(.callback(let error)):
+              onError(error)
+            }
+          }
+        } catch {
+          onError(error)
+        }
+      }
+
+      func updateProgress(transferId: Data, transmitted: Int, total: Int) {
+        do {
+          if var transfer = try db().fetchFileTransfers(.init(id: [transferId])).first {
+            transfer.progress = total > 0 ? Float(transmitted) / Float(total) : 0
+            try db().saveFileTransfer(transfer)
+          }
+        } catch {
+          onError(error)
+        }
+      }
+
+      func saveData(transferId: Data, data: Data) {
+        do {
+          if var transfer = try db().fetchFileTransfers(.init(id: [transferId])).first {
+            transfer.progress = 1
+            transfer.data = data
+            try db().saveFileTransfer(transfer)
+          }
+        } catch {
+          onError(error)
+        }
+      }
+
+      return messenger.registerReceiveFileCallback(.init { result in
+        switch result {
+        case .success(let file):
+          receiveFile(file)
+
+        case .failure(let error):
+          onError(error)
+        }
+      })
+    }
+  }
+}
+
+extension ReceiveFileHandler {
+  public static let unimplemented = ReceiveFileHandler(
+    run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
+  )
+}
diff --git a/Sources/AppCore/RootViewController.swift b/Sources/AppCore/RootViewController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..2c794a01704c45e4f0e234c736b8b1e4fed8321e
--- /dev/null
+++ b/Sources/AppCore/RootViewController.swift
@@ -0,0 +1,194 @@
+import UIKit
+import Combine
+import Dependencies
+
+public final class RootViewController: UIViewController {
+  @Dependency(\.app.statusBar) var statusBar
+  @Dependency(\.app.hudManager) var hudManager
+  @Dependency(\.app.toastManager) var toastManager
+  
+  var hud: HUDView?
+  var cancellables = Set<AnyCancellable>()
+  public let navController: UINavigationController
+  
+  var toastTimer: Timer?
+  let toastTopPadding: CGFloat = 10
+  var topToastConstraint: NSLayoutConstraint?
+  
+  public init(_ content: UINavigationController) {
+    self.navController = content
+    super.init(nibName: nil, bundle: nil)
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  public override var preferredStatusBarStyle: UIStatusBarStyle  {
+    statusBar.get()
+  }
+  
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    
+    addChild(navController)
+    view.addSubview(navController.view)
+    navController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+    navController.view.frame = view.bounds
+    navController.didMove(toParent: self)
+    
+    statusBar
+      .observe()
+      .receive(on: DispatchQueue.main)
+      .sink { [weak self] _ in
+        UIView.animate(withDuration: 0.2) {
+          self?.setNeedsStatusBarAppearanceUpdate()
+        }
+      }.store(in: &cancellables)
+    
+    toastManager
+      .observe()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] model in
+        let toastView = ToastView(model: model)
+        add(toastView: toastView)
+        present(toastView: toastView)
+      }.store(in: &cancellables)
+    
+    hudManager
+      .observe()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] model in
+        guard let model else {
+          guard let hud else { return }
+          UIView.animate(withDuration: 0.2) {
+            hud.alpha = 0.0
+          } completion: { _ in
+            hud.removeFromSuperview()
+            self.hud = nil
+          }
+          return
+        }
+        add(hudView: HUDView().setup(model: model))
+      }.store(in: &cancellables)
+  }
+}
+
+extension RootViewController {
+  @objc private func didPanToast(_ sender: UIPanGestureRecognizer) {
+    guard let toastView = sender.view else { return }
+    
+    switch sender.state {
+    case .began, .changed:
+      toastTimer?.invalidate()
+      let padding = toastTopPadding + min(0, sender.translation(in: view).y)
+      topToastConstraint?.constant = padding
+      
+    case .cancelled, .ended, .failed:
+      let halfFrameHeight = -0.5 * toastView.frame.height
+      let verticalTranslation = sender.translation(in: toastView).y
+      let didSwipeAboveHalf = verticalTranslation < halfFrameHeight
+      
+      if didSwipeAboveHalf {
+        dismiss(toastView: toastView)
+      } else {
+        present(toastView: toastView)
+      }
+      
+    case .possible:
+      break
+    @unknown default:
+      break
+    }
+  }
+  
+  private func dismiss(toastView: UIView) {
+    toastView.isUserInteractionEnabled = false
+    topToastConstraint?.constant = -(toastView.frame.height + view.safeAreaLayoutGuide.layoutFrame.minY)
+    
+    topToastConstraint = nil
+    UIView.animate(withDuration: 0.25) {
+      self.view.setNeedsLayout()
+      self.view.layoutIfNeeded()
+    } completion: { _ in
+      toastView.isUserInteractionEnabled = true
+      toastView.removeFromSuperview()
+      self.toastManager.dismiss()
+    }
+  }
+  
+  private func add(toastView: UIView) {
+    let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPanToast(_:)))
+    toastView.addGestureRecognizer(gestureRecognizer)
+    
+    toastView.translatesAutoresizingMaskIntoConstraints = false
+    view.addSubview(toastView)
+    
+    NSLayoutConstraint.activate([
+      toastView.heightAnchor.constraint(equalToConstant: 78),
+      toastView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 20),
+      toastView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: -20)
+    ])
+    
+    topToastConstraint = toastView.topAnchor.constraint(
+      equalTo: view.safeAreaLayoutGuide.topAnchor,
+      constant: -(toastView.frame.height + view.safeAreaLayoutGuide.layoutFrame.height)
+    )
+    
+    topToastConstraint?.isActive = true
+    
+    view.setNeedsLayout()
+    view.layoutIfNeeded()
+  }
+  
+  private func present(toastView: UIView) {
+    toastView.isUserInteractionEnabled = false
+    topToastConstraint?.constant = toastTopPadding
+    
+    UIView.animate(
+      withDuration: 0.5,
+      delay: 0,
+      usingSpringWithDamping: 1,
+      initialSpringVelocity: 0.5,
+      options: .curveEaseInOut
+    ) {
+      self.view.setNeedsLayout()
+      self.view.layoutIfNeeded()
+    } completion: { _ in
+      toastView.isUserInteractionEnabled = true
+      
+      self.toastTimer?.invalidate()
+      self.toastTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { [weak self] _ in
+        guard let self else { return }
+        self.dismiss(toastView: toastView)
+      }
+    }
+  }
+}
+
+extension RootViewController {
+  private func add(hudView: HUDView) {
+    if let hud {
+      hud.removeFromSuperview()
+      self.hud = nil
+    }
+    
+    hudView.alpha = 0.0
+    hudView.translatesAutoresizingMaskIntoConstraints = false
+    view.addSubview(hudView)
+    
+    NSLayoutConstraint.activate([
+      hudView.topAnchor.constraint(equalTo: view.topAnchor),
+      hudView.leftAnchor.constraint(equalTo: view.leftAnchor),
+      hudView.rightAnchor.constraint(equalTo: view.rightAnchor),
+      hudView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
+    ])
+    
+    view.setNeedsLayout()
+    view.layoutIfNeeded()
+    
+    UIView.animate(withDuration: 0.2) {
+      hudView.alpha = 1.0
+    }
+    
+    hud = hudView
+  }
+}
diff --git a/Sources/AppCore/SendImage/SendImage.swift b/Sources/AppCore/SendImage/SendImage.swift
new file mode 100644
index 0000000000000000000000000000000000000000..04d7b69ee9dbaa6d845eaa944176b160f9117a55
--- /dev/null
+++ b/Sources/AppCore/SendImage/SendImage.swift
@@ -0,0 +1,108 @@
+import XXModels
+import XXClient
+import Foundation
+import XXMessengerClient
+import XCTestDynamicOverlay
+
+public struct SendImage {
+  public typealias OnError = (Error) -> Void
+  public typealias Completion = () -> Void
+
+  public var run: (Data, Data, @escaping OnError, @escaping Completion) -> Void
+
+  public func callAsFunction(
+    _ image: Data,
+    to recipientId: Data,
+    onError: @escaping OnError,
+    completion: @escaping Completion
+  ) {
+    run(image, recipientId, onError, completion)
+  }
+}
+
+extension SendImage {
+  public static func live(
+    messenger: Messenger,
+    db: DBManagerGetDB,
+    now: @escaping () -> Date
+  ) -> SendImage {
+    SendImage { image, recipientId, onError, completion in
+      func updateProgress(transferId: Data, progress: Float) {
+        do {
+          if var transfer = try db().fetchFileTransfers(.init(id: [transferId])).first {
+            transfer.progress = progress
+            try db().saveFileTransfer(transfer)
+          }
+        } catch {
+          onError(error)
+        }
+      }
+
+      let file = FileSend(
+        name: "image.jpg",
+        type: "image",
+        preview: nil,
+        contents: image
+      )
+      let params = MessengerSendFile.Params(
+        file: file,
+        recipientId: recipientId,
+        retry: 2,
+        callbackIntervalMS: 500
+      )
+      do {
+        let date = now()
+        let myContactId = try messenger.e2e.tryGet().getContact().getId()
+        let transferId = try messenger.sendFile(params) { info in
+          switch info {
+          case .progress(let transferId, let transmitted, let total):
+            updateProgress(
+              transferId: transferId,
+              progress: total > 0 ? Float(transmitted) / Float(total) : 0
+            )
+
+          case .finished(let transferId):
+            updateProgress(
+              transferId: transferId,
+              progress: 1
+            )
+
+          case .failed(_, .callback(let error)):
+            onError(error)
+
+          case .failed(_, .close(let error)):
+            onError(error)
+          }
+        }
+        try db().saveFileTransfer(XXModels.FileTransfer(
+          id: transferId,
+          contactId: myContactId,
+          name: file.name,
+          type: file.type,
+          data: image,
+          progress: 0,
+          isIncoming: false,
+          createdAt: date
+        ))
+        try db().saveMessage(XXModels.Message(
+          senderId: myContactId,
+          recipientId: recipientId,
+          groupId: nil,
+          date: date,
+          status: .sent,
+          isUnread: false,
+          text: "",
+          fileTransferId: transferId
+        ))
+      } catch {
+        onError(error)
+      }
+    }
+  }
+}
+
+extension SendImage {
+  public static let unimplemented = SendImage(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/SendMessage/SendMessage.swift b/Sources/AppCore/SendMessage/SendMessage.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d8ad8813c09b85fc9a6c33ef674680cb8943a2a3
--- /dev/null
+++ b/Sources/AppCore/SendMessage/SendMessage.swift
@@ -0,0 +1,86 @@
+import XXModels
+import XXClient
+import Foundation
+import XXMessengerClient
+import XCTestDynamicOverlay
+
+public struct SendMessage {
+  public typealias OnError = (Error) -> Void
+  public typealias Completion = () -> Void
+
+  public var run: (String, Data?, Data, @escaping OnError, @escaping Completion) -> Void
+
+  public func callAsFunction(
+    text: String,
+    replyingTo: Data?,
+    to recipientId: Data,
+    onError: @escaping OnError,
+    completion: @escaping Completion
+  ) {
+    run(text, replyingTo, recipientId, onError, completion)
+  }
+}
+
+extension SendMessage {
+  public static func live(
+    messenger: Messenger,
+    db: DBManagerGetDB,
+    now: @escaping () -> Date
+  ) -> SendMessage {
+    SendMessage { text, replyingTo, recipientId, onError, completion in
+      do {
+        let myContactId = try messenger.e2e.tryGet().getContact().getId()
+        let message = try db().saveMessage(.init(
+          senderId: myContactId,
+          recipientId: recipientId,
+          groupId: nil,
+          date: now(),
+          status: .sending,
+          isUnread: false,
+          text: text,
+          replyMessageId: replyingTo
+        ))
+        let payload = MessagePayload(text: message.text, replyingTo: replyingTo)
+        let report = try messenger.sendMessage(
+          recipientId: recipientId,
+          payload: try payload.encode(),
+          deliveryCallback: { deliveryReport in
+            let status: XXModels.Message.Status
+            switch deliveryReport.result {
+            case .delivered:
+              status = .sent
+            case .notDelivered(let timedOut):
+              status = timedOut ? .sendingTimedOut : .sendingFailed
+            case .failure(let error):
+              status = .sendingFailed
+              onError(error)
+            }
+            do {
+              try db().bulkUpdateMessages(
+                .init(id: [message.id]),
+                .init(status: status)
+              )
+            } catch {
+              onError(error)
+            }
+            completion()
+          }
+        )
+        if var message = try db().fetchMessages(.init(id: [message.id])).first {
+          message.networkId = report.messageId
+          message.roundURL = report.roundURL
+          _ = try db().saveMessage(message)
+        }
+      } catch {
+        onError(error)
+        completion()
+      }
+    }
+  }
+}
+
+extension SendMessage {
+  public static let unimplemented = SendMessage(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/StatusBar/StatusBarStylist.swift b/Sources/AppCore/StatusBar/StatusBarStylist.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7eaa87fdcf0db2ac136179bcc86b11cd718fd7c9
--- /dev/null
+++ b/Sources/AppCore/StatusBar/StatusBarStylist.swift
@@ -0,0 +1,59 @@
+import UIKit
+import Combine
+import XCTestDynamicOverlay
+
+public struct StatusBarStylist {
+  public var set: SetStyle
+  public var get: GetStyle
+  public var observe: ObserveStyle
+
+  public static func live() -> StatusBarStylist {
+    let styleSubject = CurrentValueSubject<UIStatusBarStyle, Never>(.lightContent)
+    return .init(
+      set: .init { styleSubject.send($0) },
+      get: .init { styleSubject.value },
+      observe: .init { styleSubject.eraseToAnyPublisher() }
+    )
+  }
+  public static let unimplemented = StatusBarStylist(
+    set: .unimplemented,
+    get: .unimplemented,
+    observe: .unimplemented
+  )
+}
+
+public struct GetStyle {
+  public var run: () -> UIStatusBarStyle
+
+  public func callAsFunction() -> UIStatusBarStyle {
+    run()
+  }
+
+  public static let unimplemented = GetStyle(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
+
+public struct SetStyle {
+  public var run: (UIStatusBarStyle) -> Void
+
+  public func callAsFunction(_ style: UIStatusBarStyle) -> Void {
+    run(style)
+  }
+
+  public static let unimplemented = SetStyle(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
+
+public struct ObserveStyle {
+  public var run: () -> AnyPublisher<UIStatusBarStyle, Never>
+
+  public func callAsFunction() -> AnyPublisher<UIStatusBarStyle, Never> {
+    run()
+  }
+
+  public static let unimplemented = ObserveStyle(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/ToastManager/ToastDismiss.swift b/Sources/AppCore/ToastManager/ToastDismiss.swift
new file mode 100644
index 0000000000000000000000000000000000000000..a328ca5757b1fc0c57a73ff6c7021d49f45242ea
--- /dev/null
+++ b/Sources/AppCore/ToastManager/ToastDismiss.swift
@@ -0,0 +1,19 @@
+import XCTestDynamicOverlay
+
+public struct ToastDismiss {
+  init(run: @escaping () -> Void) {
+    self.run = run
+  }
+
+  public var run: () -> Void
+
+  public func callAsFunction() -> Void {
+    run()
+  }
+}
+
+extension ToastDismiss {
+  public static let unimplemented = ToastDismiss(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/ToastManager/ToastEnqueue.swift b/Sources/AppCore/ToastManager/ToastEnqueue.swift
new file mode 100644
index 0000000000000000000000000000000000000000..ee0145b416e104cf445af6ba29a6216e64b9be36
--- /dev/null
+++ b/Sources/AppCore/ToastManager/ToastEnqueue.swift
@@ -0,0 +1,19 @@
+import XCTestDynamicOverlay
+
+public struct ToastEnqueue {
+  init(run: @escaping (ToastModel) -> Void) {
+    self.run = run
+  }
+
+  public var run: (ToastModel) -> Void
+
+  public func callAsFunction(_ model: ToastModel) -> Void {
+    run(model)
+  }
+}
+
+extension ToastEnqueue {
+  public static let unimplemented = ToastEnqueue(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/ToastManager/ToastManager.swift b/Sources/AppCore/ToastManager/ToastManager.swift
new file mode 100644
index 0000000000000000000000000000000000000000..ce14e820840d63894cab76e8b81d5ad4379dcc66
--- /dev/null
+++ b/Sources/AppCore/ToastManager/ToastManager.swift
@@ -0,0 +1,44 @@
+import Combine
+import XCTestDynamicOverlay
+
+public struct ToastManager {
+  public var enqueue: ToastEnqueue
+  public var dismiss: ToastDismiss
+  public var observe: ToastObserve
+}
+
+extension ToastManager {
+  public static func live() -> ToastManager {
+    class Context {
+      let queue = CurrentValueSubject<[ToastModel], Never>([])
+    }
+
+    let context = Context()
+
+    return .init(
+      enqueue: .init {
+        context.queue.value.append($0)
+      },
+      dismiss: .init {
+        guard context.queue.value.isEmpty == false else {
+          return
+        }
+        _ = context.queue.value.removeFirst()
+      },
+      observe: .init {
+        context.queue
+          .compactMap(\.first)
+          .removeDuplicates(by: { $0.id == $1.id })
+          .eraseToAnyPublisher()
+      }
+    )
+  }
+}
+
+extension ToastManager {
+  public static let unimplemented: ToastManager = .init(
+    enqueue: .unimplemented,
+    dismiss: .unimplemented,
+    observe: .unimplemented
+  )
+}
diff --git a/Sources/AppCore/ToastManager/ToastObserve.swift b/Sources/AppCore/ToastManager/ToastObserve.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d1b5fb7d2ce6b4153bbc1f283b4825eb1dfd3214
--- /dev/null
+++ b/Sources/AppCore/ToastManager/ToastObserve.swift
@@ -0,0 +1,16 @@
+import Combine
+import XCTestDynamicOverlay
+
+public struct ToastObserve {
+  public var run: () -> AnyPublisher<ToastModel, Never>
+
+  public func callAsFunction() -> AnyPublisher<ToastModel, Never> {
+    run()
+  }
+}
+
+extension ToastObserve {
+  public static let unimplemented = ToastObserve(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppCore/UI/HUDView.swift b/Sources/AppCore/UI/HUDView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..926e36420b97b084cfa35255c981885cbf240eb4
--- /dev/null
+++ b/Sources/AppCore/UI/HUDView.swift
@@ -0,0 +1,81 @@
+import UIKit
+import Shared
+import Combine
+import SnapKit
+import AppResources
+
+final class HUDView: UIView {
+  let titleLabel = UILabel()
+  let contentLabel = UILabel()
+  let stackView = UIStackView()
+  let backgroundView = UIView()
+  let actionButton = CapsuleButton()
+  let animationView = DotAnimation()
+  var cancellables = Set<AnyCancellable>()
+
+  init() {
+    super.init(frame: .zero)
+    stackView.spacing = 20
+    stackView.axis = .vertical
+
+    titleLabel.numberOfLines = 0
+    titleLabel.textAlignment = .center
+    titleLabel.textColor = Asset.neutralWhite.color
+    titleLabel.font = Fonts.Mulish.bold.font(size: 30.0)
+
+    contentLabel.numberOfLines = 0
+    contentLabel.textAlignment = .center
+    contentLabel.textColor = Asset.neutralWhite.color
+    contentLabel.font = Fonts.Mulish.regular.font(size: 15.0)
+
+    animationView.setColor(Asset.neutralWhite.color)
+    backgroundColor =  Asset.neutralDark.color.withAlphaComponent(0.9)
+
+    addSubview(backgroundView)
+    backgroundView.addSubview(stackView)
+
+    backgroundView.snp.makeConstraints {
+      $0.centerY.equalToSuperview()
+      $0.left.equalToSuperview().offset(30)
+      $0.right.equalToSuperview().offset(-30)
+    }
+
+    stackView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(15)
+      $0.left.equalToSuperview().offset(15)
+      $0.right.equalToSuperview().offset(-15)
+      $0.bottom.equalToSuperview().offset(-20)
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  func setup(model: HUDModel) -> Self {
+    if let title = model.title {
+      titleLabel.text = title
+      stackView.addArrangedSubview(titleLabel)
+    }
+    if let content = model.content {
+      contentLabel.text = content
+      stackView.addArrangedSubview(contentLabel)
+    }
+    if model.hasDotAnimation {
+      animationView.snp.makeConstraints {
+        $0.height.equalTo(20)
+      }
+      stackView.addArrangedSubview(animationView)
+    }
+    if let actionTitle = model.actionTitle {
+      actionButton.set(
+        style: .seeThroughWhite,
+        title: actionTitle
+      )
+      actionButton
+        .publisher(for: .touchUpInside)
+        .sink { model.onTapClosure?() }
+        .store(in: &cancellables)
+      stackView.addArrangedSubview(actionButton)
+    }
+    return self
+  }
+}
diff --git a/Sources/AppCore/UI/ToastView.swift b/Sources/AppCore/UI/ToastView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..a5202803704d20d44720f87a78c5149fd49c4291
--- /dev/null
+++ b/Sources/AppCore/UI/ToastView.swift
@@ -0,0 +1,78 @@
+import UIKit
+import Combine
+import AppResources
+
+final class ToastView: UIView {
+  let titleLabel = UILabel()
+  let subtitleLabel = UILabel()
+  let leftImageView = UIImageView()
+  let rightButton = UIButton()
+  let verticalStackView = UIStackView()
+  let horizontalStackView = UIStackView()
+  var cancellables = Set<AnyCancellable>()
+  
+  init(model: ToastModel) {
+    super.init(frame: .zero)
+    backgroundColor = model.color
+    layer.cornerRadius = 18.0
+    
+    titleLabel.textColor = .white
+    subtitleLabel.textColor = .white
+    leftImageView.contentMode = .center
+    
+    titleLabel.numberOfLines = 0
+    subtitleLabel.numberOfLines = 0
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 16.0)
+    subtitleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    
+    leftImageView.image = Asset.sharedSuccess.image
+    leftImageView.setContentHuggingPriority(.required, for: .horizontal)
+    
+    rightButton.titleLabel?.numberOfLines = 0
+    rightButton.titleLabel?.textAlignment = .center
+    rightButton.titleLabel?.font = Fonts.Mulish.bold.font(size: 12.0)
+    
+    verticalStackView.axis = .vertical
+    verticalStackView.distribution = .fill
+    verticalStackView.addArrangedSubview(titleLabel)
+    verticalStackView.addArrangedSubview(subtitleLabel)
+    
+    horizontalStackView.spacing = 12
+    horizontalStackView.addArrangedSubview(leftImageView)
+    horizontalStackView.addArrangedSubview(verticalStackView)
+    horizontalStackView.addArrangedSubview(rightButton)
+    
+    addSubview(horizontalStackView)
+    
+    horizontalStackView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(17)
+      $0.left.equalToSuperview().offset(20)
+      $0.right.equalToSuperview().offset(-20)
+      $0.bottom.equalToSuperview().offset(-17)
+    }
+    
+    titleLabel.text = model.title
+    leftImageView.image = model.leftImage
+    
+    if let subtitle = model.subtitle {
+      subtitleLabel.text = subtitle
+      subtitleLabel.numberOfLines = 0
+    } else {
+      subtitleLabel.isHidden = true
+    }
+    
+    if let buttonTitle = model.buttonTitle {
+      rightButton.setTitle(buttonTitle, for: .normal)
+      rightButton.setContentHuggingPriority(.required, for: .horizontal)
+    } else {
+      rightButton.isHidden = true
+    }
+    
+    rightButton
+      .publisher(for: .touchUpInside)
+      .sink { model.onTapClosure?() }
+      .store(in: &cancellables)
+  }
+  
+  required init?(coder: NSCoder) { nil }
+}
diff --git a/Sources/AppCore/URLDataLoader/URLDataLoader.swift b/Sources/AppCore/URLDataLoader/URLDataLoader.swift
new file mode 100644
index 0000000000000000000000000000000000000000..bca96e58f59d9880aca1f38d100192fdf9c97f47
--- /dev/null
+++ b/Sources/AppCore/URLDataLoader/URLDataLoader.swift
@@ -0,0 +1,22 @@
+import Foundation
+import XCTestDynamicOverlay
+
+public struct URLDataLoader {
+  public var load: (URL) throws -> Data
+
+  public func callAsFunction(_ url: URL) throws -> Data {
+    try load(url)
+  }
+}
+
+extension URLDataLoader {
+  public static let live = URLDataLoader { url in
+    try Data(contentsOf: url)
+  }
+}
+
+extension URLDataLoader {
+  public static let unimplemented = URLDataLoader(
+    load: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/AppFeature/AppDelegate.swift b/Sources/AppFeature/AppDelegate.swift
new file mode 100644
index 0000000000000000000000000000000000000000..4d8769464d0db69ad9c684c510c2f3753ea05c8c
--- /dev/null
+++ b/Sources/AppFeature/AppDelegate.swift
@@ -0,0 +1,254 @@
+import UIKit
+import AppCore
+import Defaults
+import XXClient
+import Dependencies
+import LaunchFeature
+import XXMessengerClient
+
+// MARK: - TO REMOVE FROM PRODUCTION:
+import Logging
+import PulseUI
+import AppNavigation
+import PulseLogHandler
+// MARK: -
+
+public class AppDelegate: UIResponder, UIApplicationDelegate {
+  public var window: UIWindow?
+  private var coverView: UIView?
+  private var backgroundTimer: Timer?
+  private var backgroundTask: UIBackgroundTaskIdentifier?
+
+  @Dependency(\.app.log) var log
+  @Dependency(\.navigator) var navigator
+  @Dependency(\.app.messenger) var messenger
+  @Dependency(\.pushNotificationRouter) var pushNotificationRouter
+
+  @KeyObject(.hideAppList, defaultValue: false) var shouldHideAppInAppList
+  @KeyObject(.pushNotifications, defaultValue: false) var isPushNotificationsEnabled
+
+  public func application(
+    _ application: UIApplication,
+    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+  ) -> Bool {
+    LoggingSystem.bootstrap(PersistentLogHandler.init)
+
+    UNUserNotificationCenter.current().delegate = self
+
+    let navController = UINavigationController(rootViewController: LaunchController())
+    window = UIWindow(frame: UIScreen.main.bounds)
+    window?.rootViewController = RootViewController(navController)
+    window?.makeKeyAndVisible()
+
+    pushNotificationRouter.set(.live(navigationController: navController))
+
+//    #if DEBUG
+    NotificationCenter.default.addObserver(
+      forName: UIApplication.userDidTakeScreenshotNotification,
+      object: nil,
+      queue: OperationQueue.main
+    ) { [weak self] _ in
+      guard let self else { return }
+      let pulseViewController = PulseUI.MainViewController(store: .shared)
+      self.navigator.perform(
+        PresentModal(
+          pulseViewController,
+          from: navController.topViewController!
+        )
+      )
+    }
+//    #endif
+
+    return true
+  }
+
+  public func applicationWillResignActive(_ application: UIApplication) {
+    if shouldHideAppInAppList {
+      coverView?.removeFromSuperview()
+      coverView = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
+      coverView?.frame = window?.bounds ?? .zero
+      window?.addSubview(coverView!)
+    }
+  }
+
+  public func applicationDidBecomeActive(_ application: UIApplication) {
+    application.applicationIconBadgeNumber = 0
+    coverView?.removeFromSuperview()
+  }
+
+  public func applicationWillEnterForeground(_ application: UIApplication) {
+    resumeMessenger(application)
+  }
+
+  public func applicationDidEnterBackground(_ application: UIApplication) {
+    stopMessenger(application)
+  }
+
+  public func application(
+    application: UIApplication,
+    shouldAllowExtensionPointIdentifier identifier: String
+  ) -> Bool {
+    if identifier == UIApplication.ExtensionPointIdentifier.keyboard.rawValue {
+      return false /// Disable custom keyboards
+    }
+    return true
+  }
+
+  public func application(
+    _ application: UIApplication,
+    continue userActivity: NSUserActivity,
+    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
+  ) -> Bool {
+    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
+          let incomingURL = userActivity.webpageURL,
+          let username = getUsernameFromInvitationDeepLink(incomingURL),
+          let router = pushNotificationRouter.get() else {
+      return false
+    }
+
+    router.navigateTo(.search(username: username), {})
+    return true
+  }
+}
+
+extension AppDelegate: UNUserNotificationCenterDelegate {
+  public func application(
+    _ application: UIApplication,
+    didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
+  ) {
+    if messenger.isConnected() {
+      do {
+        try messenger.registerForNotifications(token: deviceToken)
+        isPushNotificationsEnabled = true
+      } catch {
+        isPushNotificationsEnabled = false
+        log(.error(error as NSError))
+        print(error.localizedDescription)
+      }
+    }
+  }
+
+  public func userNotificationCenter(
+    _ center: UNUserNotificationCenter,
+    didReceive response: UNNotificationResponse,
+    withCompletionHandler completionHandler: @escaping () -> Void
+  ) {
+    let userInfo = response.notification.request.content.userInfo
+    guard let string = userInfo["type"] as? String,
+          let type = NotificationReport.ReportType(rawValue: string) else {
+      completionHandler()
+      return
+    }
+    var route: PushNotificationRouter.Route?
+    switch type {
+    case .e2e, .group:
+      guard let source = userInfo["source"] as? Data else {
+        completionHandler()
+        return
+      }
+      if type == .e2e {
+        route = .contactChat(id: source)
+      } else {
+        route = .groupChat(id: source)
+      }
+    default:
+      break
+    }
+
+    if let route, let router = pushNotificationRouter.get() {
+      router.navigateTo(route, completionHandler)
+    }
+  }
+
+  public func application(
+    _ application: UIApplication,
+    didReceiveRemoteNotification notification: [AnyHashable: Any],
+    fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
+  ) {
+    if application.applicationState == .background,
+       let csv = notification["notificationData"] as? String,
+       let reports = try? messenger.getNotificationReports(notificationCSV: csv) {
+      reports
+        .filter { $0.forMe }
+        .filter { $0.type != .silent }
+        .filter { $0.type != .default }
+        .map {
+          let content = UNMutableNotificationContent()
+          content.badge = 1
+          content.body = ""
+          content.sound = .default
+          content.userInfo["source"] = $0.source
+          content.userInfo["type"] = $0.type.rawValue
+          content.threadIdentifier = "new_message_identifier"
+          return content
+        }.map {
+          UNNotificationRequest(
+            identifier: Bundle.main.bundleIdentifier!,
+            content: $0,
+            trigger: UNTimeIntervalNotificationTrigger(
+              timeInterval: 1,
+              repeats: false
+            )
+          )
+        }.forEach {
+          UNUserNotificationCenter.current().add($0) { error in
+            error == nil ? completionHandler(.newData) : completionHandler(.failed)
+          }
+        }
+    } else {
+      completionHandler(.noData)
+    }
+  }
+}
+
+extension AppDelegate {
+  private func resumeMessenger(_ application: UIApplication) {
+    backgroundTimer?.invalidate()
+    backgroundTimer = nil
+    if let backgroundTask {
+      application.endBackgroundTask(backgroundTask)
+    }
+    do {
+      if messenger.isLoaded() {
+        try messenger.start()
+      }
+    } catch {
+      log(.error(error as NSError))
+      print(error.localizedDescription)
+    }
+  }
+
+  private func stopMessenger(_ application: UIApplication) {
+    guard messenger.isLoaded() else { return }
+
+    backgroundTask = application.beginBackgroundTask(withName: "STOPPING_NETWORK")
+    backgroundTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
+      guard let self else { return }
+
+      if application.backgroundTimeRemaining <= 5 {
+        do {
+          self.backgroundTimer?.invalidate()
+          try self.messenger.stop()
+        } catch {
+          self.log(.error(error as NSError))
+          print(error.localizedDescription)
+        }
+        if let backgroundTask = self.backgroundTask {
+          application.endBackgroundTask(backgroundTask)
+        }
+      }
+    }
+  }
+}
+
+func getUsernameFromInvitationDeepLink(_ url: URL) -> String? {
+  if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
+     components.scheme == "https",
+     components.host == "elixxir.io",
+     components.path == "/connect",
+     let queryItem = components.queryItems?.first(where: { $0.name == "username" }),
+     let username = queryItem.value {
+    return username
+  }
+  return nil
+}
diff --git a/Sources/AppFeature/Dependencies.swift b/Sources/AppFeature/Dependencies.swift
new file mode 100644
index 0000000000000000000000000000000000000000..16f2bf788c5f2c81625012d6805eeec163f77f24
--- /dev/null
+++ b/Sources/AppFeature/Dependencies.swift
@@ -0,0 +1,148 @@
+import ScanFeature
+import ChatFeature
+import MenuFeature
+import TermsFeature
+import Dependencies
+import AppNavigation
+import BackupFeature
+import DrawerFeature
+import SearchFeature
+import RestoreFeature
+import ContactFeature
+import WebsiteFeature
+import ProfileFeature
+import ChatListFeature
+import SettingsFeature
+import RequestsFeature
+import GroupDraftFeature
+import OnboardingFeature
+import CountryListFeature
+import CreateGroupFeature
+import ContactListFeature
+import RequestPermissionFeature
+
+extension NavigatorKey: DependencyKey {
+  public static let liveValue: Navigator = CombinedNavigator(
+    PresentModalNavigator(),
+    DismissModalNavigator(),
+    PushNavigator(),
+    PopToRootNavigator(),
+    PopToNavigator(),
+    SetStackNavigator(),
+    OpenUpNavigator(),
+    OpenLeftNavigator(),
+
+    PresentPhotoLibraryNavigator(),
+    PresentActivitySheetNavigator(),
+
+    PresentWebsiteNavigator(
+      WebsiteController.init(_:)
+    ),
+    PresentCreateGroupNavigator(
+      CreateGroupController.init(_:)
+    ),
+    PresentGroupDraftNavigator(
+      GroupDraftController.init
+    ),
+    PresentMenuNavigator(
+      MenuController.init(_:_:)
+    ),
+    PresentProfileNavigator(
+      ProfileController.init
+    ),
+    PresentChatListNavigator(
+      ChatListController.init
+    ),
+    PresentDrawerNavigator(
+      DrawerController.init(_:)
+    ),
+    PresentScanNavigator(
+      ScanContainerController.init
+    ),
+    PresentChatNavigator(
+      SingleChatController.init(_:)
+    ),
+    PresentContactNavigator(
+      ContactController.init(_:)
+    ),
+    PresentSettingsNavigator(
+      SettingsMainController.init
+    ),
+    PresentSettingsBackupNavigator(
+      BackupController.init
+    ),
+    PresentRestoreListNavigator(
+      RestoreListController.init
+    ),
+    PresentContactListNavigator(
+      ContactListController.init
+    ),
+    PresentGroupChatNavigator(
+      GroupChatController.init(_:)
+    ),
+    PresentProfileEmailNavigator(
+      ProfileEmailController.init
+    ),
+    PresentProfilePhoneNavigator(
+      ProfilePhoneController.init
+    ),
+    PresentSearchNavigator(
+      ChatListController.init,
+      SearchContainerController.init(_:)
+    ),
+    PresentRequestsNavigator(
+      RequestsContainerController.init
+    ),
+    PresentCountryListNavigator(
+      CountryListController.init(_:)
+    ),
+    PresentOnboardingEmailNavigator(
+      OnboardingEmailController.init
+    ),
+    PresentOnboardingPhoneNavigator(
+      OnboardingPhoneController.init
+    ),
+    PresentProfileCodeNavigator(
+      ProfileCodeController.init(_:_:_:)
+    ),
+    PresentOnboardingStartNavigator(
+      OnboardingStartController.init
+    ),
+    PresentSettingsAdvancedNavigator(
+      SettingsAdvancedController.init
+    ),
+    PresentTermsAndConditionsNavigator(
+      TermsConditionsController.init
+    ),
+    PresentPermissionRequestNavigator(
+      RequestPermissionController.init
+    ),
+    PresentOnboardingWelcomeNavigator(
+      OnboardingWelcomeController.init
+    ),
+    PresentSettingsAccountDeleteNavigator(
+      SettingsDeleteController.init
+    ),
+    PresentOnboardingUsernameNavigator(
+      OnboardingUsernameController.init
+    ),
+    PresentOnboardingCodeNavigator(
+      OnboardingCodeController.init(_:_:_:)
+    )
+  )
+}
+
+import LaunchFeature
+import XXMessengerClient
+
+private enum PushNotificationRouterKey: DependencyKey {
+  static var liveValue = Stored<PushNotificationRouter?>.inMemory()
+  static var testValue = Stored<PushNotificationRouter?>.unimplemented()
+}
+
+extension DependencyValues {
+  public var pushNotificationRouter: Stored<PushNotificationRouter?> {
+    get { self[PushNotificationRouterKey.self] }
+    set { self[PushNotificationRouterKey.self] = newValue }
+  }
+}
diff --git a/Sources/AppFeature/PushNotificationRouter.swift b/Sources/AppFeature/PushNotificationRouter.swift
new file mode 100644
index 0000000000000000000000000000000000000000..0ff84204ecacef4c9e12684c3678d33b555f5a48
--- /dev/null
+++ b/Sources/AppFeature/PushNotificationRouter.swift
@@ -0,0 +1,57 @@
+import UIKit
+import Dependencies
+import AppNavigation
+
+import ChatFeature
+import LaunchFeature
+import SearchFeature
+import ChatListFeature
+import RequestsFeature
+
+extension PushNotificationRouter {
+  public static func live(navigationController: UINavigationController) -> PushNotificationRouter {
+    PushNotificationRouter { route, completion in
+      @Dependency(\.navigator) var navigator
+      @Dependency(\.app.dbManager) var dbManager
+
+      if let launchController = navigationController.viewControllers.last as? LaunchController {
+        launchController.pendingPushNotificationRoute = route
+      } else {
+        switch route {
+        case .requests:
+          if !(navigationController.viewControllers.last is RequestsContainerController) {
+            navigator.perform(PresentRequests(on: navigationController))
+          }
+
+        case .search(username: let username):
+          if !(navigationController.viewControllers.last is SearchContainerController) {
+            navigator.perform(PresentSearch(
+              searching: username,
+              fromOnboarding: true,
+              on: navigationController,
+              animated: true
+            ))
+          } else {
+            (navigationController.viewControllers.last as? SearchContainerController)?
+              .startSearchingFor(username)
+          }
+
+        case .contactChat(id: let id):
+          if let contact = try? dbManager.getDB().fetchContacts(.init(id: [id])).first {
+            navigator.perform(SetStack([
+              ChatListController(), SingleChatController(contact)
+            ], on: navigationController))
+          }
+
+        case .groupChat(id: let id):
+          if let groupInfo = try? dbManager.getDB().fetchGroupInfos(.init(groupId: id)).first {
+            navigator.perform(SetStack([
+              ChatListController(), GroupChatController(groupInfo)
+            ], on: navigationController))
+          }
+        }
+      }
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/Action.swift b/Sources/AppNavigation/Action.swift
new file mode 100644
index 0000000000000000000000000000000000000000..ff697eeb21a0937ded7ee2c33961f8cde8592c69
--- /dev/null
+++ b/Sources/AppNavigation/Action.swift
@@ -0,0 +1,2 @@
+/// Navigation action
+public protocol Action {}
diff --git a/Sources/AppNavigation/CombinedNavigator.swift b/Sources/AppNavigation/CombinedNavigator.swift
new file mode 100644
index 0000000000000000000000000000000000000000..fdbb1741cfe97da7c9295797f57711bfadf17553
--- /dev/null
+++ b/Sources/AppNavigation/CombinedNavigator.swift
@@ -0,0 +1,30 @@
+/// Combines multiple navigators into a single one
+///
+/// - Action is performed using the first navigator that can handle it
+/// - When there is no navigator that can handle given action, assertion is thrown
+/// - When there are multiple navigators that can handle given action, assertion is thrown
+public struct CombinedNavigator: Navigator {
+  public init(_ navigators: Navigator...) {
+    self.navigators = navigators
+  }
+
+  public init(_ navigators: [Navigator]) {
+    self.navigators = navigators
+  }
+
+  public func perform(_ action: Action, completion: @escaping () -> Void) {
+    let navigators = self.navigators.filter { $0.canPerform(action) }
+    guard let firstNavigator = navigators.first else {
+      assertionFailure("No navigator to perform action: \(action)", #file, #line)
+      return
+    }
+    guard navigators.count == 1 else {
+      assertionFailure("Multiple navigators can perform action: \(action), \(navigators)", #file, #line)
+      return
+    }
+    firstNavigator.perform(action, completion: completion)
+  }
+
+  let navigators: [Navigator]
+  var assertionFailure: (@autoclosure () -> String, StaticString, UInt) -> Void = Swift.assertionFailure
+}
diff --git a/Sources/AppNavigation/CustomTransitions/BottomTransition.swift b/Sources/AppNavigation/CustomTransitions/BottomTransition.swift
new file mode 100644
index 0000000000000000000000000000000000000000..66b422c1f833a2b6690cea8947ee07f9481ba070
--- /dev/null
+++ b/Sources/AppNavigation/CustomTransitions/BottomTransition.swift
@@ -0,0 +1,136 @@
+import UIKit
+import Combine
+
+final class BottomTransition: NSObject, UIViewControllerAnimatedTransitioning {
+  enum Direction {
+    case present
+    case dismiss
+  }
+
+  let isDismissableOnBackground: Bool
+  var direction: Direction = .present
+  private let onDismissal: (() -> Void)?
+  private weak var darkOverlayView: UIControl?
+  private weak var topConstraint: NSLayoutConstraint?
+  private weak var bottomConstraint: NSLayoutConstraint?
+  private var cancellables = Set<AnyCancellable>()
+
+  private var presentedConstraints: [NSLayoutConstraint] = []
+  private var dismissedConstraints: [NSLayoutConstraint] = []
+  private var presentingController: UIViewController?
+
+  init(
+    _ isDismissableOnBackground: Bool = true,
+    onDismissal: (() -> Void)?
+  ) {
+    self.onDismissal = onDismissal
+    self.isDismissableOnBackground = isDismissableOnBackground
+    super.init()
+  }
+
+  func transitionDuration(
+    using context: UIViewControllerContextTransitioning?
+  ) -> TimeInterval { 0.5 }
+
+  func animateTransition(
+    using context: UIViewControllerContextTransitioning
+  ) {
+    switch direction {
+    case .present:
+      present(using: context)
+    case .dismiss:
+      dismiss(using: context)
+    }
+  }
+
+  private func present(using context: UIViewControllerContextTransitioning) {
+    guard let presentingController = context.viewController(forKey: .from),
+          let presentedView = context.view(forKey: .to) else {
+      context.completeTransition(false)
+      return
+    }
+
+    let darkOverlayView = UIControl()
+    self.darkOverlayView = darkOverlayView
+
+    darkOverlayView.alpha = 0.0
+    darkOverlayView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
+    context.containerView.addSubview(darkOverlayView)
+    darkOverlayView.frame = context.containerView.bounds
+
+    if isDismissableOnBackground {
+      darkOverlayView.addTarget(self, action: #selector(didTapOverlay), for: .touchUpInside)
+      self.presentingController = presentingController
+    }
+
+    context.containerView.addSubview(presentedView)
+    presentedView.translatesAutoresizingMaskIntoConstraints = false
+
+    presentedConstraints = [
+      presentedView.leftAnchor.constraint(equalTo: context.containerView.leftAnchor),
+      presentedView.rightAnchor.constraint(equalTo: context.containerView.rightAnchor),
+      presentedView.bottomAnchor.constraint(equalTo: context.containerView.bottomAnchor),
+      presentedView.topAnchor.constraint(
+        greaterThanOrEqualTo: context.containerView.safeAreaLayoutGuide.topAnchor,
+        constant: 60
+      )
+    ]
+
+    dismissedConstraints = [
+      presentedView.leftAnchor.constraint(equalTo: context.containerView.leftAnchor),
+      presentedView.rightAnchor.constraint(equalTo: context.containerView.rightAnchor),
+      presentedView.topAnchor.constraint(equalTo: context.containerView.bottomAnchor)
+    ]
+
+    NSLayoutConstraint.activate(dismissedConstraints)
+
+    context.containerView.setNeedsLayout()
+    context.containerView.layoutIfNeeded()
+
+    NSLayoutConstraint.deactivate(dismissedConstraints)
+    NSLayoutConstraint.activate(presentedConstraints)
+
+    UIView.animate(
+      withDuration: transitionDuration(using: context),
+      delay: 0,
+      usingSpringWithDamping: 1,
+      initialSpringVelocity: 0,
+      options: .curveEaseInOut,
+      animations: {
+        darkOverlayView.alpha = 1.0
+        context.containerView.setNeedsLayout()
+        context.containerView.layoutIfNeeded()
+      },
+      completion: { _ in
+        context.completeTransition(true)
+      })
+  }
+
+  private func dismiss(using context: UIViewControllerContextTransitioning) {
+    NSLayoutConstraint.deactivate(presentedConstraints)
+    NSLayoutConstraint.activate(dismissedConstraints)
+
+    UIView.animate(
+      withDuration: transitionDuration(using: context),
+      delay: 0,
+      usingSpringWithDamping: 1,
+      initialSpringVelocity: 0,
+      options: .curveEaseInOut,
+      animations: { [weak darkOverlayView] in
+        darkOverlayView?.alpha = 0.0
+        context.containerView.setNeedsLayout()
+        context.containerView.layoutIfNeeded()
+      },
+      completion: { [weak self] _ in
+        context.completeTransition(true)
+        self?.onDismissal?()
+      }
+    )
+  }
+
+  @objc private func didTapOverlay() {
+    if let presentingController, isDismissableOnBackground {
+      presentingController.dismiss(animated: true)
+    }
+  }
+}
diff --git a/Sources/AppNavigation/CustomTransitions/BottomTransitioningDelegate.swift b/Sources/AppNavigation/CustomTransitions/BottomTransitioningDelegate.swift
new file mode 100644
index 0000000000000000000000000000000000000000..b30e549738b953c01702f1ca6897b6f50899fa9c
--- /dev/null
+++ b/Sources/AppNavigation/CustomTransitions/BottomTransitioningDelegate.swift
@@ -0,0 +1,25 @@
+import UIKit
+
+final class BottomTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
+  var isDismissableOnBackgroundTouch: Bool = true
+  private var transition: BottomTransition?
+
+  func animationController(
+    forPresented presented: UIViewController,
+    presenting: UIViewController,
+    source: UIViewController
+  ) -> UIViewControllerAnimatedTransitioning? {
+    transition = BottomTransition(isDismissableOnBackgroundTouch) { [weak self] in
+      guard let self else { return }
+      self.transition = nil
+    }
+    return transition
+  }
+
+  func animationController(
+    forDismissed dismissed: UIViewController
+  ) -> UIViewControllerAnimatedTransitioning? {
+    transition?.direction = .dismiss
+    return transition
+  }
+}
diff --git a/Sources/AppNavigation/CustomTransitions/LeftAnimator.swift b/Sources/AppNavigation/CustomTransitions/LeftAnimator.swift
new file mode 100644
index 0000000000000000000000000000000000000000..6944551740e8cd93364a9ddf06e2cc8adac38b0d
--- /dev/null
+++ b/Sources/AppNavigation/CustomTransitions/LeftAnimator.swift
@@ -0,0 +1,23 @@
+import UIKit
+
+protocol LeftAnimating {
+  func animate(in containerView: UIView, to progress: CGFloat)
+}
+
+struct LeftAnimator: LeftAnimating {
+  func animate(in containerView: UIView, to progress: CGFloat) {
+    guard let fromView = containerView.viewWithTag(LeftPresentTransition.fromViewTag) else { return }
+
+    let cornerRadius = progress * 24
+    let shadowOpacity = Float(progress)
+    let offsetX = containerView.bounds.size.width * 0.5 * progress
+    let offsetY = containerView.bounds.size.height * 0.08 * progress
+    let scale = 1 - (0.25 * progress)
+
+    fromView.subviews.first?.layer.cornerRadius = cornerRadius
+    fromView.layer.shadowOpacity = shadowOpacity
+    fromView.transform = CGAffineTransform.identity
+      .translatedBy(x: offsetX, y: offsetY)
+      .scaledBy(x: scale, y: scale)
+  }
+}
diff --git a/Sources/AppNavigation/CustomTransitions/LeftDismissInteractor.swift b/Sources/AppNavigation/CustomTransitions/LeftDismissInteractor.swift
new file mode 100644
index 0000000000000000000000000000000000000000..c7d7f7868da64721df0ebb52249c7807813f64bb
--- /dev/null
+++ b/Sources/AppNavigation/CustomTransitions/LeftDismissInteractor.swift
@@ -0,0 +1,69 @@
+import UIKit
+
+protocol LeftDismissInteracting: UIViewControllerInteractiveTransitioning {
+  var interactionInProgress: Bool { get }
+  func setup(view: UIView, action: @escaping (() -> Void))
+}
+
+final class LeftDismissInteractor:
+  UIPercentDrivenInteractiveTransition, LeftDismissInteracting {
+  private var action: (() -> Void)?
+  public var interactionInProgress = false
+  private var shouldFinishTransition = false
+
+  func setup(view: UIView, action: @escaping (() -> Void)) {
+    view.addGestureRecognizer(UIPanGestureRecognizer(
+      target: self,
+      action: #selector(handlePanGesture(_:))
+    ))
+    view.addGestureRecognizer(UITapGestureRecognizer(
+      target: self,
+      action: #selector(handleTapGesture(_:))
+    ))
+    self.action = action
+  }
+
+  @objc
+  private func handleTapGesture(_ recognizer: UITapGestureRecognizer) {
+    action?()
+  }
+
+  @objc
+  private func handlePanGesture(_ recognizer: UIPanGestureRecognizer) {
+    guard let view = recognizer.view,
+          let containerView = view.superview
+    else { return }
+
+    let viewWidth = containerView.bounds.size.width
+    guard viewWidth > 0 else { return }
+
+    let translation = recognizer.translation(in: view)
+    let progress = min(1, max(0, -translation.x / (viewWidth * 0.8)))
+
+    switch recognizer.state {
+    case .possible, .failed:
+      interactionInProgress = false
+      
+    case .began:
+      interactionInProgress = true
+      shouldFinishTransition = false
+      action?()
+
+    case .changed:
+      shouldFinishTransition = progress >= 0.5
+      update(progress)
+
+    case .cancelled:
+      interactionInProgress = false
+      cancel()
+
+    case .ended:
+      interactionInProgress = false
+      shouldFinishTransition ? finish() : cancel()
+
+    @unknown default:
+      interactionInProgress = false
+      cancel()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/CustomTransitions/LeftDismissTransition.swift b/Sources/AppNavigation/CustomTransitions/LeftDismissTransition.swift
new file mode 100644
index 0000000000000000000000000000000000000000..001ccf8ace53ab9fa411eb2a6014cfe6acaf36b9
--- /dev/null
+++ b/Sources/AppNavigation/CustomTransitions/LeftDismissTransition.swift
@@ -0,0 +1,34 @@
+import UIKit
+
+final class LeftDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
+  let menuAnimator: LeftAnimating
+  let viewAnimator: UIViewAnimating.Type
+
+  init(
+    menuAnimator: LeftAnimating,
+    viewAnimator: UIViewAnimating.Type
+  ) {
+    self.menuAnimator = menuAnimator
+    self.viewAnimator = viewAnimator
+    super.init()
+  }
+
+  func transitionDuration(
+    using context: UIViewControllerContextTransitioning?
+  ) -> TimeInterval { 0.25 }
+
+  func animateTransition(
+    using context: UIViewControllerContextTransitioning
+  ) {
+    viewAnimator.animate(
+      withDuration: transitionDuration(using: context),
+      animations: {
+        self.menuAnimator.animate(in: context.containerView, to: 0)
+      },
+      completion: { _ in
+        let isCancelled = context.transitionWasCancelled
+        context.completeTransition(isCancelled == false)
+      }
+    )
+  }
+}
diff --git a/Sources/AppNavigation/CustomTransitions/LeftPresentTransition.swift b/Sources/AppNavigation/CustomTransitions/LeftPresentTransition.swift
new file mode 100644
index 0000000000000000000000000000000000000000..3af434fff9f4d7248e28c44ba56d00c93b2e6ad1
--- /dev/null
+++ b/Sources/AppNavigation/CustomTransitions/LeftPresentTransition.swift
@@ -0,0 +1,68 @@
+import UIKit
+
+final class LeftPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
+  let dismissInteractor: LeftDismissInteracting
+  let menuAnimator: LeftAnimating
+  let viewAnimator: UIViewAnimating.Type
+
+  static let fromViewTag = UUID().hashValue
+
+  init(
+    dismissInteractor: LeftDismissInteracting,
+    menuAnimator: LeftAnimating,
+    viewAnimator: UIViewAnimating.Type
+  ) {
+    self.dismissInteractor = dismissInteractor
+    self.menuAnimator = menuAnimator
+    self.viewAnimator = viewAnimator
+    super.init()
+  }
+
+  func transitionDuration(
+    using context: UIViewControllerContextTransitioning?
+  ) -> TimeInterval { 0.25 }
+
+  func animateTransition(
+    using context: UIViewControllerContextTransitioning
+  ) {
+    guard let fromVC = context.viewController(forKey: .from),
+          let fromSnapshot = fromVC.view.snapshotView(afterScreenUpdates: true),
+          let toVC = context.viewController(forKey: .to)
+    else {
+      context.completeTransition(false)
+      return
+    }
+
+    context.containerView.addSubview(toVC.view)
+    toVC.view.frame = context.containerView.bounds
+
+    let fromView = UIView()
+    fromView.tag = Self.fromViewTag
+    context.containerView.addSubview(fromView)
+    fromView.frame = context.containerView.bounds
+    fromView.layer.shadowColor = UIColor.black.cgColor
+    fromView.layer.shadowOpacity = 1
+    fromView.layer.shadowOffset = .zero
+    fromView.layer.shadowRadius = 32
+    fromView.addSubview(fromSnapshot)
+    fromSnapshot.frame = fromView.bounds
+    fromSnapshot.layer.cornerRadius = 0
+    fromSnapshot.layer.masksToBounds = true
+
+    dismissInteractor.setup(
+      view: fromView,
+      action: { fromVC.dismiss(animated: true) }
+    )
+
+    viewAnimator.animate(
+      withDuration: transitionDuration(using: context),
+      animations: {
+        self.menuAnimator.animate(in: context.containerView, to: 1)
+      },
+      completion: { _ in
+        let isCancelled = context.transitionWasCancelled
+        context.completeTransition(isCancelled == false)
+      }
+    )
+  }
+}
diff --git a/Sources/AppNavigation/CustomTransitions/LeftTransitioningDelegate.swift b/Sources/AppNavigation/CustomTransitions/LeftTransitioningDelegate.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7b918587e8b49a67227fd4d04a9a08aec095a979
--- /dev/null
+++ b/Sources/AppNavigation/CustomTransitions/LeftTransitioningDelegate.swift
@@ -0,0 +1,55 @@
+import UIKit
+
+protocol UIViewAnimating {
+  static func animate(
+    withDuration duration: TimeInterval,
+    animations: @escaping (() -> Void),
+    completion: ((Bool) -> Void)?
+  )
+}
+
+extension UIView: UIViewAnimating {}
+
+final class LeftTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
+  let menuAnimator: LeftAnimating
+  let viewAnimator: UIViewAnimating.Type
+  let dismissInteractor: LeftDismissInteracting
+
+  init(
+    dismissInteractor: LeftDismissInteracting = LeftDismissInteractor(),
+    menuAnimator: LeftAnimating = LeftAnimator(),
+    viewAnimator: UIViewAnimating.Type = UIView.self
+  ) {
+    self.dismissInteractor = dismissInteractor
+    self.menuAnimator = menuAnimator
+    self.viewAnimator = viewAnimator
+    super.init()
+  }
+
+  func animationController(
+    forPresented presented: UIViewController,
+    presenting: UIViewController,
+    source: UIViewController
+  ) -> UIViewControllerAnimatedTransitioning? {
+    LeftPresentTransition(
+      dismissInteractor: dismissInteractor,
+      menuAnimator: menuAnimator,
+      viewAnimator: viewAnimator
+    )
+  }
+
+  func animationController(
+    forDismissed dismissed: UIViewController
+  ) -> UIViewControllerAnimatedTransitioning? {
+    LeftDismissTransition(
+      menuAnimator: menuAnimator,
+      viewAnimator: viewAnimator
+    )
+  }
+
+  func interactionControllerForDismissal(
+    using animator: UIViewControllerAnimatedTransitioning
+  ) -> UIViewControllerInteractiveTransitioning? {
+    dismissInteractor.interactionInProgress ? dismissInteractor : nil
+  }
+}
diff --git a/Sources/AppNavigation/Dependency.swift b/Sources/AppNavigation/Dependency.swift
new file mode 100644
index 0000000000000000000000000000000000000000..c11e4ce52060f110c1a42a5436eed5a3bde96740
--- /dev/null
+++ b/Sources/AppNavigation/Dependency.swift
@@ -0,0 +1,26 @@
+import Dependencies
+import XCTestDynamicOverlay
+
+public enum NavigatorKey: TestDependencyKey {
+  public static let testValue: Navigator = UnimplementedNavigator()
+}
+
+public extension DependencyValues {
+  var navigator: Navigator {
+    get { self[NavigatorKey.self] }
+    set { self[NavigatorKey.self] = newValue }
+  }
+}
+
+public struct UnimplementedNavigator: Navigator {
+  public init() {}
+
+  public func perform(_ action: Action, completion: @escaping () -> Void) {
+    XCTestDynamicOverlay.XCTFail("UnimplementedNavigator.perform not implemented")
+  }
+
+  public func canPerform(_ action: Action) -> Bool {
+    XCTestDynamicOverlay.XCTFail("UnimplementedNavigator.canPerform not implemented")
+    return false
+  }
+}
diff --git a/Sources/AppNavigation/Generic/DismissModal.swift b/Sources/AppNavigation/Generic/DismissModal.swift
new file mode 100644
index 0000000000000000000000000000000000000000..5511e991db9441133e56b4336bfe71eb5a6e4591
--- /dev/null
+++ b/Sources/AppNavigation/Generic/DismissModal.swift
@@ -0,0 +1,33 @@
+import UIKit
+
+/// Dismiss view controller presented from provided view controller
+public struct DismissModal: Action {
+  /// - Parameters:
+  ///   - parent: Parent view controller from which dismiss happens
+  ///   - animated: Animate the transition
+  public init(
+    from parent: UIViewController,
+    animated: Bool = true
+  ) {
+    self.parent = parent
+    self.animated = animated
+  }
+
+  /// Parent view controller from which dismiss happens
+  public var parent: UIViewController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `DismissModal` action
+public struct DismissModalNavigator: TypedNavigator {
+  public init() {}
+
+  public func perform(_ action: DismissModal, completion: @escaping () -> Void) {
+    action.parent.dismiss(
+      animated: action.animated,
+      completion: completion
+    )
+  }
+}
diff --git a/Sources/AppNavigation/Generic/OpenLeft.swift b/Sources/AppNavigation/Generic/OpenLeft.swift
new file mode 100644
index 0000000000000000000000000000000000000000..90302e792da6cc98fa2456e800820b4d4e00088b
--- /dev/null
+++ b/Sources/AppNavigation/Generic/OpenLeft.swift
@@ -0,0 +1,45 @@
+import UIKit
+
+/// Open left view controller on provided parent view controller
+public struct OpenLeft: Action {
+  /// - Parameters:
+  ///   - viewController: View controller to present
+  ///   - parent: Parent view controller from which presentation should happen
+  ///   - animated: Animate the transition
+  public init(
+    _ viewController: UIViewController,
+    from parent: UIViewController,
+    animated: Bool = true
+  ) {
+    self.viewController = viewController
+    self.parent = parent
+    self.animated = animated
+  }
+
+  /// View controller to present
+  public var viewController: UIViewController
+
+  /// Parent view controller from which presentation should happen
+  public var parent: UIViewController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `OpenLeft` action
+public struct OpenLeftNavigator: TypedNavigator {
+  let transitioningDelegate = LeftTransitioningDelegate()
+
+  public init() {}
+
+  public func perform(_ action: OpenLeft, completion: @escaping () -> Void) {
+    action.viewController.transitioningDelegate = transitioningDelegate
+    action.viewController.modalPresentationStyle = .overFullScreen
+
+    action.parent.present(
+      action.viewController,
+      animated: action.animated,
+      completion: completion
+    )
+  }
+}
diff --git a/Sources/AppNavigation/Generic/OpenUp.swift b/Sources/AppNavigation/Generic/OpenUp.swift
new file mode 100644
index 0000000000000000000000000000000000000000..dfcedc3fafa521134e3ca871e803538503b43f02
--- /dev/null
+++ b/Sources/AppNavigation/Generic/OpenUp.swift
@@ -0,0 +1,52 @@
+import UIKit
+
+/// Open up view controller on provided parent view controller
+public struct OpenUp: Action {
+  /// - Parameters:
+  ///   - viewController: View controller to present
+  ///   - parent: Parent view controller from which presentation should happen
+  ///   - animated: Animate the transition
+  ///   - dismissable: Dismissable upon background touch flag
+  public init(
+    _ viewController: UIViewController,
+    from parent: UIViewController,
+    animated: Bool = true,
+    dismissable: Bool = true
+  ) {
+    self.viewController = viewController
+    self.parent = parent
+    self.animated = animated
+    self.dismissable = dismissable
+  }
+
+  /// View controller to present
+  public var viewController: UIViewController
+
+  /// Parent view controller from which presentation should happen
+  public var parent: UIViewController
+
+  /// Animate the transition
+  public var animated: Bool
+
+  /// Dismissable upon background touch flag
+  public var dismissable: Bool
+}
+
+/// Performs `OpenUp` action
+public struct OpenUpNavigator: TypedNavigator {
+  let transitioningDelegate = BottomTransitioningDelegate()
+
+  public init() {}
+
+  public func perform(_ action: OpenUp, completion: @escaping () -> Void) {
+    transitioningDelegate.isDismissableOnBackgroundTouch = action.dismissable
+    action.viewController.transitioningDelegate = transitioningDelegate
+    action.viewController.modalPresentationStyle = .overFullScreen
+
+    action.parent.present(
+      action.viewController,
+      animated: action.animated,
+      completion: completion
+    )
+  }
+}
diff --git a/Sources/AppNavigation/Generic/PopTo.swift b/Sources/AppNavigation/Generic/PopTo.swift
new file mode 100644
index 0000000000000000000000000000000000000000..15af276aa8f70d29b7a3266be72555c0bd69bbb9
--- /dev/null
+++ b/Sources/AppNavigation/Generic/PopTo.swift
@@ -0,0 +1,44 @@
+import UIKit
+
+/// Pop to the view controller on a given navigation controller
+public struct PopTo: Action {
+  /// - Parameters:
+  ///   - viewController: View controller to which should pop
+  ///   - navigationController: Navigation controller on which pop should happen
+  ///   - animated: Animate the transition
+  public init(
+    _ viewController: UIViewController,
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.viewController = viewController
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// View controller to which should pop
+  public var viewController: UIViewController
+
+  /// Navigation controller on which pop should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PopTo` action
+public struct PopToNavigator: TypedNavigator {
+  public init() {}
+
+  public func perform(_ action: PopTo, completion: @escaping () -> Void) {
+    action.navigationController.popToViewController(
+      action.viewController,
+      animated: action.animated
+    )
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/Generic/PopToRoot.swift b/Sources/AppNavigation/Generic/PopToRoot.swift
new file mode 100644
index 0000000000000000000000000000000000000000..5918a3476f9ba0a23d6413173dc764e7df7a7052
--- /dev/null
+++ b/Sources/AppNavigation/Generic/PopToRoot.swift
@@ -0,0 +1,35 @@
+import UIKit
+
+/// Pops to root view controller on a given navigation controller
+public struct PopToRoot: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which pop should happen
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which pop should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PopToRoot` action
+public struct PopToRootNavigator: TypedNavigator {
+  public init() {}
+
+  public func perform(_ action: PopToRoot, completion: @escaping () -> Void) {
+    action.navigationController.popToRootViewController(animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/Generic/PresentModal.swift b/Sources/AppNavigation/Generic/PresentModal.swift
new file mode 100644
index 0000000000000000000000000000000000000000..23db172f067f1131a7e557f70c356c00c27d70cd
--- /dev/null
+++ b/Sources/AppNavigation/Generic/PresentModal.swift
@@ -0,0 +1,40 @@
+import UIKit
+
+/// Present view controller on provided parent view controller
+public struct PresentModal: Action {
+  /// - Parameters:
+  ///   - viewController: View controller to present
+  ///   - parent: Parent view controller from which presentation should happen
+  ///   - animated: Animate the transition
+  public init(
+    _ viewController: UIViewController,
+    from parent: UIViewController,
+    animated: Bool = true
+  ) {
+    self.viewController = viewController
+    self.parent = parent
+    self.animated = animated
+  }
+
+  /// View controller to present
+  public var viewController: UIViewController
+
+  /// Parent view controller from which presentation should happen
+  public var parent: UIViewController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentModal` action
+public struct PresentModalNavigator: TypedNavigator {
+  public init() {}
+
+  public func perform(_ action: PresentModal, completion: @escaping () -> Void) {
+    action.parent.present(
+      action.viewController,
+      animated: action.animated,
+      completion: completion
+    )
+  }
+}
diff --git a/Sources/AppNavigation/Generic/Push.swift b/Sources/AppNavigation/Generic/Push.swift
new file mode 100644
index 0000000000000000000000000000000000000000..a0a01ea670a9bfe255e128b30547eeb79dfbcca6
--- /dev/null
+++ b/Sources/AppNavigation/Generic/Push.swift
@@ -0,0 +1,41 @@
+import UIKit
+
+/// Push view controller on a given navigation controller
+public struct Push: Action {
+  /// - Parameters:
+  ///   - viewController: View controller to which should be pushed
+  ///   - navigationController: Navigation controller on which push should happen
+  ///   - animated: Animate the transition
+  public init(
+    _ viewController: UIViewController,
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.viewController = viewController
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// View controller to which should be pushed
+  public var viewController: UIViewController
+
+  /// Navigation controller on which push should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `Push` action
+public struct PushNavigator: TypedNavigator {
+  public init() {}
+
+  public func perform(_ action: Push, completion: @escaping () -> Void) {
+    action.navigationController.pushViewController(action.viewController, animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/Generic/SetStack.swift b/Sources/AppNavigation/Generic/SetStack.swift
new file mode 100644
index 0000000000000000000000000000000000000000..23ca3a679bf6ddbdb6869aff208a776172161594
--- /dev/null
+++ b/Sources/AppNavigation/Generic/SetStack.swift
@@ -0,0 +1,44 @@
+import UIKit
+
+/// Sets view controllers on a given navigation controller
+public struct SetStack: Action {
+  /// - Parameters:
+  ///   - viewControllers: View controllers that should be set
+  ///   - navigationController: Navigation controller on which view controllers should be set
+  ///   - animated: Animate the transition
+  public init(
+    _ viewControllers: [UIViewController],
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.viewControllers = viewControllers
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// View controllers that should be set
+  public var viewControllers: [UIViewController]
+
+  /// Navigation controller on which view controllers should be set
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `SetStack` action
+public struct SetStackNavigator: TypedNavigator {
+  public init() {}
+
+  public func perform(_ action: SetStack, completion: @escaping () -> Void) {
+    action.navigationController.setViewControllers(
+      action.viewControllers,
+      animated: action.animated
+    )
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/Navigator.swift b/Sources/AppNavigation/Navigator.swift
new file mode 100644
index 0000000000000000000000000000000000000000..c37ca8d190ffd9f19fb667dcf37fd1718a9f9e64
--- /dev/null
+++ b/Sources/AppNavigation/Navigator.swift
@@ -0,0 +1,23 @@
+/// Navigator that performs a navigation action
+public protocol Navigator {
+  /// Returns true if the navigator can perform the action
+  /// - Default implementation returns true for any action
+  /// - Parameter action: navigation action
+  func canPerform(_ action: Action) -> Bool
+
+  /// Performs the navigation action
+  /// - Parameters:
+  ///   - action: navigation action
+  ///   - completion: closure that will be executed after performing the action
+  func perform(_ action: Action, completion: @escaping () -> Void)
+}
+
+public extension Navigator {
+  func canPerform(_ action: Action) -> Bool { true }
+
+  /// Performs the navigation action with empty completion closure
+  /// - Parameter action: navigation action
+  func perform(_ action: Action) {
+    perform(action, completion: {})
+  }
+}
diff --git a/Sources/AppNavigation/PresentActivitySheet.swift b/Sources/AppNavigation/PresentActivitySheet.swift
new file mode 100644
index 0000000000000000000000000000000000000000..1ba69910f41607fc78fd86d99f75ee46f02a6de7
--- /dev/null
+++ b/Sources/AppNavigation/PresentActivitySheet.swift
@@ -0,0 +1,43 @@
+import UIKit
+
+/// Presents `UIActivityViewController` on a given parent view controller
+public struct PresentActivitySheet: Action {
+  /// - Parameters:
+  ///   - items: Items to be displayed at the activity sheet controller
+  ///   - parent: Parent view controller from which presentation should happen
+  ///   - animated: Animate the transition
+  public init(
+    items: [Any],
+    from parent: UIViewController,
+    animated: Bool = true
+  ) {
+    self.items = items
+    self.parent = parent
+    self.animated = animated
+  }
+
+  /// Items to be displayed at the activity sheet controller
+  public var items: [Any]
+
+  /// Parent view controller from which presentation should happen
+  public var parent: UIViewController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentActivitySheet` action
+public struct PresentActivitySheetNavigator: TypedNavigator {
+  public init() {}
+
+  public func perform(_ action: PresentActivitySheet, completion: @escaping () -> Void) {
+    action.parent.present(
+      UIActivityViewController(
+        activityItems: action.items,
+        applicationActivities: nil
+      ),
+      animated: action.animated,
+      completion: completion
+    )
+  }
+}
diff --git a/Sources/AppNavigation/PresentCamera.swift b/Sources/AppNavigation/PresentCamera.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f5fa18ef1f7f2446a637e0a35fadc906590b3534
--- /dev/null
+++ b/Sources/AppNavigation/PresentCamera.swift
@@ -0,0 +1,37 @@
+import UIKit
+
+/// Presents `Camera` on provided parent view controller
+public struct PresentCamera: Action {
+  /// - Parameters:
+  ///   - parent: Parent view controller from which presentation should happen
+  ///   - animated: Animate the transition
+  public init(
+    from parent: UIViewController,
+    animated: Bool = true
+  ) {
+    self.parent = parent
+    self.animated = animated
+  }
+
+  /// Parent view controller from which presentation should happen
+  public var parent: UIViewController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentCamera` action
+public struct PresentCameraNavigator: TypedNavigator {
+  public init() {}
+
+  public func perform(_ action: PresentCamera, completion: @escaping () -> Void) {
+    let imagePickerController = UIImagePickerController()
+    imagePickerController.sourceType = .camera
+    imagePickerController.delegate = action.parent as? UIImagePickerControllerDelegate & UINavigationControllerDelegate
+    action.parent.present(
+      imagePickerController,
+      animated: action.animated,
+      completion: completion
+    )
+  }
+}
diff --git a/Sources/AppNavigation/PresentChat.swift b/Sources/AppNavigation/PresentChat.swift
new file mode 100644
index 0000000000000000000000000000000000000000..ef6ab52f5e7d00be89a1d0b5b329aa7496727e7a
--- /dev/null
+++ b/Sources/AppNavigation/PresentChat.swift
@@ -0,0 +1,49 @@
+import UIKit
+import XXModels
+
+/// Pushes `Chat` on a given navigation controller
+public struct PresentChat: Action {
+  /// - Parameters:
+  ///   - contact: Model to build the view controller which will be pushed
+  ///   - navigationController: Navigation controller on which push should happen
+  ///   - animated: Animate the transition
+  public init(
+    contact: Contact,
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.contact = contact
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Model to build the view controller which will be pushed
+  public var contact: Contact
+
+  /// Navigation controller on which push should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentChat` action
+public struct PresentChatNavigator: TypedNavigator {
+  /// View controller which should be pushed
+  var viewController: (Contact) -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be pushed
+  public init(_ viewController: @escaping (Contact) -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentChat, completion: @escaping () -> Void) {
+    action.navigationController.pushViewController(viewController(action.contact), animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentChatList.swift b/Sources/AppNavigation/PresentChatList.swift
new file mode 100644
index 0000000000000000000000000000000000000000..1cdca2246f9a9de7e3796caa57cef56f67c65270
--- /dev/null
+++ b/Sources/AppNavigation/PresentChatList.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Sets `ChatList` on a given navigation controller stack
+public struct PresentChatList: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which stack should be set
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which stack should be set
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentChatList` action
+public struct PresentChatListNavigator: TypedNavigator {
+  /// View controller which should be set in navigation stack
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be set in navigation stack
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentChatList, completion: @escaping () -> Void) {
+    action.navigationController.setViewControllers([viewController()], animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentContact.swift b/Sources/AppNavigation/PresentContact.swift
new file mode 100644
index 0000000000000000000000000000000000000000..dd1fca4beb590248d1baff41b5c70e1c735182bc
--- /dev/null
+++ b/Sources/AppNavigation/PresentContact.swift
@@ -0,0 +1,49 @@
+import UIKit
+import XXModels
+
+/// Pushes `Contact` on a given navigation controller
+public struct PresentContact: Action {
+  /// - Parameters:
+  ///   - contact: Model to build the view controller which will be pushed
+  ///   - navigationController: Navigation controller on which push should happen
+  ///   - animated: Animate the transition
+  public init(
+    contact: Contact,
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.contact = contact
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Model to build the view controller which will be pushed
+  public var contact: Contact
+
+  /// Navigation controller on which push should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentContact` action
+public struct PresentContactNavigator: TypedNavigator {
+  /// View controller which should be pushed
+  var viewController: (Contact) -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be pushed
+  public init(_ viewController: @escaping (Contact) -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentContact, completion: @escaping () -> Void) {
+    action.navigationController.pushViewController(viewController(action.contact), animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentContactList.swift b/Sources/AppNavigation/PresentContactList.swift
new file mode 100644
index 0000000000000000000000000000000000000000..bdf9d30fc3a9118ced62cb0ae8e6c44322f48f0e
--- /dev/null
+++ b/Sources/AppNavigation/PresentContactList.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Sets `ContactList` on a given navigation controller stack
+public struct PresentContactList: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which stack should be set
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which stack should be set
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentContactList` action
+public struct PresentContactListNavigator: TypedNavigator {
+  /// View controller to which should be set in navigation stack
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be set in navigation stack
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentContactList, completion: @escaping () -> Void) {
+    action.navigationController.setViewControllers([viewController()], animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentCountryList.swift b/Sources/AppNavigation/PresentCountryList.swift
new file mode 100644
index 0000000000000000000000000000000000000000..5365cf6b1e2c925fdf8fee7dd7a34b17efe0c499
--- /dev/null
+++ b/Sources/AppNavigation/PresentCountryList.swift
@@ -0,0 +1,47 @@
+import UIKit
+
+/// Presents `CountryList` on a given parent view controller
+public struct PresentCountryList: Action {
+  /// - Parameters:
+  ///   - completion: Completion closure with the selected country model
+  ///   - parent: Parent view controller from which presentation should happen
+  ///   - animated: Animate the transition
+  public init(
+    completion: @escaping (Any) -> Void,
+    from parent: UIViewController,
+    animated: Bool = true
+  ) {
+    self.completion = completion
+    self.parent = parent
+    self.animated = animated
+  }
+
+  /// Completion closure with the selected country model
+  public var completion: (Any) -> Void
+
+  /// Parent view controller from which presentation should happen
+  public var parent: UIViewController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentCountryList` action
+public struct PresentCountryListNavigator: TypedNavigator {
+  /// View controller which should be presented
+  var viewController: (@escaping (Any) -> Void) -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: view controller which should be presented
+  public init(_ viewController: @escaping (@escaping (Any) -> Void) -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentCountryList, completion: @escaping () -> Void) {
+    action.parent.present(
+      viewController(action.completion),
+      animated: action.animated,
+      completion: completion
+    )
+  }
+}
diff --git a/Sources/AppNavigation/PresentCreateGroupDrawer.swift b/Sources/AppNavigation/PresentCreateGroupDrawer.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8abf07fdaa88842ef7d9ef9bdc8b657d375dc872
--- /dev/null
+++ b/Sources/AppNavigation/PresentCreateGroupDrawer.swift
@@ -0,0 +1,56 @@
+import UIKit
+import XXModels
+
+/// Opens up `CreateGroup` on a given parent view controller
+public struct PresentCreateGroup: Action {
+  /// - Parameters:
+  ///   - members: Collection of contacts that will be in the group
+  ///   - parent: Parent view controller from which presentation should happen
+  ///   - animated: Animate the transition
+  public init(
+    members: [Contact],
+    from parent: UIViewController,
+    animated: Bool = true
+  ) {
+    self.members = members
+    self.parent = parent
+    self.animated = animated
+  }
+
+  /// Collection of contacts that will be in the group
+  public var members: [Contact]
+
+  /// Parent view controller from which presentation should happen
+  public var parent: UIViewController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentCreateGroup` action
+public struct PresentCreateGroupNavigator: TypedNavigator {
+  /// Custom transitioning delegate
+  let transitioningDelegate = BottomTransitioningDelegate()
+
+  /// View controller which should be opened up
+  var viewController: ([Contact]) -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: view controller which should be presented
+  public init(_ viewController: @escaping ([Contact]) -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentCreateGroup, completion: @escaping () -> Void) {
+    transitioningDelegate.isDismissableOnBackgroundTouch = true
+    let controller = viewController(action.members)
+    controller.transitioningDelegate = transitioningDelegate
+    controller.modalPresentationStyle = .overFullScreen
+
+    action.parent.present(
+      controller,
+      animated: action.animated,
+      completion: completion
+    )
+  }
+}
diff --git a/Sources/AppNavigation/PresentDrawer.swift b/Sources/AppNavigation/PresentDrawer.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8e49f814efafa6552f2c3f2b8c37ef79a83bef8f
--- /dev/null
+++ b/Sources/AppNavigation/PresentDrawer.swift
@@ -0,0 +1,61 @@
+import UIKit
+
+/// Opens up `Drawer` on a given parent view controller
+public struct PresentDrawer: Action {
+  /// - Parameters:
+  ///   - items: Collection of drawer items that will be present on the view controller
+  ///   - isDismissable: Flag that differentiates whether this presentation is dismissable on background touch
+  ///   - parent: Parent view controller from which presentation should happen
+  ///   - animated: Animate the transition
+  public init(
+    items: [Any],
+    isDismissable: Bool,
+    from parent: UIViewController,
+    animated: Bool = true
+  ) {
+    self.items = items
+    self.isDismissable = isDismissable
+    self.parent = parent
+    self.animated = animated
+  }
+
+  /// Collection of drawer items that will be present on the view controller
+  public var items: [Any]
+
+  /// Flag that differentiates whether this presentation is dismissable on background touch
+  public var isDismissable: Bool
+
+  /// Parent view controller from which presentation should happen
+  public var parent: UIViewController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentDrawer` action
+public struct PresentDrawerNavigator: TypedNavigator {
+  /// Custom transitioning delegate
+  let transitioningDelegate = BottomTransitioningDelegate()
+
+  /// View controller which should be opened up
+  var viewController: ([Any]) -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: view controller which should be presented
+  public init(_ viewController: @escaping ([Any]) -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentDrawer, completion: @escaping () -> Void) {
+    transitioningDelegate.isDismissableOnBackgroundTouch = action.isDismissable
+    let controller = viewController(action.items)
+    controller.transitioningDelegate = transitioningDelegate
+    controller.modalPresentationStyle = .overFullScreen
+
+    action.parent.present(
+      controller,
+      animated: action.animated,
+      completion: completion
+    )
+  }
+}
diff --git a/Sources/AppNavigation/PresentGroupChat.swift b/Sources/AppNavigation/PresentGroupChat.swift
new file mode 100644
index 0000000000000000000000000000000000000000..e5695a778eca7820292b449d0209d697fa006162
--- /dev/null
+++ b/Sources/AppNavigation/PresentGroupChat.swift
@@ -0,0 +1,49 @@
+import UIKit
+import XXModels
+
+/// Pushes `GroupChat` on a given navigation controller
+public struct PresentGroupChat: Action {
+  /// - Parameters:
+  ///   - groupInfo: Model to build the view controller which will be pushed
+  ///   - navigationController: Navigation controller on which push should happen
+  ///   - animated: Animate the transition
+  public init(
+    groupInfo: GroupInfo,
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.groupInfo = groupInfo
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Model to build the view controller which will be pushed
+  public var groupInfo: GroupInfo
+
+  /// Navigation controller on which push should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentGroupChat` action
+public struct PresentGroupChatNavigator: TypedNavigator {
+  /// View controller which should be pushed
+  var viewController: (GroupInfo) -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be pushed
+  public init(_ viewController: @escaping (GroupInfo) -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentGroupChat, completion: @escaping () -> Void) {
+    action.navigationController.pushViewController(viewController(action.groupInfo), animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentGroupDraft.swift b/Sources/AppNavigation/PresentGroupDraft.swift
new file mode 100644
index 0000000000000000000000000000000000000000..9190507f7e89baf10c89357c3245162d6f1126fe
--- /dev/null
+++ b/Sources/AppNavigation/PresentGroupDraft.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Pushes `GroupDraft` on a given navigation controller
+public struct PresentGroupDraft: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which push should happen
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which push should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentGroupDraft` action
+public struct PresentGroupDraftNavigator: TypedNavigator {
+  /// View controller which should be pushed
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be pushed
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentGroupDraft, completion: @escaping () -> Void) {
+    action.navigationController.pushViewController(viewController(), animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentMemberList.swift b/Sources/AppNavigation/PresentMemberList.swift
new file mode 100644
index 0000000000000000000000000000000000000000..3fa6582f85d7e92b9d476248454c3dda3629efc8
--- /dev/null
+++ b/Sources/AppNavigation/PresentMemberList.swift
@@ -0,0 +1,14 @@
+import XXModels
+
+public struct PresentMemberList: Action {
+  public var members: [Contact]
+  public var animated: Bool
+
+  public init(
+    members: [Contact],
+    animated: Bool = true
+  ) {
+    self.members = members
+    self.animated = animated
+  }
+}
diff --git a/Sources/AppNavigation/PresentMenu.swift b/Sources/AppNavigation/PresentMenu.swift
new file mode 100644
index 0000000000000000000000000000000000000000..59561cc1acf0f0f5121f26d89f1b5b14a22a4cd1
--- /dev/null
+++ b/Sources/AppNavigation/PresentMenu.swift
@@ -0,0 +1,66 @@
+import UIKit
+
+/// Options that can be lead to a flow on the menu UI
+public enum MenuItem {
+  case join
+  case scan
+  case chats
+  case share
+  case profile
+  case contacts
+  case requests
+  case settings
+  case dashboard
+}
+
+/// Opens left `Menu` on a given parent view controller
+public struct PresentMenu: Action {
+  /// - Parameters:
+  ///   - currentItem: A correlation with the flow that this controller is being presented
+  ///   - parent: Parent view controller from which presentation should happen
+  ///   - animated: Animate the transition
+  public init(
+    currentItem: MenuItem,
+    from parent: UIViewController,
+    animated: Bool = true
+  ) {
+    self.currentItem = currentItem
+    self.parent = parent
+    self.animated = animated
+  }
+
+  /// A correlation with the flow that this controller is being presented
+  public var currentItem: MenuItem
+
+  /// Parent view controller from which presentation should happen
+  public var parent: UIViewController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentMenu` action
+public struct PresentMenuNavigator: TypedNavigator {
+  /// Custom transitioning delegate
+  let transitioningDelegate = LeftTransitioningDelegate()
+
+  /// View controller which should be opened left
+  var viewController: (MenuItem, UINavigationController?) -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: view controller which should be presented
+  public init(_ viewController: @escaping (MenuItem, UINavigationController?) -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentMenu, completion: @escaping () -> Void) {
+    let controller = viewController(action.currentItem, action.parent.navigationController)
+    controller.transitioningDelegate = transitioningDelegate
+    controller.modalPresentationStyle = .overFullScreen
+    action.parent.present(
+      controller,
+      animated: action.animated,
+      completion: completion
+    )
+  }
+}
diff --git a/Sources/AppNavigation/PresentNickname.swift b/Sources/AppNavigation/PresentNickname.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8c6bd57c346a9beed71e8b40595e2b7367590600
--- /dev/null
+++ b/Sources/AppNavigation/PresentNickname.swift
@@ -0,0 +1,60 @@
+import UIKit
+
+/// Opens up `Nickname` on a given parent view controller
+public struct PresentNickname: Action {
+  /// - Parameters:
+  ///   - prefilled: Optional value to be set as placeholder/pre-existent text
+  ///   - completion: Closure that passes the value of the text set
+  ///   - parent: Parent view controller from which presentation should happen
+  ///   - animated: Animate the transition
+  public init(
+    prefilled: String?,
+    completion: @escaping (String) -> Void,
+    from parent: UIViewController,
+    animated: Bool = true
+  ) {
+    self.prefilled = prefilled
+    self.completion = completion
+    self.parent = parent
+    self.animated = animated
+  }
+
+  /// Optional value to be set as placeholder/pre-existent text
+  public var prefilled: String?
+
+  /// Closure that passes the value of the text set
+  public var completion: (String) -> Void
+
+  /// Parent view controller from which presentation should happen
+  public var parent: UIViewController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentNickname` action
+public struct PresentNicknameNavigator: TypedNavigator {
+  /// Custom transitioning delegate
+  let transitioningDelegate = BottomTransitioningDelegate()
+
+  /// View controller which should be opened up
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: view controller which should be presented
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentNickname, completion: @escaping () -> Void) {
+    let controller = viewController()
+    controller.transitioningDelegate = transitioningDelegate
+    controller.modalPresentationStyle = .overFullScreen
+
+    action.parent.present(
+      controller,
+      animated: action.animated,
+      completion: completion
+    )
+  }
+}
diff --git a/Sources/AppNavigation/PresentOnboardingCode.swift b/Sources/AppNavigation/PresentOnboardingCode.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7ed2f50c8dd725db9673ff22e71762cbef3d8ecf
--- /dev/null
+++ b/Sources/AppNavigation/PresentOnboardingCode.swift
@@ -0,0 +1,61 @@
+import UIKit
+
+/// Pushes `OnboardingCode` on a given navigation controller
+public struct PresentOnboardingCode: Action {
+  /// - Parameters:
+  ///   - isEmail: Flag to differentiate email or phone code
+  ///   - content: Content that is being set if confirmation code gets validated
+  ///   - confirmationId: Confirmation id to validate with third-party
+  ///   - navigationController: Navigation controller on which push should happen
+  ///   - animated: Animate the transition
+  public init(
+    isEmail: Bool,
+    content: String,
+    confirmationId: String,
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.isEmail = isEmail
+    self.content = content
+    self.confirmationId = confirmationId
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Flag to differentiate email or phone code
+  public var isEmail: Bool
+
+  /// Content that is being set if confirmation code gets validated
+  public var content: String
+
+  /// Confirmation id to validate with third-party
+  public var confirmationId: String
+
+  /// Navigation controller on which push should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentOnboardingCode` action
+public struct PresentOnboardingCodeNavigator: TypedNavigator {
+  /// View controller which should be pushed
+  var viewController: (Bool, String, String) -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be pushed
+  public init(_ viewController: @escaping (Bool, String, String) -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentOnboardingCode, completion: @escaping () -> Void) {
+    let controller = viewController(action.isEmail, action.content, action.confirmationId)
+    action.navigationController.pushViewController(controller, animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentOnboardingEmail.swift b/Sources/AppNavigation/PresentOnboardingEmail.swift
new file mode 100644
index 0000000000000000000000000000000000000000..4f57119e0157d8b480cc55c41fea90bde38eac2e
--- /dev/null
+++ b/Sources/AppNavigation/PresentOnboardingEmail.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Sets `OnboardingEmail` on a given navigation controller stack
+public struct PresentOnboardingEmail: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which stack should be set
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which stack should be set
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentOnboardingEmail` action
+public struct PresentOnboardingEmailNavigator: TypedNavigator {
+  /// View controller which should be set in navigation stack
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be set in navigation stack
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentOnboardingEmail, completion: @escaping () -> Void) {
+    action.navigationController.setViewControllers([viewController()], animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentOnboardingPhone.swift b/Sources/AppNavigation/PresentOnboardingPhone.swift
new file mode 100644
index 0000000000000000000000000000000000000000..eca7c89605f99e1f6ec8e982150907f7f7ef80d0
--- /dev/null
+++ b/Sources/AppNavigation/PresentOnboardingPhone.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Sets `OnboardingPhone` on a given navigation controller stack
+public struct PresentOnboardingPhone: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which stack should be set
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which stack should be set
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentOnboardingPhone` action
+public struct PresentOnboardingPhoneNavigator: TypedNavigator {
+  /// View controller which should be set in navigation stack
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be set in navigation stack
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentOnboardingPhone, completion: @escaping () -> Void) {
+    action.navigationController.setViewControllers([viewController()], animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentOnboardingStart.swift b/Sources/AppNavigation/PresentOnboardingStart.swift
new file mode 100644
index 0000000000000000000000000000000000000000..e531c6909ea2e1087dfea80e608dcd384c7dafef
--- /dev/null
+++ b/Sources/AppNavigation/PresentOnboardingStart.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Sets `OnboardingStart` on a given navigation controller stack
+public struct PresentOnboardingStart: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which stack should be set
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which stack should be set
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentOnboardingStart` action
+public struct PresentOnboardingStartNavigator: TypedNavigator {
+  /// View controller which should be set in navigation stack
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be set in navigation stack
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentOnboardingStart, completion: @escaping () -> Void) {
+    action.navigationController.setViewControllers([viewController()], animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentOnboardingUsername.swift b/Sources/AppNavigation/PresentOnboardingUsername.swift
new file mode 100644
index 0000000000000000000000000000000000000000..428dc24a0b84c014e3473e575d7f2058ff92180e
--- /dev/null
+++ b/Sources/AppNavigation/PresentOnboardingUsername.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Pushes `OnboardingUsername` on a given navigation controller
+public struct PresentOnboardingUsername: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which push should happen
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which push should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentOnboardingUsername` action
+public struct PresentOnboardingUsernameNavigator: TypedNavigator {
+  /// View controller which should be pushed
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be pushed
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentOnboardingUsername, completion: @escaping () -> Void) {
+    action.navigationController.pushViewController(viewController(), animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentOnboardingWelcome.swift b/Sources/AppNavigation/PresentOnboardingWelcome.swift
new file mode 100644
index 0000000000000000000000000000000000000000..0df955e7e77208e5dcc675220621b7f800403f82
--- /dev/null
+++ b/Sources/AppNavigation/PresentOnboardingWelcome.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Sets `OnboardingWelcome` on a given navigation controller stack
+public struct PresentOnboardingWelcome: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which stack should be set
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which stack should be set
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentOnboardingWelcome` action
+public struct PresentOnboardingWelcomeNavigator: TypedNavigator {
+  /// View controller which should be set in navigation stack
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be set in navigation stack
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentOnboardingWelcome, completion: @escaping () -> Void) {
+    action.navigationController.setViewControllers([viewController()], animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentPassphrase.swift b/Sources/AppNavigation/PresentPassphrase.swift
new file mode 100644
index 0000000000000000000000000000000000000000..6e208f26b048d5649c4badf2cafd5a3d43fd9d0e
--- /dev/null
+++ b/Sources/AppNavigation/PresentPassphrase.swift
@@ -0,0 +1,15 @@
+public struct PresentPassphrase: Action {
+  public var onCancel: () -> Void
+  public var onPasspharse: (String) -> Void
+  public var animated: Bool
+
+  public init(
+    onCancel: @escaping () -> Void,
+    onPassphrase: @escaping (String) -> Void,
+    animated: Bool = true
+  ) {
+    self.onCancel = onCancel
+    self.onPasspharse = onPassphrase
+    self.animated = animated
+  }
+}
diff --git a/Sources/AppNavigation/PresentPermissionRequest.swift b/Sources/AppNavigation/PresentPermissionRequest.swift
new file mode 100644
index 0000000000000000000000000000000000000000..76c89f1c94a814061473dfcb003466dc9795cddb
--- /dev/null
+++ b/Sources/AppNavigation/PresentPermissionRequest.swift
@@ -0,0 +1,59 @@
+import UIKit
+
+/// Types of permissions that can be requested to the user
+public enum PermissionType {
+  /// Device camera permission type
+  case camera
+
+  /// Camera roll and library permission type
+  case library
+
+  /// Device microphone permission type
+  case microphone
+}
+
+/// Presents `PermissionRequest` on provided parent view controller
+public struct PresentPermissionRequest: Action {
+  /// - Parameters:
+  ///   - type: Type of permission that is being requested
+  ///   - parent: Parent view controller from which presentation should happen
+  ///   - animated: Animate the transition
+  public init(
+    type: PermissionType,
+    from parent: UIViewController,
+    animated: Bool = true
+  ) {
+    self.type = type
+    self.parent = parent
+    self.animated = animated
+  }
+
+  /// Type of permission that is being requested
+  public var type: PermissionType
+
+  /// Parent view controller from which presentation should happen
+  public var parent: UIViewController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentPermissionRequest` action
+public struct PresentPermissionRequestNavigator: TypedNavigator {
+  /// View controller which should be presented
+  var viewController: (PermissionType) -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be presented
+  public init(_ viewController: @escaping (PermissionType) -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentPermissionRequest, completion: @escaping () -> Void) {
+    action.parent.present(
+      viewController(action.type),
+      animated: action.animated,
+      completion: completion
+    )
+  }
+}
diff --git a/Sources/AppNavigation/PresentPhotoLibrary.swift b/Sources/AppNavigation/PresentPhotoLibrary.swift
new file mode 100644
index 0000000000000000000000000000000000000000..886b3c8b3c5bcf4468f02113d20865e3f2387d4d
--- /dev/null
+++ b/Sources/AppNavigation/PresentPhotoLibrary.swift
@@ -0,0 +1,36 @@
+import UIKit
+
+/// Presents `PhotoLibrary` on provided parent view controller
+public struct PresentPhotoLibrary: Action {
+  /// - Parameters:
+  ///   - parent: Parent view controller from which presentation should happen
+  ///   - animated: Animate the transition
+  public init(
+    from parent: UIViewController,
+    animated: Bool = true
+  ) {
+    self.parent = parent
+    self.animated = animated
+  }
+
+  /// Parent view controller from which presentation should happen
+  public var parent: UIViewController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentPhotoLibrary` action
+public struct PresentPhotoLibraryNavigator: TypedNavigator {
+  public init() {}
+
+  public func perform(_ action: PresentPhotoLibrary, completion: @escaping () -> Void) {
+    let imagePickerController = UIImagePickerController()
+    imagePickerController.delegate = action.parent as? UIImagePickerControllerDelegate & UINavigationControllerDelegate
+    action.parent.present(
+      imagePickerController,
+      animated: action.animated,
+      completion: completion
+    )
+  }
+}
diff --git a/Sources/AppNavigation/PresentProfile.swift b/Sources/AppNavigation/PresentProfile.swift
new file mode 100644
index 0000000000000000000000000000000000000000..42363e49108b8bb363987fd17c998240e8fc9554
--- /dev/null
+++ b/Sources/AppNavigation/PresentProfile.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Sets `Profile` on a given navigation controller stack
+public struct PresentProfile: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which stack should be set
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which stack should be set
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentProfile` action
+public struct PresentProfileNavigator: TypedNavigator {
+  /// View controller which should be set in navigation stack
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be set in navigation stack
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentProfile, completion: @escaping () -> Void) {
+    action.navigationController.setViewControllers([viewController()], animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentProfileCode.swift b/Sources/AppNavigation/PresentProfileCode.swift
new file mode 100644
index 0000000000000000000000000000000000000000..db50a72d8f3ef9a5e40e6188025a68d44b924d5b
--- /dev/null
+++ b/Sources/AppNavigation/PresentProfileCode.swift
@@ -0,0 +1,61 @@
+import UIKit
+
+/// Pushes `ProfileCode` on a given navigation controller
+public struct PresentProfileCode: Action {
+  /// - Parameters:
+  ///   - isEmail: Flag to differentiate email or phone code
+  ///   - content: Content that is being set if confirmation code gets validated
+  ///   - confirmationId: Confirmation id to validate with third-party
+  ///   - navigationController: Navigation controller on which push should happen
+  ///   - animated: Animate the transition
+  public init(
+    isEmail: Bool,
+    content: String,
+    confirmationId: String,
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.isEmail = isEmail
+    self.content = content
+    self.confirmationId = confirmationId
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Flag to differentiate email or phone code
+  public var isEmail: Bool
+
+  /// Content that is being set if confirmation code gets validated
+  public var content: String
+
+  /// Confirmation id to validate with third-party
+  public var confirmationId: String
+
+  /// Navigation controller on which push should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentProfileCode` action
+public struct PresentProfileCodeNavigator: TypedNavigator {
+  /// View controller which should be pushed
+  var viewController: (Bool, String, String) -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be pushed
+  public init(_ viewController: @escaping (Bool, String, String) -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentProfileCode, completion: @escaping () -> Void) {
+    let controller = viewController(action.isEmail, action.content, action.confirmationId)
+    action.navigationController.pushViewController(controller, animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentProfileEmail.swift b/Sources/AppNavigation/PresentProfileEmail.swift
new file mode 100644
index 0000000000000000000000000000000000000000..0ea2c524781d1046b806c65edd9be0931d0f7bc7
--- /dev/null
+++ b/Sources/AppNavigation/PresentProfileEmail.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Pushes `ProfileEmail` on a given navigation controller
+public struct PresentProfileEmail: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which push should happen
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which push should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentProfileEmail` action
+public struct PresentProfileEmailNavigator: TypedNavigator {
+  /// View controller which should be pushed
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be pushed
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentProfileEmail, completion: @escaping () -> Void) {
+    action.navigationController.pushViewController(viewController(), animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentProfilePhone.swift b/Sources/AppNavigation/PresentProfilePhone.swift
new file mode 100644
index 0000000000000000000000000000000000000000..c669afe41c355ee6ef39fa1c38dbea2dae2a7583
--- /dev/null
+++ b/Sources/AppNavigation/PresentProfilePhone.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Pushes `ProfilePhone` on a given navigation controller
+public struct PresentProfilePhone: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which push should happen
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which push should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentProfilePhone` action
+public struct PresentProfilePhoneNavigator: TypedNavigator {
+  /// View controller which should be pushed
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be pushed
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentProfilePhone, completion: @escaping () -> Void) {
+    action.navigationController.pushViewController(viewController(), animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentRequests.swift b/Sources/AppNavigation/PresentRequests.swift
new file mode 100644
index 0000000000000000000000000000000000000000..aa346ff45292234cc1576135d517b49625f04916
--- /dev/null
+++ b/Sources/AppNavigation/PresentRequests.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Sets `Requests` on a given navigation controller stack
+public struct PresentRequests: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which stack should be set
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which stack should be set
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentRequests` action
+public struct PresentRequestsNavigator: TypedNavigator {
+  /// View controller which should be set in navigation stack
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be set in navigation stack
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentRequests, completion: @escaping () -> Void) {
+    action.navigationController.setViewControllers([viewController()], animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentRestoreList.swift b/Sources/AppNavigation/PresentRestoreList.swift
new file mode 100644
index 0000000000000000000000000000000000000000..977c224537b219dff54e4ed87f87115c01af945d
--- /dev/null
+++ b/Sources/AppNavigation/PresentRestoreList.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Pushes `RestoreList` on a given navigation controller
+public struct PresentRestoreList: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which push should happen
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which push should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentRestoreList` action
+public struct PresentRestoreListNavigator: TypedNavigator {
+  /// View controller which should be pushed
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be pushed
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentRestoreList, completion: @escaping () -> Void) {
+    action.navigationController.pushViewController(viewController(), animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentSFTP.swift b/Sources/AppNavigation/PresentSFTP.swift
new file mode 100644
index 0000000000000000000000000000000000000000..dee1bde64055af2fb2fa7fa4e8006bed8270c20b
--- /dev/null
+++ b/Sources/AppNavigation/PresentSFTP.swift
@@ -0,0 +1,49 @@
+import UIKit
+
+/// Pushes `SFTP` on a given navigation controller
+public struct PresentSFTP: Action {
+  /// - Parameters:
+  ///   - completion: Completion closure with host, username and password
+  ///   - navigationController: Navigation controller on which push should happen
+  ///   - animated: Animate the transition
+  public init(
+    completion: @escaping (String, String, String) -> Void,
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.completion = completion
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Completion closure with host, username and password
+  public var completion: (String, String, String) -> Void
+
+  /// Navigation controller on which push should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentSFTP` action
+public struct PresentSFTPNavigator: TypedNavigator {
+  /// View controller which should be pushed
+  var viewController: (@escaping (String, String, String) -> Void) -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be pushed
+  public init(_ viewController: @escaping (@escaping (String, String, String) -> Void) -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentSFTP, completion: @escaping () -> Void) {
+    let controller = viewController(action.completion)
+    action.navigationController.pushViewController(controller, animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentScan.swift b/Sources/AppNavigation/PresentScan.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d4fb08cb2be013598154342439480972f26f4e23
--- /dev/null
+++ b/Sources/AppNavigation/PresentScan.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Sets `Scan` on a given navigation controller stack
+public struct PresentScan: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which stack should be set
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which stack should be set
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentScan` action
+public struct PresentScanNavigator: TypedNavigator {
+  /// View controller which should be set in navigation stack
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be set in navigation stack
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentScan, completion: @escaping () -> Void) {
+    action.navigationController.setViewControllers([viewController()], animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentSearch.swift b/Sources/AppNavigation/PresentSearch.swift
new file mode 100644
index 0000000000000000000000000000000000000000..4b553b174121c096bd8673ce0636478b181ad459
--- /dev/null
+++ b/Sources/AppNavigation/PresentSearch.swift
@@ -0,0 +1,67 @@
+import UIKit
+
+/// Sets or Pushes `Search` on a given navigation controller
+public struct PresentSearch: Action {
+  /// - Parameters:
+  ///   - searching: Optional string to be searched upon further viewModel intialization
+  ///   - fromOnboarding: Flag that differentiates if should be a push or a set stack
+  ///   - navigationController: Navigation controller on which will be pushed or stack should be set
+  ///   - animated: Animate the transition
+  public init(
+    searching: String? = nil,
+    fromOnboarding: Bool = false,
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.searching = searching
+    self.fromOnboarding = fromOnboarding
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Optional string to be searched upon further viewModel intialization
+  public var searching: String?
+
+  /// Flag that differentiates if should be a push or a set stack
+  public var fromOnboarding: Bool
+
+  /// Navigation controller on which stack should be set
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentSearch` action
+public struct PresentSearchNavigator: TypedNavigator {
+  /// View controller which should be pushed or set in navigation stack
+  var viewController: (String?) -> UIViewController
+
+  /// View controller which might have to be pushed below in navigation stack
+  var otherViewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be pushed or set in navigation stack
+  ///   - otherViewController: View controller which might have to be pushed below in navigation stack
+  public init(
+    _ otherViewController: @escaping () -> UIViewController,
+    _ viewController: @escaping (String?) -> UIViewController
+  ) {
+    self.viewController = viewController
+    self.otherViewController = otherViewController
+  }
+
+  public func perform(_ action: PresentSearch, completion: @escaping () -> Void) {
+    if action.fromOnboarding {
+      action.navigationController.setViewControllers([otherViewController(), viewController(action.searching)], animated: action.animated)
+    } else {
+      action.navigationController.pushViewController(viewController(action.searching), animated: action.animated)
+    }
+
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentSettings.swift b/Sources/AppNavigation/PresentSettings.swift
new file mode 100644
index 0000000000000000000000000000000000000000..e6a4871612234c122da83bdd6be5869b895062e8
--- /dev/null
+++ b/Sources/AppNavigation/PresentSettings.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Sets `Settings` on a given navigation controller stack
+public struct PresentSettings: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which stack should be set
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which stack should be set
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentSettings` action
+public struct PresentSettingsNavigator: TypedNavigator {
+  /// View controller which should be set in navigation stack
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be set in navigation stack
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentSettings, completion: @escaping () -> Void) {
+    action.navigationController.setViewControllers([viewController()], animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentSettingsAccountDelete.swift b/Sources/AppNavigation/PresentSettingsAccountDelete.swift
new file mode 100644
index 0000000000000000000000000000000000000000..e5691b4cda89a52c223c2c463fd96743465b1cd4
--- /dev/null
+++ b/Sources/AppNavigation/PresentSettingsAccountDelete.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Pushes `SettingsAccountDelete` on a given navigation controller
+public struct PresentSettingsAccountDelete: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which push should happen
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which push should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentSettingsAccountDelete` action
+public struct PresentSettingsAccountDeleteNavigator: TypedNavigator {
+  /// View controller which should be pushed
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be pushed
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentSettingsAccountDelete, completion: @escaping () -> Void) {
+    action.navigationController.pushViewController(viewController(), animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentSettingsAdvanced.swift b/Sources/AppNavigation/PresentSettingsAdvanced.swift
new file mode 100644
index 0000000000000000000000000000000000000000..737f02fa2f6d97a8da754f821d1860502dcfa957
--- /dev/null
+++ b/Sources/AppNavigation/PresentSettingsAdvanced.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Pushes `SettingsAdvanced` on a given navigation controller
+public struct PresentSettingsAdvanced: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which push should happen
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which push should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentSettingsAdvanced` action
+public struct PresentSettingsAdvancedNavigator: TypedNavigator {
+  /// View controller which should be pushed
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be pushed
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentSettingsAdvanced, completion: @escaping () -> Void) {
+    action.navigationController.pushViewController(viewController(), animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentSettingsBackup.swift b/Sources/AppNavigation/PresentSettingsBackup.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f1d2ec5e3808f5bc45e8a968a03f8a4c0272f89a
--- /dev/null
+++ b/Sources/AppNavigation/PresentSettingsBackup.swift
@@ -0,0 +1,42 @@
+import UIKit
+
+/// Pushes `SettingsBackup` on a given navigation controller
+public struct PresentSettingsBackup: Action {
+  /// - Parameters:
+  ///   - navigationController: Navigation controller on which push should happen
+  ///   - animated: Animate the transition
+  public init(
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Navigation controller on which push should happen
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentSettingsBackup` action
+public struct PresentSettingsBackupNavigator: TypedNavigator {
+  /// View controller which should be pushed
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be pushed
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentSettingsBackup, completion: @escaping () -> Void) {
+    action.navigationController.pushViewController(viewController(), animated: action.animated)
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentTermsAndConditions.swift b/Sources/AppNavigation/PresentTermsAndConditions.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8baa2e06fb19f87e770eb5792b898f97e94bee65
--- /dev/null
+++ b/Sources/AppNavigation/PresentTermsAndConditions.swift
@@ -0,0 +1,53 @@
+import UIKit
+
+/// Sets or Pushes `TermsAndConditions` on a given navigation controller
+public struct PresentTermsAndConditions: Action {
+  /// - Parameters:
+  ///   - replacing: Flag to differentiate if should be a push or a set stack
+  ///   - navigationController: Navigation controller on which will be pushed or stack should be set
+  ///   - animated: Animate the transition
+  public init(
+    replacing: Bool,
+    on navigationController: UINavigationController,
+    animated: Bool = true
+  ) {
+    self.replacing = replacing
+    self.navigationController = navigationController
+    self.animated = animated
+  }
+
+  /// Flag to differentiate if should be a push or a set stack
+  public var replacing: Bool
+
+  /// Navigation controller on which stack should be set
+  public var navigationController: UINavigationController
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentTermsAndConditions` action
+public struct PresentTermsAndConditionsNavigator: TypedNavigator {
+  /// View controller which should be pushed or set in navigation stack
+  var viewController: () -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be pushed or set in navigation stack
+  public init(_ viewController: @escaping () -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentTermsAndConditions, completion: @escaping () -> Void) {
+    if action.replacing {
+      action.navigationController.setViewControllers([viewController()], animated: action.animated)
+    } else {
+      action.navigationController.pushViewController(viewController(), animated: action.animated)
+    }
+
+    if action.animated, let coordinator = action.navigationController.transitionCoordinator {
+      coordinator.animate(alongsideTransition: nil, completion: { _ in completion() })
+    } else {
+      completion()
+    }
+  }
+}
diff --git a/Sources/AppNavigation/PresentWebsite.swift b/Sources/AppNavigation/PresentWebsite.swift
new file mode 100644
index 0000000000000000000000000000000000000000..02abd2eef9dfb3709e1704d66c662b93524d6471
--- /dev/null
+++ b/Sources/AppNavigation/PresentWebsite.swift
@@ -0,0 +1,48 @@
+import UIKit
+import WebKit
+
+/// Presents `Website` on a given parent view controller
+public struct PresentWebsite: Action {
+  /// - Parameters:
+  ///   - urlString: Url that will be loaded on the web view
+  ///   - parent: Parent view controller from which presentation should happen
+  ///   - animated: Animate the transition
+  public init(
+    urlString: String,
+    from parent: UIViewController,
+    animated: Bool = true
+  ) {
+    self.urlString = urlString
+    self.parent = parent
+    self.animated = animated
+  }
+
+  /// Parent view controller from which presentation should happen
+  public var parent: UIViewController
+
+  /// Url that will be loaded on the web view
+  public var urlString: String
+
+  /// Animate the transition
+  public var animated: Bool
+}
+
+/// Performs `PresentWebsite` action
+public struct PresentWebsiteNavigator: TypedNavigator {
+  /// View controller which should be presented
+  var viewController: (String) -> UIViewController
+
+  /// - Parameters:
+  ///   - viewController: View controller which should be presented
+  public init(_ viewController: @escaping (String) -> UIViewController) {
+    self.viewController = viewController
+  }
+
+  public func perform(_ action: PresentWebsite, completion: @escaping () -> Void) {
+    action.parent.present(
+      viewController(action.urlString),
+      animated: action.animated,
+      completion: completion
+    )
+  }
+}
diff --git a/Sources/AppNavigation/TypedNavigator.swift b/Sources/AppNavigation/TypedNavigator.swift
new file mode 100644
index 0000000000000000000000000000000000000000..c351c98a2257a610fd7bbd291dcdab614597ee0d
--- /dev/null
+++ b/Sources/AppNavigation/TypedNavigator.swift
@@ -0,0 +1,46 @@
+/// Navigation that can perform action of a concrete type
+public protocol TypedNavigator: Navigator {
+  /// Type of the action that the navigator can perform
+  associatedtype ActionType: Action
+
+  /// Returns true if the action can be performed by the navigator
+  /// - Default implementation returns true for any action
+  /// - Parameter action: navigation action
+  func canPerform(_ action: ActionType) -> Bool
+
+  /// Performs the navigation action
+  /// - Parameters:
+  ///   - action: navigation action
+  ///   - completion: closure that will be executed after performing the action
+  func perform(_ action: ActionType, completion: @escaping () -> Void)
+}
+
+public extension TypedNavigator {
+  /// Returns true if the navigation action is of the type handled by the navigator
+  /// - Parameter action: navigation action
+  /// - Returns: true if action can be performed
+  func canPerform(_ action: Action) -> Bool {
+    if let action = action as? ActionType {
+      return canPerform(action)
+    }
+    return false
+  }
+
+  func canPerform(_ action: ActionType) -> Bool { true }
+
+  /// Performs the navigation action with empty completion closure
+  /// - Parameter action: navigation action
+  func perform(_ action: ActionType) {
+    perform(action, completion: {})
+  }
+
+  /// Performs the navigation action if its type matches `ActionType` handled by the navigator
+  /// - Parameters:
+  ///   - action: navigation action
+  ///   - completion: closure that will be executed after performing the action
+  func perform(_ action: Action, completion: @escaping () -> Void) {
+    if let action = action as? ActionType {
+      perform(action, completion: completion)
+    }
+  }
+}
diff --git a/Sources/Shared/AutoGenerated/Assets.swift b/Sources/AppResources/Assets.swift
similarity index 100%
rename from Sources/Shared/AutoGenerated/Assets.swift
rename to Sources/AppResources/Assets.swift
diff --git a/Sources/Shared/AutoGenerated/Fonts.swift b/Sources/AppResources/Fonts.swift
similarity index 100%
rename from Sources/Shared/AutoGenerated/Fonts.swift
rename to Sources/AppResources/Fonts.swift
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsBackup/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsBackup/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsBackup/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsBackup/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsBackup/backup_success.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsBackup/backup_success.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsBackup/backup_success.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsBackup/backup_success.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsBackup/backup_success.imageset/Group 512227.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsBackup/backup_success.imageset/Group 512227.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsBackup/backup_success.imageset/Group 512227.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsBackup/backup_success.imageset/Group 512227.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_close_speaker.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_close_speaker.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_close_speaker.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_close_speaker.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_close_speaker.imageset/Icon (9).pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_close_speaker.imageset/Icon (9).pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_close_speaker.imageset/Icon (9).pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_close_speaker.imageset/Icon (9).pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_open_speaker.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_open_speaker.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_open_speaker.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_open_speaker.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_open_speaker.imageset/Icon (7).pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_open_speaker.imageset/Icon (7).pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_open_speaker.imageset/Icon (7).pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_open_speaker.imageset/Icon (7).pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_pause.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_pause.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_pause.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_pause.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_pause.imageset/Icon (8).pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_pause.imageset/Icon (8).pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_pause.imageset/Icon (8).pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_pause.imageset/Icon (8).pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_play.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_play.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_play.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_play.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_play.imageset/Icon (10).pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_play.imageset/Icon (10).pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_play.imageset/Icon (10).pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_play.imageset/Icon (10).pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_spectrum.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_spectrum.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_spectrum.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_spectrum.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_spectrum.imageset/sound.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_spectrum.imageset/sound.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_audio_spectrum.imageset/sound.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_audio_spectrum.imageset/sound.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_camera.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_camera.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_camera.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_camera.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_camera.imageset/Icon (2).pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_camera.imageset/Icon (2).pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_camera.imageset/Icon (2).pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_camera.imageset/Icon (2).pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_close.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_close.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_close.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_close.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_close.imageset/Icon (5).pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_close.imageset/Icon (5).pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_close.imageset/Icon (5).pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_close.imageset/Icon (5).pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_files.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_files.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_files.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_files.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_files.imageset/Icon (4).pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_files.imageset/Icon (4).pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_files.imageset/Icon (4).pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_files.imageset/Icon (4).pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_gallery.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_gallery.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_gallery.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_gallery.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_gallery.imageset/Icon (3).pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_gallery.imageset/Icon (3).pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_gallery.imageset/Icon (3).pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_gallery.imageset/Icon (3).pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_open.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_open.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_open.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_open.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_open.imageset/Icon-5.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_open.imageset/Icon-5.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_action_open.imageset/Icon-5.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_action_open.imageset/Icon-5.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_voice_pause.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_voice_pause.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_voice_pause.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_voice_pause.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_voice_pause.imageset/pause_brand_light.png b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_voice_pause.imageset/pause_brand_light.png
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_voice_pause.imageset/pause_brand_light.png
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_voice_pause.imageset/pause_brand_light.png
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_voice_play.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_voice_play.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_voice_play.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_voice_play.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_voice_play.imageset/Icon (6).pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_voice_play.imageset/Icon (6).pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_voice_play.imageset/Icon (6).pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_voice_play.imageset/Icon (6).pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_voice_start.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_voice_start.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_voice_start.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_voice_start.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_voice_start.imageset/Icon (1).pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_voice_start.imageset/Icon (1).pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_voice_start.imageset/Icon (1).pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_voice_start.imageset/Icon (1).pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_voice_stop.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_voice_stop.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_voice_stop.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_voice_stop.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_voice_stop.imageset/Group 2.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_voice_stop.imageset/Group 2.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_input_voice_stop.imageset/Group 2.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_input_voice_stop.imageset/Group 2.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_locker.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_locker.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_locker.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_locker.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_locker.imageset/Vector-26.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_locker.imageset/Vector-26.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_locker.imageset/Vector-26.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_locker.imageset/Vector-26.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_more.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_more.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_more.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_more.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_more.imageset/Icon-6.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_more.imageset/Icon-6.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_more.imageset/Icon-6.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_more.imageset/Icon-6.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_placeholder_image.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_placeholder_image.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_placeholder_image.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_placeholder_image.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_placeholder_image.imageset/Ellipse 1-2.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_placeholder_image.imageset/Ellipse 1-2.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_placeholder_image.imageset/Ellipse 1-2.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_placeholder_image.imageset/Ellipse 1-2.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_send.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_send.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_send.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_send.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_send.imageset/Icon-34.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_send.imageset/Icon-34.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChat/chat_send.imageset/Icon-34.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChat/chat_send.imageset/Icon-34.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_delete_swipe.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_delete_swipe.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_delete_swipe.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_delete_swipe.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_delete_swipe.imageset/Icon-7.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_delete_swipe.imageset/Icon-7.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_delete_swipe.imageset/Icon-7.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_delete_swipe.imageset/Icon-7.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_menu.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_menu.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_menu.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_menu.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_menu.imageset/Vector-19.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_menu.imageset/Vector-19.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_menu.imageset/Vector-19.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_menu.imageset/Vector-19.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_menu_delete.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_menu_delete.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_menu_delete.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_menu_delete.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_menu_delete.imageset/Icon-11.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_menu_delete.imageset/Icon-11.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_menu_delete.imageset/Icon-11.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_menu_delete.imageset/Icon-11.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_menu_pin.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_menu_pin.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_menu_pin.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_menu_pin.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_menu_pin.imageset/Vector-27.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_menu_pin.imageset/Vector-27.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_menu_pin.imageset/Vector-27.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_menu_pin.imageset/Vector-27.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_new.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_new.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_new.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_new.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_new.imageset/Icon-19.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_new.imageset/Icon-19.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_new.imageset/Icon-19.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_new.imageset/Icon-19.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_new_group.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_new_group.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_new_group.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_new_group.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_new_group.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_new_group.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_new_group.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_new_group.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_pin_swipe.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_pin_swipe.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_pin_swipe.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_pin_swipe.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_pin_swipe.imageset/Icon-9.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_pin_swipe.imageset/Icon-9.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_pin_swipe.imageset/Icon-9.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_pin_swipe.imageset/Icon-9.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_placeholder.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_placeholder.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_placeholder.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_placeholder.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_placeholder.imageset/Ellipse 1-3.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_placeholder.imageset/Ellipse 1-3.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_placeholder.imageset/Ellipse 1-3.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_placeholder.imageset/Ellipse 1-3.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_ud.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_ud.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_ud.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_ud.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_ud.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_ud.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsChatList/chat_list_ud.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsChatList/chat_list_ud.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsCode/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsCode/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsCode/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsCode/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsCode/code.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsCode/code.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsCode/code.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsCode/code.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsCode/code.imageset/circle-bg 2-3.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsCode/code.imageset/circle-bg 2-3.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsCode/code.imageset/circle-bg 2-3.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsCode/code.imageset/circle-bg 2-3.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContact/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsContact/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContact/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContact/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_add_placeholder.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_add_placeholder.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_add_placeholder.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_add_placeholder.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_add_placeholder.imageset/Group 512213.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_add_placeholder.imageset/Group 512213.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_add_placeholder.imageset/Group 512213.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_add_placeholder.imageset/Group 512213.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_details_padlock.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_details_padlock.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_details_padlock.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_details_padlock.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_details_padlock.imageset/Vector-14.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_details_padlock.imageset/Vector-14.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_details_padlock.imageset/Vector-14.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_details_padlock.imageset/Vector-14.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_nickname_edit.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_nickname_edit.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_nickname_edit.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_nickname_edit.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_nickname_edit.imageset/Group 512219.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_nickname_edit.imageset/Group 512219.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_nickname_edit.imageset/Group 512219.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_nickname_edit.imageset/Group 512219.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_request_exclamation.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_request_exclamation.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_request_exclamation.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_request_exclamation.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_request_exclamation.imageset/Icon-12.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_request_exclamation.imageset/Icon-12.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_request_exclamation.imageset/Icon-12.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_request_exclamation.imageset/Icon-12.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_request_placeholder.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_request_placeholder.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_request_placeholder.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_request_placeholder.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_request_placeholder.imageset/Group 512213.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_request_placeholder.imageset/Group 512213.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_request_placeholder.imageset/Group 512213.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_request_placeholder.imageset/Group 512213.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_send_message.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_send_message.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_send_message.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_send_message.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_send_message.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_send_message.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContact/contact_send_message.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContact/contact_send_message.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContactList/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContactList/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_avatar_remove.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_avatar_remove.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_avatar_remove.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_avatar_remove.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_avatar_remove.imageset/Icon-17.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_avatar_remove.imageset/Icon-17.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_avatar_remove.imageset/Icon-17.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_avatar_remove.imageset/Icon-17.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_new_group.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_new_group.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_new_group.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_new_group.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_new_group.imageset/Icon-23.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_new_group.imageset/Icon-23.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_new_group.imageset/Icon-23.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_new_group.imageset/Icon-23.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_placeholder.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_placeholder.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_placeholder.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_placeholder.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_placeholder.imageset/Ellipse 1-3.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_placeholder.imageset/Ellipse 1-3.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_placeholder.imageset/Ellipse 1-3.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_placeholder.imageset/Ellipse 1-3.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_requests.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_requests.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_requests.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_requests.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_requests.imageset/Icon-24.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_requests.imageset/Icon-24.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_requests.imageset/Icon-24.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_requests.imageset/Icon-24.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_search.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_search.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_search.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_search.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_search.imageset/Union-6.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_search.imageset/Union-6.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_search.imageset/Union-6.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_search.imageset/Union-6.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_user_search.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_user_search.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_user_search.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_user_search.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_user_search.imageset/Icon-16.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_user_search.imageset/Icon-16.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsContactList/contactList_user_search.imageset/Icon-16.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsContactList/contactList_user_search.imageset/Icon-16.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsDrawer/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsDrawer/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsDrawer/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsDrawer/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsDrawer/drawer_negative.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsDrawer/drawer_negative.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsDrawer/drawer_negative.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsDrawer/drawer_negative.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsDrawer/drawer_negative.imageset/circle-bg 2-9.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsDrawer/drawer_negative.imageset/circle-bg 2-9.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsDrawer/drawer_negative.imageset/circle-bg 2-9.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsDrawer/drawer_negative.imageset/circle-bg 2-9.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_chats.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_chats.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_chats.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_chats.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_chats.imageset/Vector-9.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_chats.imageset/Vector-9.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_chats.imageset/Vector-9.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_chats.imageset/Vector-9.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_contacts.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_contacts.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_contacts.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_contacts.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_contacts.imageset/Vector-10.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_contacts.imageset/Vector-10.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_contacts.imageset/Vector-10.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_contacts.imageset/Vector-10.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_dashboard.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_dashboard.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_dashboard.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_dashboard.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_dashboard.imageset/dashboardIconMd.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_dashboard.imageset/dashboardIconMd.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_dashboard.imageset/dashboardIconMd.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_dashboard.imageset/dashboardIconMd.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_profile.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_profile.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_profile.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_profile.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_profile.imageset/Vector-11.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_profile.imageset/Vector-11.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_profile.imageset/Vector-11.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_profile.imageset/Vector-11.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_requests.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_requests.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_requests.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_requests.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_requests.imageset/Icon-33.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_requests.imageset/Icon-33.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_requests.imageset/Icon-33.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_requests.imageset/Icon-33.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_scan.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_scan.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_scan.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_scan.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_scan.imageset/Union-3.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_scan.imageset/Union-3.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_scan.imageset/Union-3.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_scan.imageset/Union-3.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_settings.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_settings.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_settings.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_settings.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_settings.imageset/Vector-12.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_settings.imageset/Vector-12.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_settings.imageset/Vector-12.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_settings.imageset/Vector-12.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_share.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_share.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_share.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_share.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_share.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_share.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsMenu/menu_share.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsMenu/menu_share.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_background.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_background.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_background.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_background.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_background.imageset/photo-1599149535927-36b3fc68a1d6 1-2.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_background.imageset/photo-1599149535927-36b3fc68a1d6 1-2.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_background.imageset/photo-1599149535927-36b3fc68a1d6 1-2.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_background.imageset/photo-1599149535927-36b3fc68a1d6 1-2.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_bottom_logo_start.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_bottom_logo_start.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_bottom_logo_start.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_bottom_logo_start.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_bottom_logo_start.imageset/Powered by xx network.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_bottom_logo_start.imageset/Powered by xx network.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_bottom_logo_start.imageset/Powered by xx network.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_bottom_logo_start.imageset/Powered by xx network.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_email.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_email.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_email.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_email.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_email.imageset/circle-bg 2-6.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_email.imageset/circle-bg 2-6.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_email.imageset/circle-bg 2-6.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_email.imageset/circle-bg 2-6.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_logo.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_logo.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_logo.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_logo.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_logo.imageset/Logo.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_logo.imageset/Logo.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_logo.imageset/Logo.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_logo.imageset/Logo.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_logo_start.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_logo_start.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_logo_start.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_logo_start.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_logo_start.imageset/Group-2.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_logo_start.imageset/Group-2.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_logo_start.imageset/Group-2.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_logo_start.imageset/Group-2.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_phone.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_phone.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_phone.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_phone.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_phone.imageset/circle-bg 2-7.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_phone.imageset/circle-bg 2-7.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_phone.imageset/circle-bg 2-7.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_phone.imageset/circle-bg 2-7.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_success.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_success.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_success.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_success.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_success.imageset/circle-bg 3.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_success.imageset/circle-bg 3.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsOnboarding/onboarding_success.imageset/circle-bg 3.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsOnboarding/onboarding_success.imageset/circle-bg 3.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/permission_camera.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/permission_camera.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/permission_camera.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/permission_camera.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/permission_camera.imageset/circle-bg 2 (2).pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/permission_camera.imageset/circle-bg 2 (2).pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/permission_camera.imageset/circle-bg 2 (2).pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/permission_camera.imageset/circle-bg 2 (2).pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/permission_library.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/permission_library.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/permission_library.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/permission_library.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/permission_library.imageset/circle-bg 2 (3).pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/permission_library.imageset/circle-bg 2 (3).pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/permission_library.imageset/circle-bg 2 (3).pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/permission_library.imageset/circle-bg 2 (3).pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/permission_logo.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/permission_logo.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/permission_logo.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/permission_logo.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/permission_logo.imageset/Logo (1).pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/permission_logo.imageset/Logo (1).pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/permission_logo.imageset/Logo (1).pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/permission_logo.imageset/Logo (1).pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/permission_microphone.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/permission_microphone.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/permission_microphone.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/permission_microphone.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/permission_microphone.imageset/circle-bg 2 (1).pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/permission_microphone.imageset/circle-bg 2 (1).pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsPermissions/permission_microphone.imageset/circle-bg 2 (1).pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsPermissions/permission_microphone.imageset/circle-bg 2 (1).pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsProfile/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsProfile/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_add.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_add.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_add.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_add.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_add.imageset/Icon-21.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_add.imageset/Icon-21.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_add.imageset/Icon-21.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_add.imageset/Icon-21.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_delete.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_delete.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_delete.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_delete.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_delete.imageset/Icon-22.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_delete.imageset/Icon-22.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_delete.imageset/Icon-22.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_delete.imageset/Icon-22.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_email.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_email.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_email.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_email.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_email.imageset/Group 512237.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_email.imageset/Group 512237.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_email.imageset/Group 512237.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_email.imageset/Group 512237.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_image_button.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_image_button.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_image_button.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_image_button.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_image_button.imageset/Group 512242.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_image_button.imageset/Group 512242.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_image_button.imageset/Group 512242.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_image_button.imageset/Group 512242.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_image_placeholder.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_image_placeholder.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_image_placeholder.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_image_placeholder.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_image_placeholder.imageset/Icon-20.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_image_placeholder.imageset/Icon-20.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_image_placeholder.imageset/Icon-20.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_image_placeholder.imageset/Icon-20.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_phone.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_phone.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_phone.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_phone.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_phone.imageset/Group 512237-2.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_phone.imageset/Group 512237-2.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsProfile/profile_phone.imageset/Group 512237-2.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsProfile/profile_phone.imageset/Group 512237-2.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/request_accepted.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/request_accepted.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/request_accepted.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/request_accepted.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/request_accepted.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/request_accepted.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/request_accepted.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/request_accepted.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/request_failed_toaster.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/request_failed_toaster.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/request_failed_toaster.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/request_failed_toaster.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/request_failed_toaster.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/request_failed_toaster.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/request_failed_toaster.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/request_failed_toaster.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/request_sent_toaster.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/request_sent_toaster.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/request_sent_toaster.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/request_sent_toaster.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/request_sent_toaster.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/request_sent_toaster.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/request_sent_toaster.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/request_sent_toaster.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_received_placeholder.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_received_placeholder.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_received_placeholder.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_received_placeholder.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_received_placeholder.imageset/circle-bg 2-11.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_received_placeholder.imageset/circle-bg 2-11.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_received_placeholder.imageset/circle-bg 2-11.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_received_placeholder.imageset/circle-bg 2-11.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_resend.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_resend.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_resend.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_resend.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_resend.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_resend.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_resend.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_resend.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_resent.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_resent.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_resent.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_resent.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_resent.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_resent.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_resent.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_resent.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_tab_failed.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_tab_failed.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_tab_failed.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_tab_failed.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_tab_failed.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_tab_failed.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_tab_failed.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_tab_failed.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_tab_received.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_tab_received.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_tab_received.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_tab_received.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_tab_received.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_tab_received.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_tab_received.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_tab_received.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_tab_sent.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_tab_sent.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_tab_sent.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_tab_sent.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_tab_sent.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_tab_sent.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_tab_sent.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_tab_sent.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_verification_failed.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_verification_failed.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_verification_failed.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_verification_failed.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_verification_failed.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_verification_failed.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRequests/requests_verification_failed.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRequests/requests_verification_failed.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRestore/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_SFTP.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_SFTP.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_SFTP.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_SFTP.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_SFTP.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_SFTP.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_SFTP.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_SFTP.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_drive.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_drive.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_drive.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_drive.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_drive.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_drive.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_drive.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_drive.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_dropbox.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_dropbox.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_dropbox.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_dropbox.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_dropbox.imageset/Icon-3.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_dropbox.imageset/Icon-3.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_dropbox.imageset/Icon-3.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_dropbox.imageset/Icon-3.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_icloud.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_icloud.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_icloud.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_icloud.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_icloud.imageset/Icon-2.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_icloud.imageset/Icon-2.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_icloud.imageset/Icon-2.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_icloud.imageset/Icon-2.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_success.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_success.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_success.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_success.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_success.imageset/Group 512227.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_success.imageset/Group 512227.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsRestore/restore_success.imageset/Group 512227.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsRestore/restore_success.imageset/Group 512227.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_add.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_add.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_add.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_add.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_add.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_add.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_add.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_add.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_copy.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_copy.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_copy.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_copy.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_copy.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_copy.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_copy.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_copy.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_dropdown.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_dropdown.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_dropdown.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_dropdown.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_dropdown.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_dropdown.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_dropdown.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_dropdown.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_email.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_email.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_email.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_email.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_email.imageset/Vector-23.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_email.imageset/Vector-23.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_email.imageset/Vector-23.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_email.imageset/Vector-23.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_error.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_error.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_error.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_error.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_error.imageset/Icon-36.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_error.imageset/Icon-36.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_error.imageset/Icon-36.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_error.imageset/Icon-36.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_phone.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_phone.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_phone.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_phone.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_phone.imageset/Vector-24.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_phone.imageset/Vector-24.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_phone.imageset/Vector-24.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_phone.imageset/Vector-24.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_qr.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_qr.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_qr.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_qr.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_qr.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_qr.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_qr.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_qr.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_scan.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_scan.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_scan.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_scan.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_scan.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_scan.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsScan/scan_scan.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsScan/scan_scan.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_email.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_email.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_email.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_email.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_email.imageset/Vector-18.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_email.imageset/Vector-18.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_email.imageset/Vector-18.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_email.imageset/Vector-18.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_lens.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_lens.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_lens.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_lens.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_lens.imageset/Vector-25.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_lens.imageset/Vector-25.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_lens.imageset/Vector-25.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_lens.imageset/Vector-25.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_phone.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_phone.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_phone.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_phone.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_phone.imageset/Vector-17.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_phone.imageset/Vector-17.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_phone.imageset/Vector-17.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_phone.imageset/Vector-17.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_placeholder_image.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_placeholder_image.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_placeholder_image.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_placeholder_image.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_placeholder_image.imageset/Ellipse 1.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_placeholder_image.imageset/Ellipse 1.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_placeholder_image.imageset/Ellipse 1.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_placeholder_image.imageset/Ellipse 1.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_tab_email.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_tab_email.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_tab_email.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_tab_email.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_tab_email.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_tab_email.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_tab_email.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_tab_email.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_tab_phone.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_tab_phone.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_tab_phone.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_tab_phone.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_tab_phone.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_tab_phone.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_tab_phone.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_tab_phone.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_tab_qr.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_tab_qr.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_tab_qr.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_tab_qr.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_tab_qr.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_tab_qr.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_tab_qr.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_tab_qr.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_tab_username.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_tab_username.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_tab_username.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_tab_username.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_tab_username.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_tab_username.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_tab_username.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_tab_username.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_username.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_username.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_username.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_username.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_username.imageset/Vector-16.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_username.imageset/Vector-16.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSearch/search_username.imageset/Vector-16.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSearch/search_username.imageset/Vector-16.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/Icon-32.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/Icon-32.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/Icon-32.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/Icon-32.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/Icon-32.imageset/Icon-32.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/Icon-32.imageset/Icon-32.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/Icon-32.imageset/Icon-32.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/Icon-32.imageset/Icon-32.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_advanced.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_advanced.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_advanced.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_advanced.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_advanced.imageset/Icon-31.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_advanced.imageset/Icon-31.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_advanced.imageset/Icon-31.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_advanced.imageset/Icon-31.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_biometrics.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_biometrics.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_biometrics.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_biometrics.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_biometrics.imageset/Icon-25.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_biometrics.imageset/Icon-25.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_biometrics.imageset/Icon-25.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_biometrics.imageset/Icon-25.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_crash.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_crash.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_crash.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_crash.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_crash.imageset/Group.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_crash.imageset/Group.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_crash.imageset/Group.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_crash.imageset/Group.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_delete.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_delete.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_delete.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_delete.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_delete.imageset/Icon-35.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_delete.imageset/Icon-35.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_delete.imageset/Icon-35.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_delete.imageset/Icon-35.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_delete_large.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_delete_large.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_delete_large.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_delete_large.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_delete_large.imageset/Group 512235.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_delete_large.imageset/Group 512235.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_delete_large.imageset/Group 512235.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_delete_large.imageset/Group 512235.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_disclosure.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_disclosure.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_disclosure.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_disclosure.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_disclosure.imageset/Vector-5.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_disclosure.imageset/Vector-5.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_disclosure.imageset/Vector-5.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_disclosure.imageset/Vector-5.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_download.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_download.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_download.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_download.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_download.imageset/variant=download.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_download.imageset/variant=download.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_download.imageset/variant=download.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_download.imageset/variant=download.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_enter.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_enter.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_enter.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_enter.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_enter.imageset/Icon-27.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_enter.imageset/Icon-27.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_enter.imageset/Icon-27.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_enter.imageset/Icon-27.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_folder.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_folder.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_folder.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_folder.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_folder.imageset/Icon-29.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_folder.imageset/Icon-29.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_folder.imageset/Icon-29.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_folder.imageset/Icon-29.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_hide.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_hide.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_hide.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_hide.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_hide.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_hide.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_hide.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_hide.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_keyboard.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_keyboard.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_keyboard.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_keyboard.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_keyboard.imageset/Icon-26.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_keyboard.imageset/Icon-26.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_keyboard.imageset/Icon-26.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_keyboard.imageset/Icon-26.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_logs.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_logs.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_logs.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_logs.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_logs.imageset/Vector-21.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_logs.imageset/Vector-21.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_logs.imageset/Vector-21.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_logs.imageset/Vector-21.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_notifications.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_notifications.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_notifications.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_notifications.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_notifications.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_notifications.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_notifications.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_notifications.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_privacy.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_privacy.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_privacy.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_privacy.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_privacy.imageset/Icon-28.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_privacy.imageset/Icon-28.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsSettings/settings_privacy.imageset/Icon-28.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsSettings/settings_privacy.imageset/Icon-28.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/balloon.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/balloon.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/balloon.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/balloon.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/balloon.imageset/Icon-18.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/balloon.imageset/Icon-18.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/balloon.imageset/Icon-18.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/balloon.imageset/Icon-18.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/eye_closed.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/eye_closed.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/eye_closed.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/eye_closed.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/eye_closed.imageset/notVisibleIcon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/eye_closed.imageset/notVisibleIcon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/eye_closed.imageset/notVisibleIcon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/eye_closed.imageset/notVisibleIcon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/eye_open.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/eye_open.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/eye_open.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/eye_open.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/eye_open.imageset/visibleIconLg.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/eye_open.imageset/visibleIconLg.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/eye_open.imageset/visibleIconLg.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/eye_open.imageset/visibleIconLg.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/info_icon.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/info_icon.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/info_icon.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/info_icon.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/info_icon.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/info_icon.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/info_icon.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/info_icon.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/info_icon_grey.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/info_icon_grey.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/info_icon_grey.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/info_icon_grey.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/info_icon_grey.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/info_icon_grey.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/info_icon_grey.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/info_icon_grey.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/lens.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/lens.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/lens.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/lens.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/lens.imageset/searchIcon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/lens.imageset/searchIcon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/lens.imageset/searchIcon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/lens.imageset/searchIcon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/navigation_bar_back.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/navigation_bar_back.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/navigation_bar_back.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/navigation_bar_back.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/navigation_bar_back.imageset/Vector.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/navigation_bar_back.imageset/Vector.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/navigation_bar_back.imageset/Vector.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/navigation_bar_back.imageset/Vector.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/person_gray.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/person_gray.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/person_gray.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/person_gray.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/person_gray.imageset/Icon-13.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/person_gray.imageset/Icon-13.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/person_gray.imageset/Icon-13.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/person_gray.imageset/Icon-13.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/person_placeholder.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/person_placeholder.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/person_placeholder.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/person_placeholder.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/person_placeholder.imageset/circle-bg 2-11.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/person_placeholder.imageset/circle-bg 2-11.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/person_placeholder.imageset/circle-bg 2-11.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/person_placeholder.imageset/circle-bg 2-11.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/reply_abort.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/reply_abort.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/reply_abort.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/reply_abort.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/reply_abort.imageset/closeIconDark512.png b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/reply_abort.imageset/closeIconDark512.png
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/reply_abort.imageset/closeIconDark512.png
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/reply_abort.imageset/closeIconDark512.png
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/reply_abort.imageset/closeIconDark512@2x.png b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/reply_abort.imageset/closeIconDark512@2x.png
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/reply_abort.imageset/closeIconDark512@2x.png
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/reply_abort.imageset/closeIconDark512@2x.png
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/reply_abort.imageset/closeIconDark512@3x.png b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/reply_abort.imageset/closeIconDark512@3x.png
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/reply_abort.imageset/closeIconDark512@3x.png
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/reply_abort.imageset/closeIconDark512@3x.png
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_cross.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_cross.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_cross.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_cross.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_cross.imageset/Icon-8.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_cross.imageset/Icon-8.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_cross.imageset/Icon-8.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_cross.imageset/Icon-8.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_error.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_error.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_error.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_error.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_error.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_error.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_error.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_error.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_group.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_group.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_group.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_group.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_group.imageset/Icon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_group.imageset/Icon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_group.imageset/Icon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_group.imageset/Icon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_scan.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_scan.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_scan.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_scan.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_scan.imageset/Union-7.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_scan.imageset/Union-7.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_scan.imageset/Union-7.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_scan.imageset/Union-7.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_success.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_success.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_success.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_success.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_success.imageset/Icon-2.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_success.imageset/Icon-2.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_success.imageset/Icon-2.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_success.imageset/Icon-2.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_white_exclamation.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_white_exclamation.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_white_exclamation.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_white_exclamation.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_white_exclamation.imageset/alertDidntsendIcon.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_white_exclamation.imageset/alertDidntsendIcon.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/shared_white_exclamation.imageset/alertDidntsendIcon.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/shared_white_exclamation.imageset/alertDidntsendIcon.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/splash.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/splash.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/splash.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/splash.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/AssetsShared/splash.imageset/group.pdf b/Sources/AppResources/Resources/Assets.xcassets/AssetsShared/splash.imageset/group.pdf
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/AssetsShared/splash.imageset/group.pdf
rename to Sources/AppResources/Resources/Assets.xcassets/AssetsShared/splash.imageset/group.pdf
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsAccent/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsAccent/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsAccent/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsAccent/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsAccent/accent_danger.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsAccent/accent_danger.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsAccent/accent_danger.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsAccent/accent_danger.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsAccent/accent_safe.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsAccent/accent_safe.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsAccent/accent_safe.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsAccent/accent_safe.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsAccent/accent_success.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsAccent/accent_success.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsAccent/accent_success.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsAccent/accent_success.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsAccent/accent_warning.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsAccent/accent_warning.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsAccent/accent_warning.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsAccent/accent_warning.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsBrand/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsBrand/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsBrand/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsBrand/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsBrand/brand_background.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsBrand/brand_background.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsBrand/brand_background.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsBrand/brand_background.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsBrand/brand_bubble.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsBrand/brand_bubble.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsBrand/brand_bubble.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsBrand/brand_bubble.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsBrand/brand_default.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsBrand/brand_default.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsBrand/brand_default.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsBrand/brand_default.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsBrand/brand_light.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsBrand/brand_light.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsBrand/brand_light.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsBrand/brand_light.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsBrand/brand_primary.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsBrand/brand_primary.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsBrand/brand_primary.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsBrand/brand_primary.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_active.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_active.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_active.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_active.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_body.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_body.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_body.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_body.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_dark.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_dark.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_dark.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_dark.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_disabled.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_disabled.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_disabled.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_disabled.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_line.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_line.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_line.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_line.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_overlay.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_overlay.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_overlay.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_overlay.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_secondary.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_secondary.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_secondary.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_secondary.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_secondary_alternative.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_secondary_alternative.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_secondary_alternative.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_secondary_alternative.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_weak.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_weak.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_weak.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_weak.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_white.colorset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_white.colorset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/ColorsNeutral/neutral_white.colorset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/ColorsNeutral/neutral_white.colorset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/transfer_image_placeholder.imageset/Contents.json b/Sources/AppResources/Resources/Assets.xcassets/transfer_image_placeholder.imageset/Contents.json
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/transfer_image_placeholder.imageset/Contents.json
rename to Sources/AppResources/Resources/Assets.xcassets/transfer_image_placeholder.imageset/Contents.json
diff --git a/Sources/Shared/Resources/Assets.xcassets/transfer_image_placeholder.imageset/placeholder-images-image_large.png b/Sources/AppResources/Resources/Assets.xcassets/transfer_image_placeholder.imageset/placeholder-images-image_large.png
similarity index 100%
rename from Sources/Shared/Resources/Assets.xcassets/transfer_image_placeholder.imageset/placeholder-images-image_large.png
rename to Sources/AppResources/Resources/Assets.xcassets/transfer_image_placeholder.imageset/placeholder-images-image_large.png
diff --git a/Sources/Shared/Resources/Fonts/Mulish-Black.ttf b/Sources/AppResources/Resources/Fonts/Mulish-Black.ttf
similarity index 100%
rename from Sources/Shared/Resources/Fonts/Mulish-Black.ttf
rename to Sources/AppResources/Resources/Fonts/Mulish-Black.ttf
diff --git a/Sources/Shared/Resources/Fonts/Mulish-BlackItalic.ttf b/Sources/AppResources/Resources/Fonts/Mulish-BlackItalic.ttf
similarity index 100%
rename from Sources/Shared/Resources/Fonts/Mulish-BlackItalic.ttf
rename to Sources/AppResources/Resources/Fonts/Mulish-BlackItalic.ttf
diff --git a/Sources/Shared/Resources/Fonts/Mulish-Bold.ttf b/Sources/AppResources/Resources/Fonts/Mulish-Bold.ttf
similarity index 100%
rename from Sources/Shared/Resources/Fonts/Mulish-Bold.ttf
rename to Sources/AppResources/Resources/Fonts/Mulish-Bold.ttf
diff --git a/Sources/Shared/Resources/Fonts/Mulish-BoldItalic.ttf b/Sources/AppResources/Resources/Fonts/Mulish-BoldItalic.ttf
similarity index 100%
rename from Sources/Shared/Resources/Fonts/Mulish-BoldItalic.ttf
rename to Sources/AppResources/Resources/Fonts/Mulish-BoldItalic.ttf
diff --git a/Sources/Shared/Resources/Fonts/Mulish-ExtraBold.ttf b/Sources/AppResources/Resources/Fonts/Mulish-ExtraBold.ttf
similarity index 100%
rename from Sources/Shared/Resources/Fonts/Mulish-ExtraBold.ttf
rename to Sources/AppResources/Resources/Fonts/Mulish-ExtraBold.ttf
diff --git a/Sources/Shared/Resources/Fonts/Mulish-ExtraBoldItalic.ttf b/Sources/AppResources/Resources/Fonts/Mulish-ExtraBoldItalic.ttf
similarity index 100%
rename from Sources/Shared/Resources/Fonts/Mulish-ExtraBoldItalic.ttf
rename to Sources/AppResources/Resources/Fonts/Mulish-ExtraBoldItalic.ttf
diff --git a/Sources/Shared/Resources/Fonts/Mulish-ExtraLight.ttf b/Sources/AppResources/Resources/Fonts/Mulish-ExtraLight.ttf
similarity index 100%
rename from Sources/Shared/Resources/Fonts/Mulish-ExtraLight.ttf
rename to Sources/AppResources/Resources/Fonts/Mulish-ExtraLight.ttf
diff --git a/Sources/Shared/Resources/Fonts/Mulish-ExtraLightItalic.ttf b/Sources/AppResources/Resources/Fonts/Mulish-ExtraLightItalic.ttf
similarity index 100%
rename from Sources/Shared/Resources/Fonts/Mulish-ExtraLightItalic.ttf
rename to Sources/AppResources/Resources/Fonts/Mulish-ExtraLightItalic.ttf
diff --git a/Sources/Shared/Resources/Fonts/Mulish-Italic.ttf b/Sources/AppResources/Resources/Fonts/Mulish-Italic.ttf
similarity index 100%
rename from Sources/Shared/Resources/Fonts/Mulish-Italic.ttf
rename to Sources/AppResources/Resources/Fonts/Mulish-Italic.ttf
diff --git a/Sources/Shared/Resources/Fonts/Mulish-Light.ttf b/Sources/AppResources/Resources/Fonts/Mulish-Light.ttf
similarity index 100%
rename from Sources/Shared/Resources/Fonts/Mulish-Light.ttf
rename to Sources/AppResources/Resources/Fonts/Mulish-Light.ttf
diff --git a/Sources/Shared/Resources/Fonts/Mulish-LightItalic.ttf b/Sources/AppResources/Resources/Fonts/Mulish-LightItalic.ttf
similarity index 100%
rename from Sources/Shared/Resources/Fonts/Mulish-LightItalic.ttf
rename to Sources/AppResources/Resources/Fonts/Mulish-LightItalic.ttf
diff --git a/Sources/Shared/Resources/Fonts/Mulish-Medium.ttf b/Sources/AppResources/Resources/Fonts/Mulish-Medium.ttf
similarity index 100%
rename from Sources/Shared/Resources/Fonts/Mulish-Medium.ttf
rename to Sources/AppResources/Resources/Fonts/Mulish-Medium.ttf
diff --git a/Sources/Shared/Resources/Fonts/Mulish-MediumItalic.ttf b/Sources/AppResources/Resources/Fonts/Mulish-MediumItalic.ttf
similarity index 100%
rename from Sources/Shared/Resources/Fonts/Mulish-MediumItalic.ttf
rename to Sources/AppResources/Resources/Fonts/Mulish-MediumItalic.ttf
diff --git a/Sources/Shared/Resources/Fonts/Mulish-Regular.ttf b/Sources/AppResources/Resources/Fonts/Mulish-Regular.ttf
similarity index 100%
rename from Sources/Shared/Resources/Fonts/Mulish-Regular.ttf
rename to Sources/AppResources/Resources/Fonts/Mulish-Regular.ttf
diff --git a/Sources/Shared/Resources/Fonts/Mulish-SemiBold.ttf b/Sources/AppResources/Resources/Fonts/Mulish-SemiBold.ttf
similarity index 100%
rename from Sources/Shared/Resources/Fonts/Mulish-SemiBold.ttf
rename to Sources/AppResources/Resources/Fonts/Mulish-SemiBold.ttf
diff --git a/Sources/Shared/Resources/Fonts/Mulish-SemiBoldItalic.ttf b/Sources/AppResources/Resources/Fonts/Mulish-SemiBoldItalic.ttf
similarity index 100%
rename from Sources/Shared/Resources/Fonts/Mulish-SemiBoldItalic.ttf
rename to Sources/AppResources/Resources/Fonts/Mulish-SemiBoldItalic.ttf
diff --git a/Sources/Shared/Resources/en.lproj/Localizable.strings b/Sources/AppResources/Resources/en.lproj/Localizable.strings
similarity index 99%
rename from Sources/Shared/Resources/en.lproj/Localizable.strings
rename to Sources/AppResources/Resources/en.lproj/Localizable.strings
index a82b151e1ff1fb0a65ddffb5060c922057679532..3dfbb4a622ecde1973cc79fb0e692d78add2cefa 100644
--- a/Sources/Shared/Resources/en.lproj/Localizable.strings
+++ b/Sources/AppResources/Resources/en.lproj/Localizable.strings
@@ -668,6 +668,10 @@
 = "Set password and continue";
 "backup.passphrase.cancel"
 = "Cancel";
+"backup.restore.passphrase.title"
+= "Backup password";
+"backup.restore.passphrase.subtitle"
+= "Please enter your backup password that you used when you did the backup setup";
 
 "backup.iCloud"
 = "iCloud";
@@ -983,7 +987,7 @@
 "createGroup.drawer.title"
 = "Create Group";
 "createGroup.drawer.subtitle"
-= "You are about to create a group message with %@ users. The information below will be visible to all members of the group.";
+= "You are about to create a group message with other %@ users. The information below will be visible to all members of the group.";
 "createGroup.drawer.input"
 = "Group Name";
 "createGroup.drawer.otherInput"
diff --git a/Sources/Shared/AutoGenerated/Strings.swift b/Sources/AppResources/Strings.swift
similarity index 99%
rename from Sources/Shared/AutoGenerated/Strings.swift
rename to Sources/AppResources/Strings.swift
index 3177da720c32c5e4209fa4dc0c9c1211d6e65b5e..4ced950dd511ef22ad7f25f4ddb1c1192587ed7e 100644
--- a/Sources/Shared/AutoGenerated/Strings.swift
+++ b/Sources/AppResources/Strings.swift
@@ -290,6 +290,14 @@ public enum Localized {
         public static let title = Localized.tr("Localizable", "backup.passphrase.input.title")
       }
     }
+    public enum Restore {
+      public enum Passphrase {
+        /// Please enter your backup password that you used when you did the backup setup
+        public static let subtitle = Localized.tr("Localizable", "backup.restore.passphrase.subtitle")
+        /// Backup password
+        public static let title = Localized.tr("Localizable", "backup.restore.passphrase.title")
+      }
+    }
     public enum Setup {
       /// Setup your #backup service#.
       public static let title = Localized.tr("Localizable", "backup.setup.title")
@@ -617,7 +625,7 @@ public enum Localized {
       public static let otherPlaceholder = Localized.tr("Localizable", "createGroup.drawer.otherPlaceholder")
       /// Secret Family
       public static let placeholder = Localized.tr("Localizable", "createGroup.drawer.placeholder")
-      /// You are about to create a group message with %@ users. The information below will be visible to all members of the group.
+      /// You are about to create a group message with other %@ users. The information below will be visible to all members of the group.
       public static func subtitle(_ p1: Any) -> String {
         return Localized.tr("Localizable", "createGroup.drawer.subtitle", String(describing: p1))
       }
diff --git a/Sources/Shared/swiftgen.yml b/Sources/AppResources/swiftgen.yml
similarity index 77%
rename from Sources/Shared/swiftgen.yml
rename to Sources/AppResources/swiftgen.yml
index 1811db25daa24e73f82a0022cc191d14e15f664b..3e4548e0eb8a79eefb0bb9f37d8d3ef7cc9338da 100644
--- a/Sources/Shared/swiftgen.yml
+++ b/Sources/AppResources/swiftgen.yml
@@ -2,7 +2,7 @@ strings:
   inputs: Resources/en.lproj
   outputs:
       templateName: structured-swift5
-      output: AutoGenerated/Strings.swift
+      output: Strings.swift
       params:
         enumName: Localized
         publicAccess: true
@@ -11,7 +11,7 @@ xcassets:
   inputs: Resources/Assets.xcassets
   outputs:
     templateName: swift5
-    output: AutoGenerated/Assets.swift
+    output: Assets.swift
     params:
       publicAccess: true
 
@@ -19,7 +19,7 @@ fonts:
   inputs: Resources/Fonts
   outputs:
     templateName: swift5
-    output: AutoGenerated/Fonts.swift
+    output: Fonts.swift
     params:
       enumName: Fonts
       publicAccess: true
diff --git a/Sources/BackupFeature/Controllers/BackupConfigController.swift b/Sources/BackupFeature/Controllers/BackupConfigController.swift
index a901e6b9668fc89df2cd1ae1184ca655fa8bdb1b..f2e0220380a545b5d8a79cc5c7517d7676930ba1 100644
--- a/Sources/BackupFeature/Controllers/BackupConfigController.swift
+++ b/Sources/BackupFeature/Controllers/BackupConfigController.swift
@@ -1,334 +1,335 @@
 import UIKit
-import Models
 import Shared
 import Combine
+import CloudFiles
+import AppResources
+import AppNavigation
 import DrawerFeature
-import DependencyInjection
+import ComposableArchitecture
 
 final class BackupConfigController: UIViewController {
-    @Dependency private var coordinator: BackupCoordinating
-
-    lazy private var screenView = BackupConfigView()
-
-    private let viewModel: BackupConfigViewModel
-    private var cancellables = Set<AnyCancellable>()
-    private var drawerCancellables = Set<AnyCancellable>()
+  @Dependency(\.navigator) var navigator: Navigator
+
+  private lazy var screenView = BackupConfigView()
+
+  private let viewModel: BackupConfigViewModel
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
+
+  private var wifiOnly = false
+  private var manualBackups = false
+  private var serviceName: String = ""
+
+  override func loadView() {
+    view = screenView
+  }
+
+  init(_ viewModel: BackupConfigViewModel) {
+    self.viewModel = viewModel
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  override func viewDidLoad() {
+    super.viewDidLoad()
+    setupBindings()
+  }
+
+  private func setupBindings() {
+    viewModel.actionState()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in screenView.actionView.setState($0) }
+      .store(in: &cancellables)
+
+    viewModel.connectedServices()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in decorate(connectedServices: $0) }
+      .store(in: &cancellables)
+
+    viewModel.enabledService()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in decorate(enabledService: $0) }
+      .store(in: &cancellables)
+
+    viewModel.automatic()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.frequencyDetailView.subtitleLabel.text = $0 ? "Automatic" : "Manual"
+        manualBackups = !$0
+      }.store(in: &cancellables)
+
+    viewModel.wifiOnly()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.infrastructureDetailView.subtitleLabel.text = $0 ? "Wi-Fi Only" : "Wi-Fi and Cellular"
+        wifiOnly = $0
+      }.store(in: &cancellables)
+
+    viewModel.lastBackup()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        guard let backup = $0 else {
+          screenView.latestBackupDetailView.subtitleLabel.text = "Never"
+          return
+        }
 
-    private var wifiOnly = false
-    private var manualBackups = false
-    private var serviceName: String = ""
+        screenView.latestBackupDetailView.subtitleLabel.text = backup.lastModified.backupStyle()
+      }.store(in: &cancellables)
+
+    screenView.actionView.backupNowButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewModel.didTapBackupNow() }
+      .store(in: &cancellables)
+
+    screenView.frequencyDetailView
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in presentFrequencyDrawer(manual: manualBackups) }
+      .store(in: &cancellables)
+
+    screenView.infrastructureDetailView
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in presentInfrastructureDrawer(wifiOnly: wifiOnly) }
+      .store(in: &cancellables)
+
+    screenView.googleDriveButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewModel.didTapService(.drive, self) }
+      .store(in: &cancellables)
+
+    screenView.googleDriveButton.switcherView
+      .publisher(for: .valueChanged)
+      .sink { [unowned self] in viewModel.didToggleService(self, .drive, screenView.googleDriveButton.switcherView.isOn) }
+      .store(in: &cancellables)
+
+    screenView.dropboxButton.switcherView
+      .publisher(for: .valueChanged)
+      .sink { [unowned self] in viewModel.didToggleService(self, .dropbox, screenView.dropboxButton.switcherView.isOn) }
+      .store(in: &cancellables)
+
+    screenView.sftpButton.switcherView
+      .publisher(for: .valueChanged)
+      .sink { [unowned self] in viewModel.didToggleService(self, .sftp, screenView.sftpButton.switcherView.isOn) }
+      .store(in: &cancellables)
+
+    screenView.iCloudButton.switcherView
+      .publisher(for: .valueChanged)
+      .sink { [unowned self] in viewModel.didToggleService(self, .icloud, screenView.iCloudButton.switcherView.isOn) }
+      .store(in: &cancellables)
+
+    screenView.dropboxButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewModel.didTapService(.dropbox, self) }
+      .store(in: &cancellables)
+
+    screenView.sftpButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewModel.didTapService(.sftp, self) }
+      .store(in: &cancellables)
+
+    screenView.iCloudButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewModel.didTapService(.icloud, self) }
+      .store(in: &cancellables)
+  }
+
+  private func decorate(enabledService: CloudService?) {
+    var button: BackupSwitcherButton?
+
+    switch enabledService {
+    case .none:
+      break
+    case .icloud:
+      serviceName = Localized.Backup.iCloud
+      button = screenView.iCloudButton
+    case .dropbox:
+      serviceName = Localized.Backup.dropbox
+      button = screenView.dropboxButton
+    case .drive:
+      serviceName = Localized.Backup.googleDrive
+      button = screenView.googleDriveButton
+    case .sftp:
+      serviceName = Localized.Backup.sftp
+      button = screenView.sftpButton
+    }
 
-    override func loadView() {
-        view = screenView
+    screenView.enabledSubtitleLabel.text
+    = Localized.Backup.Config.disclaimer(serviceName)
+    screenView.frequencyDetailView.titleLabel.text
+    = Localized.Backup.Config.frequency(serviceName).uppercased()
+
+    guard let button = button else {
+      screenView.sftpButton.isHidden = false
+      screenView.iCloudButton.isHidden = false
+      screenView.dropboxButton.isHidden = false
+      screenView.googleDriveButton.isHidden = false
+
+      screenView.sftpButton.switcherView.isOn = false
+      screenView.iCloudButton.switcherView.isOn = false
+      screenView.dropboxButton.switcherView.isOn = false
+      screenView.googleDriveButton.switcherView.isOn = false
+
+      screenView.frequencyDetailView.isHidden = true
+      screenView.enabledSubtitleView.isHidden = true
+      screenView.latestBackupDetailView.isHidden = true
+      screenView.infrastructureDetailView.isHidden = true
+      return
     }
 
-    init(_ viewModel: BackupConfigViewModel) {
-        self.viewModel = viewModel
-        super.init(nibName: nil, bundle: nil)
+    screenView.frequencyDetailView.isHidden = false
+    screenView.enabledSubtitleView.isHidden = false
+    screenView.latestBackupDetailView.isHidden = false
+    screenView.infrastructureDetailView.isHidden = false
+
+    [screenView.iCloudButton,
+     screenView.dropboxButton,
+     screenView.googleDriveButton,
+     screenView.sftpButton].forEach {
+      $0.isHidden = $0 != button
+      $0.switcherView.isOn = $0 == button
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  private func decorate(connectedServices: Set<CloudService>) {
+    if connectedServices.contains(.icloud) {
+      screenView.iCloudButton.showSwitcher(enabled: false)
+    } else {
+      screenView.iCloudButton.showChevron()
+    }
 
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        setupBindings()
+    if connectedServices.contains(.dropbox) {
+      screenView.dropboxButton.showSwitcher(enabled: false)
+    } else {
+      screenView.dropboxButton.showChevron()
     }
 
-    private func setupBindings() {
-        viewModel.actionState()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.actionView.setState($0) }
-            .store(in: &cancellables)
-
-        viewModel.connectedServices()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in decorate(connectedServices: $0) }
-            .store(in: &cancellables)
-
-        viewModel.enabledService()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in decorate(enabledService: $0) }
-            .store(in: &cancellables)
-
-        viewModel.automatic()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                screenView.frequencyDetailView.subtitleLabel.text = $0 ? "Automatic" : "Manual"
-                manualBackups = !$0
-            }.store(in: &cancellables)
-
-        viewModel.wifiOnly()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                screenView.infrastructureDetailView.subtitleLabel.text = $0 ? "Wi-Fi Only" : "Wi-Fi and Cellular"
-                wifiOnly = $0
-            }.store(in: &cancellables)
-
-        viewModel.lastBackup()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                guard let backup = $0 else {
-                    screenView.latestBackupDetailView.subtitleLabel.text = "Never"
-                    return
-                }
-
-                screenView.latestBackupDetailView.subtitleLabel.text = backup.date.backupStyle()
-            }.store(in: &cancellables)
-
-        screenView.actionView.backupNowButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewModel.didTapBackupNow() }
-            .store(in: &cancellables)
-
-        screenView.frequencyDetailView
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in presentFrequencyDrawer(manual: manualBackups) }
-            .store(in: &cancellables)
-
-        screenView.infrastructureDetailView
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in presentInfrastructureDrawer(wifiOnly: wifiOnly) }
-            .store(in: &cancellables)
-
-        screenView.googleDriveButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewModel.didTapService(.drive, self) }
-            .store(in: &cancellables)
-
-        screenView.googleDriveButton.switcherView
-            .publisher(for: .valueChanged)
-            .sink { [unowned self] in viewModel.didToggleService(self, .drive, screenView.googleDriveButton.switcherView.isOn) }
-            .store(in: &cancellables)
-
-        screenView.dropboxButton.switcherView
-            .publisher(for: .valueChanged)
-            .sink { [unowned self] in viewModel.didToggleService(self, .dropbox, screenView.dropboxButton.switcherView.isOn) }
-            .store(in: &cancellables)
-
-        screenView.sftpButton.switcherView
-            .publisher(for: .valueChanged)
-            .sink { [unowned self] in viewModel.didToggleService(self, .sftp, screenView.sftpButton.switcherView.isOn) }
-            .store(in: &cancellables)
-
-        screenView.iCloudButton.switcherView
-            .publisher(for: .valueChanged)
-            .sink { [unowned self] in viewModel.didToggleService(self, .icloud, screenView.iCloudButton.switcherView.isOn) }
-            .store(in: &cancellables)
-
-        screenView.dropboxButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewModel.didTapService(.dropbox, self) }
-            .store(in: &cancellables)
-
-        screenView.sftpButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewModel.didTapService(.sftp, self) }
-            .store(in: &cancellables)
-
-        screenView.iCloudButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewModel.didTapService(.icloud, self) }
-            .store(in: &cancellables)
+    if connectedServices.contains(.drive) {
+      screenView.googleDriveButton.showSwitcher(enabled: false)
+    } else {
+      screenView.googleDriveButton.showChevron()
     }
 
-    private func decorate(enabledService: CloudService?) {
-        var button: BackupSwitcherButton?
-
-        switch enabledService {
-        case .none:
-            break
-        case .icloud:
-            serviceName = Localized.Backup.iCloud
-            button = screenView.iCloudButton
-        case .dropbox:
-            serviceName = Localized.Backup.dropbox
-            button = screenView.dropboxButton
-        case .drive:
-            serviceName = Localized.Backup.googleDrive
-            button = screenView.googleDriveButton
-        case .sftp:
-            serviceName = Localized.Backup.sftp
-            button = screenView.sftpButton
+    if connectedServices.contains(.sftp) {
+      screenView.sftpButton.showSwitcher(enabled: false)
+    } else {
+      screenView.sftpButton.showChevron()
+    }
+  }
+
+  private func presentInfrastructureDrawer(wifiOnly: Bool) {
+    let cancelButton = DrawerCapsuleButton(model: .init(
+      title: Localized.ChatList.Dashboard.cancel,
+      style: .seeThrough
+    ))
+    let wifiOnlyButton = DrawerRadio(
+      title: "Wi-Fi Only",
+      isSelected: wifiOnly
+    )
+    let wifiAndCellularButton = DrawerRadio(
+      title: "Wi-Fi and Cellular",
+      isSelected: !wifiOnly,
+      spacingAfter: 40
+    )
+
+    wifiOnlyButton
+      .action
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          self.viewModel.didChooseWifiOnly(true)
         }
-
-        screenView.enabledSubtitleLabel.text
-        = Localized.Backup.Config.disclaimer(serviceName)
-        screenView.frequencyDetailView.titleLabel.text
-        = Localized.Backup.Config.frequency(serviceName).uppercased()
-
-        guard let button = button else {
-            screenView.sftpButton.isHidden = false
-            screenView.iCloudButton.isHidden = false
-            screenView.dropboxButton.isHidden = false
-            screenView.googleDriveButton.isHidden = false
-
-            screenView.sftpButton.switcherView.isOn = false
-            screenView.iCloudButton.switcherView.isOn = false
-            screenView.dropboxButton.switcherView.isOn = false
-            screenView.googleDriveButton.switcherView.isOn = false
-
-            screenView.frequencyDetailView.isHidden = true
-            screenView.enabledSubtitleView.isHidden = true
-            screenView.latestBackupDetailView.isHidden = true
-            screenView.infrastructureDetailView.isHidden = true
-            return
+      }.store(in: &drawerCancellables)
+
+    wifiAndCellularButton
+      .action
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          self.viewModel.didChooseWifiOnly(false)
         }
-
-        screenView.frequencyDetailView.isHidden = false
-        screenView.enabledSubtitleView.isHidden = false
-        screenView.latestBackupDetailView.isHidden = false
-        screenView.infrastructureDetailView.isHidden = false
-
-        [screenView.iCloudButton,
-         screenView.dropboxButton,
-         screenView.googleDriveButton,
-         screenView.sftpButton].forEach {
-            $0.isHidden = $0 != button
-            $0.switcherView.isOn = $0 == button
+      }.store(in: &drawerCancellables)
+
+    cancelButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
         }
-    }
-
-    private func decorate(connectedServices: Set<CloudService>) {
-        if connectedServices.contains(.icloud) {
-            screenView.iCloudButton.showSwitcher(enabled: false)
-        } else {
-            screenView.iCloudButton.showChevron()
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.extraBold.font(size: 28.0),
+        text: Localized.Backup.Config.infrastructure,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 30
+      ),
+      wifiOnlyButton,
+      wifiAndCellularButton,
+      cancelButton
+    ], isDismissable: true, from: self))
+  }
+
+  private func presentFrequencyDrawer(manual: Bool) {
+    let cancelButton = DrawerCapsuleButton(model: .init(
+      title: Localized.ChatList.Dashboard.cancel,
+      style: .seeThrough
+    ))
+    let manualButton = DrawerRadio(
+      title: "Manual",
+      isSelected: manual
+    )
+    let automaticButton = DrawerRadio(
+      title: "Automatic",
+      isSelected: !manual,
+      spacingAfter: 40
+    )
+    manualButton
+      .action
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          self.viewModel.didChooseAutomatic(false)
         }
-
-        if connectedServices.contains(.dropbox) {
-            screenView.dropboxButton.showSwitcher(enabled: false)
-        } else {
-            screenView.dropboxButton.showChevron()
+      }.store(in: &drawerCancellables)
+
+    automaticButton
+      .action
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          self.viewModel.didChooseAutomatic(true)
         }
-
-        if connectedServices.contains(.drive) {
-            screenView.googleDriveButton.showSwitcher(enabled: false)
-        } else {
-            screenView.googleDriveButton.showChevron()
+      }.store(in: &drawerCancellables)
+
+    cancelButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
         }
-
-        if connectedServices.contains(.sftp) {
-            screenView.sftpButton.showSwitcher(enabled: false)
-        } else {
-            screenView.sftpButton.showChevron()
-        }
-    }
-
-    private func presentInfrastructureDrawer(wifiOnly: Bool) {
-        let cancelButton = DrawerCapsuleButton(model: .init(
-            title: Localized.ChatList.Dashboard.cancel,
-            style: .seeThrough
-        ))
-
-        let wifiOnlyButton = DrawerRadio(
-            title: "Wi-Fi Only",
-            isSelected: wifiOnly
-        )
-
-        let wifiAndCellularButton = DrawerRadio(
-            title: "Wi-Fi and Cellular",
-            isSelected: !wifiOnly,
-            spacingAfter: 40
-        )
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.extraBold.font(size: 28.0),
-                text: Localized.Backup.Config.infrastructure,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 30
-            ),
-            wifiOnlyButton,
-            wifiAndCellularButton,
-            cancelButton
-        ])
-
-        wifiOnlyButton.action
-            .sink { [unowned self] in
-                viewModel.didChooseWifiOnly(true)
-
-                drawer.dismiss(animated: true) { [weak self] in
-                    self?.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        wifiAndCellularButton.action
-            .sink { [unowned self] in
-                viewModel.didChooseWifiOnly(false)
-
-                drawer.dismiss(animated: true) { [weak self] in
-                    self?.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        cancelButton.action
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    self?.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
-
-    private func presentFrequencyDrawer(manual: Bool) {
-        let cancelButton = DrawerCapsuleButton(model: .init(
-            title: Localized.ChatList.Dashboard.cancel,
-            style: .seeThrough
-        ))
-
-        let manualButton = DrawerRadio(
-            title: "Manual",
-            isSelected: manual
-        )
-
-        let automaticButton = DrawerRadio(
-            title: "Automatic",
-            isSelected: !manual,
-            spacingAfter: 40
-        )
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.extraBold.font(size: 28.0),
-                text: Localized.Backup.Config.frequency(serviceName),
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 30
-            ),
-            manualButton,
-            automaticButton,
-            cancelButton
-        ])
-
-        manualButton.action
-            .sink { [unowned self] in
-                viewModel.didChooseAutomatic(false)
-
-                drawer.dismiss(animated: true) { [weak self] in
-                    self?.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        automaticButton.action
-            .sink { [unowned self] in
-                viewModel.didChooseAutomatic(true)
-
-                drawer.dismiss(animated: true) { [weak self] in
-                    self?.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        cancelButton.action
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    self?.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.extraBold.font(size: 28.0),
+        text: Localized.Backup.Config.frequency(serviceName),
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 30
+      ),
+      manualButton,
+      automaticButton,
+      cancelButton
+    ], isDismissable: true, from: self))
+  }
 }
diff --git a/Sources/BackupFeature/Controllers/BackupController.swift b/Sources/BackupFeature/Controllers/BackupController.swift
index 822ebbc77c3fa46acd0d842579382101aac5b3f9..ca82c50e8c1d470f1c0433f16e3a6d182ddd0604 100644
--- a/Sources/BackupFeature/Controllers/BackupController.swift
+++ b/Sources/BackupFeature/Controllers/BackupController.swift
@@ -1,73 +1,65 @@
-import HUD
 import UIKit
 import Shared
-import Models
 import Combine
-import DependencyInjection
+import AppResources
 
 public final class BackupController: UIViewController {
-    @Dependency var hud: HUD
+  private let viewModel = BackupViewModel.live()
+  private var cancellables = Set<AnyCancellable>()
 
-    private let viewModel = BackupViewModel.live()
-    private var cancellables = Set<AnyCancellable>()
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    navigationController?.navigationBar
+      .customize(backgroundColor: Asset.neutralWhite.color)
+  }
 
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        navigationController?.navigationBar
-            .customize(backgroundColor: Asset.neutralWhite.color)
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        view.backgroundColor = Asset.neutralWhite.color
-        hud.update(with: .on)
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    view.backgroundColor = Asset.neutralWhite.color
+    setupNavigationBar()
+    setupBindings()
+  }
 
-        setupNavigationBar()
-        setupBindings()
-    }
-
-    private func setupNavigationBar() {
-        let title = UILabel()
-        title.text = Localized.Backup.header
-        title.textColor = Asset.neutralActive.color
-        title.font = Fonts.Mulish.semiBold.font(size: 18.0)
-        navigationItem.leftBarButtonItem = UIBarButtonItem(customView: title)
-        navigationItem.leftItemsSupplementBackButton = true
-    }
+  private func setupNavigationBar() {
+    let title = UILabel()
+    title.text = Localized.Backup.header
+    title.textColor = Asset.neutralActive.color
+    title.font = Fonts.Mulish.semiBold.font(size: 18.0)
+    navigationItem.leftBarButtonItem = UIBarButtonItem(customView: title)
+    navigationItem.leftItemsSupplementBackButton = true
+  }
 
-    private func setupBindings() {
-        viewModel.state()
-            .receive(on: DispatchQueue.main)
-            .removeDuplicates()
-            .sink { [unowned self] in
-                hud.update(with: .none)
-
-                switch $0 {
-                case .setup:
-                    contentViewController = BackupSetupController(viewModel.setupViewModel())
-                case .config:
-                    contentViewController = BackupConfigController(viewModel.configViewModel())
-                }
-            }.store(in: &cancellables)
-    }
+  private func setupBindings() {
+    viewModel.state()
+      .receive(on: DispatchQueue.main)
+      .removeDuplicates()
+      .sink { [unowned self] in
+        switch $0 {
+        case .setup:
+          contentViewController = BackupSetupController(viewModel.setupViewModel())
+        case .config:
+          contentViewController = BackupConfigController(viewModel.configViewModel())
+        }
+      }.store(in: &cancellables)
+  }
 
-    private var contentViewController: UIViewController? {
-        didSet {
-            guard contentViewController != oldValue else { return }
+  private var contentViewController: UIViewController? {
+    didSet {
+      guard contentViewController != oldValue else { return }
 
-            if let oldValue = oldValue {
-                oldValue.willMove(toParent: nil)
-                oldValue.view.removeFromSuperview()
-                oldValue.removeFromParent()
-            }
+      if let oldValue = oldValue {
+        oldValue.willMove(toParent: nil)
+        oldValue.view.removeFromSuperview()
+        oldValue.removeFromParent()
+      }
 
-            if let newValue = contentViewController {
-                addChild(newValue)
-                view.addSubview(newValue.view)
-                newValue.view.snp.makeConstraints { $0.edges.equalToSuperview() }
-                newValue.didMove(toParent: self)
-            }
-        }
+      if let newValue = contentViewController {
+        addChild(newValue)
+        view.addSubview(newValue.view)
+        newValue.view.snp.makeConstraints { $0.edges.equalToSuperview() }
+        newValue.didMove(toParent: self)
+      }
     }
+  }
 }
diff --git a/Sources/BackupFeature/Controllers/BackupPassphraseController.swift b/Sources/BackupFeature/Controllers/BackupPassphraseController.swift
index 97c23d21b497a643a7850f3b9147723053dd1e9e..35b97b35cab02ea52f5bc5b9f800ccca3a4cba17 100644
--- a/Sources/BackupFeature/Controllers/BackupPassphraseController.swift
+++ b/Sources/BackupFeature/Controllers/BackupPassphraseController.swift
@@ -4,70 +4,70 @@ import Combine
 import InputField
 
 public final class BackupPassphraseController: UIViewController {
-    lazy private var screenView = BackupPassphraseView()
+  private lazy var screenView = BackupPassphraseView()
 
-    private var passphrase = "" {
-        didSet {
-            switch Validator.backupPassphrase.validate(passphrase) {
-            case .success:
-                screenView.continueButton.isEnabled = true
-            case .failure:
-                screenView.continueButton.isEnabled = false
-            }
-        }
+  private var passphrase = "" {
+    didSet {
+      switch Validator.backupPassphrase.validate(passphrase) {
+      case .success:
+        screenView.continueButton.isEnabled = true
+      case .failure:
+        screenView.continueButton.isEnabled = false
+      }
     }
+  }
 
-    private let cancelClosure: EmptyClosure
-    private let stringClosure: StringClosure
-    private var cancellables = Set<AnyCancellable>()
+  private let cancelClosure: () -> Void
+  private let stringClosure: (String) -> Void
+  private var cancellables = Set<AnyCancellable>()
 
-    public init(
-        _ cancelClosure: @escaping EmptyClosure,
-        _ stringClosure: @escaping StringClosure
-    ) {
-        self.stringClosure = stringClosure
-        self.cancelClosure = cancelClosure
-        super.init(nibName: nil, bundle: nil)
-    }
+  public init(
+    _ cancelClosure: @escaping () -> Void,
+    _ stringClosure: @escaping (String) -> Void
+  ) {
+    self.stringClosure = stringClosure
+    self.cancelClosure = cancelClosure
+    super.init(nibName: nil, bundle: nil)
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 
-    public override func loadView() {
-        view = screenView
-    }
+  public override func loadView() {
+    view = screenView
+  }
 
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupBindings()
-    }
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupBindings()
+  }
 
-    private func setupBindings() {
-        screenView
-            .inputField
-            .returnPublisher
-            .sink { [unowned self] in
-                screenView.inputField.endEditing(true)
-            }.store(in: &cancellables)
+  private func setupBindings() {
+    screenView
+      .inputField
+      .returnPublisher
+      .sink { [unowned self] in
+        screenView.inputField.endEditing(true)
+      }.store(in: &cancellables)
 
-        screenView
-            .inputField
-            .textPublisher
-            .sink { [unowned self] in
-                passphrase = $0.trimmingCharacters(in: .whitespacesAndNewlines)
-            }.store(in: &cancellables)
+    screenView
+      .inputField
+      .textPublisher
+      .sink { [unowned self] in
+        passphrase = $0.trimmingCharacters(in: .whitespacesAndNewlines)
+      }.store(in: &cancellables)
 
-        screenView
-            .continueButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                dismiss(animated: true) { self.stringClosure(self.passphrase) }
-            }.store(in: &cancellables)
+    screenView
+      .continueButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        dismiss(animated: true) { self.stringClosure(self.passphrase) }
+      }.store(in: &cancellables)
 
-        screenView
-            .cancelButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                dismiss(animated: true) { self.cancelClosure() }
-            }.store(in: &cancellables)
-    }
+    screenView
+      .cancelButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        dismiss(animated: true) { self.cancelClosure() }
+      }.store(in: &cancellables)
+  }
 }
diff --git a/Sources/BackupFeature/Controllers/BackupSFTPController.swift b/Sources/BackupFeature/Controllers/BackupSFTPController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7b6af4f5897855d740b6e7918071090f04765729
--- /dev/null
+++ b/Sources/BackupFeature/Controllers/BackupSFTPController.swift
@@ -0,0 +1,79 @@
+import UIKit
+import Combine
+import ScrollViewController
+
+public final class BackupSFTPController: UIViewController {
+  private lazy var screenView = BackupSFTPView()
+  private lazy var scrollViewController = ScrollViewController()
+
+  private let completion: (String, String, String) -> Void
+  private let viewModel = BackupSFTPViewModel()
+  private var cancellables = Set<AnyCancellable>()
+
+  public init(_ completion: @escaping (String, String, String) -> Void) {
+    self.completion = completion
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    navigationController?.navigationBar.customize(translucent: true)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupScrollView()
+    setupBindings()
+  }
+
+  private func setupScrollView() {
+    scrollViewController.scrollView.backgroundColor = .white
+
+    addChild(scrollViewController)
+    view.addSubview(scrollViewController.view)
+    scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
+    scrollViewController.didMove(toParent: self)
+    scrollViewController.contentView = screenView
+  }
+
+  private func setupBindings() {
+    viewModel.authPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] params in
+        completion(params.0, params.1, params.2)
+        navigationController?.popViewController(animated: true)
+      }.store(in: &cancellables)
+
+    screenView.hostField
+      .textPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in viewModel.didEnterHost($0) }
+      .store(in: &cancellables)
+
+    screenView.usernameField
+      .textPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in viewModel.didEnterUsername($0) }
+      .store(in: &cancellables)
+
+    screenView.passwordField
+      .textPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in viewModel.didEnterPassword($0) }
+      .store(in: &cancellables)
+
+    viewModel.statePublisher
+      .receive(on: DispatchQueue.main)
+      .map(\.isButtonEnabled)
+      .sink { [unowned self] in screenView.loginButton.isEnabled = $0 }
+      .store(in: &cancellables)
+
+    screenView.loginButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewModel.didTapLogin() }
+      .store(in: &cancellables)
+  }
+}
diff --git a/Sources/BackupFeature/Controllers/BackupSetupController.swift b/Sources/BackupFeature/Controllers/BackupSetupController.swift
index e22de517680d7ab5fc348b56cbb396ca80c4e091..7e697535ebfba84a6ef932e582882f89cc8718eb 100644
--- a/Sources/BackupFeature/Controllers/BackupSetupController.swift
+++ b/Sources/BackupFeature/Controllers/BackupSetupController.swift
@@ -1,10 +1,8 @@
 import UIKit
-import Models
 import Combine
-import DependencyInjection
 
 final class BackupSetupController: UIViewController {
-    lazy private var screenView = BackupSetupView()
+    private lazy var screenView = BackupSetupView()
 
     private let viewModel: BackupSetupViewModel
     private var cancellables = Set<AnyCancellable>()
diff --git a/Sources/BackupFeature/Coordinator/BackupCoordinator.swift b/Sources/BackupFeature/Coordinator/BackupCoordinator.swift
deleted file mode 100644
index f16acd561f4ca41c9f2abe0db1101a58df4cfcad..0000000000000000000000000000000000000000
--- a/Sources/BackupFeature/Coordinator/BackupCoordinator.swift
+++ /dev/null
@@ -1,69 +0,0 @@
-import UIKit
-import Shared
-import Presentation
-import ScrollViewController
-
-public protocol BackupCoordinating {
-    func toDrawer(
-        _: UIViewController,
-        from: UIViewController
-    )
-
-    func toPassphrase(
-        from: UIViewController,
-        cancelClosure: @escaping EmptyClosure,
-        passphraseClosure: @escaping StringClosure
-    )
-}
-
-public struct BackupCoordinator: BackupCoordinating {
-    var fullscreenPresenter: Presenting = FullscreenPresenter()
-
-    var passphraseFactory: (
-        @escaping EmptyClosure,
-        @escaping StringClosure
-    ) -> UIViewController
-
-    public init(
-        passphraseFactory: @escaping (
-            @escaping EmptyClosure,
-            @escaping StringClosure
-        ) -> UIViewController
-    ) {
-        self.passphraseFactory = passphraseFactory
-    }
-}
-
-public extension BackupCoordinator {
-    func toDrawer(
-        _ screen: UIViewController,
-        from parent: UIViewController
-    ) {
-        let target = ScrollViewController.embedding(screen)
-        fullscreenPresenter.present(target, from: parent)
-    }
-
-    func toPassphrase(
-        from parent: UIViewController,
-        cancelClosure: @escaping EmptyClosure,
-        passphraseClosure: @escaping StringClosure
-    ) {
-        let screen = passphraseFactory(cancelClosure, passphraseClosure)
-        let target = ScrollViewController.embedding(screen)
-        fullscreenPresenter.present(target, from: parent)
-    }
-}
-
-extension ScrollViewController {
-    static func embedding(_ viewController: UIViewController) -> ScrollViewController {
-        let scrollViewController = ScrollViewController()
-        scrollViewController.addChild(viewController)
-        scrollViewController.contentView = viewController.view
-        scrollViewController.wrapperView.handlesTouchesOutsideContent = false
-        scrollViewController.wrapperView.alignContentToBottom = true
-        scrollViewController.scrollView.bounces = false
-
-        viewController.didMove(toParent: scrollViewController)
-        return scrollViewController
-    }
-}
diff --git a/Sources/BackupFeature/Service/BackupService.swift b/Sources/BackupFeature/Service/BackupService.swift
index 626adba515d28c1a9969b067da742d07e94c8da5..c024227d5e77cc240f456052e6b4cc96beff4543 100644
--- a/Sources/BackupFeature/Service/BackupService.swift
+++ b/Sources/BackupFeature/Service/BackupService.swift
@@ -1,337 +1,258 @@
+import AppCore
 import UIKit
-import Models
 import Combine
+import XXClient
 import Defaults
-import Keychain
-import SFTPFeature
-import iCloudFeature
-import DropboxFeature
-import NetworkMonitor
-import GoogleDriveFeature
-import DependencyInjection
+import CloudFiles
+import CloudFilesSFTP
+import KeychainAccess
+import XXMessengerClient
+import ComposableArchitecture
 
 public final class BackupService {
-    @Dependency private var sftpService: SFTPService
-    @Dependency private var icloudService: iCloudInterface
-    @Dependency private var dropboxService: DropboxInterface
-    @Dependency private var networkManager: NetworkMonitoring
-    @Dependency private var keychainHandler: KeychainHandling
-    @Dependency private var driveService: GoogleDriveInterface
-
-    @KeyObject(.backupSettings, defaultValue: Data()) private var storedSettings: Data
-
-    public var passphrase: String?
-
-    public var settingsPublisher: AnyPublisher<BackupSettings, Never> {
-        settings.handleEvents(receiveSubscription: { [weak self] _ in
-            guard let self = self else { return }
-
-            let lastRefreshDate = self.settingsLastRefreshedDate ?? Date.distantPast
-
-            if Date().timeIntervalSince(lastRefreshDate) < 10 { return }
-
-            self.settingsLastRefreshedDate = Date()
-            self.refreshConnections()
-            self.refreshBackups()
-        }).eraseToAnyPublisher()
+  @Dependency(\.app.messenger) var messenger
+  @Dependency(\.app.networkMonitor) var networkMonitor
+
+  @KeyObject(.email, defaultValue: nil) var email: String?
+  @KeyObject(.phone, defaultValue: nil) var phone: String?
+  @KeyObject(.username, defaultValue: nil) var username: String?
+  @KeyObject(.backupSettings, defaultValue: nil) var storedSettings: Data?
+
+  public var backupsPublisher: AnyPublisher<[CloudService: Fetch.Metadata], Never> {
+    backupSubject.eraseToAnyPublisher()
+  }
+
+  public var connectedServicesPublisher: AnyPublisher<Set<CloudService>, Never> {
+    connectedServicesSubject.eraseToAnyPublisher()
+  }
+
+  public var settingsPublisher: AnyPublisher<CloudSettings, Never> {
+    settings.handleEvents(receiveSubscription: { [weak self] _ in
+      guard let self else { return }
+      self.connectedServicesSubject.send(CloudFilesManager.all.linkedServices())
+      self.fetchBackupOnAllProviders()
+    }).eraseToAnyPublisher()
+  }
+
+  private var cancellables = Set<AnyCancellable>()
+  private let connectedServicesSubject = CurrentValueSubject<Set<CloudService>, Never>([])
+  private let backupSubject = CurrentValueSubject<[CloudService: Fetch.Metadata], Never>([:])
+  private lazy var settings = CurrentValueSubject<CloudSettings, Never>(.init(fromData: storedSettings))
+
+  public init() {
+    settings
+      .dropFirst()
+      .removeDuplicates()
+      .sink { [unowned self] in
+        storedSettings = $0.toData()
+      }.store(in: &cancellables)
+  }
+
+  func didSetWiFiOnly(enabled: Bool) {
+    settings.value.wifiOnlyBackup = enabled
+  }
+
+  func didSetAutomaticBackup(enabled: Bool) {
+    settings.value.automaticBackups = enabled
+    shouldBackupIfSetAutomatic()
+  }
+
+  func toggle(service: CloudService, enabling: Bool) {
+    settings.value.enabledService = enabling ? service : nil
+  }
+
+  func didForceBackup() {
+    if let lastBackup = try? Data(contentsOf: getBackupURL()) {
+      performUpload(of: lastBackup)
     }
-
-    private var connType: ConnectionType = .wifi
-    private var settingsLastRefreshedDate: Date?
-    private var cancellables = Set<AnyCancellable>()
-    private lazy var settings = CurrentValueSubject<BackupSettings, Never>(.init(fromData: storedSettings))
-
-    public init() {
-        settings
-            .dropFirst()
-            .removeDuplicates()
-            .sink { [unowned self] in storedSettings = $0.toData() }
-            .store(in: &cancellables)
-
-        networkManager.connType
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in connType = $0 }
-            .store(in: &cancellables)
+  }
+
+  public func didUpdateFacts() {
+    storeFacts()
+  }
+
+  public func updateLocalBackup(_ data: Data) {
+    do {
+      try data.write(to: getBackupURL())
+      shouldBackupIfSetAutomatic()
+    } catch {
+      fatalError("Couldn't write backup to fileurl")
     }
-}
+  }
 
-extension BackupService {
-    public func performBackupIfAutomaticIsEnabled() {
-        guard settings.value.automaticBackups == true else { return }
-        performBackup()
+  private func shouldBackupIfSetAutomatic() {
+    guard let lastBackup = try? Data(contentsOf: getBackupURL()) else {
+      return // No stored backup so won't upload anything
     }
-
-    public func performBackup() {
-        guard let directoryUrl = try? FileManager.default.url(
-            for: .applicationSupportDirectory,
-            in: .userDomainMask,
-            appropriateFor: nil,
-            create: true
-        ) else { fatalError("Couldn't generate the URL to persist the backup") }
-
-        let fileUrl = directoryUrl
-            .appendingPathComponent("backup")
-            .appendingPathExtension("xxm")
-
-        guard let data = try? Data(contentsOf: fileUrl) else {
-            print(">>> Tried to backup arbitrarily but there was nothing to be backed up. Aborting...")
-            return
-        }
-
-        performBackup(data: data)
+    guard settings.value.automaticBackups else {
+      return // Backups are not set to automatic
     }
-
-    public func updateBackup(data: Data) {
-        guard let directoryUrl = try? FileManager.default.url(
-            for: .applicationSupportDirectory,
-            in: .userDomainMask,
-            appropriateFor: nil,
-            create: true
-        ) else { fatalError("Couldn't generate the URL to persist the backup") }
-
-        let fileUrl = directoryUrl
-            .appendingPathComponent("backup")
-            .appendingPathExtension("xxm")
-
-        do {
-            try data.write(to: fileUrl)
-        } catch {
-            fatalError("Couldn't write backup to fileurl")
-        }
-
-        let isWifiOnly = settings.value.wifiOnlyBackup
-        let isAutomaticEnabled = settings.value.automaticBackups
-        let hasEnabledService = settings.value.enabledService != nil
-
-        if isWifiOnly {
-            guard connType == .wifi else { return }
-        } else {
-            guard connType != .unknown else { return }
-        }
-
-        if isAutomaticEnabled && hasEnabledService {
-            performBackup()
-        }
+    guard settings.value.enabledService != nil else {
+      return // No service enabled to upload
     }
-
-    public func setBackupOnlyOnWifi(_ enabled: Bool) {
-        settings.value.wifiOnlyBackup = enabled
+    if settings.value.wifiOnlyBackup {
+      guard networkMonitor.connType() == .wifi else {
+        return // WiFi only backups, and connType != Wifi
+      }
+    } else {
+      guard networkMonitor.connType() != .unknown else {
+        return // Connectivity is unknown
+      }
     }
-
-    public func setBackupAutomatically(_ enabled: Bool) {
-        settings.value.automaticBackups = enabled
-
-        guard enabled else { return }
-        performBackup()
+    performUpload(of: lastBackup)
+  }
+
+  // MARK: - Messenger
+
+  func initializeBackup(passphrase: String) {
+    do {
+      try messenger.startBackup(
+        password: passphrase
+      )
+    } catch {
+      print(">>> Exception when calling `messenger.startBackup`: \(error.localizedDescription)")
     }
-
-    public func toggle(service: CloudService, enabling: Bool) {
-        settings.value.enabledService = enabling ? service : nil
+  }
+
+  func stopBackups() {
+    if messenger.isBackupRunning() == true {
+      do {
+        try messenger.stopBackup()
+      } catch {
+        print(">>> Exception when calling `messenger.stopBackup`: \(error.localizedDescription)")
+      }
     }
-
-    public func authorize(service: CloudService, presenting screen: UIViewController) {
-        switch service {
-        case .drive:
-            driveService.authorize(presenting: screen) { [weak self] _ in
-                guard let self = self else { return }
-                self.refreshConnections()
-                self.refreshBackups()
-            }
-        case .icloud:
-            if !icloudService.isAuthorized() {
-                icloudService.openSettings()
-            } else {
-                refreshConnections()
-                refreshBackups()
-            }
-        case .dropbox:
-            if !dropboxService.isAuthorized() {
-                dropboxService.authorize(presenting: screen)
-                    .sink { [weak self] _ in
-                        guard let self = self else { return }
-                        self.refreshConnections()
-                        self.refreshBackups()
-                    }.store(in: &cancellables)
-            }
-        case .sftp:
-            if !sftpService.isAuthorized() {
-                sftpService.authorizeFlow((screen, { [weak self] in
-                    guard let self = self else { return }
-                    screen.navigationController?.popViewController(animated: true)
-                    self.refreshConnections()
-                    self.refreshBackups()
-                }))
-            }
-        }
+  }
+
+  func storeFacts() {
+    var facts: [String: String] = [:]
+    facts["username"] = username!
+    facts["email"] = email
+    facts["phone"] = phone
+    facts["timestamp"] = "\(Date.asTimestamp)"
+    guard let backupManager = messenger.backup.get() else {
+      print(">>> Tried to store facts in JSON but there's no backup manager instance")
+      return
     }
-}
-
-extension BackupService {
-    private func refreshConnections() {
-        if icloudService.isAuthorized() && !settings.value.connectedServices.contains(.icloud) {
-            settings.value.connectedServices.insert(.icloud)
-        } else if !icloudService.isAuthorized() && settings.value.connectedServices.contains(.icloud) {
-            settings.value.connectedServices.remove(.icloud)
-        }
-
-        if dropboxService.isAuthorized() && !settings.value.connectedServices.contains(.dropbox) {
-            settings.value.connectedServices.insert(.dropbox)
-        } else if !dropboxService.isAuthorized() && settings.value.connectedServices.contains(.dropbox) {
-            settings.value.connectedServices.remove(.dropbox)
-        }
-
-        if sftpService.isAuthorized() && !settings.value.connectedServices.contains(.sftp) {
-            settings.value.connectedServices.insert(.sftp)
-        } else if !sftpService.isAuthorized() && settings.value.connectedServices.contains(.sftp) {
-            settings.value.connectedServices.remove(.sftp)
-        }
-
-        driveService.isAuthorized { [weak settings] isAuthorized in
-            guard let settings = settings else { return }
-
-            if isAuthorized && !settings.value.connectedServices.contains(.drive) {
-                settings.value.connectedServices.insert(.drive)
-            } else if !isAuthorized && settings.value.connectedServices.contains(.drive) {
-                settings.value.connectedServices.remove(.drive)
-            }
-        }
+    guard let data = try? JSONSerialization.data(withJSONObject: facts) else {
+      print(">>> Tried to generate data with json dictionary but failed")
+      return
     }
-
-    private func refreshBackups() {
-        if icloudService.isAuthorized() {
-            icloudService.downloadMetadata { [weak settings] in
-                guard let settings = settings else { return }
-
-                guard let metadata = try? $0.get() else {
-                    settings.value.backups[.icloud] = nil
-                    return
-                }
-
-                settings.value.backups[.icloud] = Backup(
-                    id: metadata.path,
-                    date: metadata.modifiedDate,
-                    size: metadata.size
-                )
-            }
-        }
-
-        if sftpService.isAuthorized() {
-            sftpService.fetchMetadata { [weak settings] in
-                guard let settings = settings else { return }
-
-                guard let metadata = try? $0.get()?.backup else {
-                    settings.value.backups[.sftp] = nil
-                    return
-                }
-
-                settings.value.backups[.sftp] = Backup(
-                    id: metadata.id,
-                    date: metadata.date,
-                    size: metadata.size
-                )
-            }
-        }
-
-        if dropboxService.isAuthorized() {
-            dropboxService.downloadMetadata { [weak settings] in
-                guard let settings = settings else { return }
-
-                guard let metadata = try? $0.get() else {
-                    settings.value.backups[.dropbox] = nil
-                    return
-                }
-
-                settings.value.backups[.dropbox] = Backup(
-                    id: metadata.path,
-                    date: metadata.modifiedDate,
-                    size: metadata.size
-                )
-            }
-        }
-
-        driveService.isAuthorized { [weak settings] isAuthorized  in
-            guard let settings = settings else { return }
-
-            if isAuthorized {
-                self.driveService.downloadMetadata {
-                    guard let metadata = try? $0.get() else { return }
-
-                    settings.value.backups[.drive] = Backup(
-                        id: metadata.identifier,
-                        date: metadata.modifiedDate,
-                        size: metadata.size
-                    )
-                }
-            } else {
-                settings.value.backups[.drive] = nil
-            }
+    guard let string = String(data: data, encoding: .utf8) else {
+      print(">>> Tried to extract string from json dict object but failed")
+      return
+    }
+    backupManager.addJSON(string)
+  }
+
+  func setupSFTP(host: String, username: String, password: String) {
+    let sftpManager = CloudFilesManager.sftp(
+      host: host,
+      username: username,
+      password: password,
+      fileName: "backup.xxm"
+    )
+
+    CloudFilesManager.all[.sftp] = sftpManager
+
+    do {
+      try sftpManager.fetch { [weak self] in
+        guard let self else { return }
+        switch $0 {
+        case .success(let metadata):
+          self.backupSubject.value[.sftp] = metadata
+        case .failure(let error):
+          print(">>> Error fetching sftp: \(error.localizedDescription)")
         }
+      }
+    } catch {
+      print(">>> Exception fetching sftp: \(error.localizedDescription)")
     }
-
-    private func performBackup(data: Data) {
-        guard let enabledService = settings.value.enabledService else {
-            fatalError("Trying to backup but nothing is enabled")
+  }
+
+  func authorize(
+    service: CloudService,
+    presenting screen: UIViewController
+  ) {
+    guard let manager = CloudFilesManager.all[service] else {
+      print(">>> Tried to link/auth but the enabled service is not set")
+      return
+    }
+    do {
+      try manager.link(screen) { [weak self] in
+        guard let self else { return }
+        switch $0 {
+        case .success:
+          self.connectedServicesSubject.value.insert(service)
+          self.fetchBackupOnAllProviders()
+        case .failure(let error):
+          self.connectedServicesSubject.value.remove(service)
+          print(">>> Failed to link/auth \(service): \(error.localizedDescription)")
         }
+      }
+    } catch {
+      print(">>> Exception trying to link/auth \(service): \(error.localizedDescription)")
+    }
+  }
 
-        let url = URL(fileURLWithPath: NSTemporaryDirectory())
-            .appendingPathComponent(UUID().uuidString)
-
-        do {
-            try data.write(to: url, options: .atomic)
-        } catch {
-            print("Couldn't write to temp: \(error.localizedDescription)")
-            return
-        }
+  func fetchBackupOnAllProviders() {
+    CloudFilesManager.all.lastBackups { [weak self] in
+      guard let self else { return }
+      self.backupSubject.send($0)
+    }
+  }
 
-        switch enabledService {
-        case .drive:
-            driveService.uploadBackup(url) {
-                switch $0 {
-                case .success(let metadata):
-                    self.settings.value.backups[.drive] = .init(
-                        id: metadata.identifier,
-                        date: metadata.modifiedDate,
-                        size: metadata.size
-                    )
-                case .failure(let error):
-                    print(error.localizedDescription)
-                }
-            }
-        case .icloud:
-            icloudService.uploadBackup(url) {
-                switch $0 {
-                case .success(let metadata):
-                    self.settings.value.backups[.icloud] = .init(
-                        id: metadata.path,
-                        date: metadata.modifiedDate,
-                        size: metadata.size
-                    )
-                case .failure(let error):
-                    print(error.localizedDescription)
-                }
-            }
-        case .dropbox:
-            dropboxService.uploadBackup(url) {
-                switch $0 {
-                case .success(let metadata):
-                    self.settings.value.backups[.dropbox] = .init(
-                        id: metadata.path,
-                        date: metadata.modifiedDate,
-                        size: metadata.size
-                    )
-                case .failure(let error):
-                    print(error.localizedDescription)
-                }
-            }
-        case .sftp:
-            sftpService.uploadBackup(url: url) {
-                switch $0 {
-                case .success(let backup):
-                    self.settings.value.backups[.sftp] = backup
-                case .failure(let error):
-                    print(error.localizedDescription)
-                }
-            }
+  func performUpload(of data: Data) {
+    guard let enabledService = settings.value.enabledService else {
+      fatalError(">>> Trying to backup but nothing is enabled")
+    }
+    if enabledService == .sftp {
+      let keychain = Keychain(service: "SFTP-XXM")
+      guard let host = try? keychain.get("host"),
+            let password = try? keychain.get("pwd"),
+            let username = try? keychain.get("username") else {
+        fatalError(">>> Tried to perform an sftp backup but its not configured")
+      }
+      CloudFilesManager.all[.sftp] = .sftp(
+        host: host,
+        username: username,
+        password: password,
+        fileName: "backup.xxm"
+      )
+    }
+    guard let manager = CloudFilesManager.all[enabledService] else {
+      return // Tried to upload but the enabled service is not set
+    }
+    do {
+      try manager.upload(data) { [weak self] in
+        guard let self else { return }
+
+        switch $0 {
+        case .success(let metadata):
+          self.backupSubject.value[enabledService] = .init(
+            size: metadata.size,
+            lastModified: metadata.lastModified
+          )
+        case .failure(let error):
+          print(">>> Failed to perform a backup upload: \(error.localizedDescription)")
         }
+      }
+    } catch {
+      print(">>> Exception performing a backup upload: \(error.localizedDescription)")
     }
+  }
+
+  private func getBackupURL() -> URL {
+    guard let folderURL = try? FileManager.default.url(
+      for: .applicationSupportDirectory,
+      in: .userDomainMask,
+      appropriateFor: nil,
+      create: true
+    ) else { fatalError(">>> Couldn't generate the URL for backup") }
+
+    return folderURL
+      .appendingPathComponent("backup")
+      .appendingPathExtension("xxm")
+  }
 }
+
diff --git a/Sources/BackupFeature/Service/Dependency.swift b/Sources/BackupFeature/Service/Dependency.swift
new file mode 100644
index 0000000000000000000000000000000000000000..9d2b0df290ff5e9e90937473f0e6e4ccd0d26955
--- /dev/null
+++ b/Sources/BackupFeature/Service/Dependency.swift
@@ -0,0 +1,13 @@
+import Dependencies
+
+private enum BackupServiceDependencyKey: DependencyKey {
+  static let liveValue: BackupService = .init()
+  static let testValue: BackupService = .init()
+}
+
+extension DependencyValues {
+  public var backupService: BackupService {
+    get { self[BackupServiceDependencyKey.self] }
+    set { self[BackupServiceDependencyKey.self] = newValue }
+  }
+}
diff --git a/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift b/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift
index 0731bc4a94f5be17d6e224d5f67573ea68ea608f..410535e63a8f348ef7882aa9dfad4bd90674ae4a 100644
--- a/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift
+++ b/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift
@@ -1,107 +1,115 @@
 import UIKit
-import Models
 import Shared
+import AppCore
 import Combine
-import Foundation
-import DependencyInjection
-import HUD
+import XXClient
+import Defaults
+import CloudFiles
+import AppNavigation
+import ComposableArchitecture
 
 enum BackupActionState {
-    case backupFinished
-    case backupAllowed(Bool)
-    case backupInProgress(Float, Float)
+  case backupFinished
+  case backupAllowed(Bool)
+  case backupInProgress(Float, Float)
 }
 
 struct BackupConfigViewModel {
-    var didTapBackupNow: () -> Void
-    var didChooseWifiOnly: (Bool) -> Void
-    var didChooseAutomatic: (Bool) -> Void
-    var didToggleService: (UIViewController, CloudService, Bool) -> Void
-    var didTapService: (CloudService, UIViewController) -> Void
+  var didTapBackupNow: () -> Void
+  var didChooseWifiOnly: (Bool) -> Void
+  var didChooseAutomatic: (Bool) -> Void
+  var didToggleService: (UIViewController, CloudService, Bool) -> Void
+  var didTapService: (CloudService, UIViewController) -> Void
 
-    var wifiOnly: () -> AnyPublisher<Bool, Never>
-    var automatic: () -> AnyPublisher<Bool, Never>
-    var lastBackup: () -> AnyPublisher<Backup?, Never>
-    var actionState: () -> AnyPublisher<BackupActionState, Never>
-    var enabledService: () -> AnyPublisher<CloudService?, Never>
-    var connectedServices: () -> AnyPublisher<Set<CloudService>, Never>
+  var wifiOnly: () -> AnyPublisher<Bool, Never>
+  var automatic: () -> AnyPublisher<Bool, Never>
+  var lastBackup: () -> AnyPublisher<Fetch.Metadata?, Never>
+  var actionState: () -> AnyPublisher<BackupActionState, Never>
+  var enabledService: () -> AnyPublisher<CloudService?, Never>
+  var connectedServices: () -> AnyPublisher<Set<CloudService>, Never>
 }
 
 extension BackupConfigViewModel {
-    static func live() -> Self {
-        class Context {
-            @Dependency var hud: HUD
-            @Dependency var service: BackupService
-            @Dependency var coordinator: BackupCoordinating
-        }
-
-        let context = Context()
+  static func live() -> Self {
+    class Context {
+      @Dependency(\.navigator) var navigator: Navigator
+      @Dependency(\.backupService) var service: BackupService
+      @Dependency(\.app.hudManager) var hudManager: HUDManager
+    }
 
-        return .init(
-            didTapBackupNow: {
-                context.service.performBackup()
-                context.hud.update(with: .on)
-                DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
-                    context.hud.update(with: .none)
-                }
-            },
-            didChooseWifiOnly: context.service.setBackupOnlyOnWifi(_:),
-            didChooseAutomatic: context.service.setBackupAutomatically(_:),
-            didToggleService: { controller, service, enabling in
-                guard enabling == true else {
-                    context.service.toggle(service: service, enabling: enabling)
-                    return
-                }
+    let context = Context()
 
-                context.coordinator.toPassphrase(from: controller, cancelClosure: {
-                    context.service.toggle(service: service, enabling: false)
-                }, passphraseClosure: { passphrase in
-                    context.service.passphrase = passphrase
-                    context.hud.update(with: .onTitle("Initializing and securing your backup file will take few seconds, please keep the app open."))
-                    DispatchQueue.global().async {
-                        context.service.toggle(service: service, enabling: enabling)
+    return .init(
+      didTapBackupNow: {
+        context.service.didForceBackup()
+        context.hudManager.show()
+        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
+          context.hudManager.hide()
+        }
+      },
+      didChooseWifiOnly: context.service.didSetWiFiOnly(enabled:),
+      didChooseAutomatic: context.service.didSetAutomaticBackup(enabled:),
+      didToggleService: { controller, service, enabling in
+        guard enabling == true else {
+          context.service.toggle(service: service, enabling: false)
+          context.service.stopBackups()
+          return
+        }
+        context.navigator.perform(PresentPassphrase(onCancel: {
+          context.service.toggle(service: service, enabling: false)
+        }, onPassphrase: { passphrase in
+          context.hudManager.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.hudManager.hide()
+        }))
+      },
+      didTapService: { service, controller in
+        if service == .sftp {
+          context.navigator.perform(PresentSFTP(completion: { host, username, password in
+            context.service.setupSFTP(host: host, username: username, password: password)
+          }, on: controller.navigationController!))
+          return
+        }
 
-                        DispatchQueue.main.async {
-                            context.hud.update(with: .none)
-                        }
-                    }
-                })
-            },
-            didTapService: context.service.authorize,
-            wifiOnly: {
-                context.service.settingsPublisher
-                    .map(\.wifiOnlyBackup)
-                    .eraseToAnyPublisher()
-            },
-            automatic: {
-                context.service.settingsPublisher
-                    .map(\.automaticBackups)
-                    .eraseToAnyPublisher()
-            },
-            lastBackup: {
-                context.service.settingsPublisher
-                    .map {
-                        guard let enabledService = $0.enabledService else { return nil }
-                        return $0.backups[enabledService]
-                    }.eraseToAnyPublisher()
-            },
-            actionState: {
-                context.service.settingsPublisher
-                    .map(\.enabledService)
-                    .map { BackupActionState.backupAllowed($0 != nil) }
-                    .eraseToAnyPublisher()
-            },
-            enabledService: {
-                context.service.settingsPublisher
-                    .map(\.enabledService)
-                    .eraseToAnyPublisher()
-            },
-            connectedServices: {
-                context.service.settingsPublisher
-                    .map(\.connectedServices)
-                    .removeDuplicates()
-                    .eraseToAnyPublisher()
-            }
-        )
-    }
+        context.service.authorize(service: service, presenting: controller)
+      },
+      wifiOnly: {
+        context.service.settingsPublisher
+          .map(\.wifiOnlyBackup)
+          .eraseToAnyPublisher()
+      },
+      automatic: {
+        context.service.settingsPublisher
+          .map(\.automaticBackups)
+          .eraseToAnyPublisher()
+      },
+      lastBackup: {
+        context.service.settingsPublisher
+          .combineLatest(context.service.backupsPublisher)
+          .map { settings, backups in
+            guard let enabled = settings.enabledService else { return nil }
+            return backups[enabled]
+          }.eraseToAnyPublisher()
+      },
+      actionState: {
+        context.service.settingsPublisher
+          .map(\.enabledService)
+          .map { BackupActionState.backupAllowed($0 != nil) }
+          .eraseToAnyPublisher()
+      },
+      enabledService: {
+        context.service.settingsPublisher
+          .map(\.enabledService)
+          .eraseToAnyPublisher()
+      },
+      connectedServices: {
+        context.service.connectedServicesPublisher
+          .removeDuplicates()
+          .eraseToAnyPublisher()
+      }
+    )
+  }
 }
diff --git a/Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift b/Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..bd18ee5f294fafcdf2ad8d28eb8465b36429f916
--- /dev/null
+++ b/Sources/BackupFeature/ViewModels/BackupSFTPViewModel.swift
@@ -0,0 +1,101 @@
+import UIKit
+import Shout
+import Socket
+import Shared
+import Combine
+import Foundation
+import CloudFiles
+import CloudFilesSFTP
+
+import AppCore
+import ComposableArchitecture
+
+struct SFTPViewState {
+  var host: String = ""
+  var username: String = ""
+  var password: String = ""
+  var isButtonEnabled: Bool = false
+}
+
+final class BackupSFTPViewModel {
+  @Dependency(\.app.hudManager) var hudManager: HUDManager
+
+  var statePublisher: AnyPublisher<SFTPViewState, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
+
+  var authPublisher: AnyPublisher<(String, String, String), Never> {
+    authSubject.eraseToAnyPublisher()
+  }
+
+  private let stateSubject = CurrentValueSubject<SFTPViewState, Never>(.init())
+  private let authSubject = PassthroughSubject<(String, String, String), Never>()
+
+  func didEnterHost(_ string: String) {
+    stateSubject.value.host = string
+    validate()
+  }
+
+  func didEnterUsername(_ string: String) {
+    stateSubject.value.username = string
+    validate()
+  }
+
+  func didEnterPassword(_ string: String) {
+    stateSubject.value.password = string
+    validate()
+  }
+
+  func didTapLogin() {
+    hudManager.show()
+
+    let host = stateSubject.value.host
+    let username = stateSubject.value.username
+    let password = stateSubject.value.password
+
+    let anyController = UIViewController()
+
+    DispatchQueue.global().async { [weak self] in
+      guard let self else { return }
+      do {
+        try CloudFilesManager.sftp(
+          host: host,
+          username: username,
+          password: password,
+          fileName: "backup.xxm"
+        ).link(anyController) {
+          switch $0 {
+          case .success:
+            self.hudManager.hide()
+            self.authSubject.send((host, username, password))
+          case .failure(let error):
+            var message = "An error occurred while trying to link SFTP: "
+
+            if case let CloudFilesSFTP.SFTP.SFTPError.link(linkError) = error {
+              if let sshError = linkError as? SSHError {
+                message.append(sshError.message)
+              } else if let socketError = linkError as? Socket.Error, let reason = socketError.errorReason {
+                message.append(reason)
+              } else {
+                message.append(error.localizedDescription)
+              }
+            } else {
+              message.append(error.localizedDescription)
+            }
+
+            self.hudManager.show(.init(content: message))
+          }
+        }
+      } catch {
+        self.hudManager.show(.init(error: error))
+      }
+    }
+  }
+
+  private func validate() {
+    stateSubject.value.isButtonEnabled =
+    !stateSubject.value.host.isEmpty &&
+    !stateSubject.value.username.isEmpty &&
+    !stateSubject.value.password.isEmpty
+  }
+}
diff --git a/Sources/BackupFeature/ViewModels/BackupSetupViewModel.swift b/Sources/BackupFeature/ViewModels/BackupSetupViewModel.swift
index cc647d9aaecc7507eed9d517909dbf3229b6901f..a470e6c647c987e1af9668fe3a57807075377980 100644
--- a/Sources/BackupFeature/ViewModels/BackupSetupViewModel.swift
+++ b/Sources/BackupFeature/ViewModels/BackupSetupViewModel.swift
@@ -1,21 +1,20 @@
 import UIKit
-import Models
 import Shared
 import Combine
-import GoogleDriveFeature
-import DependencyInjection
+import CloudFiles
+import ComposableArchitecture
 
 struct BackupSetupViewModel {
-    var didTapService: (CloudService, UIViewController) -> Void
+  var didTapService: (CloudService, UIViewController) -> Void
 }
 
 extension BackupSetupViewModel {
-    static func live() -> Self {
-        class Context {
-            @Dependency var service: BackupService
-        }
-
-        let context = Context()
-        return .init(didTapService: context.service.authorize)
+  static func live() -> Self {
+    class Context {
+      @Dependency(\.backupService) var service: BackupService
     }
+
+    let context = Context()
+    return .init(didTapService: context.service.authorize)
+  }
 }
diff --git a/Sources/BackupFeature/ViewModels/BackupViewModel.swift b/Sources/BackupFeature/ViewModels/BackupViewModel.swift
index 6522ee1215a4ee84000c85600389bf53d52aa9bc..378590c3edadce9b7036e2f262fcedc6f36c9626 100644
--- a/Sources/BackupFeature/ViewModels/BackupViewModel.swift
+++ b/Sources/BackupFeature/ViewModels/BackupViewModel.swift
@@ -1,35 +1,34 @@
 import Combine
-import DependencyInjection
+import ComposableArchitecture
 
 enum BackupViewState: Equatable {
-    case setup
-    case config
+  case setup
+  case config
 }
 
 struct BackupViewModel {
-    var setupViewModel: () -> BackupSetupViewModel
-    var configViewModel: () -> BackupConfigViewModel
+  var setupViewModel: () -> BackupSetupViewModel
+  var configViewModel: () -> BackupConfigViewModel
 
-    var state: () -> AnyPublisher<BackupViewState, Never>
+  var state: () -> AnyPublisher<BackupViewState, Never>
 }
 
 extension BackupViewModel {
-    static func live() -> Self {
-        class Context {
-            @Dependency var service: BackupService
-        }
+  static func live() -> Self {
+    class Context {
+      @Dependency(\.backupService) var service: BackupService
+    }
 
-        let context = Context()
+    let context = Context()
 
-        return .init(
-            setupViewModel: { BackupSetupViewModel.live() },
-            configViewModel: { BackupConfigViewModel.live() },
-            state: {
-                context.service.settingsPublisher
-                    .map(\.connectedServices)
-                    .map { $0.isEmpty ? BackupViewState.setup : .config }
-                    .eraseToAnyPublisher()
-            }
-        )
-    }
+    return .init(
+      setupViewModel: { BackupSetupViewModel.live() },
+      configViewModel: { BackupConfigViewModel.live() },
+      state: {
+        context.service.connectedServicesPublisher
+          .map { $0.isEmpty ? BackupViewState.setup : .config }
+          .eraseToAnyPublisher()
+      }
+    )
+  }
 }
diff --git a/Sources/BackupFeature/Views/BackupActionView.swift b/Sources/BackupFeature/Views/BackupActionView.swift
index 705263e0ef815ae9fab0ff5657cc536f02c98101..ae30fe5575fef2d271afb848c3720af29cd6b75b 100644
--- a/Sources/BackupFeature/Views/BackupActionView.swift
+++ b/Sources/BackupFeature/Views/BackupActionView.swift
@@ -1,125 +1,126 @@
 import UIKit
 import Shared
+import AppResources
 
 final class BackupActionView: UIView {
-    let stackView = UIStackView()
-    let backupNowButton = CapsuleButton()
+  let stackView = UIStackView()
+  let backupNowButton = CapsuleButton()
 
-    let progressView = UIView()
-    let progressLabel = UILabel()
-    let progressBarPartial = UIView()
-    let progressBarFull = UIView()
+  let progressView = UIView()
+  let progressLabel = UILabel()
+  let progressBarPartial = UIView()
+  let progressBarFull = UIView()
 
-    let finishedView = UIView()
-    let finishedLabel = UILabel()
-    let finishedImage = UIImageView()
+  let finishedView = UIView()
+  let finishedLabel = UILabel()
+  let finishedImage = UIImageView()
 
-    init() {
-        super.init(frame: .zero)
+  init() {
+    super.init(frame: .zero)
 
-        setupProgressView()
-        setupFinishedView()
+    setupProgressView()
+    setupFinishedView()
 
-        backupNowButton.set(style: .brandColored, title: Localized.Backup.Config.backupNow)
+    backupNowButton.set(style: .brandColored, title: Localized.Backup.Config.backupNow)
 
-        stackView.spacing = 15
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(backupNowButton)
-        stackView.addArrangedSubview(progressView)
-        stackView.addArrangedSubview(finishedView)
+    stackView.spacing = 15
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(backupNowButton)
+    stackView.addArrangedSubview(progressView)
+    stackView.addArrangedSubview(finishedView)
 
-        addSubview(stackView)
+    addSubview(stackView)
 
-        stackView.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.equalToSuperview()
-        }
+    stackView.snp.makeConstraints { make in
+      make.top.equalToSuperview()
+      make.left.equalToSuperview()
+      make.right.equalToSuperview()
+      make.bottom.equalToSuperview()
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 
-    private func setupFinishedView() {
-        finishedImage.contentMode = .center
-        finishedImage.image = Asset.restoreSuccess.image
+  private func setupFinishedView() {
+    finishedImage.contentMode = .center
+    finishedImage.image = Asset.restoreSuccess.image
 
-        finishedLabel.text = "Backup completed!"
-        finishedLabel.textColor = Asset.neutralBody.color
-        finishedLabel.font = Fonts.Mulish.regular.font(size: 16.0)
+    finishedLabel.text = "Backup completed!"
+    finishedLabel.textColor = Asset.neutralBody.color
+    finishedLabel.font = Fonts.Mulish.regular.font(size: 16.0)
 
-        finishedView.addSubview(finishedImage)
-        finishedView.addSubview(finishedLabel)
+    finishedView.addSubview(finishedImage)
+    finishedView.addSubview(finishedLabel)
 
-        finishedImage.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview()
-            make.bottom.equalToSuperview()
-        }
+    finishedImage.snp.makeConstraints { make in
+      make.top.equalToSuperview()
+      make.left.equalToSuperview()
+      make.bottom.equalToSuperview()
+    }
 
-        finishedLabel.snp.makeConstraints { make in
-            make.left.equalTo(finishedImage.snp.right).offset(10)
-            make.centerY.equalTo(finishedImage)
-            make.right.lessThanOrEqualToSuperview()
-        }
+    finishedLabel.snp.makeConstraints { make in
+      make.left.equalTo(finishedImage.snp.right).offset(10)
+      make.centerY.equalTo(finishedImage)
+      make.right.lessThanOrEqualToSuperview()
+    }
+  }
+
+  private func setupProgressView() {
+    progressLabel.textColor = Asset.neutralDisabled.color
+    progressLabel.font = Fonts.Mulish.regular.font(size: 14.0)
+
+    progressBarFull.backgroundColor = Asset.neutralLine.color
+    progressBarPartial.backgroundColor = Asset.brandPrimary.color
+    progressBarFull.layer.masksToBounds = true
+    progressBarFull.layer.cornerRadius = 4
+
+    progressBarFull.addSubview(progressBarPartial)
+    progressView.addSubview(progressLabel)
+    progressView.addSubview(progressBarFull)
+
+    progressBarFull.snp.makeConstraints { make in
+      make.top.equalToSuperview()
+      make.left.equalToSuperview()
+      make.right.equalToSuperview()
+      make.height.equalTo(8)
     }
 
-    private func setupProgressView() {
-        progressLabel.textColor = Asset.neutralDisabled.color
-        progressLabel.font = Fonts.Mulish.regular.font(size: 14.0)
-
-        progressBarFull.backgroundColor = Asset.neutralLine.color
-        progressBarPartial.backgroundColor = Asset.brandPrimary.color
-        progressBarFull.layer.masksToBounds = true
-        progressBarFull.layer.cornerRadius = 4
-
-        progressBarFull.addSubview(progressBarPartial)
-        progressView.addSubview(progressLabel)
-        progressView.addSubview(progressBarFull)
-
-        progressBarFull.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.height.equalTo(8)
-        }
-
-        progressLabel.snp.makeConstraints { make in
-            make.top.equalTo(progressBarFull.snp.bottom).offset(10)
-            make.left.equalToSuperview()
-            make.bottom.equalToSuperview()
-        }
-
-        progressBarPartial.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview()
-            make.width.equalToSuperview().multipliedBy(0.5)
-            make.bottom.equalToSuperview()
-        }
+    progressLabel.snp.makeConstraints { make in
+      make.top.equalTo(progressBarFull.snp.bottom).offset(10)
+      make.left.equalToSuperview()
+      make.bottom.equalToSuperview()
     }
 
-    func setState(_ state: BackupActionState) {
-        switch state {
-        case .backupFinished:
-            backupNowButton.isHidden = true
-            progressView.isHidden = true
-            finishedView.isHidden = false
-
-        case .backupAllowed(let bool):
-            backupNowButton.isHidden = false
-            progressView.isHidden = true
-            finishedView.isHidden = true
-            backupNowButton.isEnabled = bool
-
-        case .backupInProgress(let uploaded, let total):
-            backupNowButton.isHidden = true
-            progressView.isHidden = false
-            finishedView.isHidden = true
-
-            let uploadedKb = String(format: "%.1f kb", uploaded/1000)
-            let totalkb = String(format: "%.1f kb", total/1000)
-
-            progressLabel.text = "Uploaded \(uploadedKb) of \(totalkb) (\(total/uploaded)%)"
-        }
+    progressBarPartial.snp.makeConstraints { make in
+      make.top.equalToSuperview()
+      make.left.equalToSuperview()
+      make.width.equalToSuperview().multipliedBy(0.5)
+      make.bottom.equalToSuperview()
+    }
+  }
+
+  func setState(_ state: BackupActionState) {
+    switch state {
+    case .backupFinished:
+      backupNowButton.isHidden = true
+      progressView.isHidden = true
+      finishedView.isHidden = false
+
+    case .backupAllowed(let bool):
+      backupNowButton.isHidden = false
+      progressView.isHidden = true
+      finishedView.isHidden = true
+      backupNowButton.isEnabled = bool
+
+    case .backupInProgress(let uploaded, let total):
+      backupNowButton.isHidden = true
+      progressView.isHidden = false
+      finishedView.isHidden = true
+
+      let uploadedKb = String(format: "%.1f kb", uploaded/1000)
+      let totalkb = String(format: "%.1f kb", total/1000)
+
+      progressLabel.text = "Uploaded \(uploadedKb) of \(totalkb) (\(total/uploaded)%)"
     }
+  }
 }
diff --git a/Sources/BackupFeature/Views/BackupConfigView.swift b/Sources/BackupFeature/Views/BackupConfigView.swift
index 1c65f58f7222f1c069284c717518904dc2e596ca..60555ce672c01384fd2037af58a3803b465560ef 100644
--- a/Sources/BackupFeature/Views/BackupConfigView.swift
+++ b/Sources/BackupFeature/Views/BackupConfigView.swift
@@ -1,117 +1,118 @@
 import UIKit
 import Shared
+import AppResources
 
 final class BackupConfigView: UIView {
-    let titleLabel = UILabel()
-    let subtitleLabel = UILabel()
-    let actionView = BackupActionView()
-
-    let stackView = UIStackView()
-    let sftpButton = BackupSwitcherButton()
-    let iCloudButton = BackupSwitcherButton()
-    let dropboxButton = BackupSwitcherButton()
-    let googleDriveButton = BackupSwitcherButton()
-
-    let enabledSubtitleView = UIView()
-    let enabledSubtitleLabel = UILabel()
-    let frequencyDetailView = BackupDetailView()
-    let latestBackupDetailView = BackupDetailView()
-    let infrastructureDetailView = BackupDetailView()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        titleLabel.textColor = Asset.neutralDark.color
-        titleLabel.text = Localized.Backup.Config.title
-        titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
-
-        enabledSubtitleLabel.numberOfLines = 0
-        enabledSubtitleLabel.textColor = Asset.neutralWeak.color
-        enabledSubtitleLabel.font = Fonts.Mulish.regular.font(size: 14.0)
-
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.15
-
-        let attString = NSAttributedString(
-            string: Localized.Backup.subtitle,
-            attributes: [
-                .foregroundColor: Asset.neutralBody.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
-                .paragraphStyle: paragraph
-            ])
-
-        subtitleLabel.numberOfLines = 0
-        subtitleLabel.attributedText = attString
-
-        sftpButton.titleLabel.text = Localized.Backup.sftp
-        sftpButton.logoImageView.image = Asset.restoreSFTP.image
-
-        iCloudButton.titleLabel.text = Localized.Backup.iCloud
-        iCloudButton.logoImageView.image = Asset.restoreIcloud.image
-
-        dropboxButton.titleLabel.text = Localized.Backup.dropbox
-        dropboxButton.logoImageView.image = Asset.restoreDropbox.image
-
-        googleDriveButton.titleLabel.text = Localized.Backup.googleDrive
-        googleDriveButton.logoImageView.image = Asset.restoreDrive.image
-
-        latestBackupDetailView.titleLabel.text = Localized.Backup.Config.latestBackup
-        frequencyDetailView.accessoryImageView.image = Asset.settingsDisclosure.image
-
-        infrastructureDetailView.titleLabel.text = Localized.Backup.Config.infrastructure.uppercased()
-        infrastructureDetailView.accessoryImageView.image = Asset.settingsDisclosure.image
-
-        enabledSubtitleView.addSubview(enabledSubtitleLabel)
-
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(googleDriveButton)
-        stackView.addArrangedSubview(iCloudButton)
-        stackView.addArrangedSubview(dropboxButton)
-        stackView.addArrangedSubview(sftpButton)
-        stackView.addArrangedSubview(enabledSubtitleView)
-        stackView.addArrangedSubview(latestBackupDetailView)
-        stackView.addArrangedSubview(frequencyDetailView)
-        stackView.addArrangedSubview(infrastructureDetailView)
-
-        addSubview(titleLabel)
-        addSubview(subtitleLabel)
-        addSubview(actionView)
-        addSubview(stackView)
-
-        titleLabel.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(15)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-41)
-        }
-
-        enabledSubtitleLabel.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(-10)
-            $0.left.equalToSuperview().offset(92)
-            $0.right.equalToSuperview().offset(-48)
-            $0.bottom.equalToSuperview()
-        }
-
-        subtitleLabel.snp.makeConstraints {
-            $0.top.equalTo(titleLabel.snp.bottom).offset(8)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-41)
-        }
-
-        actionView.snp.makeConstraints {
-            $0.top.equalTo(subtitleLabel.snp.bottom).offset(15)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-38)
-        }
-
-        stackView.snp.makeConstraints {
-            $0.top.equalTo(actionView.snp.bottom).offset(28)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.lessThanOrEqualToSuperview()
-        }
+  let titleLabel = UILabel()
+  let subtitleLabel = UILabel()
+  let actionView = BackupActionView()
+
+  let stackView = UIStackView()
+  let sftpButton = BackupSwitcherButton()
+  let iCloudButton = BackupSwitcherButton()
+  let dropboxButton = BackupSwitcherButton()
+  let googleDriveButton = BackupSwitcherButton()
+
+  let enabledSubtitleView = UIView()
+  let enabledSubtitleLabel = UILabel()
+  let frequencyDetailView = BackupDetailView()
+  let latestBackupDetailView = BackupDetailView()
+  let infrastructureDetailView = BackupDetailView()
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    titleLabel.textColor = Asset.neutralDark.color
+    titleLabel.text = Localized.Backup.Config.title
+    titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
+
+    enabledSubtitleLabel.numberOfLines = 0
+    enabledSubtitleLabel.textColor = Asset.neutralWeak.color
+    enabledSubtitleLabel.font = Fonts.Mulish.regular.font(size: 14.0)
+
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.15
+
+    let attString = NSAttributedString(
+      string: Localized.Backup.subtitle,
+      attributes: [
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+        .paragraphStyle: paragraph
+      ])
+
+    subtitleLabel.numberOfLines = 0
+    subtitleLabel.attributedText = attString
+
+    sftpButton.titleLabel.text = Localized.Backup.sftp
+    sftpButton.logoImageView.image = Asset.restoreSFTP.image
+
+    iCloudButton.titleLabel.text = Localized.Backup.iCloud
+    iCloudButton.logoImageView.image = Asset.restoreIcloud.image
+
+    dropboxButton.titleLabel.text = Localized.Backup.dropbox
+    dropboxButton.logoImageView.image = Asset.restoreDropbox.image
+
+    googleDriveButton.titleLabel.text = Localized.Backup.googleDrive
+    googleDriveButton.logoImageView.image = Asset.restoreDrive.image
+
+    latestBackupDetailView.titleLabel.text = Localized.Backup.Config.latestBackup
+    frequencyDetailView.accessoryImageView.image = Asset.settingsDisclosure.image
+
+    infrastructureDetailView.titleLabel.text = Localized.Backup.Config.infrastructure.uppercased()
+    infrastructureDetailView.accessoryImageView.image = Asset.settingsDisclosure.image
+
+    enabledSubtitleView.addSubview(enabledSubtitleLabel)
+
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(googleDriveButton)
+    stackView.addArrangedSubview(iCloudButton)
+    stackView.addArrangedSubview(dropboxButton)
+    stackView.addArrangedSubview(sftpButton)
+    stackView.addArrangedSubview(enabledSubtitleView)
+    stackView.addArrangedSubview(latestBackupDetailView)
+    stackView.addArrangedSubview(frequencyDetailView)
+    stackView.addArrangedSubview(infrastructureDetailView)
+
+    addSubview(titleLabel)
+    addSubview(subtitleLabel)
+    addSubview(actionView)
+    addSubview(stackView)
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(15)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
     }
 
-    required init?(coder: NSCoder) { nil }
+    enabledSubtitleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(-10)
+      $0.left.equalToSuperview().offset(92)
+      $0.right.equalToSuperview().offset(-48)
+      $0.bottom.equalToSuperview()
+    }
+
+    subtitleLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
+    }
+
+    actionView.snp.makeConstraints {
+      $0.top.equalTo(subtitleLabel.snp.bottom).offset(15)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-38)
+    }
+
+    stackView.snp.makeConstraints {
+      $0.top.equalTo(actionView.snp.bottom).offset(28)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.lessThanOrEqualToSuperview()
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/BackupFeature/Views/BackupDetailView.swift b/Sources/BackupFeature/Views/BackupDetailView.swift
index d28647330d7cd7b81d516ad7d4b506f712d94d0c..28b75bb1418c0a2e7ae4d9f16920cdea019db646 100644
--- a/Sources/BackupFeature/Views/BackupDetailView.swift
+++ b/Sources/BackupFeature/Views/BackupDetailView.swift
@@ -1,40 +1,41 @@
 import UIKit
 import Shared
+import AppResources
 
 final class BackupDetailView: UIControl {
-    let titleLabel = UILabel()
-    let subtitleLabel = UILabel()
-    let accessoryImageView = UIImageView()
-
-    init() {
-        super.init(frame: .zero)
-
-        titleLabel.font = Fonts.Mulish.bold.font(size: 12.0)
-        subtitleLabel.font = Fonts.Mulish.regular.font(size: 16.0)
-
-        titleLabel.textColor = Asset.neutralWeak.color
-        subtitleLabel.textColor = Asset.neutralActive.color
-
-        addSubview(titleLabel)
-        addSubview(subtitleLabel)
-        addSubview(accessoryImageView)
-
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(20)
-            make.left.equalToSuperview().offset(92)
-        }
-
-        subtitleLabel.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(4)
-            make.left.equalTo(titleLabel)
-            make.bottom.equalToSuperview().offset(-2)
-        }
-
-        accessoryImageView.snp.makeConstraints { make in
-            make.right.equalToSuperview().offset(-48)
-            make.centerY.equalTo(titleLabel.snp.bottom)
-        }
+  let titleLabel = UILabel()
+  let subtitleLabel = UILabel()
+  let accessoryImageView = UIImageView()
+
+  init() {
+    super.init(frame: .zero)
+
+    titleLabel.font = Fonts.Mulish.bold.font(size: 12.0)
+    subtitleLabel.font = Fonts.Mulish.regular.font(size: 16.0)
+
+    titleLabel.textColor = Asset.neutralWeak.color
+    subtitleLabel.textColor = Asset.neutralActive.color
+
+    addSubview(titleLabel)
+    addSubview(subtitleLabel)
+    addSubview(accessoryImageView)
+
+    titleLabel.snp.makeConstraints { make in
+      make.top.equalToSuperview().offset(20)
+      make.left.equalToSuperview().offset(92)
+    }
+
+    subtitleLabel.snp.makeConstraints { make in
+      make.top.equalTo(titleLabel.snp.bottom).offset(4)
+      make.left.equalTo(titleLabel)
+      make.bottom.equalToSuperview().offset(-2)
+    }
+
+    accessoryImageView.snp.makeConstraints { make in
+      make.right.equalToSuperview().offset(-48)
+      make.centerY.equalTo(titleLabel.snp.bottom)
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/BackupFeature/Views/BackupPassphraseView.swift b/Sources/BackupFeature/Views/BackupPassphraseView.swift
index f2ff27b4d01652f72abab2482a70db9f031d5e55..ea0630520b803a717c5b8d8ed949867b74a52279 100644
--- a/Sources/BackupFeature/Views/BackupPassphraseView.swift
+++ b/Sources/BackupFeature/Views/BackupPassphraseView.swift
@@ -1,80 +1,81 @@
 import UIKit
 import Shared
 import InputField
+import AppResources
 
 final class BackupPassphraseView: UIView {
-    let titleLabel = UILabel()
-    let stackView = UIStackView()
-    let inputField = InputField()
-    let subtitleLabel = UILabel()
-    let cancelButton = CapsuleButton()
-    let continueButton = CapsuleButton()
+  let titleLabel = UILabel()
+  let stackView = UIStackView()
+  let inputField = InputField()
+  let subtitleLabel = UILabel()
+  let cancelButton = CapsuleButton()
+  let continueButton = CapsuleButton()
 
-    init() {
-        super.init(frame: .zero)
-        layer.cornerRadius = 40
-        backgroundColor = Asset.neutralWhite.color
-        layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
+  init() {
+    super.init(frame: .zero)
+    layer.cornerRadius = 40
+    backgroundColor = Asset.neutralWhite.color
+    layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
 
-        setupInput()
-        setupLabels()
-        setupButtons()
-        setupStackView()
-    }
+    setupInput()
+    setupLabels()
+    setupButtons()
+    setupStackView()
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 
-    private func setupInput() {
-        inputField.setup(
-            style: .regular,
-            title: Localized.Backup.Passphrase.Input.title,
-            placeholder: Localized.Backup.Passphrase.Input.placeholder,
-            rightView: .toggleSecureEntry,
-            subtitleColor: Asset.neutralDisabled.color,
-            allowsEmptySpace: false,
-            autocapitalization: .none,
-            contentType: .newPassword
-        )
-    }
+  private func setupInput() {
+    inputField.setup(
+      style: .regular,
+      title: Localized.Backup.Passphrase.Input.title,
+      placeholder: Localized.Backup.Passphrase.Input.placeholder,
+      rightView: .toggleSecureEntry,
+      subtitleColor: Asset.neutralDisabled.color,
+      allowsEmptySpace: false,
+      autocapitalization: .none,
+      contentType: .newPassword
+    )
+  }
 
-    private func setupLabels() {
-        titleLabel.textAlignment = .left
-        titleLabel.text = Localized.Backup.Passphrase.title
-        titleLabel.textColor = Asset.neutralActive.color
-        titleLabel.font = Fonts.Mulish.bold.font(size: 26.0)
+  private func setupLabels() {
+    titleLabel.textAlignment = .left
+    titleLabel.text = Localized.Backup.Passphrase.title
+    titleLabel.textColor = Asset.neutralActive.color
+    titleLabel.font = Fonts.Mulish.bold.font(size: 26.0)
 
-        subtitleLabel.numberOfLines = 0
-        subtitleLabel.textAlignment = .left
-        subtitleLabel.textColor = Asset.neutralActive.color
-        subtitleLabel.text = Localized.Backup.Passphrase.subtitle
-        subtitleLabel.font = Fonts.Mulish.regular.font(size: 16.0)
-    }
+    subtitleLabel.numberOfLines = 0
+    subtitleLabel.textAlignment = .left
+    subtitleLabel.textColor = Asset.neutralActive.color
+    subtitleLabel.text = Localized.Backup.Passphrase.subtitle
+    subtitleLabel.font = Fonts.Mulish.regular.font(size: 16.0)
+  }
 
-    private func setupButtons() {
-        cancelButton.setStyle(.seeThrough)
-        cancelButton.setTitle(Localized.Backup.Passphrase.cancel, for: .normal)
+  private func setupButtons() {
+    cancelButton.setStyle(.seeThrough)
+    cancelButton.setTitle(Localized.Backup.Passphrase.cancel, for: .normal)
 
-        continueButton.isEnabled = false
-        continueButton.setStyle(.brandColored)
-        continueButton.setTitle(Localized.Backup.Passphrase.continue, for: .normal)
-    }
+    continueButton.isEnabled = false
+    continueButton.setStyle(.brandColored)
+    continueButton.setTitle(Localized.Backup.Passphrase.continue, for: .normal)
+  }
 
-    private func setupStackView() {
-        stackView.spacing = 20
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(titleLabel)
-        stackView.addArrangedSubview(subtitleLabel)
-        stackView.addArrangedSubview(inputField)
-        stackView.addArrangedSubview(continueButton)
-        stackView.addArrangedSubview(cancelButton)
+  private func setupStackView() {
+    stackView.spacing = 20
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(titleLabel)
+    stackView.addArrangedSubview(subtitleLabel)
+    stackView.addArrangedSubview(inputField)
+    stackView.addArrangedSubview(continueButton)
+    stackView.addArrangedSubview(cancelButton)
 
-        addSubview(stackView)
+    addSubview(stackView)
 
-        stackView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(60)
-            $0.left.equalToSuperview().offset(50)
-            $0.right.equalToSuperview().offset(-50)
-            $0.bottom.equalToSuperview().offset(-70)
-        }
+    stackView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(60)
+      $0.left.equalToSuperview().offset(50)
+      $0.right.equalToSuperview().offset(-50)
+      $0.bottom.equalToSuperview().offset(-70)
     }
+  }
 }
diff --git a/Sources/BackupFeature/Views/BackupSetupView.swift b/Sources/BackupFeature/Views/BackupSetupView.swift
index 3e19d50034f4d03ba62d3e4d038d82d24196af89..30ae722dd7b2c630a861cc50dd9f808311446f43 100644
--- a/Sources/BackupFeature/Views/BackupSetupView.swift
+++ b/Sources/BackupFeature/Views/BackupSetupView.swift
@@ -1,99 +1,100 @@
 import UIKit
 import Shared
+import AppResources
 
 final class BackupSetupView: UIView {
-    let titleLabel = UILabel()
-    let subtitleLabel = UILabel()
-
-    let stackView = UIStackView()
-    let sftpButton = BackupSwitcherButton()
-    let iCloudButton = BackupSwitcherButton()
-    let dropboxButton = BackupSwitcherButton()
-    let googleDriveButton = BackupSwitcherButton()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        let title = Localized.Backup.Setup.title
-
-        let attString = NSMutableAttributedString(string: title)
-        let firstParagraph = NSMutableParagraphStyle()
-        firstParagraph.alignment = .left
-        firstParagraph.lineHeightMultiple = 1
-
-        attString.addAttribute(.paragraphStyle, value: firstParagraph)
-        attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
-        attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
-
-        attString.addAttributes(attributes: [
-            .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
-            .foregroundColor: Asset.brandPrimary.color
-        ], betweenCharacters: "#")
-
-        titleLabel.numberOfLines = 0
-        titleLabel.attributedText = attString
-
-        let secondParagraph = NSMutableParagraphStyle()
-        secondParagraph.alignment = .left
-        secondParagraph.lineHeightMultiple = 1.15
-
-        let secondAttString = NSAttributedString(
-            string: Localized.Backup.subtitle,
-            attributes: [
-                .foregroundColor: Asset.neutralBody.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
-                .paragraphStyle: secondParagraph
-            ])
-
-        subtitleLabel.numberOfLines = 0
-        subtitleLabel.attributedText = secondAttString
-
-        iCloudButton.titleLabel.text = Localized.Backup.iCloud
-        iCloudButton.logoImageView.image = Asset.restoreIcloud.image
-        iCloudButton.showChevron()
-
-        dropboxButton.titleLabel.text = Localized.Backup.dropbox
-        dropboxButton.logoImageView.image = Asset.restoreDropbox.image
-        dropboxButton.showChevron()
-
-        googleDriveButton.titleLabel.text = Localized.Backup.googleDrive
-        googleDriveButton.logoImageView.image = Asset.restoreDrive.image
-        googleDriveButton.showChevron()
-
-        sftpButton.titleLabel.text = Localized.Backup.sftp
-        sftpButton.logoImageView.image = Asset.restoreSFTP.image
-        sftpButton.showChevron()
-
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(googleDriveButton)
-        stackView.addArrangedSubview(iCloudButton)
-        stackView.addArrangedSubview(dropboxButton)
-        stackView.addArrangedSubview(sftpButton)
-
-        addSubview(titleLabel)
-        addSubview(subtitleLabel)
-        addSubview(stackView)
-
-        titleLabel.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(15)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-41)
-        }
-
-        subtitleLabel.snp.makeConstraints {
-            $0.top.equalTo(titleLabel.snp.bottom).offset(8)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-41)
-        }
-
-        stackView.snp.makeConstraints {
-            $0.top.equalTo(subtitleLabel.snp.bottom).offset(28)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.lessThanOrEqualToSuperview()
-        }
+  let titleLabel = UILabel()
+  let subtitleLabel = UILabel()
+
+  let stackView = UIStackView()
+  let sftpButton = BackupSwitcherButton()
+  let iCloudButton = BackupSwitcherButton()
+  let dropboxButton = BackupSwitcherButton()
+  let googleDriveButton = BackupSwitcherButton()
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    let title = Localized.Backup.Setup.title
+
+    let attString = NSMutableAttributedString(string: title)
+    let firstParagraph = NSMutableParagraphStyle()
+    firstParagraph.alignment = .left
+    firstParagraph.lineHeightMultiple = 1
+
+    attString.addAttribute(.paragraphStyle, value: firstParagraph)
+    attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
+    attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
+
+    attString.addAttributes(attributes: [
+      .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
+      .foregroundColor: Asset.brandPrimary.color
+    ], betweenCharacters: "#")
+
+    titleLabel.numberOfLines = 0
+    titleLabel.attributedText = attString
+
+    let secondParagraph = NSMutableParagraphStyle()
+    secondParagraph.alignment = .left
+    secondParagraph.lineHeightMultiple = 1.15
+
+    let secondAttString = NSAttributedString(
+      string: Localized.Backup.subtitle,
+      attributes: [
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+        .paragraphStyle: secondParagraph
+      ])
+
+    subtitleLabel.numberOfLines = 0
+    subtitleLabel.attributedText = secondAttString
+
+    iCloudButton.titleLabel.text = Localized.Backup.iCloud
+    iCloudButton.logoImageView.image = Asset.restoreIcloud.image
+    iCloudButton.showChevron()
+
+    dropboxButton.titleLabel.text = Localized.Backup.dropbox
+    dropboxButton.logoImageView.image = Asset.restoreDropbox.image
+    dropboxButton.showChevron()
+
+    googleDriveButton.titleLabel.text = Localized.Backup.googleDrive
+    googleDriveButton.logoImageView.image = Asset.restoreDrive.image
+    googleDriveButton.showChevron()
+
+    sftpButton.titleLabel.text = Localized.Backup.sftp
+    sftpButton.logoImageView.image = Asset.restoreSFTP.image
+    sftpButton.showChevron()
+
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(googleDriveButton)
+    stackView.addArrangedSubview(iCloudButton)
+    stackView.addArrangedSubview(dropboxButton)
+    stackView.addArrangedSubview(sftpButton)
+
+    addSubview(titleLabel)
+    addSubview(subtitleLabel)
+    addSubview(stackView)
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(15)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
     }
 
-    required init?(coder: NSCoder) { nil }
+    subtitleLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
+    }
+
+    stackView.snp.makeConstraints {
+      $0.top.equalTo(subtitleLabel.snp.bottom).offset(28)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.lessThanOrEqualToSuperview()
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/BackupFeature/Views/BackupSwitcherButton.swift b/Sources/BackupFeature/Views/BackupSwitcherButton.swift
index 2c115cdc9b723cc7c2ece188e17ef5e3a7ea7533..dfd3a32ea1b66967f9df5da2ae4b62553beab12b 100644
--- a/Sources/BackupFeature/Views/BackupSwitcherButton.swift
+++ b/Sources/BackupFeature/Views/BackupSwitcherButton.swift
@@ -1,69 +1,70 @@
 import UIKit
 import Shared
+import AppResources
 
 final class BackupSwitcherButton: UIControl {
-    let titleLabel = UILabel()
-    let separatorView = UIView()
-    let switcherView = UISwitch()
-    let logoImageView = UIImageView()
-    let chevronImageView = UIImageView()
+  let titleLabel = UILabel()
+  let separatorView = UIView()
+  let switcherView = UISwitch()
+  let logoImageView = UIImageView()
+  let chevronImageView = UIImageView()
 
-    init() {
-        super.init(frame: .zero)
+  init() {
+    super.init(frame: .zero)
 
-        titleLabel.textColor = Asset.neutralActive.color
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 16.0)
+    titleLabel.textColor = Asset.neutralActive.color
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 16.0)
 
-        switcherView.onTintColor = Asset.brandLight.color
-        chevronImageView.image = Asset.settingsDisclosure.image
-        separatorView.backgroundColor = Asset.neutralLine.color
+    switcherView.onTintColor = Asset.brandLight.color
+    chevronImageView.image = Asset.settingsDisclosure.image
+    separatorView.backgroundColor = Asset.neutralLine.color
 
-        addSubview(separatorView)
-        addSubview(logoImageView)
-        addSubview(titleLabel)
-        addSubview(switcherView)
-        addSubview(chevronImageView)
+    addSubview(separatorView)
+    addSubview(logoImageView)
+    addSubview(titleLabel)
+    addSubview(switcherView)
+    addSubview(chevronImageView)
 
-        logoImageView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(20)
-            make.left.equalToSuperview().offset(36)
-            make.bottom.equalToSuperview().offset(-20)
-        }
+    logoImageView.snp.makeConstraints { make in
+      make.top.equalToSuperview().offset(20)
+      make.left.equalToSuperview().offset(36)
+      make.bottom.equalToSuperview().offset(-20)
+    }
 
-        titleLabel.snp.makeConstraints { make in
-            make.left.equalTo(logoImageView.snp.right).offset(15)
-            make.centerY.equalTo(logoImageView)
-        }
+    titleLabel.snp.makeConstraints { make in
+      make.left.equalTo(logoImageView.snp.right).offset(15)
+      make.centerY.equalTo(logoImageView)
+    }
 
-        chevronImageView.snp.makeConstraints { make in
-            make.centerY.equalTo(logoImageView)
-            make.right.equalToSuperview().offset(-48)
-        }
+    chevronImageView.snp.makeConstraints { make in
+      make.centerY.equalTo(logoImageView)
+      make.right.equalToSuperview().offset(-48)
+    }
 
-        switcherView.snp.makeConstraints { make in
-            make.right.equalToSuperview().offset(-25)
-            make.centerY.equalTo(logoImageView)
-        }
+    switcherView.snp.makeConstraints { make in
+      make.right.equalToSuperview().offset(-25)
+      make.centerY.equalTo(logoImageView)
+    }
 
-        separatorView.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.height.equalTo(1)
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-        }
+    separatorView.snp.makeConstraints { make in
+      make.top.equalToSuperview()
+      make.height.equalTo(1)
+      make.left.equalToSuperview().offset(24)
+      make.right.equalToSuperview().offset(-24)
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 
-    func showSwitcher(enabled: Bool) {
-        switcherView.isOn = enabled
-        switcherView.isHidden = false
-        chevronImageView.isHidden = true
-    }
+  func showSwitcher(enabled: Bool) {
+    switcherView.isOn = enabled
+    switcherView.isHidden = false
+    chevronImageView.isHidden = true
+  }
 
-    func showChevron() {
-        switcherView.isOn = false
-        switcherView.isHidden = true
-        chevronImageView.isHidden = false
-    }
+  func showChevron() {
+    switcherView.isOn = false
+    switcherView.isHidden = true
+    chevronImageView.isHidden = false
+  }
 }
diff --git a/Sources/BackupFeature/Views/RestoreSFTPView.swift b/Sources/BackupFeature/Views/RestoreSFTPView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..3d12ccbcf180961a0bdc989404b2571aa944b841
--- /dev/null
+++ b/Sources/BackupFeature/Views/RestoreSFTPView.swift
@@ -0,0 +1,84 @@
+import UIKit
+import Shared
+import InputField
+import AppResources
+
+final class BackupSFTPView: UIView {
+  let titleLabel = UILabel()
+  let subtitleLabel = UILabel()
+  let hostField = OutlinedInputField()
+  let usernameField = OutlinedInputField()
+  let passwordField = OutlinedInputField()
+  let loginButton = CapsuleButton()
+  let stackView = UIStackView()
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    titleLabel.textColor = Asset.neutralDark.color
+    titleLabel.text = Localized.AccountRestore.Sftp.title
+    titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
+
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.15
+
+    let attString = NSMutableAttributedString(
+      string: Localized.AccountRestore.Sftp.subtitle,
+      attributes: [
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+        .paragraphStyle: paragraph
+      ])
+
+    attString.setAttributes(
+      attributes: [
+        .foregroundColor: Asset.neutralDark.color,
+        .font: Fonts.Mulish.bold.font(size: 12.0) as Any,
+        .paragraphStyle: paragraph
+      ], betweenCharacters: "*")
+
+    subtitleLabel.numberOfLines = 0
+    subtitleLabel.attributedText = attString
+
+    hostField.setup(title: Localized.AccountRestore.Sftp.host)
+    usernameField.setup(title: Localized.AccountRestore.Sftp.username)
+    passwordField.setup(title: Localized.AccountRestore.Sftp.password, sensitive: true)
+
+    loginButton.set(style: .brandColored, title: Localized.AccountRestore.Sftp.login)
+
+    stackView.spacing = 30
+    stackView.axis = .vertical
+    stackView.distribution = .fillEqually
+    stackView.addArrangedSubview(hostField)
+    stackView.addArrangedSubview(usernameField)
+    stackView.addArrangedSubview(passwordField)
+    stackView.addArrangedSubview(loginButton)
+
+    addSubview(titleLabel)
+    addSubview(subtitleLabel)
+    addSubview(stackView)
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(safeAreaLayoutGuide).offset(15)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
+    }
+
+    subtitleLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
+    }
+
+    stackView.snp.makeConstraints {
+      $0.top.equalTo(subtitleLabel.snp.bottom).offset(28)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-38)
+      $0.bottom.lessThanOrEqualToSuperview()
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+}
diff --git a/Sources/ChatFeature/CellFactory.swift b/Sources/ChatFeature/CellFactory.swift
new file mode 100644
index 0000000000000000000000000000000000000000..2f7767ebe4902933f0615d000ca48e6ebf9ed8ee
--- /dev/null
+++ b/Sources/ChatFeature/CellFactory.swift
@@ -0,0 +1,76 @@
+//import UIKit
+//import XCTestDynamicOverlay
+//
+//public struct CellFactory<Model> {
+//  public struct Registrar {
+//    public init(register: @escaping (UICollectionView) -> Void) {
+//      self.register = register
+//    }
+//
+//    public var register: (UICollectionView) -> Void
+//
+//    public func callAsFunction(in view: UICollectionView) {
+//      register(view)
+//    }
+//  }
+//
+//  public struct Builder {
+//    public init(build: @escaping (Model, UICollectionView, IndexPath) -> UICollectionViewCell?) {
+//      self.build = build
+//    }
+//
+//    public var build: (Model, UICollectionView, IndexPath) -> UICollectionViewCell?
+//
+//    public func callAsFunction(
+//      for model: Model,
+//      in view: UICollectionView,
+//      at indexPath: IndexPath
+//    ) -> UICollectionViewCell? {
+//      build(model, view, indexPath)
+//    }
+//  }
+//
+//  public init(
+//    register: Registrar,
+//    build: Builder
+//  ) {
+//    self.register = register
+//    self.build = build
+//  }
+//
+//  public var register: Registrar
+//  public var build: Builder
+//}
+//
+//extension CellFactory {
+//  public static func combined(_ factories: CellFactory...) -> CellFactory {
+//    combined(factories)
+//  }
+//
+//  public static func combined(_ factories: [CellFactory]) -> CellFactory {
+//    CellFactory(
+//      register: .init { collectionView in
+//        factories.forEach { $0.register(in: collectionView) }
+//      },
+//      build: .init { model, collectionView, indexPath in
+//        for factory in factories {
+//          if let cell = factory.build(for: model, in: collectionView, at: indexPath) {
+//            return cell
+//          }
+//        }
+//        return nil
+//      }
+//    )
+//  }
+//}
+//
+//#if DEBUG
+//extension CellFactory {
+//  public static func unimplemented() -> CellFactory {
+//    CellFactory(
+//      register: .init(register: XCTUnimplemented("\(Self.self).Registrar")),
+//      build: .init(build: XCTUnimplemented("\(Self.self).Builder"))
+//    )
+//  }
+//}
+//#endif
diff --git a/Sources/ChatFeature/Controllers/GroupChatController.swift b/Sources/ChatFeature/Controllers/GroupChatController.swift
index 5cae4a063a3969e806e1b7c9089d888d345357f3..43ed50008506da291cc8d9233bd468423e4ad8b3 100644
--- a/Sources/ChatFeature/Controllers/GroupChatController.swift
+++ b/Sources/ChatFeature/Controllers/GroupChatController.swift
@@ -1,18 +1,17 @@
-import HUD
 import UIKit
-import Theme
-import Models
 import Shared
 import Combine
+import AppCore
 import XXModels
 import Voxophone
 import ChatLayout
-import Integration
+import Dependencies
+import AppResources
+import AppNavigation
 import DrawerFeature
 import DifferenceKit
 import ReportingFeature
 import ChatInputFeature
-import DependencyInjection
 
 typealias OutgoingGroupTextCell = CollectionCell<FlexibleSpace, StackMessageView>
 typealias IncomingGroupTextCell = CollectionCell<StackMessageView, FlexibleSpace>
@@ -22,602 +21,670 @@ typealias OutgoingFailedGroupTextCell = CollectionCell<FlexibleSpace, StackMessa
 typealias OutgoingFailedGroupReplyCell = CollectionCell<FlexibleSpace, ReplyStackMessageView>
 
 public final class GroupChatController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var session: SessionType
-    @Dependency private var coordinator: ChatCoordinating
-    @Dependency private var reportingStatus: ReportingStatus
-    @Dependency private var makeReportDrawer: MakeReportDrawer
-    @Dependency private var makeAppScreenshot: MakeAppScreenshot
-    @Dependency private var statusBarController: StatusBarStyleControlling
-
-    private let members: MembersController
-    private var collectionView: UICollectionView!
-    lazy private var header = GroupHeaderView()
-    private let inputComponent: ChatInputView
-
-    private let chatLayout = ChatLayout()
-    private var animator: ManualAnimator?
-    private let viewModel: GroupChatViewModel
-    private let layoutDelegate = LayoutDelegate()
-    private var cancellables = Set<AnyCancellable>()
-    private var sections = [ArraySection<ChatSection, Message>]()
-    private var currentInterfaceActions = SetActor<Set<InterfaceActions>, ReactionTypes>()
-
-    public override var canBecomeFirstResponder: Bool { true }
-    public override var inputAccessoryView: UIView? { inputComponent }
-
-    public init(_ info: GroupInfo) {
-        let viewModel = GroupChatViewModel(info)
-        self.viewModel = viewModel
-        self.members = .init(with: info.members)
-
-        self.inputComponent = ChatInputView(store: .init(
-            initialState: .init(canAddAttachments: false),
-            reducer: chatInputReducer,
-            environment: .init(
-                voxophone: try! DependencyInjection.Container.shared.resolve() as Voxophone,
-                sendAudio: { _ in },
-                didTapCamera: {},
-                didTapLibrary: {},
-                sendText: { viewModel.send($0) },
-                didTapAbortReply: { viewModel.abortReply() },
-                didTapMicrophone: { false }
-            )
-        ))
-
-        super.init(nibName: nil, bundle: nil)
-
-        let memberList = info.members.map {
-            Member(
-                title: ($0.nickname ?? $0.username) ?? "Fetching username...",
-                photo: $0.photo
-            )
-        }
-
-        header.setup(title: info.group.name, memberList: memberList)
-    }
-
-    public required init?(coder: NSCoder) { nil }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        statusBarController.style.send(.darkContent)
-        navigationController?.navigationBar.customize(
-            backgroundColor: Asset.neutralWhite.color,
-            shadowColor: Asset.neutralDisabled.color
-        )
-    }
-
-    public override func viewDidAppear(_ animated: Bool) {
-        super.viewDidAppear(animated)
-        collectionView.collectionViewLayout.invalidateLayout()
-        becomeFirstResponder()
+  @Dependency(\.navigator) var navigator
+  @Dependency(\.app.dbManager) var dbManager
+  @Dependency(\.app.statusBar) var statusBar
+  @Dependency(\.reportingStatus) var reportingStatus
+
+//  @Dependency var makeReportDrawer: MakeReportDrawer
+//  @Dependency var makeAppScreenshot: MakeAppScreenshot
+
+  private var collectionView: UICollectionView!
+  private lazy var header = GroupHeaderView()
+  private let inputComponent: ChatInputView
+
+  private var animator: ManualAnimator?
+  private let viewModel: GroupChatViewModel
+  private let layoutDelegate = LayoutDelegate()
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
+  private let chatLayout = CollectionViewChatLayout()
+  private var sections = [ArraySection<ChatSection, Message>]()
+  private var currentInterfaceActions = SetActor<Set<InterfaceActions>, ReactionTypes>()
+
+  public override var canBecomeFirstResponder: Bool { true }
+  public override var inputAccessoryView: UIView? { inputComponent }
+
+  public init(_ info: GroupInfo) {
+    let viewModel = GroupChatViewModel(info)
+    self.viewModel = viewModel
+
+    self.inputComponent = ChatInputView(store: .init(
+      initialState: .init(canAddAttachments: false),
+      reducer: chatInputReducer,
+      environment: .init(
+        voxophone: Voxophone(), //try! DI.Container.shared.resolve() as Voxophone,
+        sendAudio: { _ in },
+        didTapCamera: {},
+        didTapLibrary: {},
+        sendText: { viewModel.send($0) },
+        didTapAbortReply: { viewModel.abortReply() },
+        didTapMicrophone: { false }
+      )
+    ))
+
+    super.init(nibName: nil, bundle: nil)
+
+    let memberList = info.members.map {
+      Member(
+        title: ($0.nickname ?? $0.username) ?? "Fetching username...",
+        photo: $0.photo
+      )
     }
 
-    private var isFirstAppearance = true
-
-    public override func viewDidLayoutSubviews() {
-        super.viewDidLayoutSubviews()
-
-        if isFirstAppearance {
-            isFirstAppearance = false
-            let insets = UIEdgeInsets(
-                top: 0,
-                left: 0,
-                bottom: inputComponent.bounds.height - view.safeAreaInsets.bottom,
-                right: 0
-            )
-            collectionView.contentInset = insets
-            collectionView.scrollIndicatorInsets = insets
-        }
+    header.setup(title: info.group.name, memberList: memberList)
+  }
+
+  public required init?(coder: NSCoder) { nil }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    statusBar.set(.darkContent)
+    navigationController?.navigationBar.customize(
+      backgroundColor: Asset.neutralWhite.color,
+      shadowColor: Asset.neutralDisabled.color
+    )
+  }
+
+  public override func viewDidAppear(_ animated: Bool) {
+    super.viewDidAppear(animated)
+    collectionView.collectionViewLayout.invalidateLayout()
+    becomeFirstResponder()
+  }
+
+  private var isFirstAppearance = true
+
+  public override func viewDidLayoutSubviews() {
+    super.viewDidLayoutSubviews()
+
+    if isFirstAppearance {
+      isFirstAppearance = false
+      let insets = UIEdgeInsets(
+        top: 0,
+        left: 0,
+        bottom: inputComponent.bounds.height - view.safeAreaInsets.bottom,
+        right: 0
+      )
+      collectionView.contentInset = insets
+      collectionView.scrollIndicatorInsets = insets
     }
-
-    public override func viewWillDisappear(_ animated: Bool) {
-        super.viewWillDisappear(animated)
-        viewModel.readAll()
+  }
+
+  public override func viewWillDisappear(_ animated: Bool) {
+    super.viewWillDisappear(animated)
+    viewModel.readAll()
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+
+    setupNavigationBar()
+    setupCollectionView()
+    setupInputController()
+    setupBindings()
+
+    KeyboardListener.shared.add(delegate: self)
+  }
+
+  private func setupNavigationBar() {
+    let more = UIButton()
+    more.setImage(Asset.chatMore.image, for: .normal)
+    more.addTarget(self, action: #selector(didTapDots), for: .touchUpInside)
+
+    navigationItem.titleView = header
+    navigationItem.rightBarButtonItem = UIBarButtonItem(customView: more)
+  }
+
+  private func setupCollectionView() {
+    chatLayout.configure(layoutDelegate)
+    collectionView = .init(on: view, with: chatLayout)
+    collectionView.register(IncomingGroupTextCell.self)
+    collectionView.register(OutgoingGroupTextCell.self)
+    collectionView.register(IncomingGroupReplyCell.self)
+    collectionView.register(OutgoingGroupReplyCell.self)
+    collectionView.register(OutgoingFailedGroupTextCell.self)
+    collectionView.register(OutgoingFailedGroupReplyCell.self)
+    collectionView.registerSectionHeader(SectionHeaderView.self)
+    collectionView.dataSource = self
+    collectionView.delegate = self
+  }
+
+  private func setupInputController() {
+    inputComponent.setMaxHeight { [weak self] in
+      guard let self else { return 150 }
+
+      let maxHeight = self.collectionView.frame.height
+      - self.collectionView.adjustedContentInset.top
+      - self.collectionView.adjustedContentInset.bottom
+      + self.inputComponent.bounds.height
+
+      return maxHeight * 0.9
     }
 
-    public override func viewDidLoad() {
-        super.viewDidLoad()
+    viewModel.replyPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] senderTitle, messageText in
+        inputComponent.setupReply(message: messageText, sender: senderTitle)
+      }
+      .store(in: &cancellables)
+  }
+
+  private func setupBindings() {
+    viewModel
+      .routesPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        switch $0 {
+        case .waitingRound:
+          let button = DrawerCapsuleButton(model: .init(
+            title: Localized.Chat.RoundDrawer.action,
+            style: .brandColored
+          ))
 
-        setupNavigationBar()
-        setupCollectionView()
-        setupInputController()
-        setupBindings()
+          button
+            .action
+            .receive(on: DispatchQueue.main)
+            .sink { [unowned self] in
+              navigator.perform(DismissModal(from: self)) { [weak self] in
+                guard let self else { return }
+                self.drawerCancellables.removeAll()
+              }
+            }.store(in: &drawerCancellables)
+
+          navigator.perform(PresentDrawer(items: [
+            DrawerText(
+              font: Fonts.Mulish.semiBold.font(size: 14.0),
+              text: Localized.Chat.RoundDrawer.title,
+              color: Asset.neutralWeak.color,
+              lineHeightMultiple: 1.35,
+              spacingAfter: 25
+            ), button
+          ], isDismissable: false, from: self))
+
+        case .webview(let urlString):
+          navigator.perform(PresentWebsite(urlString: urlString, from: self))
+        }
+      }.store(in: &cancellables)
+
+    viewModel
+      .reportPopupPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] contact in
+        let cancelButton = CapsuleButton()
+        cancelButton.setStyle(.seeThrough)
+        cancelButton.setTitle(Localized.Chat.Report.cancel, for: .normal)
+
+        let reportButton = CapsuleButton()
+        reportButton.setStyle(.red)
+        reportButton.setTitle(Localized.Chat.Report.action, for: .normal)
+
+        reportButton
+          .publisher(for: .touchUpInside)
+          .receive(on: DispatchQueue.main)
+          .sink { [unowned self] in
+            navigator.perform(DismissModal(from: self)) { [weak self] in
+              guard let self else { return }
+              self.drawerCancellables.removeAll()
+//              let screenshot = try! self.makeAppScreenshot()
+//              self.viewModel.report(contact: contact, screenshot: screenshot) {
+//                self.collectionView.reloadData()
+//              }
+            }
+          }.store(in: &drawerCancellables)
+
+        cancelButton
+          .publisher(for: .touchUpInside)
+          .receive(on: DispatchQueue.main)
+          .sink { [unowned self] in
+            navigator.perform(DismissModal(from: self)) { [weak self] in
+              guard let self else { return }
+              self.drawerCancellables.removeAll()
+            }
+          }.store(in: &drawerCancellables)
+
+        navigator.perform(PresentDrawer(items: [
+          DrawerImage(
+            image: Asset.drawerNegative.image
+          ),
+          DrawerText(
+            font: Fonts.Mulish.semiBold.font(size: 18.0),
+            text: Localized.Chat.Report.title,
+            color: Asset.neutralActive.color
+          ),
+          DrawerText(
+            font: Fonts.Mulish.semiBold.font(size: 14.0),
+            text: Localized.Chat.Report.subtitle,
+            color: Asset.neutralWeak.color,
+            lineHeightMultiple: 1.35,
+            spacingAfter: 25
+          ),
+          DrawerStack(
+            axis: .vertical,
+            spacing: 20.0,
+            views: [reportButton, cancelButton]
+          )
+        ], isDismissable: true, from: self))
+      }.store(in: &cancellables)
+
+    viewModel
+      .messages
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] sections in
+        func process() {
+          let changeSet = StagedChangeset(source: self.sections, target: sections).flattenIfPossible()
+          collectionView.reload(
+            using: changeSet,
+            interrupt: { changeSet in
+              guard !self.sections.isEmpty else { return true }
+              return false
+            }, onInterruptedReload: {
+              guard let lastSection = self.sections.last else { return }
+              let positionSnapshot = ChatLayoutPositionSnapshot(
+                indexPath: IndexPath(
+                  item: lastSection.elements.count - 1,
+                  section: self.sections.count - 1
+                ),
+                kind: .cell,
+                edge: .bottom
+              )
+
+              self.collectionView.reloadData()
+              self.chatLayout.restoreContentOffset(with: positionSnapshot)
+            },
+            completion: nil,
+            setData: { self.sections = $0 }
+          )
+        }
 
-        KeyboardListener.shared.add(delegate: self)
-    }
+        guard currentInterfaceActions.options.isEmpty else {
+          let reaction = SetActor<Set<InterfaceActions>, ReactionTypes>.Reaction(
+            type: .delayedUpdate,
+            action: .onEmpty,
+            executionType: .once,
+            actionBlock: { [weak self] in
+              guard let _ = self else { return }
+              process()
+            }
+          )
 
-    private func setupNavigationBar() {
-        let more = UIButton()
-        more.setImage(Asset.chatMore.image, for: .normal)
-        more.addTarget(self, action: #selector(didTapDots), for: .touchUpInside)
+          currentInterfaceActions.add(reaction: reaction)
+          return
+        }
 
-        navigationItem.titleView = header
-        navigationItem.rightBarButtonItem = UIBarButtonItem(customView: more)
+        process()
+      }
+      .store(in: &cancellables)
+  }
+
+  @objc private func didTapDots() {
+    navigator.perform(PresentMemberList(members: viewModel.info.members))
+  }
+
+  func scrollToBottom(completion: (() -> Void)? = nil) {
+    let contentOffsetAtBottom = CGPoint(
+      x: collectionView.contentOffset.x,
+      y: chatLayout.collectionViewContentSize.height
+      - collectionView.frame.height + collectionView.adjustedContentInset.bottom
+    )
+
+    guard contentOffsetAtBottom.y > collectionView.contentOffset.y else { completion?(); return }
+
+    let initialOffset = collectionView.contentOffset.y
+    let delta = contentOffsetAtBottom.y - initialOffset
+
+    if abs(delta) > chatLayout.visibleBounds.height {
+      animator = ManualAnimator()
+      animator?.animate(duration: TimeInterval(0.25), curve: .easeInOut) { [weak self] percentage in
+        guard let self else { return }
+
+        self.collectionView.contentOffset = CGPoint(x: self.collectionView.contentOffset.x, y: initialOffset + (delta * percentage))
+        if percentage == 1.0 {
+          self.animator = nil
+          let positionSnapshot = ChatLayoutPositionSnapshot(indexPath: IndexPath(item: 0, section: 0), kind: .footer, edge: .bottom)
+          self.chatLayout.restoreContentOffset(with: positionSnapshot)
+          self.currentInterfaceActions.options.remove(.scrollingToBottom)
+          completion?()
+        }
+      }
+    } else {
+      currentInterfaceActions.options.insert(.scrollingToBottom)
+      UIView.animate(withDuration: 0.25, animations: {
+        self.collectionView.setContentOffset(contentOffsetAtBottom, animated: true)
+      }, completion: { [weak self] _ in
+        self?.currentInterfaceActions.options.remove(.scrollingToBottom)
+        completion?()
+      })
     }
+  }
+}
 
-    private func setupCollectionView() {
-        chatLayout.configure(layoutDelegate)
-        collectionView = .init(on: view, with: chatLayout)
-        collectionView.register(IncomingGroupTextCell.self)
-        collectionView.register(OutgoingGroupTextCell.self)
-        collectionView.register(IncomingGroupReplyCell.self)
-        collectionView.register(OutgoingGroupReplyCell.self)
-        collectionView.register(OutgoingFailedGroupTextCell.self)
-        collectionView.register(OutgoingFailedGroupReplyCell.self)
-        collectionView.registerSectionHeader(SectionHeaderView.self)
-        collectionView.dataSource = self
-        collectionView.delegate = self
+extension GroupChatController: UICollectionViewDataSource {
+  public func numberOfSections(in collectionView: UICollectionView) -> Int {
+    sections.count
+  }
+
+  public func collectionView(_ collectionView: UICollectionView,
+                             viewForSupplementaryElementOfKind kind: String,
+                             at indexPath: IndexPath) -> UICollectionReusableView {
+    let sectionHeader: SectionHeaderView = collectionView.dequeueSupplementaryView(forIndexPath: indexPath)
+    sectionHeader.title.text = sections[indexPath.section].model.date.asDayOfMonth()
+    return sectionHeader
+  }
+
+  public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+    sections[section].elements.count
+  }
+
+  public func collectionView(
+    _ collectionView: UICollectionView,
+    cellForItemAt indexPath: IndexPath
+  ) -> UICollectionViewCell {
+
+    var item = sections[indexPath.section].elements[indexPath.item]
+    let canReply: () -> Bool = {
+      (item.status == .sent || item.status == .received) && item.networkId != nil
     }
 
-    private func setupInputController() {
-        inputComponent.setMaxHeight { [weak self] in
-            guard let self = self else { return 150 }
-
-            let maxHeight = self.collectionView.frame.height
-            - self.collectionView.adjustedContentInset.top
-            - self.collectionView.adjustedContentInset.bottom
-            + self.inputComponent.bounds.height
-
-            return maxHeight * 0.9
-        }
-
-        viewModel.replyPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] senderTitle, messageText in
-                inputComponent.setupReply(message: messageText, sender: senderTitle)
-            }
-            .store(in: &cancellables)
+    let performReply: () -> Void = { [weak self] in
+      self?.viewModel.didRequestReply(item)
     }
 
-    private func setupBindings() {
-        viewModel.routesPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                switch $0 {
-                case .waitingRound:
-                    coordinator.toDrawer(makeWaitingRoundDrawer(), from: self)
-                case .webview(let urlString):
-                    coordinator.toWebview(with: urlString, from: self)
-                }
-            }.store(in: &cancellables)
-
-        viewModel.hudPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [hud] in hud.update(with: $0) }
-            .store(in: &cancellables)
+    let name: (Data) -> String = viewModel.getName(from:)
+    let showRound: (String?) -> Void = viewModel.showRoundFrom(_:)
+    let replyContent: (Data) -> (String, String) = viewModel.getReplyContent(for:)
 
-        viewModel.reportPopupPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] contact in
-                presentReportDrawer(contact)
-            }.store(in: &cancellables)
+    var isSenderBanned = false
 
-        viewModel.messages
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] sections in
-                func process() {
-                    let changeSet = StagedChangeset(source: self.sections, target: sections).flattenIfPossible()
-                    collectionView.reload(
-                        using: changeSet,
-                        interrupt: { changeSet in
-                            guard !self.sections.isEmpty else { return true }
-                            return false
-                        }, onInterruptedReload: {
-                            guard let lastSection = self.sections.last else { return }
-                            let positionSnapshot = ChatLayoutPositionSnapshot(
-                                indexPath: IndexPath(
-                                    item: lastSection.elements.count - 1,
-                                    section: self.sections.count - 1
-                                ),
-                                kind: .cell,
-                                edge: .bottom
-                            )
-
-                            self.collectionView.reloadData()
-                            self.chatLayout.restoreContentOffset(with: positionSnapshot)
-                        },
-                        completion: nil,
-                        setData: { self.sections = $0 }
-                    )
-                }
-
-                guard currentInterfaceActions.options.isEmpty else {
-                    let reaction = SetActor<Set<InterfaceActions>, ReactionTypes>.Reaction(
-                        type: .delayedUpdate,
-                        action: .onEmpty,
-                        executionType: .once,
-                        actionBlock: { [weak self] in
-                            guard let _ = self else { return }
-                            process()
-                        }
-                    )
-
-                    currentInterfaceActions.add(reaction: reaction)
-                    return
-                }
-
-                process()
-            }
-            .store(in: &cancellables)
+    if let sender = try? dbManager.getDB().fetchContacts(.init(id: [item.senderId])).first {
+      isSenderBanned = sender.isBanned
     }
 
-    @objc private func didTapDots() {
-        coordinator.toMembersList(members, from: self)
-    }
+    if item.status == .received {
+      guard isSenderBanned == false else {
+        item.text = "This user has been banned"
 
-    private func presentReportDrawer(_ contact: Contact) {
-        var config = MakeReportDrawer.Config()
-        config.onReport = { [weak self] in
-            guard let self = self else { return }
-            let screenshot = try! self.makeAppScreenshot()
-            self.viewModel.report(contact: contact, screenshot: screenshot) {
-                self.collectionView.reloadData()
-            }
-        }
-        let drawer = makeReportDrawer(config)
-        coordinator.toDrawer(drawer, from: self)
-    }
-
-    private func makeWaitingRoundDrawer() -> UIViewController {
-        let text = DrawerText(
-            font: Fonts.Mulish.semiBold.font(size: 14.0),
-            text: Localized.Chat.RoundDrawer.title,
-            color: Asset.neutralWeak.color,
-            lineHeightMultiple: 1.35,
-            spacingAfter: 25
+        let cell: IncomingGroupTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+        Bubbler.buildGroup(
+          bubble: cell.leftView,
+          with: item,
+          with: "Banned user"
         )
 
-        let button = DrawerCapsuleButton(model: .init(
-            title: Localized.Chat.RoundDrawer.action,
-            style: .brandColored
-        ))
+        cell.canReply = false
+        cell.performReply = {}
+        cell.leftView.didTapShowRound = {}
 
-        let drawer = DrawerController(with: [text, button])
+        return cell
+      }
 
-        button.action
-            .receive(on: DispatchQueue.main)
-            .sink { [weak drawer] in
-                drawer?.dismiss(animated: true)
-            }.store(in: &drawer.cancellables)
+      if let replyMessageId = item.replyMessageId {
+        let cell: IncomingGroupReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
-        return drawer
-    }
+        Bubbler.buildReplyGroup(
+          bubble: cell.leftView,
+          with: item,
+          reply: replyContent(replyMessageId),
+          sender: name(item.senderId)
+        )
 
-    func scrollToBottom(completion: (() -> Void)? = nil) {
-        let contentOffsetAtBottom = CGPoint(
-            x: collectionView.contentOffset.x,
-            y: chatLayout.collectionViewContentSize.height
-            - collectionView.frame.height + collectionView.adjustedContentInset.bottom
+        cell.canReply = canReply()
+        cell.performReply = performReply
+        cell.leftView.didTapShowRound = { showRound(item.roundURL) }
+
+        return cell
+      } else {
+        let cell: IncomingGroupTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+        Bubbler.buildGroup(
+          bubble: cell.leftView,
+          with: item,
+          with: name(item.senderId)
         )
 
-        guard contentOffsetAtBottom.y > collectionView.contentOffset.y else { completion?(); return }
+        cell.canReply = canReply()
+        cell.performReply = performReply
+        cell.leftView.didTapShowRound = { showRound(item.roundURL) }
+
+        return cell
+      }
+    } else if item.status == .sendingFailed {
+      if let replyMessageId = item.replyMessageId {
+        let cell: OutgoingFailedGroupReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+
+        Bubbler.buildReplyGroup(
+          bubble: cell.rightView,
+          with: item,
+          reply: replyContent(replyMessageId),
+          sender: name(item.senderId)
+        )
 
-        let initialOffset = collectionView.contentOffset.y
-        let delta = contentOffsetAtBottom.y - initialOffset
+        cell.canReply = canReply()
+        cell.performReply = performReply
 
-        if abs(delta) > chatLayout.visibleBounds.height {
-            animator = ManualAnimator()
-            animator?.animate(duration: TimeInterval(0.25), curve: .easeInOut) { [weak self] percentage in
-                guard let self = self else { return }
+        return cell
+      } else {
+        let cell: OutgoingFailedGroupTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
-                self.collectionView.contentOffset = CGPoint(x: self.collectionView.contentOffset.x, y: initialOffset + (delta * percentage))
-                if percentage == 1.0 {
-                    self.animator = nil
-                    let positionSnapshot = ChatLayoutPositionSnapshot(indexPath: IndexPath(item: 0, section: 0), kind: .footer, edge: .bottom)
-                    self.chatLayout.restoreContentOffset(with: positionSnapshot)
-                    self.currentInterfaceActions.options.remove(.scrollingToBottom)
-                    completion?()
-                }
-            }
-        } else {
-            currentInterfaceActions.options.insert(.scrollingToBottom)
-            UIView.animate(withDuration: 0.25, animations: {
-                self.collectionView.setContentOffset(contentOffsetAtBottom, animated: true)
-            }, completion: { [weak self] _ in
-                self?.currentInterfaceActions.options.remove(.scrollingToBottom)
-                completion?()
-            })
-        }
-    }
-}
+        Bubbler.buildGroup(
+          bubble: cell.rightView,
+          with: item,
+          with: name(item.senderId)
+        )
 
-extension GroupChatController: UICollectionViewDataSource {
-    public func numberOfSections(in collectionView: UICollectionView) -> Int {
-        sections.count
-    }
+        cell.canReply = canReply()
+        cell.performReply = performReply
 
-    public func collectionView(_ collectionView: UICollectionView,
-                               viewForSupplementaryElementOfKind kind: String,
-                               at indexPath: IndexPath) -> UICollectionReusableView {
-        let sectionHeader: SectionHeaderView = collectionView.dequeueSupplementaryView(forIndexPath: indexPath)
-        sectionHeader.title.text = sections[indexPath.section].model.date.asDayOfMonth()
-        return sectionHeader
-    }
+        return cell
+      }
+    } else if item.status == .sendingTimedOut {
+      if let replyMessageId = item.replyMessageId {
+        let cell: OutgoingFailedGroupReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
-    public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
-        sections[section].elements.count
-    }
+        Bubbler.buildReplyGroup(
+          bubble: cell.rightView,
+          with: item,
+          reply: replyContent(replyMessageId),
+          sender: name(item.senderId)
+        )
 
-    public func collectionView(
-        _ collectionView: UICollectionView,
-        cellForItemAt indexPath: IndexPath
-    ) -> UICollectionViewCell {
+        cell.canReply = false
+        cell.performReply = performReply
 
-        var item = sections[indexPath.section].elements[indexPath.item]
-        let canReply: () -> Bool = {
-            (item.status == .sent || item.status == .received) && item.networkId != nil
-        }
+        return cell
+      } else {
+        let cell: OutgoingFailedGroupTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
-        let performReply: () -> Void = { [weak self] in
-            self?.viewModel.didRequestReply(item)
-        }
+        Bubbler.buildGroup(
+          bubble: cell.rightView,
+          with: item,
+          with: name(item.senderId)
+        )
 
-        let name: (Data) -> String = viewModel.getName(from:)
-        let showRound: (String?) -> Void = viewModel.showRoundFrom(_:)
-        let replyContent: (Data) -> (String, String) = viewModel.getReplyContent(for:)
+        cell.canReply = false
+        cell.performReply = performReply
 
-        var isSenderBanned = false
+        return cell
+      }
+    } else {
+      if let replyMessageId = item.replyMessageId {
+        let cell: OutgoingGroupReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
-        if let sender = try? session.dbManager.fetchContacts(.init(id: [item.senderId])).first {
-            isSenderBanned = sender.isBanned
-        }
+        Bubbler.buildReplyGroup(
+          bubble: cell.rightView,
+          with: item,
+          reply: replyContent(replyMessageId),
+          sender: name(item.senderId)
+        )
 
-        if item.status == .received {
-            guard isSenderBanned == false else {
-                item.text = "This user has been banned"
+        cell.canReply = canReply()
+        cell.performReply = performReply
+        cell.rightView.didTapShowRound = { showRound(item.roundURL) }
 
-                let cell: IncomingGroupTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-                Bubbler.buildGroup(
-                    bubble: cell.leftView,
-                    with: item,
-                    with: "Banned user"
-                )
+        return cell
+      } else {
+        let cell: OutgoingGroupTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
-                cell.canReply = false
-                cell.performReply = {}
-                cell.leftView.didTapShowRound = {}
+        Bubbler.buildGroup(
+          bubble: cell.rightView,
+          with: item,
+          with: name(item.senderId)
+        )
 
-                return cell
-            }
+        cell.canReply = canReply()
+        cell.performReply = performReply
+        cell.rightView.didTapShowRound = { showRound(item.roundURL) }
 
-            if let replyMessageId = item.replyMessageId {
-                let cell: IncomingGroupReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-
-                Bubbler.buildReplyGroup(
-                    bubble: cell.leftView,
-                    with: item,
-                    reply: replyContent(replyMessageId),
-                    sender: name(item.senderId)
-                )
-
-                cell.canReply = canReply()
-                cell.performReply = performReply
-                cell.leftView.didTapShowRound = { showRound(item.roundURL) }
-
-                return cell
-            } else {
-                let cell: IncomingGroupTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-                Bubbler.buildGroup(
-                    bubble: cell.leftView,
-                    with: item,
-                    with: name(item.senderId)
-                )
-
-                cell.canReply = canReply()
-                cell.performReply = performReply
-                cell.leftView.didTapShowRound = { showRound(item.roundURL) }
-
-                return cell
-            }
-        } else if item.status == .sendingFailed {
-            if let replyMessageId = item.replyMessageId {
-                let cell: OutgoingFailedGroupReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-
-                Bubbler.buildReplyGroup(
-                    bubble: cell.rightView,
-                    with: item,
-                    reply: replyContent(replyMessageId),
-                    sender: name(item.senderId)
-                )
-
-                cell.canReply = canReply()
-                cell.performReply = performReply
-
-                return cell
-            } else {
-                let cell: OutgoingFailedGroupTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-
-                Bubbler.buildGroup(
-                    bubble: cell.rightView,
-                    with: item,
-                    with: name(item.senderId)
-                )
-
-                cell.canReply = canReply()
-                cell.performReply = performReply
-
-                return cell
-            }
-        } else {
-            if let replyMessageId = item.replyMessageId {
-                let cell: OutgoingGroupReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-
-                Bubbler.buildReplyGroup(
-                    bubble: cell.rightView,
-                    with: item,
-                    reply: replyContent(replyMessageId),
-                    sender: name(item.senderId)
-                )
-
-                cell.canReply = canReply()
-                cell.performReply = performReply
-                cell.rightView.didTapShowRound = { showRound(item.roundURL) }
-
-                return cell
-            } else {
-                let cell: OutgoingGroupTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-
-                Bubbler.buildGroup(
-                    bubble: cell.rightView,
-                    with: item,
-                    with: name(item.senderId)
-                )
-
-                cell.canReply = canReply()
-                cell.performReply = performReply
-                cell.rightView.didTapShowRound = { showRound(item.roundURL) }
-
-                return cell
-            }
-        }
+        return cell
+      }
     }
+  }
 }
 
 extension GroupChatController: KeyboardListenerDelegate {
-    fileprivate var isUserInitiatedScrolling: Bool {
-        return collectionView.isDragging || collectionView.isDecelerating
+  fileprivate var isUserInitiatedScrolling: Bool {
+    return collectionView.isDragging || collectionView.isDecelerating
+  }
+
+  func keyboardWillChangeFrame(info: KeyboardInfo) {
+    let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first
+
+    guard !currentInterfaceActions.options.contains(.changingFrameSize),
+          collectionView.contentInsetAdjustmentBehavior != .never,
+          let keyboardFrame = keyWindow?.convert(info.frameEnd, to: view),
+          collectionView.convert(collectionView.bounds, to: keyWindow).maxY > info.frameEnd.minY else {
+      return
     }
-
-    func keyboardWillChangeFrame(info: KeyboardInfo) {
-        let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first
-
-        guard !currentInterfaceActions.options.contains(.changingFrameSize),
-              collectionView.contentInsetAdjustmentBehavior != .never,
-              let keyboardFrame = keyWindow?.convert(info.frameEnd, to: view),
-              collectionView.convert(collectionView.bounds, to: keyWindow).maxY > info.frameEnd.minY else {
-                  return
-              }
-        currentInterfaceActions.options.insert(.changingKeyboardFrame)
-        let newBottomInset = collectionView.frame.minY + collectionView.frame.size.height - keyboardFrame.minY - collectionView.safeAreaInsets.bottom
-        if newBottomInset > 0,
-           collectionView.contentInset.bottom != newBottomInset {
-            let positionSnapshot = chatLayout.getContentOffsetSnapshot(from: .bottom)
-
-            currentInterfaceActions.options.insert(.changingContentInsets)
-            UIView.animate(withDuration: info.animationDuration, animations: {
-                self.collectionView.performBatchUpdates({
-                    self.collectionView.contentInset.bottom = newBottomInset
-                    self.collectionView.verticalScrollIndicatorInsets.bottom = newBottomInset
-                }, completion: nil)
-
-                if let positionSnapshot = positionSnapshot, !self.isUserInitiatedScrolling {
-                    self.chatLayout.restoreContentOffset(with: positionSnapshot)
-                }
-            }, completion: { _ in
-                self.currentInterfaceActions.options.remove(.changingContentInsets)
-            })
+    currentInterfaceActions.options.insert(.changingKeyboardFrame)
+    let newBottomInset = collectionView.frame.minY + collectionView.frame.size.height - keyboardFrame.minY - collectionView.safeAreaInsets.bottom
+    if newBottomInset > 0,
+       collectionView.contentInset.bottom != newBottomInset {
+      let positionSnapshot = chatLayout.getContentOffsetSnapshot(from: .bottom)
+
+      currentInterfaceActions.options.insert(.changingContentInsets)
+      UIView.animate(withDuration: info.animationDuration, animations: {
+        self.collectionView.performBatchUpdates({
+          self.collectionView.contentInset.bottom = newBottomInset
+          self.collectionView.verticalScrollIndicatorInsets.bottom = newBottomInset
+        }, completion: nil)
+
+        if let positionSnapshot = positionSnapshot, !self.isUserInitiatedScrolling {
+          self.chatLayout.restoreContentOffset(with: positionSnapshot)
         }
+      }, completion: { _ in
+        self.currentInterfaceActions.options.remove(.changingContentInsets)
+      })
     }
+  }
 
-    func keyboardDidChangeFrame(info: KeyboardInfo) {
-        guard currentInterfaceActions.options.contains(.changingKeyboardFrame) else { return }
-        currentInterfaceActions.options.remove(.changingKeyboardFrame)
-    }
+  func keyboardDidChangeFrame(info: KeyboardInfo) {
+    guard currentInterfaceActions.options.contains(.changingKeyboardFrame) else { return }
+    currentInterfaceActions.options.remove(.changingKeyboardFrame)
+  }
 }
 
 extension GroupChatController: UICollectionViewDelegate {
-    private func makeTargetedPreview(for configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
-        guard let identifier = configuration.identifier as? String,
-              let first = identifier.components(separatedBy: "|").first,
-              let last = identifier.components(separatedBy: "|").last,
-              let item = Int(first), let section = Int(last),
-              let cell = collectionView.cellForItem(at: IndexPath(item: item, section: section)) else {
-                  return nil
-              }
-
-        let parameters = UIPreviewParameters()
-        parameters.backgroundColor = .clear
-
-        if sections[section].elements[item].status == .received {
-            var leftView: UIView!
-
-            if let cell = cell as? IncomingGroupReplyCell {
-                leftView = cell.leftView
-            } else if let cell = cell as? IncomingGroupTextCell {
-                leftView = cell.leftView
-            }
+  private func makeTargetedPreview(for configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
+    guard let identifier = configuration.identifier as? String,
+          let first = identifier.components(separatedBy: "|").first,
+          let last = identifier.components(separatedBy: "|").last,
+          let item = Int(first), let section = Int(last),
+          let cell = collectionView.cellForItem(at: IndexPath(item: item, section: section)) else {
+      return nil
+    }
 
-            parameters.visiblePath = UIBezierPath(roundedRect: leftView.bounds, cornerRadius: 13)
-            return UITargetedPreview(view: leftView, parameters: parameters)
-        }
+    let parameters = UIPreviewParameters()
+    parameters.backgroundColor = .clear
 
-        var rightView: UIView!
+    if sections[section].elements[item].status == .received {
+      var leftView: UIView!
 
-        if let cell = cell as? OutgoingGroupTextCell {
-            rightView = cell.rightView
-        } else if let cell = cell as? OutgoingGroupReplyCell {
-            rightView = cell.rightView
-        }
+      if let cell = cell as? IncomingGroupReplyCell {
+        leftView = cell.leftView
+      } else if let cell = cell as? IncomingGroupTextCell {
+        leftView = cell.leftView
+      }
 
-        parameters.visiblePath = UIBezierPath(roundedRect: rightView.bounds, cornerRadius: 13)
-        return UITargetedPreview(view: rightView, parameters: parameters)
+      parameters.visiblePath = UIBezierPath(roundedRect: leftView.bounds, cornerRadius: 13)
+      return UITargetedPreview(view: leftView, parameters: parameters)
     }
 
-    public func collectionView(
-        _ collectionView: UICollectionView,
-        previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration
-    ) -> UITargetedPreview? {
-        makeTargetedPreview(for: configuration)
-    }
+    var rightView: UIView!
 
-    public func collectionView(
-        _ collectionView: UICollectionView,
-        previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration
-    ) -> UITargetedPreview? {
-        makeTargetedPreview(for: configuration)
+    if let cell = cell as? OutgoingGroupTextCell {
+      rightView = cell.rightView
+    } else if let cell = cell as? OutgoingGroupReplyCell {
+      rightView = cell.rightView
     }
 
-    public func collectionView(
-        _ collectionView: UICollectionView,
-        contextMenuConfigurationForItemAt indexPath: IndexPath,
-        point: CGPoint
-    ) -> UIContextMenuConfiguration? {
-        UIContextMenuConfiguration(
-            identifier: "\(indexPath.item)|\(indexPath.section)" as NSCopying,
-            previewProvider: nil
-        ) { [weak self] suggestedActions in
-
-            guard let self = self else { return nil }
-
-            let item = self.sections[indexPath.section].elements[indexPath.item]
-
-            let copy = UIAction(title: Localized.Chat.BubbleMenu.copy, state: .off) { _ in
-                UIPasteboard.general.string = item.text
-            }
-
-            let reply = UIAction(title: Localized.Chat.BubbleMenu.reply, state: .off) { [weak self] _ in
-                self?.viewModel.didRequestReply(item)
-            }
-
-            let delete = UIAction(title: Localized.Chat.BubbleMenu.delete, state: .off) { [weak self] _ in
-                self?.viewModel.didRequestDelete([item])
-            }
-
-            let report = UIAction(title: Localized.Chat.BubbleMenu.report, state: .off) { [weak self] _ in
-                self?.viewModel.didRequestReport(item)
-            }
-
-            let retry = UIAction(title: Localized.Chat.BubbleMenu.retry, state: .off) { [weak self] _ in
-                self?.viewModel.retry(item)
-            }
-
-            var children = [UIAction]()
-
-            if item.status == .sendingFailed {
-                children = [copy, retry, delete]
-            } else if item.status == .sending {
-                children = [copy]
-            } else {
-                children = [copy, reply, delete]
-
-                if self.reportingStatus.isEnabled() {
-                    children.append(report)
-                }
-            }
-
-            return UIMenu(title: "", children: children)
+    parameters.visiblePath = UIBezierPath(roundedRect: rightView.bounds, cornerRadius: 13)
+    return UITargetedPreview(view: rightView, parameters: parameters)
+  }
+
+  public func collectionView(
+    _ collectionView: UICollectionView,
+    previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration
+  ) -> UITargetedPreview? {
+    makeTargetedPreview(for: configuration)
+  }
+
+  public func collectionView(
+    _ collectionView: UICollectionView,
+    previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration
+  ) -> UITargetedPreview? {
+    makeTargetedPreview(for: configuration)
+  }
+
+  public func collectionView(
+    _ collectionView: UICollectionView,
+    contextMenuConfigurationForItemAt indexPath: IndexPath,
+    point: CGPoint
+  ) -> UIContextMenuConfiguration? {
+    UIContextMenuConfiguration(
+      identifier: "\(indexPath.item)|\(indexPath.section)" as NSCopying,
+      previewProvider: nil
+    ) { [weak self] suggestedActions in
+
+      guard let self else {
+        fatalError()
+        //return nil
+      }
+
+      let item = self.sections[indexPath.section].elements[indexPath.item]
+
+      let copy = UIAction(title: Localized.Chat.BubbleMenu.copy, state: .off) { _ in
+        UIPasteboard.general.string = item.text
+      }
+
+      let reply = UIAction(title: Localized.Chat.BubbleMenu.reply, state: .off) { [weak self] _ in
+        self?.viewModel.didRequestReply(item)
+      }
+
+      let delete = UIAction(title: Localized.Chat.BubbleMenu.delete, state: .off) { [weak self] _ in
+        self?.viewModel.didRequestDelete([item])
+      }
+
+      let report = UIAction(title: Localized.Chat.BubbleMenu.report, state: .off) { [weak self] _ in
+        self?.viewModel.didRequestReport(item)
+      }
+
+      let retry = UIAction(title: Localized.Chat.BubbleMenu.retry, state: .off) { [weak self] _ in
+        self?.viewModel.retry(item)
+      }
+
+      var children = [UIAction]()
+
+      if item.status == .sendingFailed {
+        children = [copy, retry, delete]
+      } else if item.status == .sending {
+        children = [copy]
+      } else {
+        children = [copy, reply, delete]
+
+        if self.reportingStatus.isEnabled() {
+          children.append(report)
         }
+      }
+
+      return UIMenu(title: "", children: children)
     }
+  }
 }
diff --git a/Sources/ChatFeature/Controllers/MembersController.swift b/Sources/ChatFeature/Controllers/MembersController.swift
index e7bf2c3ece345ace155ed7babbf5c9ec05bf1caf..086114eaafc2da90ceec2c80a61d926d255d62c9 100644
--- a/Sources/ChatFeature/Controllers/MembersController.swift
+++ b/Sources/ChatFeature/Controllers/MembersController.swift
@@ -1,83 +1,83 @@
 import UIKit
-import Models
 import Shared
 import XXModels
+import AppResources
 
 final class MembersController: UIViewController {
-    lazy private var stackView = UIStackView()
-
-    private let members: [Contact]
-
-    init(with members: [Contact]) {
-        self.members = members
-        super.init(nibName: nil, bundle: nil)
+  private lazy var stackView = UIStackView()
+  
+  private let members: [Contact]
+  
+  init(with members: [Contact]) {
+    self.members = members
+    super.init(nibName: nil, bundle: nil)
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    
+    view.layer.cornerRadius = 15
+    view.layer.masksToBounds = true
+    view.backgroundColor = Asset.neutralWhite.color
+    
+    stackView.axis = .vertical
+    stackView.distribution = .fillEqually
+    view.addSubview(stackView)
+    
+    stackView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(10)
+      $0.left.right.equalToSuperview()
+      $0.bottom.equalTo(view.safeAreaLayoutGuide)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-
-        view.layer.cornerRadius = 15
-        view.layer.masksToBounds = true
-        view.backgroundColor = Asset.neutralWhite.color
-
-        stackView.axis = .vertical
-        stackView.distribution = .fillEqually
-        view.addSubview(stackView)
-
-        stackView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(10)
-            $0.left.right.equalToSuperview()
-            $0.bottom.equalTo(view.safeAreaLayoutGuide)
-        }
-
-        members.forEach {
-            let memberView = MemberView()
-            let assignedTitle = ($0.nickname ?? $0.username) ?? "Fetching username..."
-            memberView.titleLabel.text = assignedTitle
-            memberView.avatarView.setupProfile(title: assignedTitle, image: $0.photo, size: .small)
-            stackView.addArrangedSubview(memberView)
-        }
+    
+    members.forEach {
+      let memberView = MemberView()
+      let assignedTitle = ($0.nickname ?? $0.username) ?? "Fetching username..."
+      memberView.titleLabel.text = assignedTitle
+      memberView.avatarView.setupProfile(title: assignedTitle, image: $0.photo, size: .small)
+      stackView.addArrangedSubview(memberView)
     }
+  }
 }
 
 private final class MemberView: UIView {
-    let titleLabel = UILabel()
-    let avatarView = AvatarView()
-    let separatorView = UIView()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-        titleLabel.textColor = Asset.neutralBody.color
-        titleLabel.font = Fonts.Mulish.bold.font(size: 12.0)
-        separatorView.backgroundColor = Asset.neutralLine.color
-
-        addSubview(titleLabel)
-        addSubview(avatarView)
-        addSubview(separatorView)
-
-        avatarView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(10)
-            $0.width.height.equalTo(30)
-            $0.left.equalToSuperview().offset(25)
-            $0.centerY.equalToSuperview()
-        }
-
-        titleLabel.snp.makeConstraints {
-            $0.centerY.equalTo(avatarView)
-            $0.left.equalTo(avatarView.snp.right).offset(14)
-            $0.right.lessThanOrEqualToSuperview().offset(-10)
-        }
-
-        separatorView.snp.makeConstraints {
-            $0.height.equalTo(1)
-            $0.left.equalToSuperview().offset(25)
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
+  let titleLabel = UILabel()
+  let avatarView = AvatarView()
+  let separatorView = UIView()
+  
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+    titleLabel.textColor = Asset.neutralBody.color
+    titleLabel.font = Fonts.Mulish.bold.font(size: 12.0)
+    separatorView.backgroundColor = Asset.neutralLine.color
+    
+    addSubview(titleLabel)
+    addSubview(avatarView)
+    addSubview(separatorView)
+    
+    avatarView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(10)
+      $0.width.height.equalTo(30)
+      $0.left.equalToSuperview().offset(25)
+      $0.centerY.equalToSuperview()
     }
-
-    required init?(coder: NSCoder) { nil }
+    
+    titleLabel.snp.makeConstraints {
+      $0.centerY.equalTo(avatarView)
+      $0.left.equalTo(avatarView.snp.right).offset(14)
+      $0.right.lessThanOrEqualToSuperview().offset(-10)
+    }
+    
+    separatorView.snp.makeConstraints {
+      $0.height.equalTo(1)
+      $0.left.equalToSuperview().offset(25)
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
+  }
+  
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/ChatFeature/Controllers/RetrySheetController.swift b/Sources/ChatFeature/Controllers/RetrySheetController.swift
index 48424f434d189d76a29a29c278e3ba34fe659ca5..4605017e1368171f2b705c45412831c89f8d229d 100644
--- a/Sources/ChatFeature/Controllers/RetrySheetController.swift
+++ b/Sources/ChatFeature/Controllers/RetrySheetController.swift
@@ -10,7 +10,7 @@ public final class RetrySheetController: UIViewController {
 
     // MARK: UI
 
-    lazy private var screenView = RetrySheetView()
+    private lazy var screenView = RetrySheetView()
 
     // MARK: Properties
 
diff --git a/Sources/ChatFeature/Controllers/SheetController.swift b/Sources/ChatFeature/Controllers/SheetController.swift
index 974f086d5df232b982c0ab101b0f04f48f1bc578..12dd2e5a2a0e4b888f64e9277973109f19a8cadf 100644
--- a/Sources/ChatFeature/Controllers/SheetController.swift
+++ b/Sources/ChatFeature/Controllers/SheetController.swift
@@ -2,50 +2,51 @@ import UIKit
 import Combine
 
 final class SheetController: UIViewController {
-    enum Action {
-        case clear
-        case details
-        case report
-    }
-
-    lazy private var screenView = SheetView()
-
-    var actionPublisher: AnyPublisher<Action, Never> {
-        actionRelay.eraseToAnyPublisher()
-    }
-
-    private var cancellables = Set<AnyCancellable>()
-    private let actionRelay = PassthroughSubject<Action, Never>()
-
-    public override func loadView() {
-        view = screenView
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-
-        screenView.clearButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                dismiss(animated: true) { [weak actionRelay] in
-                    actionRelay?.send(.clear)
-                }
-            }.store(in: &cancellables)
-
-        screenView.detailsButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                dismiss(animated: true) { [weak actionRelay] in
-                    actionRelay?.send(.details)
-                }
-            }.store(in: &cancellables)
-
-        screenView.reportButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                dismiss(animated: true) { [weak actionRelay] in
-                    actionRelay?.send(.report)
-                }
-            }.store(in: &cancellables)
-    }
+  enum Action {
+    case clear
+    case details
+    case report
+  }
+
+  private lazy var screenView = SheetView()
+
+  var actionPublisher: AnyPublisher<Action, Never> {
+    actionRelay.eraseToAnyPublisher()
+  }
+
+  private var cancellables = Set<AnyCancellable>()
+  private let actionRelay = PassthroughSubject<Action, Never>()
+
+  public override func loadView() {
+    view = screenView
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+
+    screenView
+      .clearButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        dismiss(animated: true) { [weak actionRelay] in
+          actionRelay?.send(.clear)
+        }
+      }.store(in: &cancellables)
+
+    screenView.detailsButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        dismiss(animated: true) { [weak actionRelay] in
+          actionRelay?.send(.details)
+        }
+      }.store(in: &cancellables)
+
+    screenView.reportButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        dismiss(animated: true) { [weak actionRelay] in
+          actionRelay?.send(.report)
+        }
+      }.store(in: &cancellables)
+  }
 }
diff --git a/Sources/ChatFeature/Controllers/SingleChatController.swift b/Sources/ChatFeature/Controllers/SingleChatController.swift
index b8d8b7703b180178f4c39ab6f04a73c0e7e7b4d6..4ae089ad95fab266d3300d0f73c1b19345e59c81 100644
--- a/Sources/ChatFeature/Controllers/SingleChatController.swift
+++ b/Sources/ChatFeature/Controllers/SingleChatController.swift
@@ -1,711 +1,759 @@
-import HUD
 import UIKit
-import Theme
-import Models
 import Shared
 import Combine
-import XXLogger
-import QuickLook
+import AppCore
 import XXModels
+import QuickLook
 import Voxophone
 import ChatLayout
+import Dependencies
+import AppResources
 import DrawerFeature
+import AppNavigation
 import DifferenceKit
 import ChatInputFeature
 import ReportingFeature
-import DependencyInjection
 import ScrollViewController
 
 extension FlexibleSpace: CollectionCellContent {
-    func prepareForReuse() {}
+  func prepareForReuse() {}
 }
 
 extension Message: Differentiable {
-    public var differenceIdentifier: Int64 { id! }
+  public var differenceIdentifier: Int64 { id! }
 }
 
 public final class SingleChatController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var logger: XXLogger
-    @Dependency private var voxophone: Voxophone
-    @Dependency private var coordinator: ChatCoordinating
-    @Dependency private var reportingStatus: ReportingStatus
-    @Dependency private var makeReportDrawer: MakeReportDrawer
-    @Dependency private var makeAppScreenshot: MakeAppScreenshot
-    @Dependency private var statusBarController: StatusBarStyleControlling
-
-    lazy private var infoView = UIControl()
-    lazy private var nameLabel = UILabel()
-    lazy private var avatarView = AvatarView()
-
-    lazy private var moreButton = UIButton()
-    lazy private var screenView = ChatView()
-    lazy private var sheet = SheetController()
-
-    private let inputComponent: ChatInputView
-    private var collectionView: UICollectionView!
-
-    private let chatLayout = ChatLayout()
-    private var animator: ManualAnimator?
-    private let viewModel: SingleChatViewModel
-    private let layoutDelegate = LayoutDelegate()
-    private var cancellables = Set<AnyCancellable>()
-    private var sections = [ArraySection<ChatSection, Message>]()
-    private var currentInterfaceActions: SetActor<Set<InterfaceActions>, ReactionTypes> = SetActor()
-
-    var fileURL: URL?
-
-    public override func loadView() { view = screenView }
-    public override var canBecomeFirstResponder: Bool { true }
-    public override var inputAccessoryView: UIView? { inputComponent }
-
-    public init(_ contact: Contact) {
-        let viewModel = SingleChatViewModel(contact)
-        self.viewModel = viewModel
-
-        self.inputComponent = ChatInputView(store: .init(
-            initialState: .init(canAddAttachments: true),
-            reducer: chatInputReducer,
-            environment: .init(
-                voxophone: try! DependencyInjection.Container.shared.resolve() as Voxophone,
-                sendAudio: { viewModel.didSendAudio(url: $0) },
-                didTapCamera: { viewModel.didTest(permission: .camera) },
-                didTapLibrary: { viewModel.didTest(permission: .library) },
-                sendText: { viewModel.send($0) },
-                didTapAbortReply: { viewModel.abortReply() },
-                didTapMicrophone: { viewModel.didTest(permission: .microphone) }
-            )
-        ))
-
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        statusBarController.style.send(.darkContent)
-        navigationController?.navigationBar.customize(
-            backgroundColor: Asset.neutralWhite.color,
-            shadowColor: Asset.neutralDisabled.color
-        )
-    }
-
-    public override func viewDidAppear(_ animated: Bool) {
-        super.viewDidAppear(animated)
-        collectionView.collectionViewLayout.invalidateLayout()
-        becomeFirstResponder()
-        viewModel.viewDidAppear()
-    }
-
-    private var isFirstAppearance = true
-
-    public override func viewDidLayoutSubviews() {
-        super.viewDidLayoutSubviews()
-
-        if isFirstAppearance {
-            isFirstAppearance = false
-            let insets = UIEdgeInsets(
-                top: 0,
-                left: 0,
-                bottom: inputComponent.bounds.height - view.safeAreaInsets.bottom,
-                right: 0
-            )
-            collectionView.contentInset = insets
-            collectionView.scrollIndicatorInsets = insets
-        }
-    }
-
-    public override func viewWillDisappear(_ animated: Bool) {
-        super.viewWillDisappear(animated)
-        viewModel.readAll()
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
+//  @Dependency var voxophone: Voxophone
+//  @Dependency var makeReportDrawer: MakeReportDrawer
+//  @Dependency var makeAppScreenshot: MakeAppScreenshot
+
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+  @Dependency(\.reportingStatus) var reportingStatus: ReportingStatus
+
+  let voxophone = Voxophone()
+
+  private lazy var infoView = UIControl()
+  private lazy var nameLabel = UILabel()
+  private lazy var avatarView = AvatarView()
+
+  private lazy var moreButton = UIButton()
+  private lazy var screenView = ChatView()
+  private lazy var sheet = SheetController()
+
+  private let inputComponent: ChatInputView
+  private var collectionView: UICollectionView!
+
+  private var animator: ManualAnimator?
+  private let viewModel: SingleChatViewModel
+  private let layoutDelegate = LayoutDelegate()
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
+  private let chatLayout = CollectionViewChatLayout()
+  private var sections = [ArraySection<ChatSection, Message>]()
+  private var currentInterfaceActions: SetActor<Set<InterfaceActions>, ReactionTypes> = SetActor()
+
+  var fileURL: URL?
+
+  public override func loadView() { view = screenView }
+  public override var canBecomeFirstResponder: Bool { true }
+  public override var inputAccessoryView: UIView? { inputComponent }
+
+  public init(_ contact: Contact) {
+    let viewModel = SingleChatViewModel(contact)
+    self.viewModel = viewModel
+
+    self.inputComponent = ChatInputView(store: .init(
+      initialState: .init(canAddAttachments: true),
+      reducer: chatInputReducer,
+      environment: .init(
+        voxophone: Voxophone(), //try! DI.Container.shared.resolve() as Voxophone,
+        sendAudio: { viewModel.didSendAudio(url: $0) },
+        didTapCamera: { viewModel.didTest(permission: .camera) },
+        didTapLibrary: { viewModel.didTest(permission: .library) },
+        sendText: { viewModel.send($0) },
+        didTapAbortReply: { viewModel.abortReply() },
+        didTapMicrophone: { viewModel.didTest(permission: .microphone) }
+      )
+    ))
+
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    statusBar.set(.darkContent)
+    navigationController?.navigationBar.customize(
+      backgroundColor: Asset.neutralWhite.color,
+      shadowColor: Asset.neutralDisabled.color
+    )
+  }
+
+  public override func viewDidAppear(_ animated: Bool) {
+    super.viewDidAppear(animated)
+    collectionView.collectionViewLayout.invalidateLayout()
+    becomeFirstResponder()
+    viewModel.viewDidAppear()
+  }
+
+  private var isFirstAppearance = true
+
+  public override func viewDidLayoutSubviews() {
+    super.viewDidLayoutSubviews()
+
+    if isFirstAppearance {
+      isFirstAppearance = false
+      let insets = UIEdgeInsets(
+        top: 0,
+        left: 0,
+        bottom: inputComponent.bounds.height - view.safeAreaInsets.bottom,
+        right: 0
+      )
+      collectionView.contentInset = insets
+      collectionView.scrollIndicatorInsets = insets
+    }
+  }
+
+  public override func viewWillDisappear(_ animated: Bool) {
+    super.viewWillDisappear(animated)
+    viewModel.readAll()
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+
+    viewModel
+      .contactPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in setupNavigationBar(contact: $0) }
+      .store(in: &cancellables)
+
+    setupCollectionView()
+    setupInputController()
+    setupBindings()
+
+    KeyboardListener.shared.add(delegate: self)
+    screenView.bringSubviewToFront(screenView.snackBar)
+  }
+
+  private func setupCollectionView() {
+    chatLayout.configure(layoutDelegate)
+    collectionView = .init(on: screenView, with: chatLayout)
+    collectionView.delegate = self
+    collectionView.dataSource = self
+
+    collectionView.register(OutgoingTextCell.self)
+    collectionView.register(IncomingTextCell.self)
+    collectionView.register(IncomingAudioCell.self)
+    collectionView.register(OutgoingAudioCell.self)
+    collectionView.register(IncomingImageCell.self)
+    collectionView.register(IncomingReplyCell.self)
+    collectionView.register(OutgoingImageCell.self)
+    collectionView.register(OutgoingReplyCell.self)
+    collectionView.register(OutgoingFailedTextCell.self)
+    collectionView.register(OutgoingFailedReplyCell.self)
+
+    collectionView.registerSectionHeader(SectionHeaderView.self)
+  }
+
+  private func setupNavigationBar(contact: Contact) {
+    screenView.set(name: contact.nickname ?? contact.username!)
+    avatarView.snp.makeConstraints { $0.width.height.equalTo(35) }
+
+    let title = (contact.nickname ?? contact.username) ?? ""
+    avatarView.setupProfile(title: title, image: contact.photo, size: .small)
+
+    nameLabel.text = title
+    nameLabel.textColor = Asset.neutralActive.color
+    nameLabel.font = Fonts.Mulish.semiBold.font(size: 18.0)
+
+    moreButton.setImage(Asset.chatMore.image, for: .normal)
+    moreButton.addTarget(self, action: #selector(didTapDots), for: .touchUpInside)
+
+    infoView.addTarget(self, action: #selector(didTapInfo), for: .touchUpInside)
+
+    infoView.addSubview(avatarView)
+    infoView.addSubview(nameLabel)
+
+    avatarView.snp.makeConstraints {
+      $0.top.left.bottom.equalToSuperview()
+    }
+
+    nameLabel.snp.makeConstraints {
+      $0.centerY.equalToSuperview()
+      $0.left.equalTo(avatarView.snp.right).offset(13)
+      $0.right.lessThanOrEqualToSuperview()
+    }
+
+    navigationItem.rightBarButtonItem = UIBarButtonItem(customView: moreButton)
+    navigationItem.leftBarButtonItem = UIBarButtonItem(customView: infoView)
+    navigationItem.leftItemsSupplementBackButton = true
+  }
+
+  private func setupInputController() {
+    inputComponent.setMaxHeight { [weak self] in
+      guard let self else { return 150 }
+
+      let maxHeight = self.collectionView.frame.height
+      - self.collectionView.adjustedContentInset.top
+      - self.collectionView.adjustedContentInset.bottom
+      + self.inputComponent.bounds.height
+
+      return maxHeight * 0.9
+    }
+
+    viewModel.replyPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] senderTitle, messageText in
+        inputComponent.setupReply(message: messageText, sender: senderTitle)
+      }
+      .store(in: &cancellables)
+
+    viewModel.navigation
+      .receive(on: DispatchQueue.main)
+      .removeDuplicates()
+      .sink { [unowned self] in
+        switch $0 {
+        case .library:
+          navigator.perform(PresentPhotoLibrary(from: self))
+        case .camera:
+          navigator.perform(PresentCamera(from: self))
+        case .cameraPermission:
+          navigator.perform(PresentPermissionRequest(type: .camera, from: self))
+        case .microphonePermission:
+          navigator.perform(PresentPermissionRequest(type: .microphone, from: self))
+        case .libraryPermission:
+          navigator.perform(PresentPermissionRequest(type: .library, from: self))
+        case .webview(let urlString):
+          navigator.perform(PresentWebsite(urlString: urlString, from: self))
+        case .waitingRound:
+          let button = DrawerCapsuleButton(model: .init(
+            title: Localized.Chat.RoundDrawer.action,
+            style: .brandColored
+          ))
 
-        viewModel
-            .contactPublisher
+          button
+            .action
             .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in setupNavigationBar(contact: $0) }
-            .store(in: &cancellables)
-
-        setupCollectionView()
-        setupInputController()
-        setupBindings()
-
-        KeyboardListener.shared.add(delegate: self)
-    }
-
-    // MARK: Private
-
-    private func setupCollectionView() {
-        chatLayout.configure(layoutDelegate)
-        collectionView = .init(on: screenView, with: chatLayout)
-        collectionView.delegate = self
-        collectionView.dataSource = self
-
-        collectionView.register(OutgoingTextCell.self)
-        collectionView.register(IncomingTextCell.self)
-        collectionView.register(IncomingAudioCell.self)
-        collectionView.register(OutgoingAudioCell.self)
-        collectionView.register(IncomingImageCell.self)
-        collectionView.register(IncomingReplyCell.self)
-        collectionView.register(OutgoingImageCell.self)
-        collectionView.register(OutgoingReplyCell.self)
-        collectionView.register(OutgoingFailedTextCell.self)
-        collectionView.register(OutgoingFailedReplyCell.self)
-
-        collectionView.registerSectionHeader(SectionHeaderView.self)
-    }
-
-    private func setupNavigationBar(contact: Contact) {
-        screenView.set(name: contact.nickname ?? contact.username!)
-        avatarView.snp.makeConstraints { $0.width.height.equalTo(35) }
-
-        let title = (contact.nickname ?? contact.username) ?? ""
-        avatarView.setupProfile(title: title, image: contact.photo, size: .small)
-
-        nameLabel.text = title
-        nameLabel.textColor = Asset.neutralActive.color
-        nameLabel.font = Fonts.Mulish.semiBold.font(size: 18.0)
-
-        moreButton.setImage(Asset.chatMore.image, for: .normal)
-        moreButton.addTarget(self, action: #selector(didTapDots), for: .touchUpInside)
-
-        infoView.addTarget(self, action: #selector(didTapInfo), for: .touchUpInside)
-
-        infoView.addSubview(avatarView)
-        infoView.addSubview(nameLabel)
+            .sink { [unowned self] in
+              navigator.perform(DismissModal(from: self)) { [weak self] in
+                guard let self else { return }
+                self.drawerCancellables.removeAll()
+              }
+            }.store(in: &drawerCancellables)
 
-        avatarView.snp.makeConstraints {
-            $0.top.left.bottom.equalToSuperview()
+          navigator.perform(PresentDrawer(items: [
+            DrawerText(
+              font: Fonts.Mulish.semiBold.font(size: 14.0),
+              text: Localized.Chat.RoundDrawer.title,
+              color: Asset.neutralWeak.color,
+              lineHeightMultiple: 1.35,
+              spacingAfter: 25
+            ),
+            button
+          ], isDismissable: true, from: self))
+        case .none:
+          break
         }
 
-        nameLabel.snp.makeConstraints {
-            $0.centerY.equalToSuperview()
-            $0.left.equalTo(avatarView.snp.right).offset(13)
-            $0.right.lessThanOrEqualToSuperview()
+        viewModel.didNavigateSomewhere()
+      }.store(in: &cancellables)
+  }
+
+  private func setupBindings() {
+    sheet
+      .actionPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        switch $0 {
+        case .clear:
+          presentDeleteAllDrawer()
+        case .details:
+          navigator.perform(PresentContact(
+            contact: viewModel.contact,
+            on: navigationController!
+          ))
+        case .report:
+          presentReportDrawer()
         }
+      }.store(in: &cancellables)
 
-        navigationItem.rightBarButtonItem = UIBarButtonItem(customView: moreButton)
-        navigationItem.leftBarButtonItem = UIBarButtonItem(customView: infoView)
-        navigationItem.leftItemsSupplementBackButton = true
-    }
-
-    private func setupInputController() {
-        inputComponent.setMaxHeight { [weak self] in
-            guard let self = self else { return 150 }
-
-            let maxHeight = self.collectionView.frame.height
-            - self.collectionView.adjustedContentInset.top
-            - self.collectionView.adjustedContentInset.bottom
-            + self.inputComponent.bounds.height
+    viewModel
+      .shouldDisplayEmptyView
+      .removeDuplicates()
+      .sink { [unowned self] in
+        screenView.titleLabel.isHidden = !$0
 
-            return maxHeight * 0.9
+        if $0 == true {
+          screenView.bringSubviewToFront(screenView.titleLabel)
+        }
+      }.store(in: &cancellables)
+
+    viewModel
+      .reportPopupPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        presentReportDrawer()
+      }.store(in: &cancellables)
+
+    viewModel
+      .isOnline
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [weak screenView] in
+        screenView?.displayNetworkIssue(!$0)
+      }.store(in: &cancellables)
+
+    viewModel
+      .messages
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] sections in
+        func process() {
+          let changeSet = StagedChangeset(source: self.sections, target: sections).flattenIfPossible()
+          collectionView.reload(
+            using: changeSet,
+            interrupt: { changeSet in
+              guard !self.sections.isEmpty else { return true }
+              return false
+            }, onInterruptedReload: {
+              guard let lastSection = self.sections.last else { return }
+              let positionSnapshot = ChatLayoutPositionSnapshot(
+                indexPath: IndexPath(
+                  item: lastSection.elements.count - 1,
+                  section: self.sections.count - 1
+                ),
+                kind: .cell,
+                edge: .bottom
+              )
+
+              self.collectionView.reloadData()
+              self.chatLayout.restoreContentOffset(with: positionSnapshot)
+            },
+            completion: nil,
+            setData: { self.sections = $0 }
+          )
         }
 
-        viewModel.replyPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] senderTitle, messageText in
-                inputComponent.setupReply(message: messageText, sender: senderTitle)
-            }
-            .store(in: &cancellables)
-
-        viewModel.navigation
-            .receive(on: DispatchQueue.main)
-            .removeDuplicates()
-            .sink { [unowned self] in
-                switch $0 {
-                case .library:
-                    coordinator.toLibrary(from: self)
-                case .camera:
-                    coordinator.toCamera(from: self)
-                case .cameraPermission:
-                    coordinator.toPermission(type: .camera, from: self)
-                case .microphonePermission:
-                     coordinator.toPermission(type: .microphone, from: self)
-                case .libraryPermission:
-                    coordinator.toPermission(type: .library, from: self)
-                case .webview(let urlString):
-                    coordinator.toWebview(with: urlString, from: self)
-                case .waitingRound:
-                    coordinator.toDrawer(makeWaitingRoundDrawer(), from: self)
-                case .none:
-                    break
-                }
-
-                viewModel.didNavigateSomewhere()
-            }.store(in: &cancellables)
-    }
-
-    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
-                switch $0 {
-                case .clear:
-                    presentDeleteAllDrawer()
-                case .details:
-                    coordinator.toContact(viewModel.contact, from: self)
-                case .report:
-                    presentReportDrawer()
-                }
-            }.store(in: &cancellables)
-
-        viewModel
-            .shouldDisplayEmptyView
-            .removeDuplicates()
-            .sink { [unowned self] in
-                screenView.titleLabel.isHidden = !$0
-
-                if $0 == true {
-                    screenView.bringSubviewToFront(screenView.titleLabel)
-                }
-            }.store(in: &cancellables)
-
-        viewModel.reportPopupPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                presentReportDrawer()
-            }.store(in: &cancellables)
-
-        viewModel.isOnline
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [weak screenView] in screenView?.displayNetworkIssue(!$0) }
-            .store(in: &cancellables)
-
-        viewModel.messages
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] sections in
-                func process() {
-                    let changeSet = StagedChangeset(source: self.sections, target: sections).flattenIfPossible()
-                    collectionView.reload(
-                        using: changeSet,
-                        interrupt: { changeSet in
-                            guard !self.sections.isEmpty else { return true }
-                            return false
-                        }, onInterruptedReload: {
-                            guard let lastSection = self.sections.last else { return }
-                            let positionSnapshot = ChatLayoutPositionSnapshot(
-                                indexPath: IndexPath(
-                                    item: lastSection.elements.count - 1,
-                                    section: self.sections.count - 1
-                                ),
-                                kind: .cell,
-                                edge: .bottom
-                            )
-
-                            self.collectionView.reloadData()
-                            self.chatLayout.restoreContentOffset(with: positionSnapshot)
-                        },
-                        completion: nil,
-                        setData: { self.sections = $0 }
-                    )
-                }
-
-                guard currentInterfaceActions.options.isEmpty else {
-                    let reaction = SetActor<Set<InterfaceActions>, ReactionTypes>.Reaction(
-                        type: .delayedUpdate,
-                        action: .onEmpty,
-                        executionType: .once,
-                        actionBlock: { [weak self] in
-                            guard let _ = self else { return }
-                            process()
-                        }
-                    )
-
-                    currentInterfaceActions.add(reaction: reaction)
-                    return
-                }
-
-                process()
+        guard currentInterfaceActions.options.isEmpty else {
+          let reaction = SetActor<Set<InterfaceActions>, ReactionTypes>.Reaction(
+            type: .delayedUpdate,
+            action: .onEmpty,
+            executionType: .once,
+            actionBlock: { [weak self] in
+              guard let _ = self else { return }
+              process()
             }
-            .store(in: &cancellables)
-    }
-
-    func scrollToBottom(completion: (() -> Void)? = nil) {
-        let contentOffsetAtBottom = CGPoint(
-            x: collectionView.contentOffset.x,
-            y: chatLayout.collectionViewContentSize.height
-            - collectionView.frame.height + collectionView.adjustedContentInset.bottom
-        )
-
-        guard contentOffsetAtBottom.y > collectionView.contentOffset.y else { completion?(); return }
-
-        let initialOffset = collectionView.contentOffset.y
-        let delta = contentOffsetAtBottom.y - initialOffset
+          )
 
-        if abs(delta) > chatLayout.visibleBounds.height {
-            animator = ManualAnimator()
-            animator?.animate(duration: TimeInterval(0.25), curve: .easeInOut) { [weak self] percentage in
-                guard let self = self else { return }
-
-                self.collectionView.contentOffset = CGPoint(x: self.collectionView.contentOffset.x, y: initialOffset + (delta * percentage))
-                if percentage == 1.0 {
-                    self.animator = nil
-                    let positionSnapshot = ChatLayoutPositionSnapshot(indexPath: IndexPath(item: 0, section: 0), kind: .footer, edge: .bottom)
-                    self.chatLayout.restoreContentOffset(with: positionSnapshot)
-                    self.currentInterfaceActions.options.remove(.scrollingToBottom)
-                    completion?()
-                }
-            }
-        } else {
-            currentInterfaceActions.options.insert(.scrollingToBottom)
-            UIView.animate(withDuration: 0.25, animations: {
-                self.collectionView.setContentOffset(contentOffsetAtBottom, animated: true)
-            }, completion: { [weak self] _ in
-                self?.currentInterfaceActions.options.remove(.scrollingToBottom)
-                completion?()
-            })
+          currentInterfaceActions.add(reaction: reaction)
+          return
         }
-    }
-
-    private func makeWaitingRoundDrawer() -> UIViewController {
-        let text = DrawerText(
-            font: Fonts.Mulish.semiBold.font(size: 14.0),
-            text: Localized.Chat.RoundDrawer.title,
-            color: Asset.neutralWeak.color,
-            lineHeightMultiple: 1.35,
-            spacingAfter: 25
-        )
-
-        let button = DrawerCapsuleButton(model: .init(
-            title: Localized.Chat.RoundDrawer.action,
-            style: .brandColored
-        ))
 
-        let drawer = DrawerController(with: [text, button])
-
-        button.action
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned drawer] in drawer.dismiss(animated: true) }
-            .store(in: &drawer.cancellables)
-
-        return drawer
-    }
-
-    private func presentReportDrawer() {
-        var config = MakeReportDrawer.Config()
-        config.onReport = { [weak self] in
-            guard let self = self else { return }
-            let screenshot = try! self.makeAppScreenshot()
-            self.viewModel.report(screenshot: screenshot) { success in
-                guard success else { return }
-                self.navigationController?.popViewController(animated: true)
-            }
+        process()
+      }
+      .store(in: &cancellables)
+  }
+
+  func scrollToBottom(completion: (() -> Void)? = nil) {
+    let contentOffsetAtBottom = CGPoint(
+      x: collectionView.contentOffset.x,
+      y: chatLayout.collectionViewContentSize.height
+      - collectionView.frame.height + collectionView.adjustedContentInset.bottom
+    )
+
+    guard contentOffsetAtBottom.y > collectionView.contentOffset.y else { completion?(); return }
+
+    let initialOffset = collectionView.contentOffset.y
+    let delta = contentOffsetAtBottom.y - initialOffset
+
+    if abs(delta) > chatLayout.visibleBounds.height {
+      animator = ManualAnimator()
+      animator?.animate(duration: TimeInterval(0.25), curve: .easeInOut) { [weak self] percentage in
+        guard let self else { return }
+
+        self.collectionView.contentOffset = CGPoint(x: self.collectionView.contentOffset.x, y: initialOffset + (delta * percentage))
+        if percentage == 1.0 {
+          self.animator = nil
+          let positionSnapshot = ChatLayoutPositionSnapshot(indexPath: IndexPath(item: 0, section: 0), kind: .footer, edge: .bottom)
+          self.chatLayout.restoreContentOffset(with: positionSnapshot)
+          self.currentInterfaceActions.options.remove(.scrollingToBottom)
+          completion?()
         }
-        let drawer = makeReportDrawer(config)
-        coordinator.toDrawer(drawer, from: self)
-    }
-
-    private func presentDeleteAllDrawer() {
-        let clearButton = CapsuleButton()
-        clearButton.setStyle(.red)
-        clearButton.setTitle(Localized.Chat.Clear.action, for: .normal)
-
-        let cancelButton = CapsuleButton()
-        cancelButton.setStyle(.seeThrough)
-        cancelButton.setTitle(Localized.Chat.Clear.cancel, for: .normal)
-
-        let drawer = DrawerController(with: [
-            DrawerImage(
-                image: Asset.drawerNegative.image
-            ),
-            DrawerText(
-                font: Fonts.Mulish.semiBold.font(size: 18.0),
-                text: Localized.Chat.Clear.title,
-                color: Asset.neutralActive.color
-            ),
-            DrawerText(
-                font: Fonts.Mulish.semiBold.font(size: 14.0),
-                text: Localized.Chat.Clear.subtitle,
-                color: Asset.neutralWeak.color,
-                lineHeightMultiple: 1.35,
-                spacingAfter: 25
-            ),
-            DrawerStack(
-                spacing: 20.0,
-                views: [clearButton, cancelButton]
-            )
-        ])
-
-        clearButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned drawer, weak self] in
-                drawer.dismiss(animated: true) {
-                    self?.viewModel.didRequestDeleteAll()
-                }
-            }
-            .store(in: &drawer.cancellables)
-
-        cancelButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned drawer] in drawer.dismiss(animated: true) }
-            .store(in: &drawer.cancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
-
-    private func previewItemAt(_ indexPath: IndexPath) {
-        let item = sections[indexPath.section].elements[indexPath.item]
-        guard let ftid = item.fileTransferId,
-              item.status != .receiving,
-              item.status != .receivingFailed else { return }
-
-        let ft = viewModel.getFileTransferWith(id: ftid)
-        fileURL = FileManager.url(for: "\(ft.name).\(ft.type)")
-        coordinator.toPreview(from: self)
-    }
-
-    // MARK: Selectors
-
-    @objc private func didTapDots() {
-        coordinator.toMenuSheet(sheet, from: self)
-    }
-
-    @objc private func didTapInfo() {
-        coordinator.toContact(viewModel.contact, from: self)
-    }
+      }
+    } else {
+      currentInterfaceActions.options.insert(.scrollingToBottom)
+      UIView.animate(withDuration: 0.25, animations: {
+        self.collectionView.setContentOffset(contentOffsetAtBottom, animated: true)
+      }, completion: { [weak self] _ in
+        self?.currentInterfaceActions.options.remove(.scrollingToBottom)
+        completion?()
+      })
+    }
+  }
+
+  private func presentReportDrawer() {
+    let cancelButton = CapsuleButton()
+    cancelButton.setStyle(.seeThrough)
+    cancelButton.setTitle(Localized.Chat.Report.cancel, for: .normal)
+
+    let reportButton = CapsuleButton()
+    reportButton.setStyle(.red)
+    reportButton.setTitle(Localized.Chat.Report.action, for: .normal)
+
+    reportButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+//          let screenshot = try! self.makeAppScreenshot()
+//          self.viewModel.report(screenshot: screenshot) { success in
+//            guard success else { return }
+//            self.navigationController?.popViewController(animated: true)
+//          }
+        }
+      }.store(in: &drawerCancellables)
+
+    cancelButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerImage(
+        image: Asset.drawerNegative.image
+      ),
+      DrawerText(
+        font: Fonts.Mulish.semiBold.font(size: 18.0),
+        text: Localized.Chat.Report.title,
+        color: Asset.neutralActive.color
+      ),
+      DrawerText(
+        font: Fonts.Mulish.semiBold.font(size: 14.0),
+        text: Localized.Chat.Report.subtitle,
+        color: Asset.neutralWeak.color,
+        lineHeightMultiple: 1.35,
+        spacingAfter: 25
+      ),
+      DrawerStack(
+        axis: .vertical,
+        spacing: 20.0,
+        views: [reportButton, cancelButton]
+      )
+    ], isDismissable: true, from: self))
+  }
+
+  private func presentDeleteAllDrawer() {
+    let clearButton = CapsuleButton()
+    clearButton.setStyle(.red)
+    clearButton.setTitle(Localized.Chat.Clear.action, for: .normal)
+
+    let cancelButton = CapsuleButton()
+    cancelButton.setStyle(.seeThrough)
+    cancelButton.setTitle(Localized.Chat.Clear.cancel, for: .normal)
+
+    clearButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          self.viewModel.didRequestDeleteAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    cancelButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerImage(
+        image: Asset.drawerNegative.image
+      ),
+      DrawerText(
+        font: Fonts.Mulish.semiBold.font(size: 18.0),
+        text: Localized.Chat.Clear.title,
+        color: Asset.neutralActive.color
+      ),
+      DrawerText(
+        font: Fonts.Mulish.semiBold.font(size: 14.0),
+        text: Localized.Chat.Clear.subtitle,
+        color: Asset.neutralWeak.color,
+        lineHeightMultiple: 1.35,
+        spacingAfter: 25
+      ),
+      DrawerStack(
+        spacing: 20.0,
+        views: [clearButton, cancelButton]
+      )
+    ], isDismissable: true, from: self))
+  }
+
+  private func previewItemAt(_ indexPath: IndexPath) {
+    let item = sections[indexPath.section].elements[indexPath.item]
+    guard let ftid = item.fileTransferId,
+          item.status != .receiving,
+          item.status != .receivingFailed else { return }
+
+    let ft = viewModel.getFileTransferWith(id: ftid)
+    fileURL = FileManager.url(for: "\(ft.name).\(ft.type)")
+    //coordinator.toPreview(from: self)
+  }
+
+  @objc private func didTapDots() {
+    //coordinator.toMenuSheet(sheet, from: self)
+  }
+
+  @objc private func didTapInfo() {
+    navigator.perform(PresentContact(
+      contact: viewModel.contact,
+      on: navigationController!
+    ))
+  }
 }
 
 extension SingleChatController: UICollectionViewDataSource {
-    public func numberOfSections(in collectionView: UICollectionView) -> Int {
-        sections.count
-    }
-
-    public func collectionView(
-        _ collectionView: UICollectionView,
-        viewForSupplementaryElementOfKind kind: String,
-        at indexPath: IndexPath
-    ) -> UICollectionReusableView {
-        let sectionHeader: SectionHeaderView = collectionView.dequeueSupplementaryView(forIndexPath: indexPath)
-        sectionHeader.title.text = sections[indexPath.section].model.date.asDayOfMonth()
-        return sectionHeader
-    }
-
-    public func collectionView(
-        _ collectionView: UICollectionView,
-        numberOfItemsInSection section: Int
-    ) -> Int {
-        sections[section].elements.count
-    }
-
-    public func collectionView(
-        _ collectionView: UICollectionView,
-        cellForItemAt indexPath: IndexPath
-    ) -> UICollectionViewCell {
-
-        let showRound: (String?) -> Void = viewModel.showRoundFrom(_:)
-        let item = sections[indexPath.section].elements[indexPath.item]
-        let replyContent: (Data) -> (String, String) = viewModel.getReplyContent(for:)
-        let performReply: () -> Void = { [weak self] in self?.viewModel.didRequestReply(item) }
-
-        let factory = CellFactory.combined(factories: [
-            .incomingImage(transfer: viewModel.getFileTransferWith(id:)),
-            .outgoingImage(transfer: viewModel.getFileTransferWith(id:)),
-            .incomingAudio(voxophone: voxophone, transfer: viewModel.getFileTransferWith(id:)),
-            .outgoingAudio(voxophone: voxophone, transfer: viewModel.getFileTransferWith(id:)),
-            .incomingText(performReply: performReply, showRound: showRound),
-            .outgoingText(performReply: performReply, showRound: showRound),
-            .outgoingFailedText(performReply: performReply),
-            .incomingReply(performReply: performReply, replyContent: replyContent, showRound: showRound),
-            .outgoingReply(performReply: performReply, replyContent: replyContent, showRound: showRound),
-            .outgoingFailedReply(performReply: performReply, replyContent: replyContent)
-        ])
-
-        return factory(item: item, collectionView: collectionView, indexPath: indexPath)
-    }
+  public func numberOfSections(in collectionView: UICollectionView) -> Int {
+    sections.count
+  }
+
+  public func collectionView(
+    _ collectionView: UICollectionView,
+    viewForSupplementaryElementOfKind kind: String,
+    at indexPath: IndexPath
+  ) -> UICollectionReusableView {
+    let sectionHeader: SectionHeaderView = collectionView.dequeueSupplementaryView(forIndexPath: indexPath)
+    sectionHeader.title.text = sections[indexPath.section].model.date.asDayOfMonth()
+    return sectionHeader
+  }
+
+  public func collectionView(
+    _ collectionView: UICollectionView,
+    numberOfItemsInSection section: Int
+  ) -> Int {
+    sections[section].elements.count
+  }
+
+  public func collectionView(
+    _ collectionView: UICollectionView,
+    cellForItemAt indexPath: IndexPath
+  ) -> UICollectionViewCell {
+
+    let showRound: (String?) -> Void = viewModel.showRoundFrom(_:)
+    let item = sections[indexPath.section].elements[indexPath.item]
+    let replyContent: (Data) -> (String, String) = viewModel.getReplyContent(for:)
+    let performReply: () -> Void = { [weak self] in self?.viewModel.didRequestReply(item) }
+
+    let factory = CellFactory.combined(factories: [
+      .incomingImage(transfer: viewModel.getFileTransferWith(id:)),
+      .outgoingImage(transfer: viewModel.getFileTransferWith(id:)),
+      .incomingAudio(voxophone: voxophone, transfer: viewModel.getFileTransferWith(id:)),
+      .outgoingAudio(voxophone: voxophone, transfer: viewModel.getFileTransferWith(id:)),
+      .incomingText(performReply: performReply, showRound: showRound),
+      .outgoingText(performReply: performReply, showRound: showRound),
+      .outgoingFailedText(performReply: performReply),
+      .incomingReply(performReply: performReply, replyContent: replyContent, showRound: showRound),
+      .outgoingReply(performReply: performReply, replyContent: replyContent, showRound: showRound),
+      .outgoingFailedReply(performReply: performReply, replyContent: replyContent)
+    ])
+
+    return factory(item: item, collectionView: collectionView, indexPath: indexPath)
+  }
 }
 
 extension SingleChatController: KeyboardListenerDelegate {
-    fileprivate var isUserInitiatedScrolling: Bool {
-        collectionView.isDragging || collectionView.isDecelerating
-    }
+  fileprivate var isUserInitiatedScrolling: Bool {
+    collectionView.isDragging || collectionView.isDecelerating
+  }
 
-    func keyboardWillChangeFrame(info: KeyboardInfo) {
-        let keyWindow = UIApplication.shared
-            .connectedScenes
-            .flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
-            .first { $0.isKeyWindow }
+  func keyboardWillChangeFrame(info: KeyboardInfo) {
+    let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first
 
-        guard let keyWindow = keyWindow else {
-            fatalError("[keyboardWillChangeFrame]: Couldn't get key window")
-        }
-
-        let keyboardFrame = keyWindow.convert(info.frameEnd, to: view)
-
-        guard !currentInterfaceActions.options.contains(.changingFrameSize),
-              collectionView.contentInsetAdjustmentBehavior != .never,
-              collectionView.convert(collectionView.bounds, to: keyWindow).maxY > info.frameEnd.minY else { return }
-
-        currentInterfaceActions.options.insert(.changingKeyboardFrame)
-        let newBottomInset = collectionView.frame.minY + collectionView.frame.size.height - keyboardFrame.minY - collectionView.safeAreaInsets.bottom
-        if newBottomInset > 0,
-           collectionView.contentInset.bottom != newBottomInset {
-            let positionSnapshot = chatLayout.getContentOffsetSnapshot(from: .bottom)
-
-            currentInterfaceActions.options.insert(.changingContentInsets)
-            UIView.animate(withDuration: info.animationDuration, animations: {
-                self.collectionView.performBatchUpdates({
-                    self.collectionView.contentInset.bottom = newBottomInset
-                    self.collectionView.verticalScrollIndicatorInsets.bottom = newBottomInset
-                }, completion: nil)
-
-                if let positionSnapshot = positionSnapshot, !self.isUserInitiatedScrolling {
-                    self.chatLayout.restoreContentOffset(with: positionSnapshot)
-                }
-            }, completion: { _ in
-                self.currentInterfaceActions.options.remove(.changingContentInsets)
-            })
-        }
-    }
-
-    func keyboardDidChangeFrame(info: KeyboardInfo) {
-        guard currentInterfaceActions.options.contains(.changingKeyboardFrame) else { return }
-        currentInterfaceActions.options.remove(.changingKeyboardFrame)
+    guard let keyWindow = keyWindow else {
+      fatalError("[keyboardWillChangeFrame]: Couldn't get key window")
     }
-}
-
-extension SingleChatController: UICollectionViewDelegate {
-    private func makeTargetedPreview(for configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
-        guard let identifier = configuration.identifier as? String,
-              let first = identifier.components(separatedBy: "|").first,
-              let last = identifier.components(separatedBy: "|").last,
-              let item = Int(first), let section = Int(last),
-              let cell = collectionView.cellForItem(at: IndexPath(item: item, section: section)) else {
-                  return nil
-              }
 
-        let parameters = UIPreviewParameters()
-        parameters.backgroundColor = .clear
+    let keyboardFrame = keyWindow.convert(info.frameEnd, to: view)
 
-        let status = sections[section].elements[item].status
+    guard !currentInterfaceActions.options.contains(.changingFrameSize),
+          collectionView.contentInsetAdjustmentBehavior != .never,
+          collectionView.convert(collectionView.bounds, to: keyWindow).maxY > info.frameEnd.minY else { return }
 
-        if status == .received || status == .receiving {
-            var leftView: UIView!
+    currentInterfaceActions.options.insert(.changingKeyboardFrame)
+    let newBottomInset = collectionView.frame.minY + collectionView.frame.size.height - keyboardFrame.minY - collectionView.safeAreaInsets.bottom
+    if newBottomInset > 0,
+       collectionView.contentInset.bottom != newBottomInset {
+      let positionSnapshot = chatLayout.getContentOffsetSnapshot(from: .bottom)
 
-            if let cell = cell as? IncomingReplyCell {
-                leftView = cell.leftView
-            } else if let cell = cell as? IncomingAudioCell {
-                leftView = cell.leftView
-            } else if let cell = cell as? IncomingTextCell {
-                leftView = cell.leftView
-            } else if let cell = cell as? IncomingImageCell {
-                leftView = cell.leftView
-            }
-
-            parameters.visiblePath = UIBezierPath(roundedRect: leftView.bounds, cornerRadius: 13)
-            return UITargetedPreview(view: leftView, parameters: parameters)
-        }
+      currentInterfaceActions.options.insert(.changingContentInsets)
+      UIView.animate(withDuration: info.animationDuration, animations: {
+        self.collectionView.performBatchUpdates({
+          self.collectionView.contentInset.bottom = newBottomInset
+          self.collectionView.verticalScrollIndicatorInsets.bottom = newBottomInset
+        }, completion: nil)
 
-        #warning("TODO: Refactor")
-
-        var rightView: UIView!
-
-        if let cell = cell as? OutgoingTextCell {
-            rightView = cell.rightView
-        } else if let cell = cell as? OutgoingAudioCell {
-            rightView = cell.rightView
-        } else if let cell = cell as? OutgoingReplyCell {
-            rightView = cell.rightView
-        } else if let cell = cell as? OutgoingImageCell {
-            rightView = cell.rightView
-        } else if let cell = cell as? OutgoingFailedTextCell {
-            rightView = cell.rightView
-        } else if let cell = cell as? OutgoingFailedReplyCell {
-            rightView = cell.rightView
+        if let positionSnapshot = positionSnapshot, !self.isUserInitiatedScrolling {
+          self.chatLayout.restoreContentOffset(with: positionSnapshot)
         }
-
-        parameters.visiblePath = UIBezierPath(roundedRect: rightView.bounds, cornerRadius: 13)
-        return UITargetedPreview(view: rightView, parameters: parameters)
+      }, completion: { _ in
+        self.currentInterfaceActions.options.remove(.changingContentInsets)
+      })
     }
+  }
 
-    public func collectionView(
-        _ collectionView: UICollectionView,
-        previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration
-    ) -> UITargetedPreview? {
-        makeTargetedPreview(for: configuration)
-    }
-
-    public func collectionView(
-        _ collectionView: UICollectionView,
-        previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration
-    ) -> UITargetedPreview? {
-        makeTargetedPreview(for: configuration)
-    }
+  func keyboardDidChangeFrame(info: KeyboardInfo) {
+    guard currentInterfaceActions.options.contains(.changingKeyboardFrame) else { return }
+    currentInterfaceActions.options.remove(.changingKeyboardFrame)
+  }
+}
 
-    public func collectionView(
-        _ collectionView: UICollectionView,
-        contextMenuConfigurationForItemAt indexPath: IndexPath,
-        point: CGPoint
-    ) -> UIContextMenuConfiguration? {
-        UIContextMenuConfiguration(
-            identifier: "\(indexPath.item)|\(indexPath.section)" as NSCopying,
-            previewProvider: nil
-        ) { [weak self] _ in
-
-            guard let self = self else { return nil }
-            let item = self.sections[indexPath.section].elements[indexPath.item]
-
-            var children = [
-                ActionFactory.build(from: item, action: .copy, closure: self.viewModel.didRequestCopy(_:)),
-                ActionFactory.build(from: item, action: .retry, closure: self.viewModel.didRequestRetry(_:)),
-                ActionFactory.build(from: item, action: .reply, closure: self.viewModel.didRequestReply(_:)),
-                ActionFactory.build(from: item, action: .delete, closure: self.viewModel.didRequestDeleteSingle(_:))
-            ]
-
-            if self.reportingStatus.isEnabled() {
-                children.append(
-                    ActionFactory.build(from: item, action: .report, closure: self.viewModel.didRequestReport(_:))
-                )
-            }
+extension SingleChatController: UICollectionViewDelegate {
+  private func makeTargetedPreview(for configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
+    guard let identifier = configuration.identifier as? String,
+          let first = identifier.components(separatedBy: "|").first,
+          let last = identifier.components(separatedBy: "|").last,
+          let item = Int(first), let section = Int(last),
+          let cell = collectionView.cellForItem(at: IndexPath(item: item, section: section)) else {
+      return nil
+    }
+
+    let parameters = UIPreviewParameters()
+    parameters.backgroundColor = .clear
+
+    let status = sections[section].elements[item].status
+
+    if status == .received || status == .receiving {
+      var leftView: UIView!
+
+      if let cell = cell as? IncomingReplyCell {
+        leftView = cell.leftView
+      } else if let cell = cell as? IncomingAudioCell {
+        leftView = cell.leftView
+      } else if let cell = cell as? IncomingTextCell {
+        leftView = cell.leftView
+      } else if let cell = cell as? IncomingImageCell {
+        leftView = cell.leftView
+      }
+
+      parameters.visiblePath = UIBezierPath(roundedRect: leftView.bounds, cornerRadius: 13)
+      return UITargetedPreview(view: leftView, parameters: parameters)
+    }
+
+    var rightView: UIView!
+
+    if let cell = cell as? OutgoingTextCell {
+      rightView = cell.rightView
+    } else if let cell = cell as? OutgoingAudioCell {
+      rightView = cell.rightView
+    } else if let cell = cell as? OutgoingReplyCell {
+      rightView = cell.rightView
+    } else if let cell = cell as? OutgoingImageCell {
+      rightView = cell.rightView
+    } else if let cell = cell as? OutgoingFailedTextCell {
+      rightView = cell.rightView
+    } else if let cell = cell as? OutgoingFailedReplyCell {
+      rightView = cell.rightView
+    }
+
+    parameters.visiblePath = UIBezierPath(roundedRect: rightView.bounds, cornerRadius: 13)
+    return UITargetedPreview(view: rightView, parameters: parameters)
+  }
+
+  public func collectionView(
+    _ collectionView: UICollectionView,
+    previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration
+  ) -> UITargetedPreview? {
+    makeTargetedPreview(for: configuration)
+  }
+
+  public func collectionView(
+    _ collectionView: UICollectionView,
+    previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration
+  ) -> UITargetedPreview? {
+    makeTargetedPreview(for: configuration)
+  }
+
+  public func collectionView(
+    _ collectionView: UICollectionView,
+    contextMenuConfigurationForItemAt indexPath: IndexPath,
+    point: CGPoint
+  ) -> UIContextMenuConfiguration? {
+    UIContextMenuConfiguration(
+      identifier: "\(indexPath.item)|\(indexPath.section)" as NSCopying,
+      previewProvider: nil
+    ) { [weak self] _ in
+
+      guard let self else { return nil }
+      let item = self.sections[indexPath.section].elements[indexPath.item]
+
+      var children = [
+        ActionFactory.build(from: item, action: .copy, closure: self.viewModel.didRequestCopy(_:)),
+        ActionFactory.build(from: item, action: .retry, closure: self.viewModel.didRequestRetry(_:)),
+        ActionFactory.build(from: item, action: .reply, closure: self.viewModel.didRequestReply(_:)),
+        ActionFactory.build(from: item, action: .delete, closure: self.viewModel.didRequestDeleteSingle(_:))
+      ]
+
+      if self.reportingStatus.isEnabled() {
+        children.append(
+          ActionFactory.build(from: item, action: .report, closure: self.viewModel.didRequestReport(_:))
+        )
+      }
 
-            return UIMenu(title: "", children: children.compactMap { $0 })
-        }
+      return UIMenu(title: "", children: children.compactMap { $0 })
     }
+  }
 
-    public func collectionView(
-        _ collectionView: UICollectionView,
-        didSelectItemAt indexPath: IndexPath
-    ) {
-        previewItemAt(indexPath)
-    }
+  public func collectionView(
+    _ collectionView: UICollectionView,
+    didSelectItemAt indexPath: IndexPath
+  ) {
+    previewItemAt(indexPath)
+  }
 }
 
 extension SingleChatController: UIImagePickerControllerDelegate {
-    public func imagePickerController(
-        _ picker: UIImagePickerController,
-        didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
-    ) {
-        picker.delegate = nil
-        picker.dismiss(animated: true)
-        guard let image = info[.originalImage] as? UIImage else { return }
-
-        DispatchQueue.global().async { [weak self] in
-            self?.viewModel.didSend(image: image)
-        }
-    }
+  public func imagePickerController(
+    _ picker: UIImagePickerController,
+    didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
+  ) {
+    picker.delegate = nil
+    picker.dismiss(animated: true)
+    guard let image = info[.originalImage] as? UIImage else { return }
+
+    DispatchQueue.global().async { [weak self] in
+      self?.viewModel.didSend(image: image)
+    }
+  }
 }
 
 extension SingleChatController: UINavigationControllerDelegate {}
 
 extension SingleChatController: QLPreviewControllerDataSource {
-    public func numberOfPreviewItems(in controller: QLPreviewController) -> Int { 1 }
+  public func numberOfPreviewItems(in controller: QLPreviewController) -> Int { 1 }
 
-    public func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
-        fileURL! as QLPreviewItem
-    }
+  public func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
+    fileURL! as QLPreviewItem
+  }
 }
 
 extension SingleChatController: QLPreviewControllerDelegate {
-    public func previewControllerDidDismiss(_ controller: QLPreviewController) {
-        fileURL = nil
-    }
+  public func previewControllerDidDismiss(_ controller: QLPreviewController) {
+    fileURL = nil
+  }
 }
diff --git a/Sources/ChatFeature/Controllers/WebController.swift b/Sources/ChatFeature/Controllers/WebController.swift
deleted file mode 100644
index b093af5ef2232a2b23c9499a16e68b2ed21e2874..0000000000000000000000000000000000000000
--- a/Sources/ChatFeature/Controllers/WebController.swift
+++ /dev/null
@@ -1,69 +0,0 @@
-import UIKit
-import WebKit
-
-public final class WebScreen: UIViewController {
-    lazy private(set) var webView = WebView()
-
-    private var url: String!
-
-    public init(url: String) {
-        self.url = url
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    public required init?(coder: NSCoder) { nil }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupScreen()
-    }
-
-    private func setupScreen() {
-        view.addSubview(webView)
-        webView.snp.makeConstraints { $0.edges.equalToSuperview() }
-
-        webView.webView.load(URLRequest(url: URL(string: url)!))
-        webView.closeButton = UIBarButtonItem(title: "Close", style: .done, target: self,
-                                              action: #selector(didTappedClose))
-    }
-
-    @objc private func didTappedClose() {
-        dismiss(animated: true)
-    }
-}
-
-final class WebView: UIView {
-
-    let webView = WKWebView()
-    let navBar = UINavigationBar()
-    var closeButton: UIBarButtonItem! {
-        didSet { navBar.topItem?.leftBarButtonItem = closeButton }
-    }
-
-    init() {
-        super.init(frame: .zero)
-        setupLayout()
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    private func setupLayout() {
-        backgroundColor = .white
-        navBar.items = [UINavigationItem(title: "")]
-        addSubview(webView)
-        addSubview(navBar)
-
-        navBar.snp.makeConstraints { make -> Void in
-            make.top.equalTo(safeAreaLayoutGuide)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-        }
-
-        webView.snp.makeConstraints { make -> Void in
-            make.bottom.equalToSuperview()
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.top.equalTo(navBar.snp.bottom)
-        }
-    }
-}
diff --git a/Sources/ChatFeature/Coordinator/ChatCoordinator.swift b/Sources/ChatFeature/Coordinator/ChatCoordinator.swift
deleted file mode 100644
index 64a8e46de2eada72bba4b371bed2b1d441a2b846..0000000000000000000000000000000000000000
--- a/Sources/ChatFeature/Coordinator/ChatCoordinator.swift
+++ /dev/null
@@ -1,106 +0,0 @@
-import UIKit
-import Models
-import Shared
-import QuickLook
-import Permissions
-import Presentation
-import XXModels
-
-public protocol ChatCoordinating {
-    func toCamera(from: UIViewController)
-    func toLibrary(from: UIViewController)
-    func toPreview(from: UIViewController)
-    func toRetrySheet(from: UIViewController)
-    func toContact(_: Contact, from: UIViewController)
-    func toWebview(with: String, from: UIViewController)
-    func toDrawer(_: UIViewController, from: UIViewController)
-    func toMenuSheet(_: UIViewController, from: UIViewController)
-    func toPermission(type: PermissionType, from: UIViewController)
-    func toMembersList(_: UIViewController, from: UIViewController)
-}
-
-public struct ChatCoordinator: ChatCoordinating {
-    var pushPresenter: Presenting = PushPresenter()
-    var modalPresenter: Presenting = ModalPresenter()
-    var bottomPresenter: Presenting = BottomPresenter()
-
-    var retryFactory: () -> UIViewController
-    var webFactory: (String) -> UIViewController
-    var previewFactory: () -> QLPreviewController
-    var contactFactory: (Contact) -> UIViewController
-    var imagePickerFactory: () -> UIImagePickerController
-    var permissionFactory: () -> RequestPermissionController
-
-    public init(
-        retryFactory: @escaping () -> UIViewController,
-        webFactory: @escaping (String) -> UIViewController,
-        previewFactory: @escaping () -> QLPreviewController,
-        contactFactory: @escaping (Contact) -> UIViewController,
-        imagePickerFactory: @escaping () -> UIImagePickerController,
-        permissionFactory: @escaping () -> RequestPermissionController
-    ) {
-        self.webFactory = webFactory
-        self.retryFactory = retryFactory
-        self.previewFactory = previewFactory
-        self.contactFactory = contactFactory
-        self.permissionFactory = permissionFactory
-        self.imagePickerFactory = imagePickerFactory
-    }
-}
-
-public extension ChatCoordinator {
-    func toPreview(from parent: UIViewController) {
-        let screen = previewFactory()
-        screen.delegate = (parent as? QLPreviewControllerDelegate)
-        screen.dataSource = (parent as? QLPreviewControllerDataSource)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toLibrary(from parent: UIViewController) {
-        let screen = imagePickerFactory()
-        screen.delegate = (parent as? (UIImagePickerControllerDelegate & UINavigationControllerDelegate))
-        screen.allowsEditing = false
-        modalPresenter.present(screen, from: parent)
-    }
-
-    func toCamera(from parent: UIViewController) {
-        let screen = imagePickerFactory()
-        screen.delegate = (parent as? (UIImagePickerControllerDelegate & UINavigationControllerDelegate))
-        screen.sourceType = .camera
-        screen.allowsEditing = false
-        modalPresenter.present(screen, from: parent)
-    }
-
-    func toRetrySheet(from parent: UIViewController) {
-        let screen = retryFactory()
-        bottomPresenter.present(screen, from: parent)
-    }
-
-    func toContact(_ contact: Contact, from parent: UIViewController) {
-        let screen = contactFactory(contact)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toWebview(with urlString: String, from parent: UIViewController) {
-        let screen = webFactory(urlString)
-        modalPresenter.present(screen, from: parent)
-    }
-
-    func toPermission(type: PermissionType, from parent: UIViewController) {
-        let screen = permissionFactory()
-        screen.setup(type: type)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toMembersList(_ screen: UIViewController, from parent: UIViewController) {
-        bottomPresenter.present(screen, from: parent)
-    }
-
-    func toDrawer(_ drawer: UIViewController, from parent: UIViewController) {
-        bottomPresenter.present(drawer, from: parent)
-    }
-
-    func toMenuSheet(_ screen: UIViewController, from parent: UIViewController) {
-        bottomPresenter.present(screen, from: parent)
-    }
-}
diff --git a/Sources/ChatFeature/Helpers/BubbleBuilder.swift b/Sources/ChatFeature/Helpers/BubbleBuilder.swift
index ae33fd01e60bfaf501583779e11eb59575444b4f..e9e53d9355f380032cf7e835c7c8531a85c8233c 100644
--- a/Sources/ChatFeature/Helpers/BubbleBuilder.swift
+++ b/Sources/ChatFeature/Helpers/BubbleBuilder.swift
@@ -1,6 +1,7 @@
 import UIKit
 import Shared
 import XXModels
+import AppResources
 
 final class Bubbler {
     static func build(
@@ -235,7 +236,15 @@ final class Bubbler {
             bubble.replyView.space.backgroundColor = Asset.brandPrimary.color
             bubble.lockerImageView.removeFromSuperview()
             bubble.revertBottomStackOrder()
-        case .sendingFailed, .sendingTimedOut:
+        case .sendingTimedOut:
+          bubble.senderLabel.removeFromSuperview()
+          bubble.backgroundColor = Asset.accentWarning.color
+          bubble.textView.textColor = Asset.neutralWhite.color
+          bubble.dateLabel.textColor = Asset.neutralWhite.color
+          roundButtonColor = Asset.neutralWhite.color
+          bubble.replyView.space.backgroundColor = Asset.neutralWhite.color
+          bubble.replyView.container.backgroundColor = Asset.brandLight.color
+        case .sendingFailed:
             bubble.senderLabel.removeFromSuperview()
             bubble.backgroundColor = Asset.accentDanger.color
             bubble.textView.textColor = Asset.neutralWhite.color
@@ -293,7 +302,13 @@ final class Bubbler {
             roundButtonColor = Asset.neutralDisabled.color
             bubble.lockerImageView.removeFromSuperview()
             bubble.revertBottomStackOrder()
-        case .sendingFailed, .sendingTimedOut:
+        case .sendingTimedOut:
+          bubble.senderLabel.removeFromSuperview()
+          bubble.backgroundColor = Asset.accentWarning.color
+          bubble.textView.textColor = Asset.neutralWhite.color
+          bubble.dateLabel.textColor = Asset.neutralWhite.color
+          roundButtonColor = Asset.neutralWhite.color
+        case .sendingFailed:
             bubble.senderLabel.removeFromSuperview()
             bubble.backgroundColor = Asset.accentDanger.color
             bubble.textView.textColor = Asset.neutralWhite.color
diff --git a/Sources/ChatFeature/Helpers/CellConfigurator.swift b/Sources/ChatFeature/Helpers/CellConfigurator.swift
index d9b61fd9d3be46405ab170259dbe82aa0b1961a0..0abf3cca2b166b5fc63da479a23a019dbfb87046 100644
--- a/Sources/ChatFeature/Helpers/CellConfigurator.swift
+++ b/Sources/ChatFeature/Helpers/CellConfigurator.swift
@@ -3,442 +3,443 @@ import Shared
 import Combine
 import XXModels
 import Voxophone
+import AppResources
 import AVFoundation
 
 struct CellFactory {
-    var canBuild: (Message) -> Bool
-
-    var build: (Message, UICollectionView, IndexPath) -> UICollectionViewCell
-
-    func callAsFunction(
-        item: Message,
-        collectionView: UICollectionView,
-        indexPath: IndexPath
-    ) -> UICollectionViewCell {
-        build(item, collectionView, indexPath)
-    }
+  var canBuild: (Message) -> Bool
+  
+  var build: (Message, UICollectionView, IndexPath) -> UICollectionViewCell
+  
+  func callAsFunction(
+    item: Message,
+    collectionView: UICollectionView,
+    indexPath: IndexPath
+  ) -> UICollectionViewCell {
+    build(item, collectionView, indexPath)
+  }
 }
 
 extension CellFactory {
-    static func combined(factories: [CellFactory]) -> Self {
-        .init(
-            canBuild: { _ in true },
-            build: { item, collectionView, indexPath in
-                guard let factory = factories.first(where: { $0.canBuild(item)}) else {
-                    fatalError("Couldn't find a factory for \(item). Did you forget to implement?")
-                }
-
-                return factory(
-                    item: item,
-                    collectionView: collectionView,
-                    indexPath: indexPath
-                )
-            }
+  static func combined(factories: [CellFactory]) -> Self {
+    .init(
+      canBuild: { _ in true },
+      build: { item, collectionView, indexPath in
+        guard let factory = factories.first(where: { $0.canBuild(item)}) else {
+          fatalError("Couldn't find a factory for \(item). Did you forget to implement?")
+        }
+        
+        return factory(
+          item: item,
+          collectionView: collectionView,
+          indexPath: indexPath
         )
-    }
+      }
+    )
+  }
 }
 
 extension CellFactory {
-    static func incomingAudio(
-        voxophone: Voxophone,
-        transfer: @escaping (Data) -> FileTransfer
-    ) -> Self {
-        .init(
-            canBuild: { item in
-                guard (item.status == .received || item.status == .receiving),
-                      item.replyMessageId == nil,
-                      item.fileTransferId != nil else { return false }
-
-                return transfer(item.fileTransferId!).type == "m4a"
-
-            }, build: { item, collectionView, indexPath in
-                let ft = transfer(item.fileTransferId!)
-                let cell: IncomingAudioCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-                let url = FileManager.url(for: "\(ft.name).\(ft.type)")!
-
-                var model = AudioMessageCellState(
-                    date: item.date,
-                    audioURL: url,
-                    isPlaying: false,
-                    transferProgress: ft.progress,
-                    isLoudspeaker: false,
-                    duration: (try? AVAudioPlayer(contentsOf: url).duration) ?? 0.0,
-                    playbackTime: 0.0
-                )
-
-                cell.leftView.setup(with: model)
-                cell.canReply = false
-                cell.performReply = {}
-
-                Bubbler.build(audioBubble: cell.leftView, with: item)
-
-                voxophone.$state
-                    .sink {
-                        switch $0 {
-                        case .playing(url, _, time: let time, _):
-                            model.isPlaying = true
-                            model.playbackTime = time
-                        default:
-                            model.isPlaying = false
-                            model.playbackTime = 0.0
-                        }
-
-                        model.isLoudspeaker = $0.isLoudspeaker
-
-                        cell.leftView.setup(with: model)
-                    }.store(in: &cell.leftView.cancellables)
-
-                cell.leftView.didTapRight = {
-                    guard item.status != .receiving else { return }
-
-                    voxophone.toggleLoudspeaker()
-                }
-
-                cell.leftView.didTapLeft = {
-                    guard item.status != .receiving else { return }
-
-                    if case .playing(url, _, _, _) = voxophone.state {
-                        voxophone.reset()
-                    } else {
-                        voxophone.load(url)
-                        voxophone.play()
-                    }
-                }
-
-                return cell
-            }
+  static func incomingAudio(
+    voxophone: Voxophone,
+    transfer: @escaping (Data) -> FileTransfer
+  ) -> Self {
+    .init(
+      canBuild: { item in
+        guard (item.status == .received || item.status == .receiving),
+              item.replyMessageId == nil,
+              item.fileTransferId != nil else { return false }
+        
+        return transfer(item.fileTransferId!).type == "m4a"
+        
+      }, build: { item, collectionView, indexPath in
+        let ft = transfer(item.fileTransferId!)
+        let cell: IncomingAudioCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+        let url = FileManager.url(for: "\(ft.name).\(ft.type)")!
+        
+        var model = AudioMessageCellState(
+          date: item.date,
+          audioURL: url,
+          isPlaying: false,
+          transferProgress: ft.progress,
+          isLoudspeaker: false,
+          duration: (try? AVAudioPlayer(contentsOf: url).duration) ?? 0.0,
+          playbackTime: 0.0
         )
-    }
-
-    static func outgoingAudio(
-        voxophone: Voxophone,
-        transfer: @escaping (Data) -> FileTransfer
-    ) -> Self {
-        .init(
-            canBuild: { item in
-                guard (item.status == .sent ||
-                       item.status == .sending ||
-                       item.status == .sendingFailed ||
-                       item.status == .sendingTimedOut)
-                        && item.replyMessageId == nil
-                        && item.fileTransferId != nil else {
-                    return false
-                }
-
-                return transfer(item.fileTransferId!).type == "m4a"
-
-            }, build: { item, collectionView, indexPath in
-                let ft = transfer(item.fileTransferId!)
-                let cell: OutgoingAudioCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-                let url = FileManager.url(for: "\(ft.name).\(ft.type)")!
-                var model = AudioMessageCellState(
-                    date: item.date,
-                    audioURL: url,
-                    isPlaying: false,
-                    transferProgress: ft.progress,
-                    isLoudspeaker: false,
-                    duration: (try? AVAudioPlayer(contentsOf: url).duration) ?? 0.0,
-                    playbackTime: 0.0
-                )
-
-                cell.rightView.setup(with: model)
-                cell.canReply = false
-                cell.performReply = {}
-
-                Bubbler.build(audioBubble: cell.rightView, with: item)
-
-                voxophone.$state
-                    .sink {
-                        switch $0 {
-                        case .playing(url, _, time: let time, _):
-                            model.isPlaying = true
-                            model.playbackTime = time
-                        default:
-                            model.isPlaying = false
-                            model.playbackTime = 0.0
-                        }
-
-                        model.isLoudspeaker = $0.isLoudspeaker
-
-                        cell.rightView.setup(with: model)
-                    }.store(in: &cell.rightView.cancellables)
-
-                cell.rightView.didTapRight = {
-                    voxophone.toggleLoudspeaker()
-                }
-
-                cell.rightView.didTapLeft = {
-                    if case .playing(url, _, _, _) = voxophone.state {
-                        voxophone.reset()
-                    } else {
-                        voxophone.load(url)
-                        voxophone.play()
-                    }
-                }
-
-                return cell
+        
+        cell.leftView.setup(with: model)
+        cell.canReply = false
+        cell.performReply = {}
+        
+        Bubbler.build(audioBubble: cell.leftView, with: item)
+        
+        voxophone.$state
+          .sink {
+            switch $0 {
+            case .playing(url, _, time: let time, _):
+              model.isPlaying = true
+              model.playbackTime = time
+            default:
+              model.isPlaying = false
+              model.playbackTime = 0.0
             }
+            
+            model.isLoudspeaker = $0.isLoudspeaker
+            
+            cell.leftView.setup(with: model)
+          }.store(in: &cell.leftView.cancellables)
+        
+        cell.leftView.didTapRight = {
+          guard item.status != .receiving else { return }
+          
+          voxophone.toggleLoudspeaker()
+        }
+        
+        cell.leftView.didTapLeft = {
+          guard item.status != .receiving else { return }
+          
+          if case .playing(url, _, _, _) = voxophone.state {
+            voxophone.reset()
+          } else {
+            voxophone.load(url)
+            voxophone.play()
+          }
+        }
+        
+        return cell
+      }
+    )
+  }
+  
+  static func outgoingAudio(
+    voxophone: Voxophone,
+    transfer: @escaping (Data) -> FileTransfer
+  ) -> Self {
+    .init(
+      canBuild: { item in
+        guard (item.status == .sent ||
+               item.status == .sending ||
+               item.status == .sendingFailed ||
+               item.status == .sendingTimedOut)
+                && item.replyMessageId == nil
+                && item.fileTransferId != nil else {
+          return false
+        }
+        
+        return transfer(item.fileTransferId!).type == "m4a"
+        
+      }, build: { item, collectionView, indexPath in
+        let ft = transfer(item.fileTransferId!)
+        let cell: OutgoingAudioCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+        let url = FileManager.url(for: "\(ft.name).\(ft.type)")!
+        var model = AudioMessageCellState(
+          date: item.date,
+          audioURL: url,
+          isPlaying: false,
+          transferProgress: ft.progress,
+          isLoudspeaker: false,
+          duration: (try? AVAudioPlayer(contentsOf: url).duration) ?? 0.0,
+          playbackTime: 0.0
         )
-    }
+        
+        cell.rightView.setup(with: model)
+        cell.canReply = false
+        cell.performReply = {}
+        
+        Bubbler.build(audioBubble: cell.rightView, with: item)
+        
+        voxophone.$state
+          .sink {
+            switch $0 {
+            case .playing(url, _, time: let time, _):
+              model.isPlaying = true
+              model.playbackTime = time
+            default:
+              model.isPlaying = false
+              model.playbackTime = 0.0
+            }
+            
+            model.isLoudspeaker = $0.isLoudspeaker
+            
+            cell.rightView.setup(with: model)
+          }.store(in: &cell.rightView.cancellables)
+        
+        cell.rightView.didTapRight = {
+          voxophone.toggleLoudspeaker()
+        }
+        
+        cell.rightView.didTapLeft = {
+          if case .playing(url, _, _, _) = voxophone.state {
+            voxophone.reset()
+          } else {
+            voxophone.load(url)
+            voxophone.play()
+          }
+        }
+        
+        return cell
+      }
+    )
+  }
 }
 
 extension CellFactory {
-    static func outgoingImage(
-        transfer:  @escaping (Data) -> FileTransfer
-    ) -> Self {
-        .init(
-            canBuild: { item in
-                guard (item.status == .sent ||
-                       item.status == .sending ||
-                       item.status == .sendingFailed ||
-                       item.status == .sendingTimedOut)
-                        && item.replyMessageId == nil
-                        && item.fileTransferId != nil else {
-                    return false
-                }
-
-                return transfer(item.fileTransferId!).type == "jpeg"
-
-            }, build: { item, collectionView, indexPath in
-                let ft = transfer(item.fileTransferId!)
-                let cell: OutgoingImageCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-
-                Bubbler.build(imageBubble: cell.rightView, with: item, with: transfer(item.fileTransferId!))
-                cell.canReply = false
-                cell.performReply = {}
-
-                if let image = UIImage(data: ft.data!) {
-                    cell.rightView.imageView.image = UIImage(cgImage: image.cgImage!, scale: image.scale, orientation: .up)
-                }
-
-                return cell
-            }
-        )
-    }
-
-    static func incomingImage(
-        transfer: @escaping (Data) -> FileTransfer
-    ) -> Self {
-        .init(
-            canBuild: { item in
-                guard (item.status == .received || item.status == .receiving)
-                        && item.replyMessageId == nil
-                        && item.fileTransferId != nil else {
-                    return false
-                }
-
-                return transfer(item.fileTransferId!).type == "jpeg"
-
-            }, build: { item, collectionView, indexPath in
-                let ft = transfer(item.fileTransferId!)
-                let cell: IncomingImageCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-
-                Bubbler.build(imageBubble: cell.leftView, with: item, with: ft)
-                cell.canReply = false
-                cell.performReply = {}
-
-                if let data = ft.data {
-                    cell.leftView.imageView.image = UIImage(data: data)
-                } else {
-                    cell.leftView.imageView.image = Asset.transferImagePlaceholder.image
-                }
-
-                return cell
-            }
-        )
-    }
+  static func outgoingImage(
+    transfer:  @escaping (Data) -> FileTransfer
+  ) -> Self {
+    .init(
+      canBuild: { item in
+        guard (item.status == .sent ||
+               item.status == .sending ||
+               item.status == .sendingFailed ||
+               item.status == .sendingTimedOut)
+                && item.replyMessageId == nil
+                && item.fileTransferId != nil else {
+          return false
+        }
+        
+        return transfer(item.fileTransferId!).type == "image"
+        
+      }, build: { item, collectionView, indexPath in
+        let ft = transfer(item.fileTransferId!)
+        let cell: OutgoingImageCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+        
+        Bubbler.build(imageBubble: cell.rightView, with: item, with: transfer(item.fileTransferId!))
+        cell.canReply = false
+        cell.performReply = {}
+        
+        if let image = UIImage(data: ft.data!) {
+          cell.rightView.imageView.image = UIImage(cgImage: image.cgImage!, scale: image.scale, orientation: .up)
+        }
+        
+        return cell
+      }
+    )
+  }
+  
+  static func incomingImage(
+    transfer: @escaping (Data) -> FileTransfer
+  ) -> Self {
+    .init(
+      canBuild: { item in
+        guard (item.status == .received || item.status == .receiving)
+                && item.replyMessageId == nil
+                && item.fileTransferId != nil else {
+          return false
+        }
+        
+        return transfer(item.fileTransferId!).type == "image"
+        
+      }, build: { item, collectionView, indexPath in
+        let ft = transfer(item.fileTransferId!)
+        let cell: IncomingImageCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+        
+        Bubbler.build(imageBubble: cell.leftView, with: item, with: ft)
+        cell.canReply = false
+        cell.performReply = {}
+        
+        if let data = ft.data {
+          cell.leftView.imageView.image = UIImage(data: data)
+        } else {
+          cell.leftView.imageView.image = Asset.transferImagePlaceholder.image
+        }
+        
+        return cell
+      }
+    )
+  }
 }
 
 extension CellFactory {
-    static func outgoingReply(
-        performReply: @escaping () -> Void,
-        replyContent: @escaping (Data) -> (String, String),
-        showRound: @escaping (String?) -> Void
-    ) -> Self {
-        .init(
-            canBuild: { item in
-                (item.status == .sent || item.status == .sending)
-                && item.replyMessageId != nil
-
-            }, build: { item, collectionView, indexPath in
-                let cell: OutgoingReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-
-                Bubbler.buildReply(
-                    bubble: cell.rightView,
-                    with: item,
-                    reply: replyContent(item.replyMessageId!)
-                )
-
-                cell.canReply = item.status == .sent
-                cell.performReply = performReply
-                cell.rightView.didTapShowRound = { showRound(item.roundURL) }
-                return cell
-            }
+  static func outgoingReply(
+    performReply: @escaping () -> Void,
+    replyContent: @escaping (Data) -> (String, String),
+    showRound: @escaping (String?) -> Void
+  ) -> Self {
+    .init(
+      canBuild: { item in
+        (item.status == .sent || item.status == .sending)
+        && item.replyMessageId != nil
+        
+      }, build: { item, collectionView, indexPath in
+        let cell: OutgoingReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+        
+        Bubbler.buildReply(
+          bubble: cell.rightView,
+          with: item,
+          reply: replyContent(item.replyMessageId!)
         )
-    }
-
-    static func incomingReply(
-        performReply: @escaping () -> Void,
-        replyContent: @escaping (Data) -> (String, String),
-        showRound: @escaping (String?) -> Void
-    ) -> Self {
-        .init(
-            canBuild: { item in
-                item.status == .received
-                && item.replyMessageId != nil
-
-            }, build: { item, collectionView, indexPath in
-                let cell: IncomingReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-
-                Bubbler.buildReply(
-                    bubble: cell.leftView,
-                    with: item,
-                    reply: replyContent(item.replyMessageId!)
-                )
-                cell.canReply = item.status == .received
-                cell.performReply = performReply
-                cell.leftView.didTapShowRound = { showRound(item.roundURL) }
-                cell.leftView.revertBottomStackOrder()
-                return cell
-            }
+        
+        cell.canReply = item.status == .sent
+        cell.performReply = performReply
+        cell.rightView.didTapShowRound = { showRound(item.roundURL) }
+        return cell
+      }
+    )
+  }
+  
+  static func incomingReply(
+    performReply: @escaping () -> Void,
+    replyContent: @escaping (Data) -> (String, String),
+    showRound: @escaping (String?) -> Void
+  ) -> Self {
+    .init(
+      canBuild: { item in
+        item.status == .received
+        && item.replyMessageId != nil
+        
+      }, build: { item, collectionView, indexPath in
+        let cell: IncomingReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+        
+        Bubbler.buildReply(
+          bubble: cell.leftView,
+          with: item,
+          reply: replyContent(item.replyMessageId!)
         )
-    }
-
-    static func outgoingFailedReply(
-        performReply: @escaping () -> Void,
-        replyContent: @escaping (Data) -> (String, String)
-    ) -> Self {
-        .init(
-            canBuild: { item in
-                (item.status == .sendingFailed || item.status == .sendingTimedOut)
-                && item.replyMessageId != nil
-
-            }, build: { item, collectionView, indexPath in
-                let cell: OutgoingFailedReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-
-                Bubbler.buildReply(
-                    bubble: cell.rightView,
-                    with: item,
-                    reply: replyContent(item.replyMessageId!)
-                )
-
-                cell.canReply = false
-                cell.performReply = performReply
-                return cell
-            }
+        cell.canReply = item.status == .received
+        cell.performReply = performReply
+        cell.leftView.didTapShowRound = { showRound(item.roundURL) }
+        cell.leftView.revertBottomStackOrder()
+        return cell
+      }
+    )
+  }
+  
+  static func outgoingFailedReply(
+    performReply: @escaping () -> Void,
+    replyContent: @escaping (Data) -> (String, String)
+  ) -> Self {
+    .init(
+      canBuild: { item in
+        (item.status == .sendingFailed || item.status == .sendingTimedOut)
+        && item.replyMessageId != nil
+        
+      }, build: { item, collectionView, indexPath in
+        let cell: OutgoingFailedReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+        
+        Bubbler.buildReply(
+          bubble: cell.rightView,
+          with: item,
+          reply: replyContent(item.replyMessageId!)
         )
-    }
+        
+        cell.canReply = false
+        cell.performReply = performReply
+        return cell
+      }
+    )
+  }
 }
 
 extension CellFactory {
-    static func incomingText(
-        performReply: @escaping () -> Void,
-        showRound: @escaping (String?) -> Void
-    ) -> Self {
-        .init(
-            canBuild: { item in
-                item.status == .received
-                && item.replyMessageId == nil
-
-            }, build: { item, collectionView, indexPath in
-                let cell: IncomingTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-
-                Bubbler.build(bubble: cell.leftView, with: item)
-                cell.canReply = item.status == .received
-                cell.performReply = performReply
-                cell.leftView.didTapShowRound = { showRound(item.roundURL) }
-                cell.leftView.revertBottomStackOrder()
-                return cell
-            }
-        )
-    }
-
-    static func outgoingText(
-        performReply: @escaping () -> Void,
-        showRound: @escaping (String?) -> Void
-    ) -> Self {
-        .init(
-            canBuild: { item in
-                (item.status == .sending || item.status == .sent)
-                && item.replyMessageId == nil
-
-            }, build: { item, collectionView, indexPath in
-                let cell: OutgoingTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-
-                Bubbler.build(bubble: cell.rightView, with: item)
-                cell.canReply = item.status == .sent
-                cell.performReply = performReply
-                cell.rightView.didTapShowRound = { showRound(item.roundURL) }
-
-                return cell
-            }
-        )
-    }
-
-    static func outgoingFailedText(performReply: @escaping () -> Void) -> Self {
-        .init(
-            canBuild: { item in
-                (item.status == .sendingFailed || item.status == .sendingTimedOut)
-                && item.replyMessageId == nil
-
-            }, build: { item, collectionView, indexPath in
-                let cell: OutgoingFailedTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-
-                Bubbler.build(bubble: cell.rightView, with: item)
-                cell.canReply = false
-                cell.performReply = performReply
-                return cell
-            }
-        )
-    }
+  static func incomingText(
+    performReply: @escaping () -> Void,
+    showRound: @escaping (String?) -> Void
+  ) -> Self {
+    .init(
+      canBuild: { item in
+        item.status == .received
+        && item.replyMessageId == nil
+        
+      }, build: { item, collectionView, indexPath in
+        let cell: IncomingTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+        
+        Bubbler.build(bubble: cell.leftView, with: item)
+        cell.canReply = item.status == .received
+        cell.performReply = performReply
+        cell.leftView.didTapShowRound = { showRound(item.roundURL) }
+        cell.leftView.revertBottomStackOrder()
+        return cell
+      }
+    )
+  }
+  
+  static func outgoingText(
+    performReply: @escaping () -> Void,
+    showRound: @escaping (String?) -> Void
+  ) -> Self {
+    .init(
+      canBuild: { item in
+        (item.status == .sending || item.status == .sent)
+        && item.replyMessageId == nil
+        
+      }, build: { item, collectionView, indexPath in
+        let cell: OutgoingTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+        
+        Bubbler.build(bubble: cell.rightView, with: item)
+        cell.canReply = item.status == .sent
+        cell.performReply = performReply
+        cell.rightView.didTapShowRound = { showRound(item.roundURL) }
+        
+        return cell
+      }
+    )
+  }
+  
+  static func outgoingFailedText(performReply: @escaping () -> Void) -> Self {
+    .init(
+      canBuild: { item in
+        (item.status == .sendingFailed || item.status == .sendingTimedOut)
+        && item.replyMessageId == nil
+        
+      }, build: { item, collectionView, indexPath in
+        let cell: OutgoingFailedTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+        
+        Bubbler.build(bubble: cell.rightView, with: item)
+        cell.canReply = false
+        cell.performReply = performReply
+        return cell
+      }
+    )
+  }
 }
 
 struct ActionFactory {
-    enum Action {
-        case copy
-        case retry
-        case reply
-        case delete
-        case report
-
-        var title: String {
-            switch self {
-
-            case .copy:
-                return Localized.Chat.BubbleMenu.copy
-            case .retry:
-                return Localized.Chat.BubbleMenu.retry
-            case .reply:
-                return Localized.Chat.BubbleMenu.reply
-            case .delete:
-                return Localized.Chat.BubbleMenu.delete
-            case .report:
-                return Localized.Chat.BubbleMenu.report
-            }
-        }
+  enum Action {
+    case copy
+    case retry
+    case reply
+    case delete
+    case report
+    
+    var title: String {
+      switch self {
+        
+      case .copy:
+        return Localized.Chat.BubbleMenu.copy
+      case .retry:
+        return Localized.Chat.BubbleMenu.retry
+      case .reply:
+        return Localized.Chat.BubbleMenu.reply
+      case .delete:
+        return Localized.Chat.BubbleMenu.delete
+      case .report:
+        return Localized.Chat.BubbleMenu.report
+      }
     }
-
-    static func build(
-        from item: Message,
-        action: Action,
-        closure: @escaping (Message) -> Void
-    ) -> UIAction? {
-
-        switch action {
-        case .report:
-            guard item.status == .received else { return nil }
-        case .reply:
-            guard item.status == .received || item.status == .sent else { return nil }
-        case .retry:
-            guard item.status == .sendingFailed || item.status == .sendingTimedOut else { return nil }
-        case .delete, .copy:
-            break
-        }
-
-        return UIAction(
-            title: action.title,
-            state: .off,
-            handler: { _ in closure(item) }
-        )
+  }
+  
+  static func build(
+    from item: Message,
+    action: Action,
+    closure: @escaping (Message) -> Void
+  ) -> UIAction? {
+    
+    switch action {
+    case .report:
+      guard item.status == .received else { return nil }
+    case .reply:
+      guard item.status == .received || item.status == .sent else { return nil }
+    case .retry:
+      guard item.status == .sendingFailed || item.status == .sendingTimedOut else { return nil }
+    case .delete, .copy:
+      break
     }
+    
+    return UIAction(
+      title: action.title,
+      state: .off,
+      handler: { _ in closure(item) }
+    )
+  }
 }
diff --git a/Sources/ChatFeature/Helpers/LayoutDelegate.swift b/Sources/ChatFeature/Helpers/LayoutDelegate.swift
index 8bbc4343318a3d7353e28a580c67777663020d9c..4a072358ea503bec6d7c8aa1b52eba20bac9c6a2 100644
--- a/Sources/ChatFeature/Helpers/LayoutDelegate.swift
+++ b/Sources/ChatFeature/Helpers/LayoutDelegate.swift
@@ -1,7 +1,7 @@
 import UIKit
 import ChatLayout
 
-extension ChatLayout {
+extension CollectionViewChatLayout {
     func configure(_ layoutDelegate: ChatLayoutDelegate) {
         delegate = layoutDelegate
         settings.estimatedItemSize = CGSize(width: 100, height: 65)
@@ -13,11 +13,11 @@ extension ChatLayout {
 }
 
 final class LayoutDelegate: ChatLayoutDelegate {
-    public func alignmentForItem(_: ChatLayout, of kind: ItemKind, at: IndexPath) -> ChatItemAlignment {
+    public func alignmentForItem(_: CollectionViewChatLayout, of kind: ItemKind, at: IndexPath) -> ChatItemAlignment {
         .fullWidth
     }
 
-    public func shouldPresentHeader(_ chatLayout: ChatLayout, at sectionIndex: Int) -> Bool {
+    public func shouldPresentHeader(_ chatLayout: CollectionViewChatLayout, at sectionIndex: Int) -> Bool {
         true
     }
 }
diff --git a/Sources/CollectionView/ViewConfigurator.swift b/Sources/ChatFeature/ViewConfigurator.swift
similarity index 100%
rename from Sources/CollectionView/ViewConfigurator.swift
rename to Sources/ChatFeature/ViewConfigurator.swift
diff --git a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
index 1dbce8a895ebfeabceb5900d3e5d258bcfb31199..e083c9c4abe8c76603091b542bf5e78e8dc00cb5 100644
--- a/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
+++ b/Sources/ChatFeature/ViewModels/GroupChatViewModel.swift
@@ -1,199 +1,263 @@
-import HUD
 import UIKit
-import Models
 import Shared
+import AppCore
 import Combine
 import XXModels
 import Defaults
 import Foundation
-import Integration
-import ToastFeature
+import AppResources
 import DifferenceKit
 import ReportingFeature
-import DependencyInjection
+import XXMessengerClient
+import ComposableArchitecture
+
+import struct XXModels.Message
+import XXClient
 
 enum GroupChatNavigationRoutes: Equatable {
-    case waitingRound
-    case webview(String)
+  case waitingRound
+  case webview(String)
 }
 
 final class GroupChatViewModel {
-    @Dependency private var session: SessionType
-    @Dependency private var sendReport: SendReport
-    @Dependency private var reportingStatus: ReportingStatus
-    @Dependency private var toastController: ToastController
+  @Dependency(\.sendReport) var sendReport
+  @Dependency(\.app.dbManager) var dbManager
+  @Dependency(\.app.messenger) var messenger
+  @Dependency(\.app.hudManager) var hudManager
+  @Dependency(\.app.toastManager) var toastManager
+  @Dependency(\.reportingStatus) var reportingStatus
 
-    @KeyObject(.username, defaultValue: nil) var username: String?
+  @KeyObject(.username, defaultValue: nil) var username: String?
 
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
+  var myId: Data {
+    try! messenger.e2e.get()!.getContact().getId()
+  }
 
-    var reportPopupPublisher: AnyPublisher<Contact, Never> {
-        reportPopupSubject.eraseToAnyPublisher()
-    }
+  var reportPopupPublisher: AnyPublisher<XXModels.Contact, Never> {
+    reportPopupSubject.eraseToAnyPublisher()
+  }
 
-    var replyPublisher: AnyPublisher<(String, String), Never> {
-        replySubject.eraseToAnyPublisher()
-    }
+  var replyPublisher: AnyPublisher<(String, String), Never> {
+    replySubject.eraseToAnyPublisher()
+  }
 
-    var routesPublisher: AnyPublisher<GroupChatNavigationRoutes, Never> {
-        routesSubject.eraseToAnyPublisher()
-    }
+  var routesPublisher: AnyPublisher<GroupChatNavigationRoutes, Never> {
+    routesSubject.eraseToAnyPublisher()
+  }
 
-    let info: GroupInfo
-    private var stagedReply: Reply?
-    private var cancellables = Set<AnyCancellable>()
-    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
-    private let reportPopupSubject = PassthroughSubject<Contact, Never>()
-    private let replySubject = PassthroughSubject<(String, String), Never>()
-    private let routesSubject = PassthroughSubject<GroupChatNavigationRoutes, Never>()
-
-    var messages: AnyPublisher<[ArraySection<ChatSection, Message>], Never> {
-        session.dbManager.fetchMessagesPublisher(.init(chat: .group(info.group.id)))
-            .assertNoFailure()
-            .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 })
-            }
-            .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()
-    }
+  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>()
 
-    init(_ info: GroupInfo) {
-        self.info = info
-    }
+  var messages: AnyPublisher<[ArraySection<ChatSection, Message>], Never> {
+    try! dbManager.getDB().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)!
+        }
 
-    func readAll() {
-        let assignment = Message.Assignments(isUnread: false)
-        let query = Message.Query(chat: .group(info.group.id))
-        _ = try? session.dbManager.bulkUpdateMessages(query, assignment)
-    }
+        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()
+  }
 
-    func didRequestDelete(_ messages: [Message]) {
-        _ = try? session.dbManager.deleteMessages(.init(id: Set(messages.map(\.id))))
-    }
+  init(_ info: GroupInfo) {
+    self.info = info
+  }
 
-    func didRequestReport(_ message: Message) {
-        if let contact = try? session.dbManager.fetchContacts(.init(id: [message.senderId])).first {
-            reportPopupSubject.send(contact)
-        }
-    }
+  func readAll() {
+    let assignment = Message.Assignments(isUnread: false)
+    let query = Message.Query(chat: .group(info.group.id))
+    _ = try? dbManager.getDB().bulkUpdateMessages(query, assignment)
+  }
+
+  func didRequestDelete(_ messages: [Message]) {
+    _ = try? dbManager.getDB().deleteMessages(.init(id: Set(messages.map(\.id))))
+  }
 
-    func send(_ text: String) {
-        session.send(.init(
-            text: text.trimmingCharacters(in: .whitespacesAndNewlines),
-            reply: stagedReply
-        ), toGroup: info.group)
-        stagedReply = nil
+  func didRequestReport(_ message: Message) {
+    if let contact = try? dbManager.getDB().fetchContacts(.init(id: [message.senderId])).first {
+      reportPopupSubject.send(contact)
     }
+  }
 
-    func retry(_ message: Message) {
-        guard let id = message.id else { return }
-        session.retryMessage(id)
+  func send(_ text: String) {
+    do {
+      var message = Message(
+        senderId: try messenger.e2e.get()!.getContact().getId(),
+        recipientId: nil,
+        groupId: info.group.id,
+        date: Date(),
+        status: .sending,
+        isUnread: false,
+        text: text.trimmingCharacters(in: .whitespacesAndNewlines),
+        replyMessageId: stagedReply?.messageId
+      )
+      message = try dbManager.getDB().saveMessage(message)
+      let report = try messenger.groupChat()!.send(
+        groupId: info.id,
+        message: MessagePayload(
+          text: text.trimmingCharacters(in: .whitespacesAndNewlines),
+          replyingTo: stagedReply?.messageId
+        ).encode()
+      )
+      message.networkId = report.messageId
+      message.date = Date.fromTimestamp(Int(report.timestamp))
+      message = try dbManager.getDB().saveMessage(message)
+      try messenger.cMix.get()?.waitForRoundResult(
+        roundList: try report.encode(),
+        timeoutMS: 15_000,
+        callback: .init(handle: { result in
+          switch result {
+          case .delivered:
+            message.status = .sent
+          case .notDelivered(timedOut: let timedOut):
+            message.status = timedOut ? .sendingTimedOut : .sendingFailed
+          }
+          _ = try? self.dbManager.getDB().saveMessage(message)
+        })
+      )
+    } catch {
+      print(error.localizedDescription)
     }
+  }
 
-    func showRoundFrom(_ roundURL: String?) {
-        if let urlString = roundURL, !urlString.isEmpty {
-            routesSubject.send(.webview(urlString))
-        } else {
-            routesSubject.send(.waitingRound)
-        }
+  func retry(_ message: Message) {
+    do {
+      var message = message
+      message.status = .sending
+      message = try dbManager.getDB().saveMessage(message)
+      let report = try messenger.groupChat()!.send(
+        groupId: info.id,
+        message: MessagePayload(
+          text: message.text.trimmingCharacters(in: .whitespacesAndNewlines),
+          replyingTo: stagedReply?.messageId
+        ).encode()
+      )
+      message.networkId = report.messageId
+      message.date = Date.fromTimestamp(Int(report.timestamp))
+      message = try dbManager.getDB().saveMessage(message)
+      try messenger.cMix.get()?.waitForRoundResult(
+        roundList: try report.encode(),
+        timeoutMS: 15_000,
+        callback: .init(handle: { result in
+          switch result {
+          case .delivered:
+            message.status = .sent
+          case .notDelivered(timedOut: let timedOut):
+            message.status = timedOut ? .sendingTimedOut : .sendingFailed
+          }
+          _ = try? self.dbManager.getDB().saveMessage(message)
+        })
+      )
+    } catch {
+      print(error.localizedDescription)
     }
+  }
 
-    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? session.dbManager.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? dbManager.getDB().fetchMessages(.init(networkId: messageId)).first else {
+      return ("[DELETED]", "[DELETED]")
     }
 
-    func getName(from senderId: Data) -> String {
-        guard senderId != session.myId else { return "You" }
+    return (getName(from: message.senderId), message.text)
+  }
 
-        guard let contact = try? session.dbManager.fetchContacts(.init(id: [senderId])).first else {
-            return "[DELETED]"
-        }
+  func getName(from senderId: Data) -> String {
+    guard senderId != myId else { return "You" }
 
-        var name = (contact.nickname ?? contact.username) ?? "Fetching username..."
+    guard let contact = try? dbManager.getDB().fetchContacts(.init(id: [senderId])).first else {
+      return "[DELETED]"
+    }
 
-        if contact.isBlocked, reportingStatus.isEnabled() {
-            name = "\(name) (Blocked)"
-        }
+    var name = (contact.nickname ?? contact.username) ?? "Fetching username..."
 
-        return name
+    if contact.isBlocked, reportingStatus.isEnabled() {
+      name = "\(name) (Blocked)"
     }
 
-    func didRequestReply(_ message: Message) {
-        guard let networkId = message.networkId else { return }
-        stagedReply = Reply(messageId: networkId, senderId: message.senderId)
-        replySubject.send(getReplyContent(for: networkId))
-    }
+    return name
+  }
 
-    func report(contact: Contact, screenshot: UIImage, completion: @escaping () -> Void) {
-        let report = Report(
-            sender: .init(
-                userId: contact.id.base64EncodedString(),
-                username: contact.username!
-            ),
-            recipient: .init(
-                userId: session.myId.base64EncodedString(),
-                username: username!
-            ),
-            type: .group,
-            screenshot: screenshot.pngData()!,
-            partyName: info.group.name,
-            partyBlob: info.group.id.base64EncodedString(),
-            partyMembers: info.members.map { Report.ReportUser(
-                userId: $0.id.base64EncodedString(),
-                username: $0.username ?? "")
-            }
-        )
-
-        hudSubject.send(.on)
-        sendReport(report) { result in
-            switch result {
-            case .failure(let error):
-                DispatchQueue.main.async {
-                    self.hudSubject.send(.error(.init(with: error)))
-                }
-
-            case .success(_):
-                self.blockContact(contact)
-                DispatchQueue.main.async {
-                    self.hudSubject.send(.none)
-                    self.presentReportConfirmation(contact: contact)
-                    completion()
-                }
-            }
+  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 ?? "")
+      }
+    )
+
+    hudManager.show()
+    sendReport(report) { result in
+      switch result {
+      case .failure(let error):
+        DispatchQueue.main.async {
+          self.hudManager.show(.init(error: error))
         }
-    }
 
-    private func blockContact(_ contact: Contact) {
-        var contact = contact
-        contact.isBlocked = true
-        _ = try? session.dbManager.saveContact(contact)
+      case .success(_):
+        self.blockContact(contact)
+        DispatchQueue.main.async {
+          self.hudManager.hide()
+          self.presentReportConfirmation(contact: contact)
+          completion()
+        }
+      }
     }
+  }
 
-    private func presentReportConfirmation(contact: Contact) {
-        let name = (contact.nickname ?? contact.username) ?? "the contact"
-        toastController.enqueueToast(model: .init(
-            title: "Your report has been sent and \(name) is now blocked.",
-            leftImage: Asset.requestSentToaster.image
-        ))
-    }
+  private func blockContact(_ contact: XXModels.Contact) {
+    var contact = contact
+    contact.isBlocked = true
+    _ = try? dbManager.getDB().saveContact(contact)
+  }
+
+  private func presentReportConfirmation(contact: XXModels.Contact) {
+    let name = (contact.nickname ?? contact.username) ?? "the contact"
+    toastManager.enqueue(.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 8ba933ad52ff63b6beca403e84bd688ccb043ac9..92b93761bcf599f72275369d966ee39e05ef22f7 100644
--- a/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
+++ b/Sources/ChatFeature/ViewModels/SingleChatViewModel.swift
@@ -1,305 +1,322 @@
-import HUD
 import UIKit
-import Models
 import Shared
+import AppCore
 import Combine
-import XXLogger
 import XXModels
-import Foundation
-import Integration
+import XXClient
 import Defaults
-import Permissions
-import ToastFeature
+import AppResources
+import Dependencies
+import AppNavigation
 import DifferenceKit
 import ReportingFeature
-import DependencyInjection
+import XXMessengerClient
+import PermissionsFeature
+
+import struct XXModels.Message
+import struct XXModels.FileTransfer
 
 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 private var logger: XXLogger
-    @Dependency private var session: SessionType
-    @Dependency private var permissions: PermissionHandling
-    @Dependency private var toastController: ToastController
-    @Dependency private var sendReport: SendReport
-
-    @KeyObject(.username, defaultValue: nil) var username: String?
-
-    var contact: Contact { contactSubject.value }
-    private var stagedReply: Reply?
-    private var cancellables = Set<AnyCancellable>()
-    private let contactSubject: CurrentValueSubject<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>()
-
-    var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-
-    var isOnline: AnyPublisher<Bool, Never> { session.isOnline }
-    var contactPublisher: AnyPublisher<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: Contact) {
-        if contact.isRecent == true {
-            var contact = contact
-            contact.isRecent = false
-            _ = try? session.dbManager.saveContact(contact)
-        }
-    }
-
-    func viewDidAppear() {
-        updateRecentState(contact)
-    }
-
-    init(_ contact: Contact) {
-        self.contactSubject = .init(contact)
-        super.init()
-
-        updateRecentState(contact)
-
-        session.dbManager.fetchContactsPublisher(Contact.Query(id: [contact.id]))
-            .assertNoFailure()
-            .compactMap { $0.first }
-            .sink { [unowned self] in contactSubject.send($0) }
-            .store(in: &cancellables)
-
-        session.dbManager.fetchMessagesPublisher(.init(chat: .direct(session.myId, contact.id)))
-            .assertNoFailure()
-            .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)
-    }
-
-    // MARK: Public
-
-    func getFileTransferWith(id: Data) -> FileTransfer {
-        guard let transfer = try? session.dbManager.fetchFileTransfers(.init(id: [id])).first else {
-            fatalError()
-        }
-
-        return transfer
-    }
-
-    func didSendAudio(url: URL) {
-        session.sendFile(url: url, to: contact)
-    }
-
-    func didSend(image: UIImage) {
-        guard let imageData = image.orientedUp().jpegData(compressionQuality: 1.0) else { return }
-        hudRelay.send(.on)
-
-        session.send(imageData: imageData, to: contact) { [weak self] in
-            switch $0 {
-            case .success:
-                self?.hudRelay.send(.none)
-            case .failure(let error):
-                self?.hudRelay.send(.error(.init(with: error)))
-            }
-        }
-    }
-
-    func readAll() {
-        let assignment = Message.Assignments(isUnread: false)
-        let query = Message.Query(chat: .direct(session.myId, contact.id))
-        _ = try? session.dbManager.bulkUpdateMessages(query, assignment)
-    }
-
-    func didRequestDeleteAll() {
-        _ = try? session.dbManager.deleteMessages(.init(chat: .direct(session.myId, contact.id)))
-    }
-
-    func didRequestRetry(_ message: Message) {
-        guard let id = message.id else { return }
-        session.retryMessage(id)
-    }
-
-    func didNavigateSomewhere() {
-        navigationRoutes.send(.none)
+  @Dependency(\.sendReport) var sendReport
+  @Dependency(\.permissions) var permissions
+  @Dependency(\.app.dbManager) var dbManager
+  @Dependency(\.app.sendImage) var sendImage
+  @Dependency(\.app.messenger) var messenger
+  @Dependency(\.app.hudManager) var hudManager
+  @Dependency(\.app.sendMessage) var sendMessage
+  @Dependency(\.app.toastManager) var toastManager
+  @Dependency(\.app.networkMonitor) var networkMonitor
+
+  @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
+      .observeStatus()
+      .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? dbManager.getDB().saveContact(contact)
     }
-
-    @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 viewDidAppear() {
+    updateRecentState(contact)
+  }
+  
+  init(_ contact: XXModels.Contact) {
+    self.contactSubject = .init(contact)
+    super.init()
+    
+    updateRecentState(contact)
+    
+    try! dbManager.getDB().fetchContactsPublisher(Contact.Query(id: [contact.id]))
+      .replaceError(with: [])
+      .compactMap { $0.first }
+      .sink { [unowned self] in contactSubject.send($0) }
+      .store(in: &cancellables)
+    
+    try! dbManager.getDB().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 false
-    }
-
-    func didRequestCopy(_ model: Message) {
-        UIPasteboard.general.string = model.text
+        
+        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 else { return }
+      self.networkMonitor.update($0)
+    }))
+  }
+  
+  func getFileTransferWith(id: Data) -> FileTransfer {
+    guard let transfer = try? dbManager.getDB().fetchFileTransfers(.init(id: [id])).first else {
+      fatalError()
     }
-
-    func didRequestDeleteSingle(_ model: Message) {
-        didRequestDelete([model])
-    }
-
-    func didRequestReport(_: Message) {
-        reportPopupSubject.send()
+    
+    return transfer
+  }
+  
+  func didSendAudio(url: URL) {}
+  
+  func didSend(image: UIImage) {
+    guard let imageData = image.orientedUp().jpegData(compressionQuality: 1.0) else { return }
+
+    sendImage(imageData, to: contact.id, onError: {
+      print("\($0.localizedDescription)")
+    }) {
+      print("finished")
     }
-
-    func abortReply() {
-        stagedReply = nil
+  }
+
+  func readAll() {
+    let assignment = Message.Assignments(isUnread: false)
+    let query = Message.Query(chat: .direct(myId, contact.id))
+    _ = try? dbManager.getDB().bulkUpdateMessages(query, assignment)
+  }
+
+  func didRequestDeleteAll() {
+    _ = try? dbManager.getDB().deleteMessages(.init(chat: .direct(myId, contact.id)))
+  }
+
+  func didRequestRetry(_ message: Message) {
+    // TODO
+  }
+
+  func didNavigateSomewhere() {
+    navigationRoutes.send(.none)
+  }
+
+  @discardableResult
+  func didTest(permission: PermissionType) -> Bool {
+    switch permission {
+    case .camera:
+      if permissions.camera.status() {
+        navigationRoutes.send(.camera)
+      } else {
+        navigationRoutes.send(.cameraPermission)
+      }
+    case .library:
+      if permissions.library.status() {
+        navigationRoutes.send(.library)
+      } else {
+        navigationRoutes.send(.libraryPermission)
+      }
+    case .microphone:
+      if permissions.microphone.status() {
+        return true
+      } else {
+        navigationRoutes.send(.microphonePermission)
+      }
     }
 
-    func send(_ string: String) {
-        let text = string.trimmingCharacters(in: .whitespacesAndNewlines)
-        let payload = Payload(text: text, reply: stagedReply)
-        session.send(payload, toContact: contact)
-        stagedReply = nil
+    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) {
+    sendMessage(
+      text: string.trimmingCharacters(in: .whitespacesAndNewlines),
+      replyingTo: stagedReply?.messageId,
+      to: contact.id,
+      onError: {
+        print("\($0.localizedDescription)")
+      }, completion: {
+        print("completed")
+      }
+    )
+  }
+
+  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? dbManager.getDB().fetchMessages(.init(networkId: messageId)).first else {
+      return ("[DELETED]", "[DELETED]")
     }
 
-    func didRequestReply(_ message: Message) {
-        guard let networkId = message.networkId else { return }
-
-        let senderTitle: String = {
-            if message.senderId == session.myId {
-                return "You"
-            } else {
-                return (contact.nickname ?? contact.username) ?? "Fetching username..."
-            }
-        }()
-
-        replySubject.send((senderTitle, message.text))
-        stagedReply = Reply(messageId: networkId, senderId: message.senderId)
+    guard let contact = try? dbManager.getDB().fetchContacts(.init(id: [message.senderId])).first else {
+      fatalError()
     }
 
-    func getReplyContent(for messageId: Data) -> (String, String) {
-        guard let message = try? session.dbManager.fetchMessages(.init(networkId: messageId)).first else {
-            return ("[DELETED]", "[DELETED]")
-        }
+    let contactTitle = (contact.nickname ?? contact.username) ?? "You"
+    return (contactTitle, message.text)
+  }
 
-        guard let contact = try? session.dbManager.fetchContacts(.init(id: [message.senderId])).first else {
-            fatalError()
-        }
-
-        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 showRoundFrom(_ roundURL: String?) {
-        if let urlString = roundURL, !urlString.isEmpty {
-            navigationRoutes.send(.webview(urlString))
-        } else {
-            navigationRoutes.send(.waitingRound)
+  }
+
+  func didRequestDelete(_ items: [Message]) {
+    _ = try? dbManager.getDB().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()!
+    )
+
+    hudManager.show()
+    sendReport(report) { result in
+      switch result {
+      case .failure(let error):
+        DispatchQueue.main.async {
+          self.hudManager.show(.init(error: error))
+          completion(false)
         }
-    }
-
-    func didRequestDelete(_ items: [Message]) {
-        _ = try? session.dbManager.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: session.myId.base64EncodedString(),
-                username: username!
-            ),
-            type: .dm,
-            screenshot: screenshot.pngData()!
-        )
-
-        hudRelay.send(.on)
-        sendReport(report) { result in
-            switch result {
-            case .failure(let error):
-                DispatchQueue.main.async {
-                    self.hudRelay.send(.error(.init(with: error)))
-                    completion(false)
-                }
-
-            case .success(_):
-                self.blockContact()
-                DispatchQueue.main.async {
-                    self.hudRelay.send(.none)
-                    self.presentReportConfirmation()
-                    completion(true)
-                }
-            }
+      case .success(_):
+        self.blockContact()
+        DispatchQueue.main.async {
+          self.hudManager.hide()
+          self.presentReportConfirmation()
+          completion(true)
         }
+      }
     }
-
-    private func blockContact() {
-        var contact = contact
-        contact.isBlocked = true
-        _ = try? session.dbManager.saveContact(contact)
-    }
-
-    private func presentReportConfirmation() {
-        let name = (contact.nickname ?? contact.username) ?? "the contact"
-        toastController.enqueueToast(model: .init(
-            title: "Your report has been sent and \(name) is now blocked.",
-            leftImage: Asset.requestSentToaster.image
-        ))
-    }
+  }
+
+  private func blockContact() {
+    var contact = contact
+    contact.isBlocked = true
+    _ = try? dbManager.getDB().saveContact(contact)
+  }
+
+  private func presentReportConfirmation() {
+    let name = (contact.nickname ?? contact.username) ?? "the contact"
+    toastManager.enqueue(.init(
+      title: "Your report has been sent and \(name) is now blocked.",
+      leftImage: Asset.requestSentToaster.image
+    ))
+  }
 }
diff --git a/Sources/ChatFeature/Views/Cells/AudioMessageView.swift b/Sources/ChatFeature/Views/Cells/AudioMessageView.swift
index 5982ffc49253c7d5b7a21939290b11f0f48e2f20..88b866abe701f4d3af9aa4041616dc1fa1a02d34 100644
--- a/Sources/ChatFeature/Views/Cells/AudioMessageView.swift
+++ b/Sources/ChatFeature/Views/Cells/AudioMessageView.swift
@@ -1,6 +1,7 @@
 import UIKit
 import Shared
 import Combine
+import AppResources
 
 typealias OutgoingAudioCell = CollectionCell<FlexibleSpace, AudioMessageView>
 typealias IncomingAudioCell = CollectionCell<AudioMessageView, FlexibleSpace>
diff --git a/Sources/ChatFeature/Views/Cells/AudioView.swift b/Sources/ChatFeature/Views/Cells/AudioView.swift
index 2a46a7a0309bf1f4a184e3d0cbfe88023f7f653d..e3d26bcbd683907b94073cfbea6aa0250e2ec0ef 100644
--- a/Sources/ChatFeature/Views/Cells/AudioView.swift
+++ b/Sources/ChatFeature/Views/Cells/AudioView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 final class AudioView: UIView {
     // MARK: UI
diff --git a/Sources/ChatFeature/Views/Cells/DocumentMessageView.swift b/Sources/ChatFeature/Views/Cells/DocumentMessageView.swift
index 2a8cf025f16d524858da87af728cbdd7bcbf1de7..8556277cc8bbe73aabf5511be4fb59333f9d2509 100644
--- a/Sources/ChatFeature/Views/Cells/DocumentMessageView.swift
+++ b/Sources/ChatFeature/Views/Cells/DocumentMessageView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 typealias OutgoingDocumentCell = CollectionCell<FlexibleSpace, DocumentMessageView>
 typealias IncomingDocumentCell = CollectionCell<DocumentMessageView, FlexibleSpace>
diff --git a/Sources/ChatFeature/Views/Cells/ImageMessageView.swift b/Sources/ChatFeature/Views/Cells/ImageMessageView.swift
index 88efa910ce3ef46a3b1f8012382b1287117b477d..92cfc9f3151cd14dccee4f52aa9bd3f14cebcf81 100644
--- a/Sources/ChatFeature/Views/Cells/ImageMessageView.swift
+++ b/Sources/ChatFeature/Views/Cells/ImageMessageView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 typealias OutgoingImageCell = CollectionCell<FlexibleSpace, ImageMessageView>
 typealias IncomingImageCell = CollectionCell<ImageMessageView, FlexibleSpace>
diff --git a/Sources/ChatFeature/Views/Cells/ReplyStackMessageView.swift b/Sources/ChatFeature/Views/Cells/ReplyStackMessageView.swift
index 20a74276710b492a845ad7b276fc79665a6a1baf..73e063e850229f3ba0f62f6105d6851a6ea4c55c 100644
--- a/Sources/ChatFeature/Views/Cells/ReplyStackMessageView.swift
+++ b/Sources/ChatFeature/Views/Cells/ReplyStackMessageView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 typealias IncomingReplyCell = CollectionCell<ReplyStackMessageView, FlexibleSpace>
 typealias OutgoingReplyCell = CollectionCell<FlexibleSpace, ReplyStackMessageView>
diff --git a/Sources/ChatFeature/Views/Cells/ReplyView.swift b/Sources/ChatFeature/Views/Cells/ReplyView.swift
index e64def34d02055d356efefd14ce5b6a4e5cab05e..3f8e1f8dcac7bfd7edcc07e62b464d27b4000370 100644
--- a/Sources/ChatFeature/Views/Cells/ReplyView.swift
+++ b/Sources/ChatFeature/Views/Cells/ReplyView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ReplyView: UIView {
     let space = UIView()
diff --git a/Sources/ChatFeature/Views/Cells/StackMessageView.swift b/Sources/ChatFeature/Views/Cells/StackMessageView.swift
index 2c5a3c9f3f6c9e86735c28eb4edb64efcfd5d44f..df89a30363624c426e6119942907437a7ad9ef79 100644
--- a/Sources/ChatFeature/Views/Cells/StackMessageView.swift
+++ b/Sources/ChatFeature/Views/Cells/StackMessageView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 typealias IncomingTextCell = CollectionCell<StackMessageView, FlexibleSpace>
 typealias OutgoingTextCell = CollectionCell<FlexibleSpace, StackMessageView>
diff --git a/Sources/ChatFeature/Views/ChatMenuView.swift b/Sources/ChatFeature/Views/ChatMenuView.swift
index 6b6f06bfe6ad54326de469f45b50ecf928aa2a3c..5ed237dd36f3d3f616337e88ab34f8ca81b2a805 100644
--- a/Sources/ChatFeature/Views/ChatMenuView.swift
+++ b/Sources/ChatFeature/Views/ChatMenuView.swift
@@ -1,6 +1,7 @@
 import UIKit
 import Shared
 import Combine
+import AppResources
 
 final class ChatMenuView: UIToolbar {
     enum Action {
diff --git a/Sources/ChatFeature/Views/ChatView.swift b/Sources/ChatFeature/Views/ChatView.swift
index ee3c7d98cdf0e96266cd110a41ac6dc8f8733f9d..34026c0cc6aa77955b0363a12e1f5bc5f6ffa244 100644
--- a/Sources/ChatFeature/Views/ChatView.swift
+++ b/Sources/ChatFeature/Views/ChatView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ChatView: UIView {
     let titleLabel = UILabel()
@@ -28,10 +29,10 @@ final class ChatView: UIView {
         networkIssueInvisibleConstraint?.isActive = true
         snackBar.translatesAutoresizingMaskIntoConstraints = false
 
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalTo(safeAreaLayoutGuide).offset(45)
-            make.left.equalToSuperview().offset(48)
-            make.right.equalToSuperview().offset(-61)
+        titleLabel.snp.makeConstraints {
+            $0.top.equalTo(safeAreaLayoutGuide).offset(45)
+            $0.left.equalToSuperview().offset(48)
+            $0.right.equalToSuperview().offset(-61)
         }
     }
 
diff --git a/Sources/ChatFeature/Views/GroupHeaderView.swift b/Sources/ChatFeature/Views/GroupHeaderView.swift
index b33415707304f070f172764a5ee8a49d89a1a10d..fdd4940153806a413e0362ac55d11afde886e31e 100644
--- a/Sources/ChatFeature/Views/GroupHeaderView.swift
+++ b/Sources/ChatFeature/Views/GroupHeaderView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 struct Member {
     let title: String
diff --git a/Sources/ChatFeature/Views/RetrySheetView.swift b/Sources/ChatFeature/Views/RetrySheetView.swift
index 44c1de62c74e6ab1f19eacb18a9fd22ff02166f1..1b79665bf9acf383edfa3fc7dfa5239447c9b962 100644
--- a/Sources/ChatFeature/Views/RetrySheetView.swift
+++ b/Sources/ChatFeature/Views/RetrySheetView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 final class RetrySheetView: UIView {
     // MARK: UI
diff --git a/Sources/ChatFeature/Views/SectionHeaderView.swift b/Sources/ChatFeature/Views/SectionHeaderView.swift
index 564ad98d840772945e8dfcda3a8921d81b23f4cd..9e78f977d123f628f72300350953dcad2aa9f1d0 100644
--- a/Sources/ChatFeature/Views/SectionHeaderView.swift
+++ b/Sources/ChatFeature/Views/SectionHeaderView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 final class SectionHeaderView: UICollectionReusableView {
     // MARK: UI
diff --git a/Sources/ChatFeature/Views/SheetButton.swift b/Sources/ChatFeature/Views/SheetButton.swift
index c20ec8931d7b7b303dc35c819f3b6ad7ba09394c..f24e8a06a62e1689957c6e570d08a19d5787faf3 100644
--- a/Sources/ChatFeature/Views/SheetButton.swift
+++ b/Sources/ChatFeature/Views/SheetButton.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 final class SheetButton: UIControl {
     enum Style {
diff --git a/Sources/ChatFeature/Views/SheetView.swift b/Sources/ChatFeature/Views/SheetView.swift
index a4cdfeb1348a6cd8b369115d6c34272df7649d87..6e305a6f5b1ee5ce1b47b1b1d1c58fa46fc359b4 100644
--- a/Sources/ChatFeature/Views/SheetView.swift
+++ b/Sources/ChatFeature/Views/SheetView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 final class SheetView: UIView {
     let stackView = UIStackView()
diff --git a/Sources/ChatFeature/Views/TextView.swift b/Sources/ChatFeature/Views/TextView.swift
index 1fbc36974b706d916aa6be5b7ce358b322a1cad5..ea7b30e3cfc95e45f677bbf750d8d27eb8c2c275 100644
--- a/Sources/ChatFeature/Views/TextView.swift
+++ b/Sources/ChatFeature/Views/TextView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 /// UITextView avoiding selection
 
diff --git a/Sources/ChatInputFeature/ActionButton.swift b/Sources/ChatInputFeature/ActionButton.swift
index 492ba1657c56ffb68bafb5c85b4f0cc94d87c4e3..8ffd5b84b508afe79f71203de25bef1bb9ce8e61 100644
--- a/Sources/ChatInputFeature/ActionButton.swift
+++ b/Sources/ChatInputFeature/ActionButton.swift
@@ -1,51 +1,48 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ActionButton: UIControl {
+  let titleLabel = UILabel()
+  let imageView = UIImageView()
+  let imageBackgroundView = UIView()
 
-    let titleLabel = UILabel()
-    let imageView = UIImageView()
-    let imageBackgroundView = UIView()
+  init() {
+    super.init(frame: .zero)
 
-    init() {
-        super.init(frame: .zero)
-        setup()
-    }
+    imageBackgroundView.layer.cornerRadius = 4
+    titleLabel.textColor = Asset.neutralDark.color
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 10.0)
+    imageBackgroundView.backgroundColor = Asset.neutralSecondary.color
+
+    addSubview(titleLabel)
+    addSubview(imageBackgroundView)
+    imageBackgroundView.addSubview(imageView)
+
+    imageView.isUserInteractionEnabled = false
+    imageBackgroundView.isUserInteractionEnabled = false
 
-    required init?(coder: NSCoder) { nil }
+    imageView.snp.makeConstraints { $0.center.equalToSuperview() }
 
-    func setup(title: String, image: UIImage) {
-        titleLabel.text = title
-        imageView.image = image
+    imageBackgroundView.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.width.equalTo(imageBackgroundView.snp.height)
     }
 
-    private func setup() {
-        imageBackgroundView.layer.cornerRadius = 4
-        titleLabel.textColor = Asset.neutralDark.color
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 10.0)
-        imageBackgroundView.backgroundColor = Asset.neutralSecondary.color
-
-        addSubview(titleLabel)
-        addSubview(imageBackgroundView)
-        imageBackgroundView.addSubview(imageView)
-
-        imageView.isUserInteractionEnabled = false
-        imageBackgroundView.isUserInteractionEnabled = false
-
-        imageView.snp.makeConstraints { $0.center.equalToSuperview() }
-
-        imageBackgroundView.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.width.equalTo(imageBackgroundView.snp.height)
-        }
-
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalTo(imageBackgroundView.snp.bottom).offset(4)
-            make.centerX.equalToSuperview()
-            make.left.greaterThanOrEqualToSuperview()
-            make.bottom.equalToSuperview()
-        }
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(imageBackgroundView.snp.bottom).offset(4)
+      $0.centerX.equalToSuperview()
+      $0.left.greaterThanOrEqualToSuperview()
+      $0.bottom.equalToSuperview()
     }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  func setup(title: String, image: UIImage) {
+    titleLabel.text = title
+    imageView.image = image
+  }
 }
diff --git a/Sources/ChatInputFeature/ActionsView.swift b/Sources/ChatInputFeature/ActionsView.swift
index f6cd9123754a31e7f70512a507cb31a965fe10bd..0668f7b94162a9d31c2dd818c892886a186ef79a 100644
--- a/Sources/ChatInputFeature/ActionsView.swift
+++ b/Sources/ChatInputFeature/ActionsView.swift
@@ -1,43 +1,39 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ActionsView: UIView {
-
-    let stack = UIStackView()
-    let cameraButton = ActionButton()
-    let libraryButton = ActionButton()
-
-    init() {
-        super.init(frame: .zero)
-        setup()
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    private func setup() {
-        cameraButton.setup(
-            title: Localized.Chat.Actions.camera,
-            image: Asset.chatInputActionCamera.image
-        )
-
-        libraryButton.setup(
-            title: Localized.Chat.Actions.gallery,
-            image: Asset.chatInputActionGallery.image
-        )
-
-        stack.spacing = 33
-        stack.axis = .horizontal
-        stack.distribution = .fillEqually
-        stack.addArrangedSubview(cameraButton)
-        stack.addArrangedSubview(libraryButton)
-
-        addSubview(stack)
-        stack.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            stack.topAnchor.constraint(equalTo: topAnchor),
-            stack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
-            stack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
-            stack.bottomAnchor.constraint(equalTo: bottomAnchor),
-        ])
-    }
+  let stack = UIStackView()
+  let cameraButton = ActionButton()
+  let libraryButton = ActionButton()
+
+  init() {
+    super.init(frame: .zero)
+    cameraButton.setup(
+      title: Localized.Chat.Actions.camera,
+      image: Asset.chatInputActionCamera.image
+    )
+    
+    libraryButton.setup(
+      title: Localized.Chat.Actions.gallery,
+      image: Asset.chatInputActionGallery.image
+    )
+
+    stack.spacing = 33
+    stack.axis = .horizontal
+    stack.distribution = .fillEqually
+    stack.addArrangedSubview(cameraButton)
+    stack.addArrangedSubview(libraryButton)
+
+    addSubview(stack)
+    stack.translatesAutoresizingMaskIntoConstraints = false
+    NSLayoutConstraint.activate([
+      stack.topAnchor.constraint(equalTo: topAnchor),
+      stack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
+      stack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
+      stack.bottomAnchor.constraint(equalTo: bottomAnchor),
+    ])
+  }
+
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/ChatInputFeature/AudioView.swift b/Sources/ChatInputFeature/AudioView.swift
index cd43082ac940a9038edd6e8506237b5be141715a..b7fb0d354800cfd719d28df6db56e2ad0cc7b0b4 100644
--- a/Sources/ChatInputFeature/AudioView.swift
+++ b/Sources/ChatInputFeature/AudioView.swift
@@ -1,56 +1,53 @@
 import UIKit
 import Shared
+import AppResources
 
 final class AudioView: UIView {
-
-    let stack = UIStackView()
-    let timeLabel = UILabel()
-    let playButton = UIButton()
-    let sendButton = UIButton()
-    let cancelButton = UIButton()
-    let stopPlaybackButton = UIButton()
-    let stopRecordingButton = UIButton()
-
-    init() {
-        super.init(frame: .zero)
-        setup()
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    private func setup() {
-        timeLabel.textAlignment = .center
-        timeLabel.textColor = Asset.neutralDark.color
-        timeLabel.font = Fonts.Mulish.semiBold.font(size: 13)
-
-        sendButton.setImage(Asset.chatSend.image, for: .normal)
-        playButton.setImage(Asset.chatInputVoicePlay.image, for: .normal)
-        cancelButton.setImage(Asset.chatInputActionClose.image, for: .normal)
-        stopPlaybackButton.setImage(Asset.chatInputVoicePause.image, for: .normal)
-        stopRecordingButton.setImage(Asset.chatInputVoiceStop.image, for: .normal)
-
-        stack.spacing = 8
-        stack.axis = .horizontal
-        stack.addArrangedSubview(cancelButton)
-        stack.addArrangedSubview(playButton)
-        stack.addArrangedSubview(stopPlaybackButton)
-        stack.addArrangedSubview(timeLabel)
-        stack.addArrangedSubview(stopRecordingButton)
-        stack.addArrangedSubview(sendButton)
-
-        cancelButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
-        playButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
-        stopPlaybackButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
-        sendButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
-        timeLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
-
-        addSubview(stack)
-        stack.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            stack.topAnchor.constraint(equalTo: topAnchor),
-            stack.leadingAnchor.constraint(equalTo: leadingAnchor),
-            stack.trailingAnchor.constraint(equalTo: trailingAnchor),
-            stack.bottomAnchor.constraint(equalTo: bottomAnchor),
-        ])
-    }
+  let stack = UIStackView()
+  let timeLabel = UILabel()
+  let playButton = UIButton()
+  let sendButton = UIButton()
+  let cancelButton = UIButton()
+  let stopPlaybackButton = UIButton()
+  let stopRecordingButton = UIButton()
+
+  init() {
+    super.init(frame: .zero)
+
+    timeLabel.textAlignment = .center
+    timeLabel.textColor = Asset.neutralDark.color
+    timeLabel.font = Fonts.Mulish.semiBold.font(size: 13)
+
+    sendButton.setImage(Asset.chatSend.image, for: .normal)
+    playButton.setImage(Asset.chatInputVoicePlay.image, for: .normal)
+    cancelButton.setImage(Asset.chatInputActionClose.image, for: .normal)
+    stopPlaybackButton.setImage(Asset.chatInputVoicePause.image, for: .normal)
+    stopRecordingButton.setImage(Asset.chatInputVoiceStop.image, for: .normal)
+
+    stack.spacing = 8
+    stack.axis = .horizontal
+    stack.addArrangedSubview(cancelButton)
+    stack.addArrangedSubview(playButton)
+    stack.addArrangedSubview(stopPlaybackButton)
+    stack.addArrangedSubview(timeLabel)
+    stack.addArrangedSubview(stopRecordingButton)
+    stack.addArrangedSubview(sendButton)
+
+    cancelButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+    playButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+    stopPlaybackButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+    sendButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+    timeLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
+
+    addSubview(stack)
+    stack.translatesAutoresizingMaskIntoConstraints = false
+    NSLayoutConstraint.activate([
+      stack.topAnchor.constraint(equalTo: topAnchor),
+      stack.leadingAnchor.constraint(equalTo: leadingAnchor),
+      stack.trailingAnchor.constraint(equalTo: trailingAnchor),
+      stack.bottomAnchor.constraint(equalTo: bottomAnchor),
+    ])
+  }
+
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/ChatInputFeature/ChatInputReducer.swift b/Sources/ChatInputFeature/ChatInputReducer.swift
index 23bb35bacf48346aaa0623316baee237072bff2b..170647ee55e1fb45bc97e81f6dfd98a8f999514c 100644
--- a/Sources/ChatInputFeature/ChatInputReducer.swift
+++ b/Sources/ChatInputFeature/ChatInputReducer.swift
@@ -1,3 +1,4 @@
+import Foundation
 import ComposableArchitecture
 
 public let chatInputReducer = Reducer<ChatInputState, ChatInputAction, ChatInputEnvironment> { state, action, env in
diff --git a/Sources/ChatInputFeature/ChatInputReply.swift b/Sources/ChatInputFeature/ChatInputReply.swift
index d5353e34ab95481fc899189efa84c6e7ad4ca86f..ff3d9f958cc5f133d4d5189c9d57305b16ecb7fd 100644
--- a/Sources/ChatInputFeature/ChatInputReply.swift
+++ b/Sources/ChatInputFeature/ChatInputReply.swift
@@ -1,78 +1,71 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ChatInputReply: UIView {
+  let nameLabel = UILabel()
+  let titleLabel = UILabel()
+  let abortButton = UIButton()
+  let messageLabel = UILabel()
 
-    let nameLabel = UILabel()
-    let titleLabel = UILabel()
-    let abortButton = UIButton()
-    let messageLabel = UILabel()
+  init() {
+    super.init(frame: .zero)
 
-    init() {
-        super.init(frame: .zero)
-        setup()
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    func setup(message: String?, sender: String?) {
-        guard let message = message else {
-            isHidden = true
-            return
-        }
-
-        isHidden = false
-        messageLabel.text = message
-        nameLabel.text = sender ?? "You"
-    }
+    titleLabel.text = "Replying to"
+    messageLabel.numberOfLines = 2
+    abortButton.setImage(Asset.replyAbort.image, for: .normal)
 
-    private func setup() {
-        titleLabel.text = "Replying to"
-        messageLabel.numberOfLines = 2
-        abortButton.setImage(Asset.replyAbort.image, for: .normal)
+    nameLabel.font = Fonts.Mulish.bold.font(size: 11.0)
+    titleLabel.font = Fonts.Mulish.regular.font(size: 12.0)
+    messageLabel.font = Fonts.Mulish.regular.font(size: 11.0)
 
-        nameLabel.font = Fonts.Mulish.bold.font(size: 11.0)
-        titleLabel.font = Fonts.Mulish.regular.font(size: 12.0)
-        messageLabel.font = Fonts.Mulish.regular.font(size: 11.0)
+    nameLabel.textColor = Asset.neutralBody.color
+    titleLabel.textColor = Asset.neutralBody.color
+    messageLabel.textColor = Asset.neutralBody.color
 
-        nameLabel.textColor = Asset.neutralBody.color
-        titleLabel.textColor = Asset.neutralBody.color
-        messageLabel.textColor = Asset.neutralBody.color
+    addSubview(nameLabel)
+    addSubview(titleLabel)
+    addSubview(abortButton)
+    addSubview(messageLabel)
 
-        addSubview(nameLabel)
-        addSubview(titleLabel)
-        addSubview(abortButton)
-        addSubview(messageLabel)
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(10)
+      $0.left.equalToSuperview().offset(19)
+      $0.right.lessThanOrEqualToSuperview()
+      $0.height.equalTo(15)
+    }
 
-        setupConstraints()
+    nameLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(11)
+      $0.left.equalTo(titleLabel)
+      $0.right.lessThanOrEqualToSuperview().offset(-30)
+      $0.height.equalTo(10)
     }
 
-    private func setupConstraints() {
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(10)
-            make.left.equalToSuperview().offset(19)
-            make.right.lessThanOrEqualToSuperview()
-            make.height.equalTo(15)
-        }
+    messageLabel.snp.makeConstraints {
+      $0.left.equalToSuperview().offset(28)
+      $0.top.equalTo(nameLabel.snp.bottom).offset(4)
+      $0.right.equalToSuperview().offset(-41)
+      $0.bottom.equalToSuperview().offset(-10)
+      $0.height.equalTo(30)
+    }
 
-        nameLabel.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(11)
-            make.left.equalTo(titleLabel)
-            make.right.lessThanOrEqualToSuperview().offset(-30)
-            make.height.equalTo(10)
-        }
+    abortButton.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(12)
+      $0.right.equalToSuperview().offset(-12)
+    }
+  }
 
-        messageLabel.snp.makeConstraints { make in
-            make.left.equalToSuperview().offset(28)
-            make.top.equalTo(nameLabel.snp.bottom).offset(4)
-            make.right.equalToSuperview().offset(-41)
-            make.bottom.equalToSuperview().offset(-10)
-            make.height.equalTo(30)
-        }
+  required init?(coder: NSCoder) { nil }
 
-        abortButton.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(12)
-            make.right.equalToSuperview().offset(-12)
-        }
+  func setup(message: String?, sender: String?) {
+    guard let message = message else {
+      isHidden = true
+      return
     }
+
+    isHidden = false
+    messageLabel.text = message
+    nameLabel.text = sender ?? "You"
+  }
 }
diff --git a/Sources/ChatInputFeature/ChatInputView.swift b/Sources/ChatInputFeature/ChatInputView.swift
index 31e5a8ae93cb5f49b64765280130dd89efe87bd1..ba81355868eb9ec1347d9b58f21a3e3edf1a7e88 100644
--- a/Sources/ChatInputFeature/ChatInputView.swift
+++ b/Sources/ChatInputFeature/ChatInputView.swift
@@ -2,229 +2,228 @@ import UIKit
 import Shared
 import Combine
 import CasePaths
-import Voxophone
+import AppResources
 import ComposableArchitecture
 
 public final class ChatInputView: UIToolbar {
-
-    public init(store: Store<ChatInputState, ChatInputAction>) {
-        self.store = store
-        self.viewStore = ViewStore(store)
-        super.init(frame: .zero)
-
-        setup()
-        observeStore()
-        setupUIActions()
-        viewStore.send(.setup)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    deinit {
-        viewStore.send(.destroy)
-    }
-
-    public func setMaxHeight(_ function: @escaping () -> CGFloat) {
-        text.maxHeight = function
-    }
-
-    public func setupReply(message: String, sender: String) {
-        viewStore.send(.text(.didTriggerReply(message, sender)))
-    }
-
-    let store: Store<ChatInputState, ChatInputAction>
-    let viewStore: ViewStore<ChatInputState, ChatInputAction>
-    private var cancellables: Set<AnyCancellable> = []
-
-    let stack = UIStackView()
-    let text = TextInputView()
-    let audio = AudioView()
-    let actions = ActionsView()
-
-    private func setup() {
-        isTranslucent = false
-        translatesAutoresizingMaskIntoConstraints = false
-        barTintColor = Asset.neutralWhite.color
-
-        stack.axis = .vertical
-        stack.spacing = 8
-        stack.addArrangedSubview(text)
-        stack.addArrangedSubview(audio)
-        stack.addArrangedSubview(actions)
-
-        addSubview(stack)
-        stack.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            stack.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 8),
-            stack.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 8),
-            stack.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -8),
-            stack.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -8),
-        ])
-    }
-
-    private func observeStore() {
-        viewStore.publisher
-            .map(\.isPresentingActions)
-            .combineLatest(viewStore.publisher.map(\.canAddAttachments))
-            .sink { [unowned self] isPresentingActions, canAddAttachments in
-                if canAddAttachments {
-                    text.showActionsButton.isHidden = isPresentingActions
-                    text.hideActionsButton.isHidden = !isPresentingActions
-                    actions.isHidden = !isPresentingActions
-                } else {
-                    text.showActionsButton.isHidden = true
-                    text.hideActionsButton.isHidden = true
-                    actions.isHidden = true
-                }
-            }
-            .store(in: &cancellables)
-
-        viewStore.publisher
-            .map(\.reply)
-            .sink { [unowned self] reply in
-                guard let reply = reply else {
-                    text.replyView.isHidden = true
-                    return
-                }
-
-                text.replyView.isHidden = false
-                text.replyView.messageLabel.text = reply.text
-                text.replyView.nameLabel.text = reply.name
-            }.store(in: &cancellables)
-
-        viewStore.publisher
-            .map(\.audio)
-            .map { $0 != nil }
-            .sink { [unowned self] in
-                text.isHidden = $0
-                audio.isHidden = !$0
-            }
-            .store(in: &cancellables)
-
-        viewStore.publisher
-            .map(\.text.isEmpty)
-            .combineLatest(viewStore.publisher.map(\.canAddAttachments))
-            .sink { [unowned self] textIsEmpty, canAddAttachments in
-                if canAddAttachments {
-                    text.sendButton.isHidden = textIsEmpty
-                    text.audioButton.isHidden = !textIsEmpty
-                } else {
-                    text.sendButton.isHidden = false
-                    text.audioButton.isHidden = true
-                }
-
-                text.sendButton.isEnabled = !textIsEmpty
-                text.placeholderView.isHidden = !textIsEmpty
-            }
-            .store(in: &cancellables)
-
-        viewStore.publisher
-            .map(\.text)
-            .sink { [unowned self] in
-                if text.textView.markedTextRange == nil {
-                    let range = text.textView.selectedTextRange
-                    text.textView.text = $0
-
-                    if let range = range {
-                        text.textView.selectedTextRange = range
-                    }
-                } else if $0 == "" {
-                    text.textView.text = $0
-                }
-
-                text.updateHeight()
-            }.store(in: &cancellables)
-
-        let timeFormatter = DateComponentsFormatter()
-        timeFormatter.unitsStyle = .positional
-        timeFormatter.allowedUnits = [.minute, .second]
-        timeFormatter.zeroFormattingBehavior = .pad
-
-        viewStore.publisher
-            .map(\.audio)
-            .sink { [unowned self] in
-                switch $0 {
-                case let .idle(_, duration):
-                    audio.playButton.isHidden = false
-                    audio.stopPlaybackButton.isHidden = true
-                    audio.stopRecordingButton.isHidden = true
-                    audio.sendButton.isHidden = false
-                    audio.timeLabel.text = timeFormatter.string(from: duration)
-
-                case let .recording(_, time):
-                    audio.playButton.isHidden = true
-                    audio.stopPlaybackButton.isHidden = true
-                    audio.stopRecordingButton.isHidden = false
-                    audio.sendButton.isHidden = true
-                    audio.timeLabel.text = timeFormatter.string(from: time)
-
-                case let .playing(_, _, time):
-                    audio.playButton.isHidden = true
-                    audio.stopPlaybackButton.isHidden = false
-                    audio.stopRecordingButton.isHidden = true
-                    audio.sendButton.isHidden = false
-                    audio.timeLabel.text = timeFormatter.string(from: time)
-
-                case .none:
-                    audio.playButton.isHidden = true
-                    audio.stopPlaybackButton.isHidden = true
-                    audio.stopRecordingButton.isHidden = true
-                    audio.sendButton.isHidden = true
-                    audio.timeLabel.text = ""
-                }
-            }
-            .store(in: &cancellables)
-    }
-
-    private func setupUIActions() {
-        text.textDidChange = { [unowned self] text in viewStore.send(.text(.didUpdate(text))) }
-
-        text.replyView.abortButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewStore.send(.text(.didTapAbortReply)) }
-            .store(in: &cancellables)
-
-        text.showActionsButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewStore.send(.text(.didTapShowActions)) }
-            .store(in: &cancellables)
-
-        text.hideActionsButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewStore.send(.text(.didTapHideActions)) }
-            .store(in: &cancellables)
-
-        text.sendButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewStore.send(.text(.didTapSend)) }
-            .store(in: &cancellables)
-
-        text.audioButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewStore.send(.text(.didTapAudio)) }
-            .store(in: &cancellables)
-
-        audio.cancelButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewStore.send(.audio(.didTapCancel)) }
-            .store(in: &cancellables)
-
-        audio.playButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewStore.send(.audio(.didTapPlay)) }
-            .store(in: &cancellables)
-
-        audio.stopPlaybackButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewStore.send(.audio(.didTapStopPlayback)) }
-            .store(in: &cancellables)
-
-        audio.stopRecordingButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewStore.send(.audio(.didTapStopRecording)) }
-            .store(in: &cancellables)
-
-        audio.sendButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewStore.send(.audio(.didTapSend)) }
-            .store(in: &cancellables)
-
-        actions.libraryButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewStore.send(.actions(.didTapLibrary)) }
-            .store(in: &cancellables)
-
-        actions.cameraButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewStore.send(.actions(.didTapCamera)) }
-            .store(in: &cancellables)
-    }
+  public init(store: Store<ChatInputState, ChatInputAction>) {
+    self.store = store
+    self.viewStore = ViewStore(store)
+    super.init(frame: .zero)
+
+    setup()
+    observeStore()
+    setupUIActions()
+    viewStore.send(.setup)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  deinit {
+    viewStore.send(.destroy)
+  }
+
+  public func setMaxHeight(_ function: @escaping () -> CGFloat) {
+    text.maxHeight = function
+  }
+
+  public func setupReply(message: String, sender: String) {
+    viewStore.send(.text(.didTriggerReply(message, sender)))
+  }
+
+  let store: Store<ChatInputState, ChatInputAction>
+  let viewStore: ViewStore<ChatInputState, ChatInputAction>
+  private var cancellables: Set<AnyCancellable> = []
+
+  let stack = UIStackView()
+  let text = TextInputView()
+  let audio = AudioView()
+  let actions = ActionsView()
+
+  private func setup() {
+    isTranslucent = false
+    translatesAutoresizingMaskIntoConstraints = false
+    barTintColor = Asset.neutralWhite.color
+
+    stack.axis = .vertical
+    stack.spacing = 8
+    stack.addArrangedSubview(text)
+    stack.addArrangedSubview(audio)
+    stack.addArrangedSubview(actions)
+
+    addSubview(stack)
+    stack.translatesAutoresizingMaskIntoConstraints = false
+    NSLayoutConstraint.activate([
+      stack.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 8),
+      stack.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 8),
+      stack.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -8),
+      stack.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -8),
+    ])
+  }
+
+  private func observeStore() {
+    viewStore.publisher
+      .map(\.isPresentingActions)
+      .combineLatest(viewStore.publisher.map(\.canAddAttachments))
+      .sink { [unowned self] isPresentingActions, canAddAttachments in
+        if canAddAttachments {
+          text.showActionsButton.isHidden = isPresentingActions
+          text.hideActionsButton.isHidden = !isPresentingActions
+          actions.isHidden = !isPresentingActions
+        } else {
+          text.showActionsButton.isHidden = true
+          text.hideActionsButton.isHidden = true
+          actions.isHidden = true
+        }
+      }
+      .store(in: &cancellables)
+
+    viewStore.publisher
+      .map(\.reply)
+      .sink { [unowned self] reply in
+        guard let reply = reply else {
+          text.replyView.isHidden = true
+          return
+        }
+
+        text.replyView.isHidden = false
+        text.replyView.messageLabel.text = reply.text
+        text.replyView.nameLabel.text = reply.name
+      }.store(in: &cancellables)
+
+    viewStore.publisher
+      .map(\.audio)
+      .map { $0 != nil }
+      .sink { [unowned self] in
+        text.isHidden = $0
+        audio.isHidden = !$0
+      }
+      .store(in: &cancellables)
+
+    viewStore.publisher
+      .map(\.text.isEmpty)
+      .combineLatest(viewStore.publisher.map(\.canAddAttachments))
+      .sink { [unowned self] textIsEmpty, canAddAttachments in
+        if canAddAttachments {
+          text.sendButton.isHidden = textIsEmpty
+          text.audioButton.isHidden = !textIsEmpty
+        } else {
+          text.sendButton.isHidden = false
+          text.audioButton.isHidden = true
+        }
+
+        text.sendButton.isEnabled = !textIsEmpty
+        text.placeholderView.isHidden = !textIsEmpty
+      }
+      .store(in: &cancellables)
+
+    viewStore.publisher
+      .map(\.text)
+      .sink { [unowned self] in
+        if text.textView.markedTextRange == nil {
+          let range = text.textView.selectedTextRange
+          text.textView.text = $0
+
+          if let range = range {
+            text.textView.selectedTextRange = range
+          }
+        } else if $0 == "" {
+          text.textView.text = $0
+        }
+
+        text.updateHeight()
+      }.store(in: &cancellables)
+
+    let timeFormatter = DateComponentsFormatter()
+    timeFormatter.unitsStyle = .positional
+    timeFormatter.allowedUnits = [.minute, .second]
+    timeFormatter.zeroFormattingBehavior = .pad
+
+    viewStore.publisher
+      .map(\.audio)
+      .sink { [unowned self] in
+        switch $0 {
+        case let .idle(_, duration):
+          audio.playButton.isHidden = false
+          audio.stopPlaybackButton.isHidden = true
+          audio.stopRecordingButton.isHidden = true
+          audio.sendButton.isHidden = false
+          audio.timeLabel.text = timeFormatter.string(from: duration)
+
+        case let .recording(_, time):
+          audio.playButton.isHidden = true
+          audio.stopPlaybackButton.isHidden = true
+          audio.stopRecordingButton.isHidden = false
+          audio.sendButton.isHidden = true
+          audio.timeLabel.text = timeFormatter.string(from: time)
+
+        case let .playing(_, _, time):
+          audio.playButton.isHidden = true
+          audio.stopPlaybackButton.isHidden = false
+          audio.stopRecordingButton.isHidden = true
+          audio.sendButton.isHidden = false
+          audio.timeLabel.text = timeFormatter.string(from: time)
+
+        case .none:
+          audio.playButton.isHidden = true
+          audio.stopPlaybackButton.isHidden = true
+          audio.stopRecordingButton.isHidden = true
+          audio.sendButton.isHidden = true
+          audio.timeLabel.text = ""
+        }
+      }
+      .store(in: &cancellables)
+  }
+
+  private func setupUIActions() {
+    text.textDidChange = { [unowned self] text in viewStore.send(.text(.didUpdate(text))) }
+
+    text.replyView.abortButton.publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewStore.send(.text(.didTapAbortReply)) }
+      .store(in: &cancellables)
+
+    text.showActionsButton.publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewStore.send(.text(.didTapShowActions)) }
+      .store(in: &cancellables)
+
+    text.hideActionsButton.publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewStore.send(.text(.didTapHideActions)) }
+      .store(in: &cancellables)
+
+    text.sendButton.publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewStore.send(.text(.didTapSend)) }
+      .store(in: &cancellables)
+
+    text.audioButton.publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewStore.send(.text(.didTapAudio)) }
+      .store(in: &cancellables)
+
+    audio.cancelButton.publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewStore.send(.audio(.didTapCancel)) }
+      .store(in: &cancellables)
+
+    audio.playButton.publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewStore.send(.audio(.didTapPlay)) }
+      .store(in: &cancellables)
+
+    audio.stopPlaybackButton.publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewStore.send(.audio(.didTapStopPlayback)) }
+      .store(in: &cancellables)
+
+    audio.stopRecordingButton.publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewStore.send(.audio(.didTapStopRecording)) }
+      .store(in: &cancellables)
+
+    audio.sendButton.publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewStore.send(.audio(.didTapSend)) }
+      .store(in: &cancellables)
+
+    actions.libraryButton.publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewStore.send(.actions(.didTapLibrary)) }
+      .store(in: &cancellables)
+
+    actions.cameraButton.publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewStore.send(.actions(.didTapCamera)) }
+      .store(in: &cancellables)
+  }
 }
diff --git a/Sources/ChatInputFeature/TextInputView.swift b/Sources/ChatInputFeature/TextInputView.swift
index 960d534fe9757710edb7cb157dd6fe6852f77d00..9a074c8cf78f058a97ff59e5f49475f5da85c1dd 100644
--- a/Sources/ChatInputFeature/TextInputView.swift
+++ b/Sources/ChatInputFeature/TextInputView.swift
@@ -1,121 +1,122 @@
 import UIKit
 import Shared
+import AppResources
 
 final class TextInputView: UIView, UITextViewDelegate {
-    let internalStack = UIStackView()
-    var replyView = ChatInputReply()
-    var placeholderView = UITextView()
-    lazy var bubble = BubbleView(internalStack, padding: 4)
-
-    let stack = UIStackView()
-    let textView = UITextView()
-    let showActionsButton = UIButton()
-    let hideActionsButton = UIButton()
-    let sendButton = UIButton()
-    let audioButton = UIButton()
-
-    var maxHeight: () -> CGFloat = { 150 }
-    var textDidChange: (String) -> Void = { _ in }
-
-    private var computedTextHeight: CGFloat {
-        let textWidth = textView.frame.size.width
-        let size = CGSize(width: textWidth, height: .greatestFiniteMagnitude)
-        return textView.sizeThatFits(size).height
+  let internalStack = UIStackView()
+  var replyView = ChatInputReply()
+  var placeholderView = UITextView()
+  lazy var bubble = BubbleView(internalStack, padding: 4)
+
+  let stack = UIStackView()
+  let textView = UITextView()
+  let showActionsButton = UIButton()
+  let hideActionsButton = UIButton()
+  let sendButton = UIButton()
+  let audioButton = UIButton()
+
+  var maxHeight: () -> CGFloat = { 150 }
+  var textDidChange: (String) -> Void = { _ in }
+
+  private var computedTextHeight: CGFloat {
+    let textWidth = textView.frame.size.width
+    let size = CGSize(width: textWidth, height: .greatestFiniteMagnitude)
+    return textView.sizeThatFits(size).height
+  }
+
+  init() {
+    super.init(frame: .zero)
+    setup()
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  override func layoutSubviews() {
+    super.layoutSubviews()
+    updateHeight()
+  }
+
+  func updateHeight() {
+    let replyHeight = replyView.isHidden ? 0 : replyView.bounds.height
+    let computedTextHeight = self.computedTextHeight
+    let computedHeight = computedTextHeight + replyHeight
+    let maxHeight = self.maxHeight()
+
+    if computedHeight < maxHeight {
+      textView.snp.updateConstraints { $0.height.equalTo(computedTextHeight) }
+      textView.isScrollEnabled = false
+    } else {
+      textView.snp.updateConstraints { $0.height.equalTo(maxHeight - replyHeight) }
+      textView.isScrollEnabled = true
     }
-
-    init() {
-        super.init(frame: .zero)
-        setup()
+  }
+
+  private func setup() {
+    replyView.isHidden = true
+    textView.autocorrectionType = .default
+    placeholderView.isUserInteractionEnabled = false
+    textView.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    placeholderView.text = Localized.Chat.placeholder
+    placeholderView.font = Fonts.Mulish.semiBold.font(size: 14.0)
+
+    textView.backgroundColor = .clear
+    placeholderView.backgroundColor = .clear
+    textView.textColor = Asset.neutralActive.color
+    bubble.backgroundColor = Asset.neutralSecondary.color
+    placeholderView.textColor = Asset.neutralDisabled.color
+
+    showActionsButton.setImage(Asset.chatInputActionOpen.image, for: .normal)
+    hideActionsButton.setImage(Asset.chatInputActionClose.image, for: .normal)
+    audioButton.setImage(Asset.chatInputVoiceStart.image, for: .normal)
+    sendButton.setImage(Asset.chatSend.image, for: .normal)
+
+    showActionsButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+    showActionsButton.setContentCompressionResistancePriority(.required, for: .horizontal)
+
+    hideActionsButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+    hideActionsButton.setContentCompressionResistancePriority(.required, for: .horizontal)
+
+    sendButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+    sendButton.setContentCompressionResistancePriority(.required, for: .horizontal)
+
+    audioButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+    audioButton.setContentCompressionResistancePriority(.required, for: .horizontal)
+
+    internalStack.axis = .vertical
+    internalStack.addArrangedSubview(replyView)
+    internalStack.addArrangedSubview(textView)
+
+    textView.addSubview(placeholderView)
+    textView.setContentHuggingPriority(.defaultLow, for: .horizontal)
+
+    placeholderView.snp.makeConstraints { make in
+      make.top.equalToSuperview()
+      make.left.equalToSuperview()
+      make.height.equalToSuperview()
+      make.width.equalToSuperview()
     }
 
-    required init?(coder: NSCoder) { nil }
-
-    override func layoutSubviews() {
-        super.layoutSubviews()
-        updateHeight()
-    }
-
-    func updateHeight() {
-        let replyHeight = replyView.isHidden ? 0 : replyView.bounds.height
-        let computedTextHeight = self.computedTextHeight
-        let computedHeight = computedTextHeight + replyHeight
-        let maxHeight = self.maxHeight()
-
-        if computedHeight < maxHeight {
-            textView.snp.updateConstraints { $0.height.equalTo(computedTextHeight) }
-            textView.isScrollEnabled = false
-        } else {
-            textView.snp.updateConstraints { $0.height.equalTo(maxHeight - replyHeight) }
-            textView.isScrollEnabled = true
-        }
-    }
-
-    private func setup() {
-        replyView.isHidden = true
-        textView.autocorrectionType = .default
-        placeholderView.isUserInteractionEnabled = false
-        textView.font = Fonts.Mulish.semiBold.font(size: 14.0)
-        placeholderView.text = Localized.Chat.placeholder
-        placeholderView.font = Fonts.Mulish.semiBold.font(size: 14.0)
-
-        textView.backgroundColor = .clear
-        placeholderView.backgroundColor = .clear
-        textView.textColor = Asset.neutralActive.color
-        bubble.backgroundColor = Asset.neutralSecondary.color
-        placeholderView.textColor = Asset.neutralDisabled.color
-
-        showActionsButton.setImage(Asset.chatInputActionOpen.image, for: .normal)
-        hideActionsButton.setImage(Asset.chatInputActionClose.image, for: .normal)
-        audioButton.setImage(Asset.chatInputVoiceStart.image, for: .normal)
-        sendButton.setImage(Asset.chatSend.image, for: .normal)
-
-        showActionsButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
-        showActionsButton.setContentCompressionResistancePriority(.required, for: .horizontal)
-
-        hideActionsButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
-        hideActionsButton.setContentCompressionResistancePriority(.required, for: .horizontal)
-
-        sendButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
-        sendButton.setContentCompressionResistancePriority(.required, for: .horizontal)
-
-        audioButton.setContentHuggingPriority(.defaultHigh, for: .horizontal)
-        audioButton.setContentCompressionResistancePriority(.required, for: .horizontal)
-
-        internalStack.axis = .vertical
-        internalStack.addArrangedSubview(replyView)
-        internalStack.addArrangedSubview(textView)
-
-        textView.addSubview(placeholderView)
-        textView.setContentHuggingPriority(.defaultLow, for: .horizontal)
-
-        placeholderView.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview()
-            make.height.equalToSuperview()
-            make.width.equalToSuperview()
-        }
-
-        stack.axis = .horizontal
-        stack.spacing = 8
-        stack.addArrangedSubview(showActionsButton)
-        stack.addArrangedSubview(hideActionsButton)
-        stack.addArrangedSubview(bubble)
-        stack.addArrangedSubview(sendButton)
-        stack.addArrangedSubview(audioButton)
-
-        addSubview(stack)
-        stack.translatesAutoresizingMaskIntoConstraints = false
-        NSLayoutConstraint.activate([
-            stack.topAnchor.constraint(equalTo: topAnchor),
-            stack.leadingAnchor.constraint(equalTo: leadingAnchor),
-            stack.trailingAnchor.constraint(equalTo: trailingAnchor),
-            stack.bottomAnchor.constraint(equalTo: bottomAnchor),
-        ])
-
-        textView.delegate = self
-    }
-
-    func textViewDidChange(_ textView: UITextView) {
-        textDidChange(textView.text)
-    }
+    stack.axis = .horizontal
+    stack.spacing = 8
+    stack.addArrangedSubview(showActionsButton)
+    stack.addArrangedSubview(hideActionsButton)
+    stack.addArrangedSubview(bubble)
+    stack.addArrangedSubview(sendButton)
+    stack.addArrangedSubview(audioButton)
+
+    addSubview(stack)
+    stack.translatesAutoresizingMaskIntoConstraints = false
+    NSLayoutConstraint.activate([
+      stack.topAnchor.constraint(equalTo: topAnchor),
+      stack.leadingAnchor.constraint(equalTo: leadingAnchor),
+      stack.trailingAnchor.constraint(equalTo: trailingAnchor),
+      stack.bottomAnchor.constraint(equalTo: bottomAnchor),
+    ])
+
+    textView.delegate = self
+  }
+
+  func textViewDidChange(_ textView: UITextView) {
+    textDidChange(textView.text)
+  }
 }
diff --git a/Sources/ChatListFeature/Controller/ChatListController.swift b/Sources/ChatListFeature/Controller/ChatListController.swift
index 57e744a3b29d7ab15371624fe2ce0932e7f9d772..7383b423c17c6acca5df900204af90d7db50c9d9 100644
--- a/Sources/ChatListFeature/Controller/ChatListController.swift
+++ b/Sources/ChatListFeature/Controller/ChatListController.swift
@@ -1,236 +1,249 @@
 import UIKit
-import Theme
-import Models
 import Shared
 import Combine
+import AppCore
 import XXModels
-import MenuFeature
-import DependencyInjection
+import AppResources
+import Dependencies
+import AppNavigation
 
 public final class ChatListController: UIViewController {
-    @Dependency private var coordinator: ChatListCoordinating
-    @Dependency private var statusBarController: StatusBarStyleControlling
-
-    lazy private var screenView = ChatListView()
-    lazy private var topLeftView = ChatListTopLeftNavView()
-    lazy private var topRightView = ChatListTopRightNavView()
-    lazy private var tableController = ChatListTableController(viewModel)
-    lazy private var searchTableController = ChatSearchTableController(viewModel)
-    private var collectionDataSource: UICollectionViewDiffableDataSource<SectionId, Contact>!
-
-    private let viewModel = ChatListViewModel()
-    private var cancellables = Set<AnyCancellable>()
-    private var drawerCancellables = Set<AnyCancellable>()
-
-    private var isEditingSearch = false {
-        didSet {
-            screenView.listContainerView
-                .showRecentsCollection(isEditingSearch ? false : shouldBeShowingRecents)
-        }
-    }
-
-    private var shouldBeShowingRecents = false {
-        didSet {
-            screenView.listContainerView
-                .showRecentsCollection(isEditingSearch ? false : shouldBeShowingRecents)
-        }
-    }
-
-    public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
-        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
-        navigationItem.backButtonTitle = ""
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func loadView() {
-        view = screenView
-    }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        statusBarController.style.send(.darkContent)
-        navigationController?.navigationBar.customize(backgroundColor: Asset.neutralWhite.color)
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+  
+  private lazy var screenView = ChatListView()
+  private lazy var topLeftView = ChatListTopLeftNavView()
+  private lazy var topRightView = ChatListTopRightNavView()
+  private lazy var tableController = ChatListTableController(viewModel)
+  private lazy var searchTableController = ChatSearchTableController(viewModel)
+  private var collectionDataSource: UICollectionViewDiffableDataSource<SectionId, Contact>!
+  
+  private let viewModel = ChatListViewModel()
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
+  
+  private var isEditingSearch = false {
+    didSet {
+      screenView.listContainerView
+        .showRecentsCollection(isEditingSearch ? false : shouldBeShowingRecents)
     }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupChatList()
-        setupBindings()
-        setupNavigationBar()
-        setupRecentContacts()
+  }
+  
+  private var shouldBeShowingRecents = false {
+    didSet {
+      screenView.listContainerView
+        .showRecentsCollection(isEditingSearch ? false : shouldBeShowingRecents)
     }
-
-    private func setupNavigationBar() {
-        navigationItem.leftBarButtonItem = UIBarButtonItem(customView: topLeftView)
-        navigationItem.rightBarButtonItem = UIBarButtonItem(customView: topRightView)
-
-        topRightView.actionPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                switch $0 {
-                case .didTapSearch:
-                    coordinator.toSearch(from: self)
-                case .didTapNewGroup:
-                    coordinator.toNewGroup(from: self)
-                }
-            }.store(in: &cancellables)
-
-        viewModel.badgeCountPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in topLeftView.updateBadge($0) }
-            .store(in: &cancellables)
-
-        topLeftView.actionPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toSideMenu(from: self) }
-            .store(in: &cancellables)
-   }
-
-    private func setupChatList() {
-        addChild(tableController)
-        addChild(searchTableController)
-
-        screenView.listContainerView.addSubview(tableController.view)
-        screenView.searchListContainerView.addSubview(searchTableController.view)
-
-        tableController.view.snp.makeConstraints {
-            $0.top.equalTo(screenView.listContainerView.collectionContainerView.snp.bottom)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
+  }
+  
+  public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
+    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
+    navigationItem.backButtonTitle = ""
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  public override func loadView() {
+    view = screenView
+  }
+  
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    statusBar.set(.darkContent)
+    navigationController?.navigationBar.customize(backgroundColor: Asset.neutralWhite.color)
+  }
+  
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupChatList()
+    setupBindings()
+    setupNavigationBar()
+    setupRecentContacts()
+  }
+  
+  private func setupNavigationBar() {
+    navigationItem.leftBarButtonItem = UIBarButtonItem(customView: topLeftView)
+    navigationItem.rightBarButtonItem = UIBarButtonItem(customView: topRightView)
+    
+    topRightView
+      .actionPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        switch $0 {
+        case .didTapSearch:
+          navigator.perform(PresentSearch(on: navigationController!))
+        case .didTapNewGroup:
+          navigator.perform(
+            PresentGroupDraft(on: navigationController!)
+          )
         }
-
-        searchTableController.view.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
-
-        tableController.didMove(toParent: self)
-        searchTableController.didMove(toParent: self)
+      }.store(in: &cancellables)
+    
+    viewModel
+      .badgeCountPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        topLeftView.updateBadge($0)
+      }.store(in: &cancellables)
+    
+    topLeftView
+      .actionPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentMenu(currentItem: .chats, from: self))
+      }.store(in: &cancellables)
+  }
+  
+  private func setupChatList() {
+    addChild(tableController)
+    addChild(searchTableController)
+    screenView.listContainerView.addSubview(tableController.view)
+    screenView.searchListContainerView.addSubview(searchTableController.view)
+    
+    tableController.view.snp.makeConstraints {
+      $0.top.equalTo(screenView.listContainerView.collectionContainerView.snp.bottom)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
     }
-
-    private func setupRecentContacts() {
-        screenView
-            .listContainerView
-            .collectionView
-            .register(ChatListRecentContactCell.self)
-
-        collectionDataSource = UICollectionViewDiffableDataSource<SectionId, Contact>(
-            collectionView: screenView.listContainerView.collectionView
-        ) { collectionView, indexPath, contact in
-            let cell: ChatListRecentContactCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-            let title = (contact.nickname ?? contact.username) ?? ""
-            cell.setup(title: title, image: contact.photo)
-            return cell
-        }
-
-        screenView.listContainerView.collectionView.delegate = self
-        screenView.listContainerView.collectionView.dataSource = collectionDataSource
-
-        viewModel.recentsPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                collectionDataSource.apply($0)
-                shouldBeShowingRecents = $0.numberOfItems > 0
-            }.store(in: &cancellables)
+    searchTableController.view.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
     }
-
-    private func setupBindings() {
-        screenView.searchView
-            .rightPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toScan(from: self) }
-            .store(in: &cancellables)
-
-        screenView.searchView
-            .textPublisher
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] query in
-                viewModel.updateSearch(query: query)
-                screenView.searchListContainerView.emptyView.updateSearched(content: query)
-            }.store(in: &cancellables)
-
-        Publishers.CombineLatest(
-            viewModel.searchPublisher,
-            screenView.searchView.textPublisher.removeDuplicates()
-        )
-        .receive(on: DispatchQueue.main)
-            .sink { [unowned self] items, query in
-                guard query.isEmpty == false else {
-                    screenView.searchListContainerView.isHidden = true
-                    screenView.listContainerView.isHidden = false
-                    screenView.bringSubviewToFront(screenView.listContainerView)
-                    return
-                }
-
-                screenView.listContainerView.isHidden = true
-                screenView.searchListContainerView.isHidden = false
-
-                guard items.numberOfItems > 0 else {
-                    screenView.searchListContainerView.emptyView.isHidden = false
-                    screenView.bringSubviewToFront(screenView.searchListContainerView)
-                    screenView.searchListContainerView.bringSubviewToFront(screenView.searchListContainerView.emptyView)
-                    return
-                }
-
-                screenView.searchListContainerView.bringSubviewToFront(searchTableController.view)
-                screenView.searchListContainerView.emptyView.isHidden = true
-            }
-            .store(in: &cancellables)
-
-        screenView.searchView
-            .isEditingPublisher
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in isEditingSearch = $0 }
-            .store(in: &cancellables)
-
-        viewModel.chatsPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                guard $0.isEmpty == false else {
-                    screenView.listContainerView.bringSubviewToFront(screenView.listContainerView.emptyView)
-                    screenView.listContainerView.emptyView.isHidden = false
-                    return
-                }
-
-                screenView.listContainerView.bringSubviewToFront(tableController.view)
-                screenView.listContainerView.emptyView.isHidden = true
-            }
-            .store(in: &cancellables)
-
-        screenView.searchListContainerView
-            .emptyView.searchButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in coordinator.toSearch(from: self) }
-            .store(in: &cancellables)
-
-        screenView.listContainerView
-            .emptyView.contactsButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toContacts(from: self) }
-            .store(in: &cancellables)
-
-        viewModel.isOnline
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [weak screenView] connected in screenView?.showConnectingBanner(!connected) }
-            .store(in: &cancellables)
+    tableController.didMove(toParent: self)
+    searchTableController.didMove(toParent: self)
+  }
+  
+  private func setupRecentContacts() {
+    screenView
+      .listContainerView
+      .collectionView
+      .register(ChatListRecentContactCell.self)
+    
+    collectionDataSource = UICollectionViewDiffableDataSource<SectionId, Contact>(
+      collectionView: screenView.listContainerView.collectionView
+    ) { collectionView, indexPath, contact in
+      let cell: ChatListRecentContactCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+      let title = (contact.nickname ?? contact.username) ?? ""
+      cell.setup(title: title, image: contact.photo)
+      return cell
     }
+    
+    screenView.listContainerView.collectionView.delegate = self
+    screenView.listContainerView.collectionView.dataSource = collectionDataSource
+    
+    viewModel
+      .recentsPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        collectionDataSource.apply($0)
+        shouldBeShowingRecents = $0.numberOfItems > 0
+      }.store(in: &cancellables)
+  }
+  
+  private func setupBindings() {
+    screenView
+      .searchView
+      .rightPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentScan(on: navigationController!))
+      }.store(in: &cancellables)
+    
+    screenView
+      .searchView
+      .textPublisher
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] query in
+        viewModel.updateSearch(query: query)
+        screenView.searchListContainerView.emptyView.updateSearched(content: query)
+      }.store(in: &cancellables)
+    
+    Publishers.CombineLatest(
+      viewModel.searchPublisher,
+      screenView.searchView.textPublisher.removeDuplicates()
+    )
+    .receive(on: DispatchQueue.main)
+    .sink { [unowned self] items, query in
+      guard query.isEmpty == false else {
+        screenView.searchListContainerView.isHidden = true
+        screenView.listContainerView.isHidden = false
+        screenView.bringSubviewToFront(screenView.listContainerView)
+        return
+      }
+      screenView.listContainerView.isHidden = true
+      screenView.searchListContainerView.isHidden = false
+      guard items.numberOfItems > 0 else {
+        screenView.searchListContainerView.emptyView.isHidden = false
+        screenView.bringSubviewToFront(screenView.searchListContainerView)
+        screenView.searchListContainerView.bringSubviewToFront(screenView.searchListContainerView.emptyView)
+        return
+      }
+      screenView.searchListContainerView.bringSubviewToFront(searchTableController.view)
+      screenView.searchListContainerView.emptyView.isHidden = true
+    }.store(in: &cancellables)
+    
+    screenView
+      .searchView
+      .isEditingPublisher
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        isEditingSearch = $0
+      }.store(in: &cancellables)
+    
+    viewModel
+      .chatsPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        guard $0.isEmpty == false else {
+          screenView.listContainerView.bringSubviewToFront(screenView.listContainerView.emptyView)
+          screenView.listContainerView.emptyView.isHidden = false
+          return
+        }
+        screenView.listContainerView.bringSubviewToFront(tableController.view)
+        screenView.listContainerView.emptyView.isHidden = true
+      }.store(in: &cancellables)
+    
+    screenView
+      .searchListContainerView
+      .emptyView
+      .searchButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        navigator.perform(PresentSearch(on: navigationController!))
+      }.store(in: &cancellables)
+    
+    screenView
+      .listContainerView
+      .emptyView
+      .contactsButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentContactList(on: navigationController!))
+      }.store(in: &cancellables)
+    
+    viewModel
+      .isOnline
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [weak screenView] connected in
+        screenView?.showConnectingBanner(!connected)
+      }.store(in: &cancellables)
+  }
 }
 
 extension ChatListController: UICollectionViewDelegate {
-    public func collectionView(
-        _ collectionView: UICollectionView,
-        didSelectItemAt indexPath: IndexPath
-    ) {
-        if let contact = collectionDataSource.itemIdentifier(for: indexPath) {
-            coordinator.toSingleChat(with: contact, from: self)
-        }
+  public func collectionView(
+    _ collectionView: UICollectionView,
+    didSelectItemAt indexPath: IndexPath
+  ) {
+    if let contact = collectionDataSource.itemIdentifier(for: indexPath) {
+      navigator.perform(PresentChat(contact: contact, on: navigationController!))
     }
+  }
 }
diff --git a/Sources/ChatListFeature/Controller/ChatListSearchTableController.swift b/Sources/ChatListFeature/Controller/ChatListSearchTableController.swift
index 46e9c20bed9b21b96d883f0ffc07db81caad0de9..284a571745afde1d577fa0a34afe48be13fd3d76 100644
--- a/Sources/ChatListFeature/Controller/ChatListSearchTableController.swift
+++ b/Sources/ChatListFeature/Controller/ChatListSearchTableController.swift
@@ -1,129 +1,127 @@
 import UIKit
 import Shared
-import Models
 import Combine
-import DependencyInjection
+import AppResources
+import Dependencies
+import AppNavigation
 
 class ChatSearchListTableViewDiffableDataSource: UITableViewDiffableDataSource<SearchSection, SearchItem> {
-    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
-        switch snapshot().sectionIdentifiers[section] {
-        case .chats:
-            return "CHATS"
-        case .connections:
-            return "CONNECTIONS"
-        }
+  override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+    switch snapshot().sectionIdentifiers[section] {
+    case .chats:
+      return "CHATS"
+    case .connections:
+      return "CONNECTIONS"
     }
+  }
 }
 
 final class ChatSearchTableController: UITableViewController {
-    @Dependency private var coordinator: ChatListCoordinating
-
-    private let viewModel: ChatListViewModel
-    private let cellHeight: CGFloat = 83.0
-    private var cancellables = Set<AnyCancellable>()
-    private var tableDataSource: ChatSearchListTableViewDiffableDataSource?
-
-    init(_ viewModel: ChatListViewModel) {
-        self.viewModel = viewModel
-        super.init(style: .grouped)
-
-        tableDataSource = ChatSearchListTableViewDiffableDataSource(
-            tableView: tableView
-        ) { table, indexPath, item in
-            let cell = table.dequeueReusableCell(forIndexPath: indexPath, ofType: ChatListCell.self)
-            switch item {
-            case .chat(let info):
-                switch info {
-                case .group(let group):
-                    cell.setupGroup(
-                        name: group.name,
-                        date: group.createdAt,
-                        preview: nil,
-                        unreadCount: 0
-                    )
-
-                case .groupChat(let groupChatInfo):
-                    cell.setupGroup(
-                        name: groupChatInfo.group.name,
-                        date: groupChatInfo.lastMessage.date,
-                        preview: groupChatInfo.lastMessage.text,
-                        unreadCount: groupChatInfo.unreadCount
-                    )
-
-                case .contactChat(let contactChatInfo):
-                    cell.setupContact(
-                        name: (contactChatInfo.contact.nickname ?? contactChatInfo.contact.username) ?? "",
-                        image: contactChatInfo.contact.photo,
-                        date: contactChatInfo.lastMessage.date,
-                        unreadCount: contactChatInfo.unreadCount,
-                        preview: contactChatInfo.lastMessage.text
-                    )
-                }
-
-            case .connection(let contact):
-                cell.setupContact(
-                    name: (contact.nickname ?? contact.username) ?? "",
-                    image: contact.photo,
-                    date: nil,
-                    unreadCount: 0,
-                    preview: contact.username ?? ""
-                )
-            }
-
-            return cell
+  @Dependency(\.navigator) var navigator: Navigator
+
+  private let viewModel: ChatListViewModel
+  private let cellHeight: CGFloat = 83.0
+  private var cancellables = Set<AnyCancellable>()
+  private var tableDataSource: ChatSearchListTableViewDiffableDataSource?
+  
+  init(_ viewModel: ChatListViewModel) {
+    self.viewModel = viewModel
+    super.init(style: .grouped)
+    
+    tableDataSource = ChatSearchListTableViewDiffableDataSource(
+      tableView: tableView
+    ) { table, indexPath, item in
+      let cell = table.dequeueReusableCell(forIndexPath: indexPath, ofType: ChatListCell.self)
+      switch item {
+      case .chat(let info):
+        switch info {
+        case .group(let group):
+          cell.setupGroup(
+            name: group.name,
+            date: group.createdAt,
+            preview: nil,
+            unreadCount: 0
+          )
+          
+        case .groupChat(let groupChatInfo):
+          cell.setupGroup(
+            name: groupChatInfo.group.name,
+            date: groupChatInfo.lastMessage.date,
+            preview: groupChatInfo.lastMessage.text,
+            unreadCount: groupChatInfo.unreadCount
+          )
+          
+        case .contactChat(let contactChatInfo):
+          cell.setupContact(
+            name: (contactChatInfo.contact.nickname ?? contactChatInfo.contact.username) ?? "",
+            image: contactChatInfo.contact.photo,
+            date: contactChatInfo.lastMessage.date,
+            unreadCount: contactChatInfo.unreadCount,
+            preview: contactChatInfo.lastMessage.text
+          )
         }
+        
+      case .connection(let contact):
+        cell.setupContact(
+          name: (contact.nickname ?? contact.username) ?? "",
+          image: contact.photo,
+          date: nil,
+          unreadCount: 0,
+          preview: contact.username ?? ""
+        )
+      }
+      
+      return cell
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    override func viewDidLoad() {
-        super.viewDidLoad()
-
-        tableView.separatorStyle = .none
-        tableView.tableFooterView = UIView()
-        tableView.sectionIndexColor = .blue
-        tableView.register(ChatListCell.self)
-        tableView.dataSource = tableDataSource
-        view.backgroundColor = Asset.neutralWhite.color
-
-        viewModel.searchPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in tableDataSource?.apply($0, animatingDifferences: false) }
-            .store(in: &cancellables)
-    }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  override func viewDidLoad() {
+    super.viewDidLoad()
+    
+    tableView.separatorStyle = .none
+    tableView.tableFooterView = UIView()
+    tableView.sectionIndexColor = .blue
+    tableView.register(ChatListCell.self)
+    tableView.dataSource = tableDataSource
+    view.backgroundColor = Asset.neutralWhite.color
+    
+    viewModel.searchPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in tableDataSource?.apply($0, animatingDifferences: false) }
+      .store(in: &cancellables)
+  }
 }
 
 extension ChatSearchTableController {
-    override func tableView(
-        _ tableView: UITableView,
-        heightForRowAt: IndexPath
-    ) -> CGFloat {
-        return cellHeight
-    }
-
-    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-        if let item = tableDataSource?.itemIdentifier(for: indexPath) {
-            switch item {
-            case .chat(let chatInfo):
-                switch chatInfo {
-                case .group(let group):
-                    if let groupInfo = viewModel.groupInfo(from: group) {
-                        coordinator.toGroupChat(with: groupInfo, from: self)
-                    }
-
-                case .groupChat(let info):
-                    if let groupInfo = viewModel.groupInfo(from: info.group) {
-                        coordinator.toGroupChat(with: groupInfo, from: self)
-                    }
-
-                case .contactChat(let info):
-                    guard info.contact.authStatus == .friend else { return }
-                    coordinator.toSingleChat(with: info.contact, from: self)
-                }
-
-            case .connection(let contact):
-                coordinator.toContact(contact, from: self)
-            }
+  override func tableView(
+    _ tableView: UITableView,
+    heightForRowAt: IndexPath
+  ) -> CGFloat {
+    return cellHeight
+  }
+  
+  override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+    if let item = tableDataSource?.itemIdentifier(for: indexPath) {
+      switch item {
+      case .chat(let chatInfo):
+        switch chatInfo {
+        case .group(let group):
+          if let groupInfo = viewModel.groupInfo(from: group) {
+            navigator.perform(PresentGroupChat(groupInfo: groupInfo, on: navigationController!))
+          }
+        case .groupChat(let info):
+          if let groupInfo = viewModel.groupInfo(from: info.group) {
+            navigator.perform(PresentGroupChat(groupInfo: groupInfo, on: navigationController!))
+          }
+        case .contactChat(let info):
+          guard info.contact.authStatus == .friend else { return }
+          navigator.perform(PresentChat(contact: info.contact, on: navigationController!))
         }
+      case .connection(let contact):
+        navigator.perform(PresentContact(contact: contact, on: navigationController!))
+      }
     }
+  }
 }
diff --git a/Sources/ChatListFeature/Controller/ChatListSheetController.swift b/Sources/ChatListFeature/Controller/ChatListSheetController.swift
index cb52ce6a9313f14e4351bcae2c5fdd82d0bee952..6b0d2d1d1c2a1754f02a4efb4b1ac8d6631c7a2a 100644
--- a/Sources/ChatListFeature/Controller/ChatListSheetController.swift
+++ b/Sources/ChatListFeature/Controller/ChatListSheetController.swift
@@ -2,41 +2,46 @@ import UIKit
 import Combine
 
 public final class ChatListSheetController: UIViewController {
-    public enum Action {
-        case delete
-        case deleteAll
-    }
-
-    lazy private var screenView = ChatListMenuView()
-
-    var didChooseAction: (Action) -> Void
-    private var cancellables = Set<AnyCancellable>()
-
-    public init(_ didChooseAction: @escaping ChatListSheetClosure) {
-        self.didChooseAction = didChooseAction
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func loadView() {
-        view = screenView
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupBindings()
-    }
-
-    private func setupBindings() {
-        screenView.deleteButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in dismiss(animated: true) { [weak self] in self?.didChooseAction(.delete) }}
-            .store(in: &cancellables)
-
-        screenView.deleteAllButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in dismiss(animated: true) { [weak self] in self?.didChooseAction(.deleteAll) }}
-            .store(in: &cancellables)
-    }
+  private lazy var screenView = ChatListMenuView()
+  
+  private let didTapDelete: () -> Void
+  private let didTapDeleteAll: () -> Void
+  private var cancellables = Set<AnyCancellable>()
+  
+  public init(
+    _ didTapDelete: @escaping () -> Void,
+    _ didTapDeleteAll: @escaping () -> Void
+  ) {
+    self.didTapDelete = didTapDelete
+    self.didTapDeleteAll = didTapDeleteAll
+    super.init(nibName: nil, bundle: nil)
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  public override func loadView() {
+    view = screenView
+  }
+  
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+
+    screenView
+      .deleteButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        dismiss(animated: true) { [weak self] in
+          self?.didTapDelete()
+        }
+      }.store(in: &cancellables)
+
+    screenView
+      .deleteAllButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        dismiss(animated: true) { [weak self] in
+          self?.didTapDeleteAll()
+        }
+      }.store(in: &cancellables)
+  }
 }
diff --git a/Sources/ChatListFeature/Controller/ChatListTableController.swift b/Sources/ChatListFeature/Controller/ChatListTableController.swift
index 8527195eee326146909a865a6768e553c10a5e3d..e1c2e06b98954ab0f99d4fa6452fd781a6ede8bd 100644
--- a/Sources/ChatListFeature/Controller/ChatListTableController.swift
+++ b/Sources/ChatListFeature/Controller/ChatListTableController.swift
@@ -1,208 +1,214 @@
 import UIKit
 import Shared
-import Models
 import Combine
 import XXModels
+import AppNavigation
 import DifferenceKit
 import DrawerFeature
-import DependencyInjection
+import Dependencies
+import AppResources
 
 extension ChatInfo: Differentiable {
-    public var differenceIdentifier: ChatInfo.ID { id }
+  public var differenceIdentifier: ChatInfo.ID { id }
 }
 
 final class ChatListTableController: UITableViewController {
-    @Dependency private var coordinator: ChatListCoordinating
-
-    private var rows = [ChatInfo]()
-    private let viewModel: ChatListViewModel
-    private let cellHeight: CGFloat = 83.0
-    private var cancellables = Set<AnyCancellable>()
-    private var drawerCancellables = Set<AnyCancellable>()
-
-    init(_ viewModel: ChatListViewModel) {
-        self.viewModel = viewModel
-        super.init(style: .grouped)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    override func viewDidLoad() {
-        super.viewDidLoad()
-
-        tableView.separatorStyle = .none
-        tableView.backgroundColor = .clear
-        tableView.alwaysBounceVertical = true
-        tableView.register(ChatListCell.self)
-        tableView.tableFooterView = UIView()
-
-        viewModel
-            .chatsPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                guard !self.rows.isEmpty else {
-                    self.rows = $0
-                    tableView.reloadData()
-                    return
-                }
-
-                self.tableView.reload(
-                    using: StagedChangeset(source: self.rows, target: $0),
-                    deleteSectionsAnimation: .automatic,
-                    insertSectionsAnimation: .automatic,
-                    reloadSectionsAnimation: .none,
-                    deleteRowsAnimation: .automatic,
-                    insertRowsAnimation: .automatic,
-                    reloadRowsAnimation: .none
-                ) { [unowned self] in
-                    self.rows = $0
-                }
-            }.store(in: &cancellables)
-    }
+  @Dependency(\.navigator) var navigator: Navigator
+
+  private var rows = [ChatInfo]()
+  private let viewModel: ChatListViewModel
+  private let cellHeight: CGFloat = 83.0
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
+  
+  init(_ viewModel: ChatListViewModel) {
+    self.viewModel = viewModel
+    super.init(style: .grouped)
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  override func viewDidLoad() {
+    super.viewDidLoad()
+    
+    tableView.separatorStyle = .none
+    tableView.backgroundColor = .clear
+    tableView.alwaysBounceVertical = true
+    tableView.register(ChatListCell.self)
+    tableView.tableFooterView = UIView()
+    
+    viewModel
+      .chatsPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        guard !self.rows.isEmpty else {
+          self.rows = $0
+          tableView.reloadData()
+          return
+        }
+        
+        self.tableView.reload(
+          using: StagedChangeset(source: self.rows, target: $0),
+          deleteSectionsAnimation: .automatic,
+          insertSectionsAnimation: .automatic,
+          reloadSectionsAnimation: .none,
+          deleteRowsAnimation: .automatic,
+          insertRowsAnimation: .automatic,
+          reloadRowsAnimation: .none
+        ) { [unowned self] in
+          self.rows = $0
+        }
+      }.store(in: &cancellables)
+  }
 }
 
 extension ChatListTableController {
-    override func tableView(
-        _ tableView: UITableView,
-        numberOfRowsInSection: Int
-    ) -> Int {
-        return rows.count
+  override func tableView(
+    _ tableView: UITableView,
+    numberOfRowsInSection: Int
+  ) -> Int {
+    return rows.count
+  }
+  
+  override func tableView(
+    _ tableView: UITableView,
+    heightForRowAt: IndexPath
+  ) -> CGFloat {
+    return cellHeight
+  }
+  
+  override func tableView(
+    _ tableView: UITableView,
+    trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
+  ) -> UISwipeActionsConfiguration? {
+    
+    let delete = UIContextualAction(style: .normal, title: nil) { [weak self] _, _, complete in
+      guard let self else { return }
+      self.didRequestDeletionOf(self.rows[indexPath.row])
+      complete(true)
     }
-
-    override func tableView(
-        _ tableView: UITableView,
-        heightForRowAt: IndexPath
-    ) -> CGFloat {
-        return cellHeight
+    
+    delete.image = Asset.chatListDeleteSwipe.image
+    delete.backgroundColor = Asset.accentDanger.color
+    return UISwipeActionsConfiguration(actions: [delete])
+  }
+  
+  override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+    switch rows[indexPath.row] {
+    case .group(let group):
+      if let groupInfo = viewModel.groupInfo(from: group) {
+        navigator.perform(PresentGroupChat(
+          groupInfo: groupInfo,
+          on: navigationController!
+        ))
+      }
+    case .groupChat(let info):
+      if let groupInfo = viewModel.groupInfo(from: info.group) {
+        navigator.perform(PresentGroupChat(
+          groupInfo: groupInfo,
+          on: navigationController!
+        ))
+      }
+    case .contactChat(let info):
+      guard info.contact.authStatus == .friend else { return }
+      navigator.perform(PresentChat(contact: info.contact, on: navigationController!))
     }
-
-    override func tableView(
-        _ tableView: UITableView,
-        trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
-    ) -> UISwipeActionsConfiguration? {
-
-        let delete = UIContextualAction(style: .normal, title: nil) { [weak self] _, _, complete in
-            guard let self = self else { return }
-            self.didRequestDeletionOf(self.rows[indexPath.row])
-            complete(true)
-        }
-
-        delete.image = Asset.chatListDeleteSwipe.image
-        delete.backgroundColor = Asset.accentDanger.color
-        return UISwipeActionsConfiguration(actions: [delete])
+  }
+  
+  override func tableView(
+    _ tableView: UITableView,
+    cellForRowAt indexPath: IndexPath
+  ) -> UITableViewCell {
+    
+    let cell = tableView.dequeueReusableCell(forIndexPath: indexPath, ofType: ChatListCell.self)
+    
+    switch rows[indexPath.row] {
+    case .group(let group):
+      cell.setupGroup(
+        name: group.name,
+        date: group.createdAt,
+        preview: nil,
+        unreadCount: 0
+      )
+      
+    case .groupChat(let info):
+      cell.setupGroup(
+        name: info.group.name,
+        date: info.lastMessage.date,
+        preview: info.lastMessage.text,
+        unreadCount: info.unreadCount
+      )
+      
+    case .contactChat(let info):
+      cell.setupContact(
+        name: (info.contact.nickname ?? info.contact.username) ?? "",
+        image: info.contact.photo,
+        date: info.lastMessage.date,
+        unreadCount: info.unreadCount,
+        preview: info.lastMessage.text
+      )
     }
-
-    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-        switch rows[indexPath.row] {
-        case .group(let group):
-            if let groupInfo = viewModel.groupInfo(from: group) {
-                coordinator.toGroupChat(with: groupInfo, from: self)
-            }
-
-        case .groupChat(let info):
-            if let groupInfo = viewModel.groupInfo(from: info.group) {
-                coordinator.toGroupChat(with: groupInfo, from: self)
-            }
-
-        case .contactChat(let info):
-            guard info.contact.authStatus == .friend else { return }
-            coordinator.toSingleChat(with: info.contact, from: self)
-        }
-    }
-
-    override func tableView(
-        _ tableView: UITableView,
-        cellForRowAt indexPath: IndexPath
-    ) -> UITableViewCell {
-
-        let cell = tableView.dequeueReusableCell(forIndexPath: indexPath, ofType: ChatListCell.self)
-
-        switch rows[indexPath.row] {
-        case .group(let group):
-            cell.setupGroup(
-                name: group.name,
-                date: group.createdAt,
-                preview: nil,
-                unreadCount: 0
-            )
-
-        case .groupChat(let info):
-            cell.setupGroup(
-                name: info.group.name,
-                date: info.lastMessage.date,
-                preview: info.lastMessage.text,
-                unreadCount: info.unreadCount
-            )
-
-        case .contactChat(let info):
-            cell.setupContact(
-                name: (info.contact.nickname ?? info.contact.username) ?? "",
-                image: info.contact.photo,
-                date: info.lastMessage.date,
-                unreadCount: info.unreadCount,
-                preview: info.lastMessage.text
-            )
-        }
-
-        return cell
+    
+    return cell
+  }
+  
+  private func didRequestDeletionOf(_ item: ChatInfo) {
+    let title: String
+    let subtitle: String
+    let actionTitle: String
+    let actionClosure: () -> Void
+    
+    switch item {
+    case .group(let group):
+      title = Localized.ChatList.DeleteGroup.title
+      subtitle = Localized.ChatList.DeleteGroup.subtitle
+      actionTitle = Localized.ChatList.DeleteGroup.action
+      actionClosure = { [weak viewModel] in viewModel?.leave(group) }
+      
+    case .contactChat(let info):
+      title = Localized.ChatList.Delete.title
+      subtitle = Localized.ChatList.Delete.subtitle
+      actionTitle = Localized.ChatList.Delete.delete
+      actionClosure = { [weak viewModel] in viewModel?.clear(info.contact) }
+      
+    case .groupChat(let info):
+      title = Localized.ChatList.DeleteGroup.title
+      subtitle = Localized.ChatList.DeleteGroup.subtitle
+      actionTitle = Localized.ChatList.DeleteGroup.action
+      actionClosure = { [weak viewModel] in viewModel?.leave(info.group) }
     }
-
-    private func didRequestDeletionOf(_ item: ChatInfo) {
-        let title: String
-        let subtitle: String
-        let actionTitle: String
-        let actionClosure: () -> Void
-
-        switch item {
-        case .group(let group):
-            title = Localized.ChatList.DeleteGroup.title
-            subtitle = Localized.ChatList.DeleteGroup.subtitle
-            actionTitle = Localized.ChatList.DeleteGroup.action
-            actionClosure = { [weak viewModel] in viewModel?.leave(group) }
-
-        case .contactChat(let info):
-            title = Localized.ChatList.Delete.title
-            subtitle = Localized.ChatList.Delete.subtitle
-            actionTitle = Localized.ChatList.Delete.delete
-            actionClosure = { [weak viewModel] in viewModel?.clear(info.contact) }
-
-        case .groupChat(let info):
-            title = Localized.ChatList.DeleteGroup.title
-            subtitle = Localized.ChatList.DeleteGroup.subtitle
-            actionTitle = Localized.ChatList.DeleteGroup.action
-            actionClosure = { [weak viewModel] in viewModel?.leave(info.group) }
+    
+    let actionButton = DrawerCapsuleButton(
+      model: .init(title: actionTitle, style: .red)
+    )
+
+    actionButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) {
+          self.drawerCancellables.removeAll()
+          actionClosure()
         }
-
-        let actionButton = DrawerCapsuleButton(model: .init(title: actionTitle, style: .red))
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
-            ),
-            DrawerText(
-                font: Fonts.Mulish.regular.font(size: 16.0),
-                text: subtitle,
-                color: Asset.neutralBody.color,
-                alignment: .left,
-                lineHeightMultiple: 1.1,
-                spacingAfter: 39
-            ),
-            actionButton
-        ])
-
-        actionButton.action.receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                    actionClosure()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: title,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 19
+      ),
+      DrawerText(
+        font: Fonts.Mulish.regular.font(size: 16.0),
+        text: subtitle,
+        color: Asset.neutralBody.color,
+        alignment: .left,
+        lineHeightMultiple: 1.1,
+        spacingAfter: 39
+      ),
+      actionButton
+    ], isDismissable: true, from: self))
+  }
 }
diff --git a/Sources/ChatListFeature/Coordinator/ChatListCoordinator.swift b/Sources/ChatListFeature/Coordinator/ChatListCoordinator.swift
deleted file mode 100644
index acd4fbcaecf645611fd30d058a0172f785b03fab..0000000000000000000000000000000000000000
--- a/Sources/ChatListFeature/Coordinator/ChatListCoordinator.swift
+++ /dev/null
@@ -1,103 +0,0 @@
-import UIKit
-import Shared
-import Models
-import XXModels
-import MenuFeature
-import ChatFeature
-import Presentation
-
-public typealias ChatListSheetClosure = (ChatListSheetController.Action) -> Void
-
-public protocol ChatListCoordinating {
-    func toScan(from: UIViewController)
-    func toSearch(from: UIViewController)
-    func toContacts(from: UIViewController)
-    func toNewGroup(from: UIViewController)
-    func toSideMenu(from: UIViewController)
-    func toContact(_: Contact, from: UIViewController)
-    func toSingleChat(with: Contact, from: UIViewController)
-    func toDrawer(_: UIViewController, from: UIViewController)
-    func toGroupChat(with: GroupInfo, from: UIViewController)
-}
-
-public struct ChatListCoordinator: ChatListCoordinating {
-    var pushPresenter: Presenting = PushPresenter()
-    var modalPresenter: Presenting = ModalPresenter()
-    var sidePresenter: Presenting = SideMenuPresenter()
-    var bottomPresenter: Presenting = BottomPresenter()
-
-    var scanFactory: () -> UIViewController
-    var searchFactory: (String?) -> UIViewController
-    var newGroupFactory: () -> UIViewController
-    var contactsFactory: () -> UIViewController
-    var contactFactory: (Contact) -> UIViewController
-    var singleChatFactory: (Contact) -> UIViewController
-    var groupChatFactory: (GroupInfo) -> UIViewController
-    var sideMenuFactory: (MenuItem, UIViewController) -> UIViewController
-
-    public init(
-        scanFactory: @escaping () -> UIViewController,
-        searchFactory: @escaping (String?) -> UIViewController,
-        newGroupFactory: @escaping () -> UIViewController,
-        contactsFactory: @escaping () -> UIViewController,
-        contactFactory: @escaping (Contact) -> UIViewController,
-        singleChatFactory: @escaping (Contact) -> UIViewController,
-        groupChatFactory: @escaping (GroupInfo) -> UIViewController,
-        sideMenuFactory: @escaping (MenuItem, UIViewController) -> UIViewController
-    ) {
-        self.scanFactory = scanFactory
-        self.searchFactory = searchFactory
-        self.contactFactory = contactFactory
-        self.newGroupFactory = newGroupFactory
-        self.contactsFactory = contactsFactory
-        self.sideMenuFactory = sideMenuFactory
-        self.groupChatFactory = groupChatFactory
-        self.singleChatFactory = singleChatFactory
-    }
-}
-
-public extension ChatListCoordinator {
-    func toSearch(from parent: UIViewController) {
-        let screen = searchFactory(nil)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toScan(from parent: UIViewController) {
-        let screen = scanFactory()
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toContacts(from parent: UIViewController) {
-        let screen = contactsFactory()
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toContact(_ contact: Contact, from parent: UIViewController) {
-        let screen = contactFactory(contact)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toSingleChat(with contact: Contact, from parent: UIViewController) {
-        let screen = singleChatFactory(contact)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toGroupChat(with group: GroupInfo, from parent: UIViewController) {
-        let screen = groupChatFactory(group)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toSideMenu(from parent: UIViewController) {
-        let screen = sideMenuFactory(.chats, parent)
-        sidePresenter.present(screen, from: parent)
-    }
-
-    func toNewGroup(from parent: UIViewController) {
-        let screen = newGroupFactory()
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toDrawer(_ drawer: UIViewController, from parent: UIViewController) {
-        bottomPresenter.present(drawer, from: parent)
-    }
-}
diff --git a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
index af0a8401592ec2715527a3af901846253c20ba80..8fea8877f547bfdf7d205e95412cf2b783f9762d 100644
--- a/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
+++ b/Sources/ChatListFeature/ViewModel/ChatListViewModel.swift
@@ -1,198 +1,200 @@
-import HUD
 import UIKit
 import Shared
-import Models
 import Combine
 import XXModels
 import Defaults
-import Integration
+import AppCore
+import Dependencies
+import XXMessengerClient
 import ReportingFeature
-import DependencyInjection
+
+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(Contact)
+  case chat(ChatInfo)
+  case connection(XXModels.Contact)
 }
 
-typealias RecentsSnapshot = NSDiffableDataSourceSnapshot<SectionId, Contact>
+typealias RecentsSnapshot = NSDiffableDataSourceSnapshot<SectionId, XXModels.Contact>
 typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem>
 
 final class ChatListViewModel {
-    @Dependency private var session: SessionType
-    @Dependency private var reportingStatus: ReportingStatus
-
-    var isOnline: AnyPublisher<Bool, Never> {
-        session.isOnline
-    }
-
-    var chatsPublisher: AnyPublisher<[ChatInfo], Never> {
-        chatsSubject.eraseToAnyPublisher()
-    }
-
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
-
-    var recentsPublisher: AnyPublisher<RecentsSnapshot, Never> {
-        let query = Contact.Query(
-            isRecent: true,
-            isBlocked: reportingStatus.isEnabled() ? false : nil,
-            isBanned: reportingStatus.isEnabled() ? false : nil
-        )
-
-        return session.dbManager.fetchContactsPublisher(query)
-            .assertNoFailure()
-            .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
-        )
-
-        let contactsStream = session.dbManager
-            .fetchContactsPublisher(contactsQuery)
-            .assertNoFailure()
-            .map { $0.filter { $0.id != self.session.myId }}
-
-        return Publishers.CombineLatest3(
-            contactsStream,
-            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(
-            session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(),
-            session.dbManager.fetchGroupsPublisher(groupQuery).assertNoFailure()
-        )
-        .map { $0.0.count + $0.1.count }
+  @Dependency(\.app.dbManager) var dbManager
+  @Dependency(\.app.messenger) var messenger
+  @Dependency(\.app.hudManager) var hudManager
+  @Dependency(\.reportingStatus) var 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 try! dbManager.getDB().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(
+      try! dbManager.getDB().fetchContactsPublisher(contactsQuery)
+        .replaceError(with: [])
+        .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() {
-        session.dbManager.fetchChatInfosPublisher(
-            ChatInfo.Query(
-                contactChatInfoQuery: .init(
-                    userId: session.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]
-                )
-            ))
-            .assertNoFailure()
-            .sink { [unowned self] in chatsSubject.send($0) }
-            .store(in: &cancellables)
-    }
-
-    func updateSearch(query: String) {
-        searchSubject.send(query)
-    }
-
-    func leave(_ group: Group) {
-        hudSubject.send(.on)
-
-        do {
-            try session.leave(group: group)
-            try session.dbManager.deleteMessages(.init(chat: .group(group.id)))
-            hudSubject.send(.none)
-        } catch {
-            hudSubject.send(.error(.init(with: error)))
+    )
+    .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(
+      try! dbManager.getDB().fetchContactsPublisher(contactsQuery).replaceError(with: []),
+      try! dbManager.getDB().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() {
+    try! dbManager.getDB().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) {
+    hudManager.show()
+    do {
+      try messenger.groupChat()!.leaveGroup(groupId: group.id)
+      try dbManager.getDB().deleteMessages(.init(chat: .group(group.id)))
+      try dbManager.getDB().deleteGroup(group)
+      hudManager.hide()
+    } catch {
+      hudManager.show(.init(error: error))
     }
-
-    func clear(_ contact: Contact) {
-        _ = try? session.dbManager.deleteMessages(.init(chat: .direct(session.myId, contact.id)))
-    }
-
-    func groupInfo(from group: Group) -> GroupInfo? {
-        let query = GroupInfo.Query(groupId: group.id)
-        guard let info = try? session.dbManager.fetchGroupInfos(query).first else {
-            return nil
-        }
-
-        return info
+  }
+  
+  func clear(_ contact: XXModels.Contact) {
+    _ = try? dbManager.getDB().deleteMessages(.init(chat: .direct(myId, contact.id)))
+  }
+  
+  func groupInfo(from group: Group) -> GroupInfo? {
+    let query = GroupInfo.Query(groupId: group.id)
+    guard let info = try? dbManager.getDB().fetchGroupInfos(query).first else {
+      return nil
     }
+    
+    return info
+  }
 }
diff --git a/Sources/ChatListFeature/Views/ChatListCell.swift b/Sources/ChatListFeature/Views/ChatListCell.swift
index bb455395abbc36921e364b27a5ca07e573726ea3..9ee550dad267464a979b43926222b85cfdf467fb 100644
--- a/Sources/ChatListFeature/Views/ChatListCell.swift
+++ b/Sources/ChatListFeature/Views/ChatListCell.swift
@@ -1,157 +1,158 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ChatListCell: UITableViewCell {
-    private let titleLabel = UILabel()
-    private let unreadView = UIView()
-    private let unreadCountLabel = UILabel()
-    private let previewLabel = UILabel()
-    private let dateLabel = UILabel()
-    private let avatarView = AvatarView()
-    private var lastDate: Date? {
-        didSet { updateTimeAgoLabel() }
+  let titleLabel = UILabel()
+  let unreadView = UIView()
+  let unreadCountLabel = UILabel()
+  let previewLabel = UILabel()
+  let dateLabel = UILabel()
+  let avatarView = AvatarView()
+  var lastDate: Date? {
+    didSet { updateTimeAgoLabel() }
+  }
+  
+  private var timer: Timer?
+  
+  deinit { timer?.invalidate() }
+  
+  override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+    super.init(style: style, reuseIdentifier: reuseIdentifier)
+    
+    previewLabel.numberOfLines = 2
+    dateLabel.textAlignment = .right
+    
+    unreadView.layer.cornerRadius = 8
+    avatarView.layer.cornerRadius = 21
+    avatarView.layer.masksToBounds = true
+    
+    dateLabel.textAlignment = .right
+    selectedBackgroundView = UIView()
+    unreadView.backgroundColor = .clear
+    backgroundColor = Asset.neutralWhite.color
+    dateLabel.textColor = Asset.neutralWeak.color
+    titleLabel.textColor = Asset.neutralActive.color
+    unreadCountLabel.textColor = Asset.neutralWhite.color
+    
+    dateLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 16.0)
+    unreadCountLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
+    
+    timer = Timer.scheduledTimer(withTimeInterval: 59, repeats: true) { [weak self] _ in
+      self?.updateTimeAgoLabel()
     }
-
-    private var timer: Timer?
-
-    deinit { timer?.invalidate() }
-
-    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        super.init(style: style, reuseIdentifier: reuseIdentifier)
-
-        previewLabel.numberOfLines = 2
-        dateLabel.textAlignment = .right
-
-        unreadView.layer.cornerRadius = 8
-        avatarView.layer.cornerRadius = 21
-        avatarView.layer.masksToBounds = true
-
-        dateLabel.textAlignment = .right
-        selectedBackgroundView = UIView()
-        unreadView.backgroundColor = .clear
-        backgroundColor = Asset.neutralWhite.color
-        dateLabel.textColor = Asset.neutralWeak.color
-        titleLabel.textColor = Asset.neutralActive.color
-        unreadCountLabel.textColor = Asset.neutralWhite.color
-
-        dateLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 16.0)
-        unreadCountLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
-
-        timer = Timer.scheduledTimer(withTimeInterval: 59, repeats: true) { [weak self] _ in
-            self?.updateTimeAgoLabel()
-        }
-
-        dateLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
-
-        contentView.addSubview(titleLabel)
-        contentView.addSubview(unreadView)
-        contentView.addSubview(avatarView)
-        contentView.addSubview(previewLabel)
-        contentView.addSubview(dateLabel)
-        unreadView.addSubview(unreadCountLabel)
-
-        avatarView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(14)
-            $0.left.equalToSuperview().offset(24)
-            $0.width.height.equalTo(48)
-        }
-
-        unreadCountLabel.snp.makeConstraints {
-            $0.center.equalToSuperview()
-        }
-
-        titleLabel.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(10)
-            $0.left.equalTo(avatarView.snp.right).offset(16)
-            $0.right.lessThanOrEqualTo(dateLabel.snp.left).offset(-10)
-        }
-
-        dateLabel.snp.makeConstraints {
-            $0.top.equalTo(titleLabel)
-            $0.right.equalToSuperview().offset(-25)
-        }
-
-        previewLabel.snp.makeConstraints {
-            $0.left.equalTo(titleLabel)
-            $0.top.equalTo(titleLabel.snp.bottom).offset(2)
-            $0.right.lessThanOrEqualTo(unreadView.snp.left).offset(-3)
-            $0.bottom.lessThanOrEqualToSuperview().offset(-10)
-        }
-
-        unreadView.snp.makeConstraints {
-            $0.right.equalTo(dateLabel)
-            $0.centerY.equalTo(previewLabel)
-            $0.width.height.equalTo(20)
-        }
+    
+    dateLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
+    
+    contentView.addSubview(titleLabel)
+    contentView.addSubview(unreadView)
+    contentView.addSubview(avatarView)
+    contentView.addSubview(previewLabel)
+    contentView.addSubview(dateLabel)
+    unreadView.addSubview(unreadCountLabel)
+    
+    avatarView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(14)
+      $0.left.equalToSuperview().offset(24)
+      $0.width.height.equalTo(48)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        lastDate = nil
-        titleLabel.text = nil
-        unreadCountLabel.text = nil
-        previewLabel.attributedText = nil
-        avatarView.prepareForReuse()
+    
+    unreadCountLabel.snp.makeConstraints {
+      $0.center.equalToSuperview()
     }
-
-    private func updateTimeAgoLabel() {
-        if let date = lastDate {
-            dateLabel.text = date.asRelativeFromNow()
-        }
+    
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(10)
+      $0.left.equalTo(avatarView.snp.right).offset(16)
+      $0.right.lessThanOrEqualTo(dateLabel.snp.left).offset(-10)
     }
-
-    func setupContact(
-        name: String,
-        image: Data?,
-        date: Date?,
-        unreadCount: Int,
-        preview: String
-    ) {
-        titleLabel.text = name
-        setPreview(string: preview)
-        avatarView.setupProfile(title: name, image: image, size: .large)
-        unreadCountLabel.text = "\(unreadCount)"
-        unreadView.backgroundColor = unreadCount > 0 ? Asset.brandPrimary.color : .clear
-
-        if let date = date {
-            lastDate = date
-        } else {
-            dateLabel.text = nil
-        }
+    
+    dateLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel)
+      $0.right.equalToSuperview().offset(-25)
     }
-
-    func setupGroup(
-        name: String,
-        date: Date,
-        preview: String?,
-        unreadCount: Int
-    ) {
-        lastDate = date
-        titleLabel.text = name
-        setPreview(string: preview)
-        avatarView.setupGroup(size: .large)
-        unreadCountLabel.text = "\(unreadCount)"
-        unreadView.backgroundColor = unreadCount > 0 ? Asset.brandPrimary.color : .clear
+    
+    previewLabel.snp.makeConstraints {
+      $0.left.equalTo(titleLabel)
+      $0.top.equalTo(titleLabel.snp.bottom).offset(2)
+      $0.right.lessThanOrEqualTo(unreadView.snp.left).offset(-3)
+      $0.bottom.lessThanOrEqualToSuperview().offset(-10)
     }
-
-    private func setPreview(string: String?) {
-        guard let preview = string else {
-            previewLabel.attributedText = nil
-            return
-        }
-
-        let paragraphStyle = NSMutableParagraphStyle()
-        paragraphStyle.lineHeightMultiple = 1.1
-
-        previewLabel.attributedText = NSAttributedString(
-            string: preview,
-            attributes: [
-                .paragraphStyle: paragraphStyle,
-                .font: Fonts.Mulish.regular.font(size: 14.0),
-                .foregroundColor: Asset.neutralSecondaryAlternative.color
-            ])
+    
+    unreadView.snp.makeConstraints {
+      $0.right.equalTo(dateLabel)
+      $0.centerY.equalTo(previewLabel)
+      $0.width.height.equalTo(20)
+    }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  override func prepareForReuse() {
+    super.prepareForReuse()
+    lastDate = nil
+    titleLabel.text = nil
+    unreadCountLabel.text = nil
+    previewLabel.attributedText = nil
+    avatarView.prepareForReuse()
+  }
+  
+  private func updateTimeAgoLabel() {
+    if let date = lastDate {
+      dateLabel.text = date.asRelativeFromNow()
+    }
+  }
+  
+  func setupContact(
+    name: String,
+    image: Data?,
+    date: Date?,
+    unreadCount: Int,
+    preview: String
+  ) {
+    titleLabel.text = name
+    setPreview(string: preview)
+    avatarView.setupProfile(title: name, image: image, size: .large)
+    unreadCountLabel.text = "\(unreadCount)"
+    unreadView.backgroundColor = unreadCount > 0 ? Asset.brandPrimary.color : .clear
+    
+    if let date = date {
+      lastDate = date
+    } else {
+      dateLabel.text = nil
+    }
+  }
+  
+  func setupGroup(
+    name: String,
+    date: Date,
+    preview: String?,
+    unreadCount: Int
+  ) {
+    lastDate = date
+    titleLabel.text = name
+    setPreview(string: preview)
+    avatarView.setupGroup(size: .large)
+    unreadCountLabel.text = "\(unreadCount)"
+    unreadView.backgroundColor = unreadCount > 0 ? Asset.brandPrimary.color : .clear
+  }
+  
+  private func setPreview(string: String?) {
+    guard let preview = string else {
+      previewLabel.attributedText = nil
+      return
     }
+    
+    let paragraphStyle = NSMutableParagraphStyle()
+    paragraphStyle.lineHeightMultiple = 1.1
+    
+    previewLabel.attributedText = NSAttributedString(
+      string: preview,
+      attributes: [
+        .paragraphStyle: paragraphStyle,
+        .font: Fonts.Mulish.regular.font(size: 14.0),
+        .foregroundColor: Asset.neutralSecondaryAlternative.color
+      ])
+  }
 }
diff --git a/Sources/ChatListFeature/Views/ChatListContainerView.swift b/Sources/ChatListFeature/Views/ChatListContainerView.swift
index 1be2c99cf0e9cc736f0c02acd1b9c3e54f5be688..77f35111eecf168a96a51a56996f91bb5e0d23a1 100644
--- a/Sources/ChatListFeature/Views/ChatListContainerView.swift
+++ b/Sources/ChatListFeature/Views/ChatListContainerView.swift
@@ -1,114 +1,96 @@
 import UIKit
 import Shared
-
-final class ChatSearchListContainerView: UIView{
-    let emptyView = ChatSearchEmptyView()
-
-    init() {
-        super.init(frame: .zero)
-
-        addSubview(emptyView)
-
-        emptyView.snp.makeConstraints {
-            $0.edges.equalToSuperview()
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-}
+import AppResources
 
 final class ChatListContainerView: UIView {
-    let separatorView = UIView()
-    let emptyView = ChatListEmptyView()
-    let collectionContainerView = UIView()
-    lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
-
-    private let layout: UICollectionViewFlowLayout = {
-        let layout = UICollectionViewFlowLayout()
-        layout.minimumLineSpacing = 35
-        layout.itemSize = CGSize(width: 56, height: 80)
-        layout.scrollDirection = .horizontal
-        return layout
-    }()
-
-    init() {
-        super.init(frame: .zero)
-
-        collectionView.showsHorizontalScrollIndicator = false
-        separatorView.backgroundColor = Asset.neutralLine.color
-        collectionView.backgroundColor = Asset.neutralWhite.color
-        collectionView.contentInset = UIEdgeInsets(top: 0, left: 30, bottom: 0, right: 30)
-
-        addSubview(emptyView)
-        addSubview(collectionContainerView)
-        collectionContainerView.addSubview(collectionView)
-        collectionContainerView.addSubview(separatorView)
-
-        collectionContainerView.snp.makeConstraints {
-            $0.bottom.equalTo(snp.top)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.height.equalTo(110)
-        }
-
-        collectionView.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-        }
-
-        separatorView.snp.makeConstraints {
-            $0.top.equalTo(collectionView.snp.bottom).offset(20)
-            $0.height.equalTo(1)
-            $0.left.equalToSuperview().offset(24)
-            $0.right.equalToSuperview().offset(-24)
-            $0.bottom.equalToSuperview()
-        }
-
-        emptyView.snp.makeConstraints {
-            $0.top.equalTo(collectionContainerView.snp.bottom)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
+  let separatorView = UIView()
+  let emptyView = ChatListEmptyView()
+  let collectionContainerView = UIView()
+  lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
+  
+  private let layout: UICollectionViewFlowLayout = {
+    let layout = UICollectionViewFlowLayout()
+    layout.minimumLineSpacing = 35
+    layout.itemSize = CGSize(width: 56, height: 80)
+    layout.scrollDirection = .horizontal
+    return layout
+  }()
+  
+  init() {
+    super.init(frame: .zero)
+    
+    collectionView.showsHorizontalScrollIndicator = false
+    separatorView.backgroundColor = Asset.neutralLine.color
+    collectionView.backgroundColor = Asset.neutralWhite.color
+    collectionView.contentInset = UIEdgeInsets(top: 0, left: 30, bottom: 0, right: 30)
+    
+    addSubview(emptyView)
+    addSubview(collectionContainerView)
+    collectionContainerView.addSubview(collectionView)
+    collectionContainerView.addSubview(separatorView)
+    
+    collectionContainerView.snp.makeConstraints {
+      $0.bottom.equalTo(snp.top)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.height.equalTo(110)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    func showRecentsCollection(_ show: Bool) {
-        if show == true && collectionContainerView.alpha != 0.0 ||
-            show == false && collectionContainerView.alpha == 0.0 {
-            return
-        }
-
-        if show == true {
-            collectionContainerView.alpha = 0.0
-            collectionContainerView.snp.updateConstraints {
-                $0.bottom.equalTo(snp.top).offset(collectionContainerView.bounds.height + 20)
-            }
-
-            UIView.animate(withDuration: 0.3, delay: 0.3, options: .curveEaseInOut) {
-                self.collectionContainerView.alpha = 1.0
-            }
-
-            UIView.animate(withDuration: 0.3, delay: 0.15, options: .curveEaseInOut) {
-                self.setNeedsLayout()
-                self.layoutIfNeeded()
-            }
-        } else {
-            collectionContainerView.alpha = 1.0
-            collectionContainerView.snp.updateConstraints {
-                $0.bottom.equalTo(snp.top)
-            }
-
-            UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseInOut) {
-                self.collectionContainerView.alpha = 0.0
-            }
-
-            UIView.animate(withDuration: 0.2, delay: 0.15, options: .curveEaseInOut) {
-                self.setNeedsLayout()
-                self.layoutIfNeeded()
-            }
-        }
+    collectionView.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+    }
+    separatorView.snp.makeConstraints {
+      $0.top.equalTo(collectionView.snp.bottom).offset(20)
+      $0.height.equalTo(1)
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
+      $0.bottom.equalToSuperview()
+    }
+    emptyView.snp.makeConstraints {
+      $0.top.equalTo(collectionContainerView.snp.bottom)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  func showRecentsCollection(_ show: Bool) {
+    if show == true && collectionContainerView.alpha != 0.0 ||
+        show == false && collectionContainerView.alpha == 0.0 {
+      return
+    }
+    
+    if show == true {
+      collectionContainerView.alpha = 0.0
+      collectionContainerView.snp.updateConstraints {
+        $0.bottom.equalTo(snp.top).offset(collectionContainerView.bounds.height + 20)
+      }
+      
+      UIView.animate(withDuration: 0.3, delay: 0.3, options: .curveEaseInOut) {
+        self.collectionContainerView.alpha = 1.0
+      }
+      
+      UIView.animate(withDuration: 0.3, delay: 0.15, options: .curveEaseInOut) {
+        self.setNeedsLayout()
+        self.layoutIfNeeded()
+      }
+    } else {
+      collectionContainerView.alpha = 1.0
+      collectionContainerView.snp.updateConstraints {
+        $0.bottom.equalTo(snp.top)
+      }
+      
+      UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseInOut) {
+        self.collectionContainerView.alpha = 0.0
+      }
+      
+      UIView.animate(withDuration: 0.2, delay: 0.15, options: .curveEaseInOut) {
+        self.setNeedsLayout()
+        self.layoutIfNeeded()
+      }
     }
+  }
 }
diff --git a/Sources/ChatListFeature/Views/ChatListEmptyView.swift b/Sources/ChatListFeature/Views/ChatListEmptyView.swift
index 374e184970bcad877ee7ca507369bb219df32265..4004ad713f9fb2e96c43ba4274b61305ce892108 100644
--- a/Sources/ChatListFeature/Views/ChatListEmptyView.swift
+++ b/Sources/ChatListFeature/Views/ChatListEmptyView.swift
@@ -1,48 +1,49 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ChatListEmptyView: UIView {
-    private let titleLabel = UILabel()
-    private let stackView = UIStackView()
-    private(set) var contactsButton = CapsuleButton()
-
-    init() {
-        super.init(frame: .zero)
-
-        backgroundColor = Asset.neutralWhite.color
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.lineHeightMultiple = 1.2
-        paragraph.alignment = .center
-
-        titleLabel.numberOfLines = 0
-        titleLabel.attributedText = NSAttributedString(
-            string: Localized.ChatList.emptyTitle,
-            attributes: [
-                .paragraphStyle: paragraph,
-                .foregroundColor: Asset.neutralActive.color,
-                .font: Fonts.Mulish.bold.font(size: 24.0)
-            ]
-        )
-
-        contactsButton.setStyle(.brandColored)
-        contactsButton.setTitle(Localized.ChatList.action, for: .normal)
-
-        stackView.spacing = 24
-        stackView.axis = .vertical
-        stackView.alignment = .center
-        stackView.addArrangedSubview(titleLabel)
-        stackView.addArrangedSubview(contactsButton)
-
-        addSubview(stackView)
-
-        stackView.snp.makeConstraints {
-            $0.centerY.equalToSuperview()
-            $0.top.greaterThanOrEqualToSuperview()
-            $0.left.equalToSuperview().offset(24)
-            $0.right.equalToSuperview().offset(-24)
-            $0.bottom.lessThanOrEqualToSuperview()
-        }
+  let titleLabel = UILabel()
+  let stackView = UIStackView()
+  let contactsButton = CapsuleButton()
+  
+  init() {
+    super.init(frame: .zero)
+    
+    backgroundColor = Asset.neutralWhite.color
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.lineHeightMultiple = 1.2
+    paragraph.alignment = .center
+    
+    titleLabel.numberOfLines = 0
+    titleLabel.attributedText = NSAttributedString(
+      string: Localized.ChatList.emptyTitle,
+      attributes: [
+        .paragraphStyle: paragraph,
+        .foregroundColor: Asset.neutralActive.color,
+        .font: Fonts.Mulish.bold.font(size: 24.0)
+      ]
+    )
+    
+    contactsButton.setStyle(.brandColored)
+    contactsButton.setTitle(Localized.ChatList.action, for: .normal)
+    
+    stackView.spacing = 24
+    stackView.axis = .vertical
+    stackView.alignment = .center
+    stackView.addArrangedSubview(titleLabel)
+    stackView.addArrangedSubview(contactsButton)
+    
+    addSubview(stackView)
+    
+    stackView.snp.makeConstraints {
+      $0.centerY.equalToSuperview()
+      $0.top.greaterThanOrEqualToSuperview()
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
+      $0.bottom.lessThanOrEqualToSuperview()
     }
-
-    required init?(coder: NSCoder) { nil }
+  }
+  
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/ChatListFeature/Views/ChatListMenuView.swift b/Sources/ChatListFeature/Views/ChatListMenuView.swift
index 132b38402fd0ebdf08be0d569e45bfc93793275e..94c18bdfca28937a535117f9ad18a90c76d06caf 100644
--- a/Sources/ChatListFeature/Views/ChatListMenuView.swift
+++ b/Sources/ChatListFeature/Views/ChatListMenuView.swift
@@ -1,69 +1,74 @@
 import UIKit
 import Shared
 import Combine
+import AppResources
 
 final class ChatListMenuView: UIToolbar {
-    enum Action {
-        case delete
-        case deleteAll
-    }
-
-    let stackView = UIStackView()
-    let deleteButton = UIButton()
-    let deleteAllButton = UIButton()
-
-    @Published var isDeleteEnabled = false
-
-    var publisher: AnyPublisher<Action, Never> {
-        actionRelay.eraseToAnyPublisher()
-    }
-
-    private var cancellables = Set<AnyCancellable>()
-    private let actionRelay = PassthroughSubject<Action, Never>()
-
-    init() {
-        super.init(frame: .zero)
-        setup()
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    private func setup() {
-        clipsToBounds = true
-        layer.cornerRadius = 15
-        barTintColor = Asset.neutralSecondary.color
-
-        deleteButton.setImage(Asset.chatListMenuDelete.image, for: .normal)
-        deleteAllButton.setTitleColor(Asset.accentDanger.color, for: .normal)
-        deleteAllButton.setTitle(Localized.ChatList.Menu.deleteAll, for: .normal)
-        deleteAllButton.titleLabel?.font = Fonts.Mulish.semiBold.font(size: 14.0)
-
-        stackView.spacing = 35
-        stackView.addArrangedSubview(deleteButton)
-        stackView.addArrangedSubview(deleteAllButton)
-        stackView.distribution = .fillEqually
-        addSubview(stackView)
-
-        translatesAutoresizingMaskIntoConstraints = false
-
-        $isDeleteEnabled
-            .assign(to: \.isEnabled, on: deleteButton)
-            .store(in: &cancellables)
-
-        deleteButton.publisher(for: .touchUpInside)
-            .sink { [weak actionRelay] in actionRelay?.send(.delete) }
-            .store(in: &cancellables)
-
-        deleteAllButton.publisher(for: .touchUpInside)
-            .sink { [weak actionRelay] in actionRelay?.send(.deleteAll) }
-            .store(in: &cancellables)
-
-        stackView.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-            make.bottom.equalToSuperview().offset(-10)
-            make.height.equalTo(83)
-        }
+  enum Action {
+    case delete
+    case deleteAll
+  }
+  
+  let stackView = UIStackView()
+  let deleteButton = UIButton()
+  let deleteAllButton = UIButton()
+  
+  @Published var isDeleteEnabled = false
+  
+  var publisher: AnyPublisher<Action, Never> {
+    actionRelay.eraseToAnyPublisher()
+  }
+  
+  var cancellables = Set<AnyCancellable>()
+  let actionRelay = PassthroughSubject<Action, Never>()
+  
+  init() {
+    super.init(frame: .zero)
+    setup()
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  private func setup() {
+    clipsToBounds = true
+    layer.cornerRadius = 15
+    barTintColor = Asset.neutralSecondary.color
+    
+    deleteButton.setImage(Asset.chatListMenuDelete.image, for: .normal)
+    deleteAllButton.setTitleColor(Asset.accentDanger.color, for: .normal)
+    deleteAllButton.setTitle(Localized.ChatList.Menu.deleteAll, for: .normal)
+    deleteAllButton.titleLabel?.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    
+    stackView.spacing = 35
+    stackView.addArrangedSubview(deleteButton)
+    stackView.addArrangedSubview(deleteAllButton)
+    stackView.distribution = .fillEqually
+    addSubview(stackView)
+    
+    translatesAutoresizingMaskIntoConstraints = false
+    
+    $isDeleteEnabled
+      .assign(to: \.isEnabled, on: deleteButton)
+      .store(in: &cancellables)
+    
+    deleteButton
+      .publisher(for: .touchUpInside)
+      .sink { [weak actionRelay] in
+        actionRelay?.send(.delete)
+      }.store(in: &cancellables)
+    
+    deleteAllButton
+      .publisher(for: .touchUpInside)
+      .sink { [weak actionRelay] in
+        actionRelay?.send(.deleteAll)
+      }.store(in: &cancellables)
+    
+    stackView.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
+      $0.bottom.equalToSuperview().offset(-10)
+      $0.height.equalTo(83)
     }
+  }
 }
diff --git a/Sources/ChatListFeature/Views/ChatListRecentContactCell.swift b/Sources/ChatListFeature/Views/ChatListRecentContactCell.swift
index 4adbdeef783245dcd73a1697f8325654952ab3e2..1174d4019d14daffa80bfc2887962efdb43fde93 100644
--- a/Sources/ChatListFeature/Views/ChatListRecentContactCell.swift
+++ b/Sources/ChatListFeature/Views/ChatListRecentContactCell.swift
@@ -1,89 +1,86 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ChatListRecentContactCell: UICollectionViewCell {
-    private let titleLabel = UILabel()
-    private let containerView = UIView()
-    private let avatarView = AvatarView()
-
-    override init(frame: CGRect) {
-        super.init(frame: frame)
-
-        contentView.backgroundColor = .white
-
-        let newLabel = UILabel()
-        newLabel.text = "NEW"
-        newLabel.textColor = Asset.neutralWhite.color
-        newLabel.font = Fonts.Mulish.bold.font(size: 8.0)
-
-        let newContainerView = UIView()
-        newContainerView.layer.cornerRadius = 6.0
-        newContainerView.layer.masksToBounds = true
-        newContainerView.backgroundColor = Asset.accentSafe.color
-
-        titleLabel.numberOfLines = 2
-        titleLabel.textAlignment = .center
-        titleLabel.lineBreakMode = .byWordWrapping
-        titleLabel.textColor = Asset.neutralDark.color
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
-
-        contentView.addSubview(titleLabel)
-        contentView.addSubview(containerView)
-
-        containerView.addSubview(avatarView)
-        containerView.addSubview(newContainerView)
-
-        newContainerView.addSubview(newLabel)
-
-        containerView.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-        }
-
-        newContainerView.snp.makeConstraints {
-            $0.top.equalTo(containerView.snp.top)
-            $0.right.equalTo(containerView.snp.right)
-        }
-
-        newLabel.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(3)
-            $0.center.equalToSuperview()
-            $0.left.equalToSuperview().offset(3)
-        }
-
-        avatarView.snp.makeConstraints {
-            $0.width.equalTo(48)
-            $0.height.equalTo(48)
-            $0.top.equalToSuperview().offset(4)
-            $0.left.equalToSuperview().offset(4)
-            $0.right.equalToSuperview().offset(-4)
-            $0.bottom.equalToSuperview().offset(-4)
-        }
-
-        titleLabel.snp.makeConstraints {
-            $0.centerX.equalToSuperview()
-            $0.top.equalTo(containerView.snp.bottom).offset(5)
-            $0.left.greaterThanOrEqualToSuperview()
-            $0.right.lessThanOrEqualToSuperview()
-            $0.bottom.lessThanOrEqualToSuperview()
-        }
+  let titleLabel = UILabel()
+  let containerView = UIView()
+  let avatarView = AvatarView()
+  
+  override init(frame: CGRect) {
+    super.init(frame: frame)
+    
+    contentView.backgroundColor = .white
+    
+    let newLabel = UILabel()
+    newLabel.text = "NEW"
+    newLabel.textColor = Asset.neutralWhite.color
+    newLabel.font = Fonts.Mulish.bold.font(size: 8.0)
+    
+    let newContainerView = UIView()
+    newContainerView.layer.cornerRadius = 6.0
+    newContainerView.layer.masksToBounds = true
+    newContainerView.backgroundColor = Asset.accentSafe.color
+    
+    titleLabel.numberOfLines = 2
+    titleLabel.textAlignment = .center
+    titleLabel.lineBreakMode = .byWordWrapping
+    titleLabel.textColor = Asset.neutralDark.color
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    
+    contentView.addSubview(titleLabel)
+    contentView.addSubview(containerView)
+    
+    containerView.addSubview(avatarView)
+    containerView.addSubview(newContainerView)
+    
+    newContainerView.addSubview(newLabel)
+    
+    containerView.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        titleLabel.text = nil
-        avatarView.prepareForReuse()
+    newContainerView.snp.makeConstraints {
+      $0.top.equalTo(containerView.snp.top)
+      $0.right.equalTo(containerView.snp.right)
     }
-
-    func setup(title: String, image: Data?) {
-        titleLabel.text = title
-        avatarView.setupProfile(
-            title: title,
-            image: image,
-            size: .large
-        )
+    newLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(3)
+      $0.center.equalToSuperview()
+      $0.left.equalToSuperview().offset(3)
+    }
+    avatarView.snp.makeConstraints {
+      $0.width.equalTo(48)
+      $0.height.equalTo(48)
+      $0.top.equalToSuperview().offset(4)
+      $0.left.equalToSuperview().offset(4)
+      $0.right.equalToSuperview().offset(-4)
+      $0.bottom.equalToSuperview().offset(-4)
+    }
+    titleLabel.snp.makeConstraints {
+      $0.centerX.equalToSuperview()
+      $0.top.equalTo(containerView.snp.bottom).offset(5)
+      $0.left.greaterThanOrEqualToSuperview()
+      $0.right.lessThanOrEqualToSuperview()
+      $0.bottom.lessThanOrEqualToSuperview()
     }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  override func prepareForReuse() {
+    super.prepareForReuse()
+    titleLabel.text = nil
+    avatarView.prepareForReuse()
+  }
+  
+  func setup(title: String, image: Data?) {
+    titleLabel.text = title
+    avatarView.setupProfile(
+      title: title,
+      image: image,
+      size: .large
+    )
+  }
 }
diff --git a/Sources/ChatListFeature/Views/ChatListTopLeftNavView.swift b/Sources/ChatListFeature/Views/ChatListTopLeftNavView.swift
index 6cc8a78df568a521afbb39b6fe69cd4197aa5e4e..9e35a63d2cabef7ec6561a174a4e39238e6773ca 100644
--- a/Sources/ChatListFeature/Views/ChatListTopLeftNavView.swift
+++ b/Sources/ChatListFeature/Views/ChatListTopLeftNavView.swift
@@ -1,72 +1,75 @@
 import UIKit
 import Shared
 import Combine
+import AppResources
 
 final class ChatListTopLeftNavView: UIView {
-    private let titleLabel = UILabel()
-    private let badgeLabel = UILabel()
-    private let menuButton = UIButton()
-    private let stackView = UIStackView()
-    private let badgeContainerView = UIView()
-
-    var actionPublisher: AnyPublisher<Void, Never> {
-        actionSubject.eraseToAnyPublisher()
+  let titleLabel = UILabel()
+  let badgeLabel = UILabel()
+  let menuButton = UIButton()
+  let stackView = UIStackView()
+  let badgeContainerView = UIView()
+  
+  var actionPublisher: AnyPublisher<Void, Never> {
+    actionSubject.eraseToAnyPublisher()
+  }
+  
+  private let actionSubject = PassthroughSubject<Void, Never>()
+  
+  init() {
+    super.init(frame: .zero)
+    
+    titleLabel.text = Localized.ChatList.title
+    titleLabel.textColor = Asset.neutralActive.color
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 18.0)
+    
+    menuButton.tintColor = Asset.neutralDark.color
+    menuButton.setImage(Asset.chatListMenu.image, for: .normal)
+    menuButton.addTarget(self, action: #selector(didTapMenu), for: .touchUpInside)
+    
+    badgeLabel.textColor = Asset.neutralWhite.color
+    badgeLabel.font = Fonts.Mulish.bold.font(size: 12.0)
+    
+    badgeContainerView.layer.cornerRadius = 5
+    badgeContainerView.layer.masksToBounds = true
+    badgeContainerView.backgroundColor = Asset.brandPrimary.color
+    
+    badgeContainerView.addSubview(badgeLabel)
+    menuButton.addSubview(badgeContainerView)
+    stackView.addArrangedSubview(menuButton)
+    stackView.addArrangedSubview(titleLabel)
+    addSubview(stackView)
+    
+    badgeLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(3)
+      $0.center.equalToSuperview()
+      $0.left.equalToSuperview().offset(3)
     }
-
-    private let actionSubject = PassthroughSubject<Void, Never>()
-
-    init() {
-        super.init(frame: .zero)
-
-        titleLabel.text = Localized.ChatList.title
-        titleLabel.textColor = Asset.neutralActive.color
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 18.0)
-
-        menuButton.tintColor = Asset.neutralDark.color
-        menuButton.setImage(Asset.chatListMenu.image, for: .normal)
-        menuButton.addTarget(self, action: #selector(didTapMenu), for: .touchUpInside)
-
-        badgeLabel.textColor = Asset.neutralWhite.color
-        badgeLabel.font = Fonts.Mulish.bold.font(size: 12.0)
-
-        badgeContainerView.layer.cornerRadius = 5
-        badgeContainerView.layer.masksToBounds = true
-        badgeContainerView.backgroundColor = Asset.brandPrimary.color
-
-        badgeContainerView.addSubview(badgeLabel)
-        menuButton.addSubview(badgeContainerView)
-        stackView.addArrangedSubview(menuButton)
-        stackView.addArrangedSubview(titleLabel)
-        addSubview(stackView)
-
-        badgeLabel.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(3)
-            $0.center.equalToSuperview()
-            $0.left.equalToSuperview().offset(3)
-        }
-
-        badgeContainerView.snp.makeConstraints {
-            $0.centerY.equalTo(menuButton.snp.top)
-            $0.centerX.equalTo(menuButton.snp.right).multipliedBy(0.8)
-        }
-
-        menuButton.snp.makeConstraints { $0.width.equalTo(50) }
-        stackView.snp.makeConstraints { $0.edges.equalToSuperview() }
+    badgeContainerView.snp.makeConstraints {
+      $0.centerY.equalTo(menuButton.snp.top)
+      $0.centerX.equalTo(menuButton.snp.right).multipliedBy(0.8)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    @objc private func didTapMenu() {
-        actionSubject.send()
+    menuButton.snp.makeConstraints {
+      $0.width.equalTo(50)
     }
-
-    func updateBadge(_ count: Int) {
-        guard count > 0 else {
-            badgeContainerView.isHidden = true
-            return
-        }
-
-        badgeLabel.text = "\(count)"
-        badgeContainerView.isHidden = false
+    stackView.snp.makeConstraints {
+      $0.edges.equalToSuperview()
+    }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  @objc private func didTapMenu() {
+    actionSubject.send()
+  }
+  
+  func updateBadge(_ count: Int) {
+    guard count > 0 else {
+      badgeContainerView.isHidden = true
+      return
     }
+    
+    badgeLabel.text = "\(count)"
+    badgeContainerView.isHidden = false
+  }
 }
diff --git a/Sources/ChatListFeature/Views/ChatListTopRightNavView.swift b/Sources/ChatListFeature/Views/ChatListTopRightNavView.swift
index 817893e1a1df3d5f50cf241cfb0d5b60ababe2ae..1e7426425483439a8e884c224aa74e59ef30a65a 100644
--- a/Sources/ChatListFeature/Views/ChatListTopRightNavView.swift
+++ b/Sources/ChatListFeature/Views/ChatListTopRightNavView.swift
@@ -1,49 +1,56 @@
 import UIKit
 import Shared
 import Combine
+import AppResources
 
 final class ChatListTopRightNavView: UIView {
-    enum Action {
-        case didTapSearch
-        case didTapNewGroup
+  enum Action {
+    case didTapSearch
+    case didTapNewGroup
+  }
+  
+  var actionPublisher: AnyPublisher<Action, Never> {
+    actionSubject.eraseToAnyPublisher()
+  }
+  
+  let stackView = UIStackView()
+  let searchButton = UIButton()
+  let newGroupButton = UIButton()
+  let actionSubject = PassthroughSubject<Action, Never>()
+  
+  init() {
+    super.init(frame: .zero)
+    
+    searchButton.tintColor = Asset.neutralDark.color
+    newGroupButton.tintColor = Asset.neutralDark.color
+    searchButton.setImage(Asset.chatListUd.image, for: .normal)
+    newGroupButton.setImage(Asset.chatListNewGroup.image, for: .normal)
+    searchButton.addTarget(self, action: #selector(didTapSearch), for: .touchUpInside)
+    newGroupButton.addTarget(self, action: #selector(didTapNewGroup), for: .touchUpInside)
+    
+    stackView.spacing = 10
+    stackView.addArrangedSubview(newGroupButton)
+    stackView.addArrangedSubview(searchButton)
+    addSubview(stackView)
+    
+    searchButton.snp.makeConstraints {
+      $0.width.equalTo(40)
     }
-
-    var actionPublisher: AnyPublisher<Action, Never> {
-        actionSubject.eraseToAnyPublisher()
+    newGroupButton.snp.makeConstraints {
+      $0.width.equalTo(40)
     }
-
-    private let stackView = UIStackView()
-    private let searchButton = UIButton()
-    private let newGroupButton = UIButton()
-    private let actionSubject = PassthroughSubject<Action, Never>()
-
-    init() {
-        super.init(frame: .zero)
-
-        searchButton.tintColor = Asset.neutralDark.color
-        newGroupButton.tintColor = Asset.neutralDark.color
-        searchButton.setImage(Asset.chatListUd.image, for: .normal)
-        newGroupButton.setImage(Asset.chatListNewGroup.image, for: .normal)
-        searchButton.addTarget(self, action: #selector(didTapSearch), for: .touchUpInside)
-        newGroupButton.addTarget(self, action: #selector(didTapNewGroup), for: .touchUpInside)
-
-        stackView.spacing = 10
-        stackView.addArrangedSubview(newGroupButton)
-        stackView.addArrangedSubview(searchButton)
-        addSubview(stackView)
-
-        searchButton.snp.makeConstraints { $0.width.equalTo(40) }
-        newGroupButton.snp.makeConstraints { $0.width.equalTo(40) }
-        stackView.snp.makeConstraints { $0.edges.equalToSuperview() }
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    @objc private func didTapSearch() {
-        actionSubject.send(.didTapSearch)
-    }
-
-    @objc private func didTapNewGroup() {
-        actionSubject.send(.didTapNewGroup)
+    stackView.snp.makeConstraints {
+      $0.edges.equalToSuperview()
     }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  @objc private func didTapSearch() {
+    actionSubject.send(.didTapSearch)
+  }
+  
+  @objc private func didTapNewGroup() {
+    actionSubject.send(.didTapNewGroup)
+  }
 }
diff --git a/Sources/ChatListFeature/Views/ChatListView.swift b/Sources/ChatListFeature/Views/ChatListView.swift
index 03a407980700123d9843d9239e4822b9247dac63..9b90b60a55e5db25a5844ac604162080c969e825 100644
--- a/Sources/ChatListFeature/Views/ChatListView.swift
+++ b/Sources/ChatListFeature/Views/ChatListView.swift
@@ -1,80 +1,73 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ChatListView: UIView {
-    let snackBar = SnackBar()
-    let containerView = UIView()
-    let searchView = SearchComponent()
-    let listContainerView = ChatListContainerView()
-    let searchListContainerView = ChatSearchListContainerView()
+  let snackBar = SnackBar()
+  let containerView = UIView()
+  let searchView = SearchComponent()
+  let listContainerView = ChatListContainerView()
+  let searchListContainerView = ChatSearchListContainerView()
+  
+  init() {
+    super.init(frame: .zero)
 
-    init() {
-        super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+    listContainerView.backgroundColor = Asset.neutralWhite.color
+    searchListContainerView.backgroundColor = Asset.neutralWhite.color
+    searchView.update(placeholder: Localized.ChatList.Search.title)
 
-        backgroundColor = Asset.neutralWhite.color
-        listContainerView.backgroundColor = Asset.neutralWhite.color
-        searchListContainerView.backgroundColor = Asset.neutralWhite.color
-        searchView.update(placeholder: Localized.ChatList.Search.title)
+    addSubview(snackBar)
+    addSubview(searchView)
+    addSubview(containerView)
+    containerView.addSubview(searchListContainerView)
+    containerView.addSubview(listContainerView)
 
-        addSubview(snackBar)
-        addSubview(searchView)
-        addSubview(containerView)
-        containerView.addSubview(searchListContainerView)
-        containerView.addSubview(listContainerView)
-
-        setupConstraints()
+    snackBar.snp.makeConstraints {
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalTo(snp.top)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    func showConnectingBanner(_ show: Bool) {
-        if show == true {
-            snackBar.alpha = 0.0
-            snackBar.snp.updateConstraints {
-                $0.bottom
-                    .equalTo(snp.top)
-                    .offset(snackBar.bounds.height)
-            }
-        } else {
-            snackBar.alpha = 1.0
-            snackBar.snp.updateConstraints {
-                $0.bottom.equalTo(snp.top)
-            }
-        }
-
-        UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseInOut) {
-            self.setNeedsLayout()
-            self.layoutIfNeeded()
-            self.snackBar.alpha = show ? 1.0 : 0.0
-        }
+    searchView.snp.makeConstraints {
+      $0.top.equalTo(snackBar.snp.bottom).offset(20)
+      $0.left.equalToSuperview().offset(20)
+      $0.right.equalToSuperview().offset(-20)
     }
-
-    private func setupConstraints() {
-        snackBar.snp.makeConstraints {
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalTo(snp.top)
-        }
-
-        searchView.snp.makeConstraints {
-            $0.top.equalTo(snackBar.snp.bottom).offset(20)
-            $0.left.equalToSuperview().offset(20)
-            $0.right.equalToSuperview().offset(-20)
-        }
-
-        containerView.snp.makeConstraints {
-            $0.top.equalTo(searchView.snp.bottom)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
-
-        listContainerView.snp.makeConstraints {
-            $0.edges.equalToSuperview()
-        }
-
-        searchListContainerView.snp.makeConstraints {
-            $0.edges.equalToSuperview()
-        }
+    containerView.snp.makeConstraints {
+      $0.top.equalTo(searchView.snp.bottom)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
+    listContainerView.snp.makeConstraints {
+      $0.edges.equalToSuperview()
+    }
+    searchListContainerView.snp.makeConstraints {
+      $0.edges.equalToSuperview()
+    }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  func showConnectingBanner(_ show: Bool) {
+    if show == true {
+      snackBar.alpha = 0.0
+      snackBar.snp.updateConstraints {
+        $0.bottom
+          .equalTo(snp.top)
+          .offset(snackBar.bounds.height)
+      }
+    } else {
+      snackBar.alpha = 1.0
+      snackBar.snp.updateConstraints {
+        $0.bottom.equalTo(snp.top)
+      }
+    }
+    
+    UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseInOut) {
+      self.setNeedsLayout()
+      self.layoutIfNeeded()
+      self.snackBar.alpha = show ? 1.0 : 0.0
     }
+  }
 }
diff --git a/Sources/ChatListFeature/Views/ChatSearchEmptyView.swift b/Sources/ChatListFeature/Views/ChatSearchEmptyView.swift
index 2fe1471c5d1f7d9d967a2ca24b50a6950c4067c3..7853c79e5eb9e3ad88377e7db9a030f861797b1f 100644
--- a/Sources/ChatListFeature/Views/ChatSearchEmptyView.swift
+++ b/Sources/ChatListFeature/Views/ChatSearchEmptyView.swift
@@ -1,57 +1,58 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ChatSearchEmptyView: UIView {
-    private let titleLabel = UILabel()
-    private let stackView = UIStackView()
-    private let descriptionLabel = UILabel()
-    private(set) var searchButton = CapsuleButton()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        titleLabel.textColor = Asset.brandPrimary.color
-        titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
-
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.lineHeightMultiple = 1.2
-
-        descriptionLabel.numberOfLines = 0
-        descriptionLabel.attributedText = NSAttributedString(
-            string: "was not found in your connections or in a chat. Click below to search for them as a new connection.",
-            attributes: [
-                .paragraphStyle: paragraph,
-                .foregroundColor: Asset.neutralActive.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0)
-            ]
-        )
-
-        searchButton.setStyle(.brandColored)
-        searchButton.setTitle("Search for a connection", for: .normal)
-
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(titleLabel)
-        stackView.addArrangedSubview(descriptionLabel)
-        stackView.addArrangedSubview(searchButton)
-
-        stackView.setCustomSpacing(10, after: titleLabel)
-        stackView.setCustomSpacing(30, after: descriptionLabel)
-
-        addSubview(stackView)
-
-        stackView.snp.makeConstraints {
-            $0.centerY.equalToSuperview().multipliedBy(0.5)
-            $0.top.greaterThanOrEqualToSuperview()
-            $0.left.equalToSuperview().offset(24)
-            $0.right.equalToSuperview().offset(-24)
-            $0.bottom.lessThanOrEqualToSuperview()
-        }
+  let titleLabel = UILabel()
+  let stackView = UIStackView()
+  let descriptionLabel = UILabel()
+  let searchButton = CapsuleButton()
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    titleLabel.textColor = Asset.brandPrimary.color
+    titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
+
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.lineHeightMultiple = 1.2
+
+    descriptionLabel.numberOfLines = 0
+    descriptionLabel.attributedText = NSAttributedString(
+      string: "was not found in your connections or in a chat. Click below to search for them as a new connection.",
+      attributes: [
+        .paragraphStyle: paragraph,
+        .foregroundColor: Asset.neutralActive.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0)
+      ]
+    )
+
+    searchButton.setStyle(.brandColored)
+    searchButton.setTitle("Search for a connection", for: .normal)
+
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(titleLabel)
+    stackView.addArrangedSubview(descriptionLabel)
+    stackView.addArrangedSubview(searchButton)
+
+    stackView.setCustomSpacing(10, after: titleLabel)
+    stackView.setCustomSpacing(30, after: descriptionLabel)
+
+    addSubview(stackView)
+
+    stackView.snp.makeConstraints {
+      $0.centerY.equalToSuperview().multipliedBy(0.5)
+      $0.top.greaterThanOrEqualToSuperview()
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
+      $0.bottom.lessThanOrEqualToSuperview()
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 
-    func updateSearched(content: String) {
-        titleLabel.text = content
-    }
+  func updateSearched(content: String) {
+    titleLabel.text = content
+  }
 }
diff --git a/Sources/ChatListFeature/Views/ChatSearchListContainerView.swift b/Sources/ChatListFeature/Views/ChatSearchListContainerView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..75c25dc6402403b45afd9c33bc72fb4c2d515a09
--- /dev/null
+++ b/Sources/ChatListFeature/Views/ChatSearchListContainerView.swift
@@ -0,0 +1,17 @@
+import UIKit
+
+final class ChatSearchListContainerView: UIView {
+  let emptyView = ChatSearchEmptyView()
+
+  init() {
+    super.init(frame: .zero)
+
+    addSubview(emptyView)
+
+    emptyView.snp.makeConstraints {
+      $0.edges.equalToSuperview()
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+}
diff --git a/Sources/CheckVersion/CheckVersion.swift b/Sources/CheckVersion/CheckVersion.swift
new file mode 100644
index 0000000000000000000000000000000000000000..32cd955542ab8311ae679007e73aaa29f8c1819f
--- /dev/null
+++ b/Sources/CheckVersion/CheckVersion.swift
@@ -0,0 +1,62 @@
+import Foundation
+import XCTestDynamicOverlay
+
+public struct CheckVersion {
+  public enum VersionState {
+    case updated
+    case outdated(String)
+    case wayTooOld(String, String)
+  }
+
+  public enum Error: Swift.Error {
+    case noLocalVersion
+    case failureFetchingRemote(FetchRemoteVersion.Error)
+  }
+
+  public typealias Completion = (Result<VersionState, Error>) -> Void
+
+  public var run: (@escaping Completion) -> Void
+
+  public func callAsFunction(_ completion: @escaping Completion) -> Void {
+    run(completion)
+  }
+}
+
+extension CheckVersion {
+  public static func live(
+    local: FetchLocalVersion = .live,
+    remote: FetchRemoteVersion = .live
+  ) -> CheckVersion {
+    .init { completion in
+      remote {
+        switch $0 {
+        case .success(let remoteModel):
+          guard let localVersion = local() else {
+            completion(.failure(.noLocalVersion))
+            return
+          }
+          if localVersion >= remoteModel.details.recommendedVersion {
+            completion(.success(.updated))
+          } else {
+            if localVersion < remoteModel.details.minimumVersion {
+              completion(.success(.wayTooOld(
+                remoteModel.details.appUrl,
+                remoteModel.details.minimumVersionMessage
+              )))
+              return
+            }
+            completion(.success(.outdated(remoteModel.details.appUrl)))
+          }
+        case .failure(let error):
+          completion(.failure(.failureFetchingRemote(error)))
+        }
+      }
+    }
+  }
+}
+
+extension CheckVersion {
+  public static let unimplemented = CheckVersion(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/CheckVersion/Dependency.swift b/Sources/CheckVersion/Dependency.swift
new file mode 100644
index 0000000000000000000000000000000000000000..636a64c83d37eb9f639076b1ec80dfd6f2e2a7ed
--- /dev/null
+++ b/Sources/CheckVersion/Dependency.swift
@@ -0,0 +1,13 @@
+import Dependencies
+
+private enum CheckVersionDependencyKey: DependencyKey {
+  static let liveValue: CheckVersion = .live()
+  static let testValue: CheckVersion = .unimplemented
+}
+
+extension DependencyValues {
+  public var checkVersion: CheckVersion {
+    get { self[CheckVersionDependencyKey.self] }
+    set { self[CheckVersionDependencyKey.self] = newValue }
+  }
+}
diff --git a/Sources/CheckVersion/FetchLocalVersion.swift b/Sources/CheckVersion/FetchLocalVersion.swift
new file mode 100644
index 0000000000000000000000000000000000000000..1954fe744e5a70f30c6d57c121b4218c73dba992
--- /dev/null
+++ b/Sources/CheckVersion/FetchLocalVersion.swift
@@ -0,0 +1,22 @@
+import Foundation
+import XCTestDynamicOverlay
+
+public struct FetchLocalVersion {
+  public var run: () -> String?
+
+  public func callAsFunction() -> String? {
+    run()
+  }
+}
+
+extension FetchLocalVersion {
+  public static let live = FetchLocalVersion {
+    Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
+  }
+}
+
+extension FetchLocalVersion {
+  public static let unimplemented = FetchLocalVersion(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/CheckVersion/FetchRemoteVersion.swift b/Sources/CheckVersion/FetchRemoteVersion.swift
new file mode 100644
index 0000000000000000000000000000000000000000..e3c19f924df47afb64a01887f6ccae05f63824fa
--- /dev/null
+++ b/Sources/CheckVersion/FetchRemoteVersion.swift
@@ -0,0 +1,51 @@
+import Foundation
+import XCTestDynamicOverlay
+
+public struct FetchRemoteVersion {
+  public enum Error: Swift.Error {
+    case noData
+    case requestError
+    case decodeFailure
+  }
+
+  public typealias Completion = (Result<Remote, Error>) -> Void
+
+  public var run: (@escaping Completion) -> Void
+
+  public func callAsFunction(_ completion: @escaping Completion) -> Void {
+    run(completion)
+  }
+}
+
+extension FetchRemoteVersion {
+  public static let live = FetchRemoteVersion { completion in
+    let urlString = "https://elixxir-bins.s3-us-west-1.amazonaws.com/client/dapps/appdb.json"
+    let request = URLRequest(
+      url: URL(string: urlString)!,
+      cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
+      timeoutInterval: 5
+    )
+    URLSession.shared.dataTask(with: request) { data, _, error in
+      guard error == nil else {
+        completion(.failure(.requestError))
+        return
+      }
+      guard let data else {
+        completion(.failure(.noData))
+        return
+      }
+      do {
+        let model = try JSONDecoder().decode(Remote.self, from: data)
+        completion(.success(model))
+      } catch {
+        completion(.failure(.decodeFailure))
+      }
+    }.resume()
+  }
+}
+
+extension FetchRemoteVersion {
+  public static let unimplemented = FetchRemoteVersion(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/CheckVersion/RemoteDetailsModel.swift b/Sources/CheckVersion/RemoteDetailsModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d549eb6359ed1c38b4403eae55bf5b45384b77fd
--- /dev/null
+++ b/Sources/CheckVersion/RemoteDetailsModel.swift
@@ -0,0 +1,13 @@
+public struct RemoteDetails: Codable {
+  public var appUrl: String
+  public var minimumVersion: String
+  public var recommendedVersion: String
+  public var minimumVersionMessage: String
+
+  private enum CodingKeys: String, CodingKey {
+    case appUrl = "new_ios_app_url"
+    case minimumVersion = "new_ios_min_version"
+    case minimumVersionMessage = "new_minimum_popup_msg"
+    case recommendedVersion = "new_ios_recommended_version"
+  }
+}
diff --git a/Sources/CheckVersion/RemoteModel.swift b/Sources/CheckVersion/RemoteModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f2a65522f1fe974fb1f31e618c55b6c5956eba7f
--- /dev/null
+++ b/Sources/CheckVersion/RemoteModel.swift
@@ -0,0 +1,7 @@
+public struct Remote: Codable {
+  var details: RemoteDetails
+
+  private enum CodingKeys: String, CodingKey {
+    case details = "dapp-id"
+  }
+}
diff --git a/Sources/CollectionView/CellFactory.swift b/Sources/CollectionView/CellFactory.swift
deleted file mode 100644
index bc7bc9bec131565d07dc5805865bdcc13e0959dc..0000000000000000000000000000000000000000
--- a/Sources/CollectionView/CellFactory.swift
+++ /dev/null
@@ -1,76 +0,0 @@
-import UIKit
-import XCTestDynamicOverlay
-
-public struct CellFactory<Model> {
-  public struct Registrar {
-    public init(register: @escaping (UICollectionView) -> Void) {
-      self.register = register
-    }
-
-    public var register: (UICollectionView) -> Void
-
-    public func callAsFunction(in view: UICollectionView) {
-      register(view)
-    }
-  }
-
-  public struct Builder {
-    public init(build: @escaping (Model, UICollectionView, IndexPath) -> UICollectionViewCell?) {
-      self.build = build
-    }
-
-    public var build: (Model, UICollectionView, IndexPath) -> UICollectionViewCell?
-
-    public func callAsFunction(
-      for model: Model,
-      in view: UICollectionView,
-      at indexPath: IndexPath
-    ) -> UICollectionViewCell? {
-      build(model, view, indexPath)
-    }
-  }
-
-  public init(
-    register: Registrar,
-    build: Builder
-  ) {
-    self.register = register
-    self.build = build
-  }
-
-  public var register: Registrar
-  public var build: Builder
-}
-
-extension CellFactory {
-  public static func combined(_ factories: CellFactory...) -> CellFactory {
-    combined(factories)
-  }
-
-  public static func combined(_ factories: [CellFactory]) -> CellFactory {
-    CellFactory(
-      register: .init { collectionView in
-        factories.forEach { $0.register(in: collectionView) }
-      },
-      build: .init { model, collectionView, indexPath in
-        for factory in factories {
-          if let cell = factory.build(for: model, in: collectionView, at: indexPath) {
-            return cell
-          }
-        }
-        return nil
-      }
-    )
-  }
-}
-
-#if DEBUG
-extension CellFactory {
-  public static func unimplemented() -> CellFactory {
-    CellFactory(
-      register: .init(register: XCTUnimplemented("\(Self.self).Registrar")),
-      build: .init(build: XCTUnimplemented("\(Self.self).Builder"))
-    )
-  }
-}
-#endif
diff --git a/Sources/ContactFeature/Controllers/ContactController.swift b/Sources/ContactFeature/Controllers/ContactController.swift
index 30dd3f5e48444176c274a0f6f18502b12e172c17..ee9bb011bd4eff7300dcff42ea5af2524434f6ce 100644
--- a/Sources/ContactFeature/Controllers/ContactController.swift
+++ b/Sources/ContactFeature/Controllers/ContactController.swift
@@ -1,437 +1,466 @@
-import HUD
 import UIKit
-import Theme
 import Shared
-import Models
 import Combine
 import XXModels
+import AppCore
+import Dependencies
+import AppResources
+import AppNavigation
 import DrawerFeature
-import DependencyInjection
 import ScrollViewController
 
 public final class ContactController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: ContactCoordinating
-    @Dependency private var statusBarController: StatusBarStyleControlling
-
-    lazy private var screenView = ContactView()
-    lazy private var scrollViewController = ScrollViewController()
-
-    private let viewModel: ContactViewModel
-    private var cancellables = Set<AnyCancellable>()
-    private var drawerCancellables = Set<AnyCancellable>()
-
-    public init(_ model: Contact) {
-        self.viewModel = ContactViewModel(model)
-        super.init(nibName: nil, bundle: nil)
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+
+  private lazy var screenView = ContactView()
+  private lazy var scrollViewController = ScrollViewController()
+
+  private let viewModel: ContactViewModel
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
+
+  public init(_ model: Contact) {
+    self.viewModel = ContactViewModel(model)
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    statusBar.set(.lightContent)
+    navigationController?.navigationBar
+      .customize(
+        backgroundColor: Asset.neutralBody.color,
+        tint: Asset.neutralWhite.color
+      )
+  }
+
+  public override func viewSafeAreaInsetsDidChange() {
+    super.viewSafeAreaInsetsDidChange()
+    screenView.updateTopOffset(-view.safeAreaInsets.top)
+    screenView.updateBottomOffset(view.safeAreaInsets.bottom)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupScrollView()
+    setupBindings()
+
+    screenView.didTapSend = { [weak self] in
+      guard let self else { return }
+      self.navigator.perform(PresentChat(
+        contact: self.viewModel.contact,
+        on: self.navigationController!
+      ))
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        statusBarController.style.send(.lightContent)
-        navigationController?.navigationBar
-            .customize(
-                backgroundColor: Asset.neutralBody.color,
-                tint: Asset.neutralWhite.color
-            )
+    screenView.didTapInfo = { [weak self] in
+      guard let self else { return }
+      self.presentInfo(
+        title: Localized.Contact.SendMessage.Info.title,
+        subtitle: Localized.Contact.SendMessage.Info.subtitle,
+        urlString: "https://links.xx.network/cmix"
+      )
     }
 
-    public override func viewSafeAreaInsetsDidChange() {
-        super.viewSafeAreaInsetsDidChange()
-        screenView.updateTopOffset(-view.safeAreaInsets.top)
-        screenView.updateBottomOffset(view.safeAreaInsets.bottom)
-    }
-    
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupScrollView()
-        setupBindings()
-
-        screenView.didTapSend = { [weak self] in
-            guard let self = self else { return }
-            self.coordinator.toSingleChat(with: self.viewModel.contact, from: self)
+    screenView.set(status: viewModel.contact.authStatus)
+  }
+
+  private func setupScrollView() {
+    addChild(scrollViewController)
+    view.addSubview(scrollViewController.view)
+    scrollViewController.view.backgroundColor = Asset.neutralWhite.color
+    scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
+    scrollViewController.didMove(toParent: self)
+    scrollViewController.contentView = screenView
+    scrollViewController.scrollView.bounces = false
+  }
+
+  private func setupBindings() {
+    screenView
+      .cardComponent
+      .avatarView
+      .editButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(
+          PresentPhotoLibrary(from: self)
+        )
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.photo)
+      .compactMap { $0 }
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.cardComponent.image = $0
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.title)
+      .compactMap { $0 }
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.cardComponent.nameLabel.text = $0
+      }.store(in: &cancellables)
+
+    viewModel
+      .popPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigationController?.popViewController(animated: true)
+      }.store(in: &cancellables)
+
+    viewModel
+      .popToRootPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigationController?.popToRootViewController(animated: true)
+      }.store(in: &cancellables)
+
+    viewModel
+      .successPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.updateToSuccess()
+      }.store(in: &cancellables)
+
+    setupScannedBindings()
+    setupReceivedBindings()
+    setupConfirmedBindings()
+    setupInProgressBindings()
+    setupSuccessBindings()
+  }
+
+  private func setupSuccessBindings() {
+    screenView
+      .successView
+      .keepAdding
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        navigationController?.popViewController(animated: true)
+      }.store(in: &cancellables)
+
+    screenView
+      .successView
+      .sentRequests
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        navigator.perform(PresentRequests(on: navigationController!))
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.username)
+      .removeDuplicates()
+      .combineLatest(
+        viewModel.statePublisher.map(\.email).removeDuplicates(),
+        viewModel.statePublisher.map(\.phone).removeDuplicates()
+      )
+      .sink { [unowned self] in
+        [Localized.Contact.username: $0.0,
+         Localized.Contact.email: $0.1,
+         Localized.Contact.phone: $0.2].forEach { pair in
+          guard let value = pair.value else { return }
+          let attributeView = AttributeComponent()
+          attributeView.set(
+            title: pair.key,
+            value: value
+          )
+          screenView.successView.stack.addArrangedSubview(attributeView)
         }
-        screenView.didTapInfo = { [weak self] in
-            self?.presentInfo(
-                title: Localized.Contact.SendMessage.Info.title,
-                subtitle: Localized.Contact.SendMessage.Info.subtitle,
-                urlString: "https://links.xx.network/cmix"
-            )
+      }.store(in: &cancellables)
+  }
+
+  private func setupScannedBindings() {
+    screenView
+      .scannedView.add
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        let nickname = (viewModel.contact.nickname ?? viewModel.contact.username) ?? ""
+        navigator.perform(PresentNickname(prefilled: nickname, completion: { [weak self] in
+          guard let self else { return }
+          self.viewModel.didTapRequest(with: $0)
+        }, from: self))
+      }.store(in: &cancellables)
+  }
+
+  private func setupReceivedBindings() {
+    screenView
+      .receivedView.accept
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        let nickname = (viewModel.contact.nickname ?? viewModel.contact.username) ?? ""
+        navigator.perform(PresentNickname(prefilled: nickname, completion: { [weak self] in
+          guard let self else { return }
+          self.viewModel.didTapAccept($0)
+        }, from: self))
+      }.store(in: &cancellables)
+
+    screenView
+      .receivedView.reject
+      .publisher(for: .touchUpInside)
+      .sink { [weak viewModel] in
+        viewModel?.didTapReject()
+      }.store(in: &cancellables)
+  }
+
+  private func setupInProgressBindings() {
+    viewModel
+      .statePublisher
+      .map(\.username)
+      .removeDuplicates()
+      .combineLatest(
+        viewModel.statePublisher.map(\.email).removeDuplicates(),
+        viewModel.statePublisher.map(\.phone).removeDuplicates()
+      )
+      .sink { [unowned self] in
+        [Localized.Contact.username: $0.0,
+         Localized.Contact.email: $0.1,
+         Localized.Contact.phone: $0.2].forEach { pair in
+          guard let value = pair.value else { return }
+
+          let attributeView = AttributeComponent()
+          attributeView.set(
+            title: pair.key,
+            value: value
+          )
+
+          screenView.inProgressView.stack.addArrangedSubview(attributeView)
         }
+      }.store(in: &cancellables)
+
+    screenView
+      .inProgressView.feedback
+      .button.publisher(for: .touchUpInside)
+      .sink { [weak viewModel] in
+        viewModel?.didTapResend()
+      }.store(in: &cancellables)
+  }
+
+  private func setupConfirmedBindings() {
+    viewModel.statePublisher
+      .receive(on: DispatchQueue.main)
+      .map(\.nickname)
+      .removeDuplicates()
+      .combineLatest(
+        viewModel.statePublisher.map(\.username).removeDuplicates(),
+        viewModel.statePublisher.map(\.email).removeDuplicates(),
+        viewModel.statePublisher.map(\.phone).removeDuplicates()
+      )
+      .sink { [unowned self] in
+        screenView.confirmedView.stackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
+
+        let nicknameAttribute = AttributeComponent()
+        nicknameAttribute.set(title: Localized.Contact.nickname, value: $0.0, style: .requiredEditable)
+        screenView.confirmedView.stackView.insertArrangedSubview(nicknameAttribute, at: 0)
+
+        nicknameAttribute
+          .actionButton
+          .publisher(for: .touchUpInside)
+          .sink { [unowned self] in
+            let nickname = (viewModel.contact.nickname ?? viewModel.contact.username) ?? ""
+            navigator.perform(PresentNickname(prefilled: nickname, completion: { [weak self] in
+              guard let self else { return }
+              self.viewModel.didUpdateNickname($0)
+            }, from: self))
+          }.store(in: &cancellables)
+
+        let usernameAttribute = AttributeComponent()
+        usernameAttribute.set(title: Localized.Contact.username, value: $0.1)
+        screenView.confirmedView.stackView.addArrangedSubview(usernameAttribute)
+
+        let emailAttribute = AttributeComponent()
+        emailAttribute.set(title: Localized.Contact.email, value: $0.2)
+        screenView.confirmedView.stackView.addArrangedSubview(emailAttribute)
+
+        let phoneAttribute = AttributeComponent()
+        phoneAttribute.set(title: Localized.Contact.phone, value: $0.3)
+        screenView.confirmedView.stackView.addArrangedSubview(phoneAttribute)
+
+        let deleteButton = RowButton()
+        deleteButton.setup(
+          title: Localized.Contact.Delete.Info.title,
+          icon: Asset.settingsDelete.image,
+          style: .delete,
+          separator: false
+        )
 
-        screenView.set(status: viewModel.contact.authStatus)
-    }
-
-    private func setupScrollView() {
-        addChild(scrollViewController)
-        view.addSubview(scrollViewController.view)
-        scrollViewController.view.backgroundColor = Asset.neutralWhite.color
-        scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
-        scrollViewController.didMove(toParent: self)
-        scrollViewController.contentView = screenView
-        scrollViewController.scrollView.bounces = false
-    }
-
-    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)
-            .sink { [unowned self] in coordinator.toPhotos(from: self) }
-            .store(in: &cancellables)
-
-        viewModel.statePublisher
-            .map(\.photo)
-            .compactMap { $0 }
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.cardComponent.image = $0 }
-            .store(in: &cancellables)
-
-        viewModel.statePublisher
-            .map(\.title)
-            .compactMap { $0 }
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.cardComponent.nameLabel.text = $0 }
-            .store(in: &cancellables)
-
-        viewModel.popPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in navigationController?.popViewController(animated: true) }
-            .store(in: &cancellables)
-
-        viewModel.popToRootPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in navigationController?.popToRootViewController(animated: true) }
-            .store(in: &cancellables)
-
-        viewModel.successPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.updateToSuccess() }
-            .store(in: &cancellables)
-
-        setupScannedBindings()
-        setupReceivedBindings()
-        setupConfirmedBindings()
-        setupInProgressBindings()
-        setupSuccessBindings()
-    }
-
-    private func setupSuccessBindings() {
-        screenView.successView.keepAdding
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in navigationController?.popViewController(animated: true) }
-            .store(in: &cancellables)
-
-        screenView.successView.sentRequests
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in coordinator.toRequests(from: self) }
-            .store(in: &cancellables)
-
-        viewModel.statePublisher
-            .map(\.username)
-            .removeDuplicates()
-            .combineLatest(
-                viewModel.statePublisher.map(\.email).removeDuplicates(),
-                viewModel.statePublisher.map(\.phone).removeDuplicates()
-            )
-            .sink { [unowned self] in
-                [Localized.Contact.username: $0.0,
-                 Localized.Contact.email: $0.1,
-                 Localized.Contact.phone: $0.2].forEach { pair in
-                    guard let value = pair.value else { return }
-
-                    let attributeView = AttributeComponent()
-                    attributeView.set(
-                        title: pair.key,
-                        value: value
-                    )
-
-                    screenView.successView.stack.addArrangedSubview(attributeView)
-                }
-            }.store(in: &cancellables)
-    }
-
-    private func setupScannedBindings() {
-        screenView.scannedView.add
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                coordinator.toNickname(
-                    from: self,
-                    prefilled: (viewModel.contact.nickname ?? viewModel.contact.username) ?? "",
-                    viewModel.didTapRequest(with:)
-                )
-            }.store(in: &cancellables)
-    }
-
-    private func setupReceivedBindings() {
-        screenView.receivedView.accept
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                coordinator.toNickname(
-                    from: self,
-                    prefilled: (viewModel.contact.nickname ?? viewModel.contact.username) ?? "",
-                    viewModel.didTapAccept(_:)
-                )
-            }.store(in: &cancellables)
-
-        screenView.receivedView.reject
-            .publisher(for: .touchUpInside)
-            .sink { [weak viewModel] in viewModel?.didTapReject() }
-            .store(in: &cancellables)
-    }
+        screenView.confirmedView.stackView.addArrangedSubview(deleteButton)
+
+        deleteButton.publisher(for: .touchUpInside)
+          .sink { [unowned self] in presentDeleteInfo() }
+          .store(in: &cancellables)
+      }.store(in: &cancellables)
+
+    screenView
+      .confirmedView
+      .clearButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        presentClearDrawer()
+      }.store(in: &cancellables)
+  }
+
+  private func presentClearDrawer() {
+    let clearButton = CapsuleButton()
+    clearButton.setStyle(.red)
+    clearButton.setTitle(Localized.Contact.Clear.action, for: .normal)
+
+    let cancelButton = CapsuleButton()
+    cancelButton.setStyle(.seeThrough)
+    cancelButton.setTitle(Localized.Contact.Clear.cancel, for: .normal)
+
+    clearButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          self.viewModel.didTapClear()
+        }
+      }.store(in: &drawerCancellables)
+
+    cancelButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerImage(
+        image: Asset.drawerNegative.image
+      ),
+      DrawerText(
+        font: Fonts.Mulish.semiBold.font(size: 18.0),
+        text: Localized.Contact.Clear.title,
+        color: Asset.neutralActive.color
+      ),
+      DrawerText(
+        font: Fonts.Mulish.semiBold.font(size: 14.0),
+        text: Localized.Contact.Clear.subtitle,
+        color: Asset.neutralWeak.color,
+        lineHeightMultiple: 1.35,
+        spacingAfter: 25
+      ),
+      DrawerStack(
+        spacing: 20.0,
+        views: [clearButton, cancelButton]
+      )
+    ], isDismissable: true, from: self))
+  }
+}
 
-    private func setupInProgressBindings() {
-        viewModel.statePublisher
-            .map(\.username)
-            .removeDuplicates()
-            .combineLatest(
-                viewModel.statePublisher.map(\.email).removeDuplicates(),
-                viewModel.statePublisher.map(\.phone).removeDuplicates()
-            )
-            .sink { [unowned self] in
-                [Localized.Contact.username: $0.0,
-                 Localized.Contact.email: $0.1,
-                 Localized.Contact.phone: $0.2].forEach { pair in
-                    guard let value = pair.value else { return }
-
-                    let attributeView = AttributeComponent()
-                    attributeView.set(
-                        title: pair.key,
-                        value: value
-                    )
-
-                    screenView.inProgressView.stack.addArrangedSubview(attributeView)
-                }
-            }.store(in: &cancellables)
-
-        screenView.inProgressView.feedback
-            .button.publisher(for: .touchUpInside)
-            .sink { [weak viewModel] in viewModel?.didTapResend() }
-            .store(in: &cancellables)
+extension ContactController: UIImagePickerControllerDelegate {
+  public func imagePickerController(
+    _ picker: UIImagePickerController,
+    didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]
+  ) {
+    var image: UIImage?
+
+    if let originalImage = info[.originalImage] as? UIImage {
+      image = originalImage
     }
 
-    private func setupConfirmedBindings() {
-        viewModel.statePublisher
-            .receive(on: DispatchQueue.main)
-            .map(\.nickname)
-            .removeDuplicates()
-            .combineLatest(
-                viewModel.statePublisher.map(\.username).removeDuplicates(),
-                viewModel.statePublisher.map(\.email).removeDuplicates(),
-                viewModel.statePublisher.map(\.phone).removeDuplicates()
-            )
-            .sink { [unowned self] in
-                screenView.confirmedView.stackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
-
-                let nicknameAttribute = AttributeComponent()
-                nicknameAttribute.set(title: Localized.Contact.nickname, value: $0.0, style: .requiredEditable)
-                screenView.confirmedView.stackView.insertArrangedSubview(nicknameAttribute, at: 0)
-
-                nicknameAttribute.actionButton.publisher(for: .touchUpInside)
-                    .sink { [unowned self] in
-                        coordinator.toNickname(
-                            from: self,
-                            prefilled: (viewModel.contact.nickname ?? viewModel.contact.username) ?? "",
-                            viewModel.didUpdateNickname(_:)
-                        )
-                    }
-                    .store(in: &cancellables)
-
-                let usernameAttribute = AttributeComponent()
-                usernameAttribute.set(title: Localized.Contact.username, value: $0.1)
-                screenView.confirmedView.stackView.addArrangedSubview(usernameAttribute)
-
-                let emailAttribute = AttributeComponent()
-                emailAttribute.set(title: Localized.Contact.email, value: $0.2)
-                screenView.confirmedView.stackView.addArrangedSubview(emailAttribute)
-
-                let phoneAttribute = AttributeComponent()
-                phoneAttribute.set(title: Localized.Contact.phone, value: $0.3)
-                screenView.confirmedView.stackView.addArrangedSubview(phoneAttribute)
-
-                let deleteButton = RowButton()
-                deleteButton.setup(
-                    title: Localized.Contact.Delete.Info.title,
-                    icon: Asset.settingsDelete.image,
-                    style: .delete,
-                    separator: false
-                )
-
-                screenView.confirmedView.stackView.addArrangedSubview(deleteButton)
-
-                deleteButton.publisher(for: .touchUpInside)
-                    .sink { [unowned self] in presentDeleteInfo() }
-                    .store(in: &cancellables)
-            }.store(in: &cancellables)
-
-        screenView.confirmedView.clearButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in presentClearDrawer() }
-            .store(in: &cancellables)
+    if let croppedImage = info[.editedImage] as? UIImage {
+      image = croppedImage
     }
 
-    private func presentClearDrawer() {
-        let clearButton = CapsuleButton()
-        clearButton.setStyle(.red)
-        clearButton.setTitle(Localized.Contact.Clear.action, for: .normal)
-
-        let cancelButton = CapsuleButton()
-        cancelButton.setStyle(.seeThrough)
-        cancelButton.setTitle(Localized.Contact.Clear.cancel, for: .normal)
-
-        let drawer = DrawerController(with: [
-            DrawerImage(
-                image: Asset.drawerNegative.image
-            ),
-            DrawerText(
-                font: Fonts.Mulish.semiBold.font(size: 18.0),
-                text: Localized.Contact.Clear.title,
-                color: Asset.neutralActive.color
-            ),
-            DrawerText(
-                font: Fonts.Mulish.semiBold.font(size: 14.0),
-                text: Localized.Contact.Clear.subtitle,
-                color: Asset.neutralWeak.color,
-                lineHeightMultiple: 1.35,
-                spacingAfter: 25
-            ),
-            DrawerStack(
-                spacing: 20.0,
-                views: [clearButton, cancelButton]
-            )
-        ])
-
-        clearButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                    self.viewModel.didTapClear()
-                }
-            }.store(in: &drawerCancellables)
-
-        cancelButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    self?.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
+    guard let image = image else {
+      picker.dismiss(animated: true)
+      return
     }
-}
-
-extension ContactController: UIImagePickerControllerDelegate {
-    public func imagePickerController(
-        _ picker: UIImagePickerController,
-        didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]
-    ) {
-        var image: UIImage?
-
-        if let originalImage = info[.originalImage] as? UIImage {
-            image = originalImage
-        }
 
-        if let croppedImage = info[.editedImage] as? UIImage {
-            image = croppedImage
-        }
-
-        guard let image = image else {
-            picker.dismiss(animated: true)
-            return
-        }
-
-        picker.dismiss(animated: true)
-        viewModel.didChoosePhoto(image)
-    }
+    picker.dismiss(animated: true)
+    viewModel.didChoosePhoto(image)
+  }
 }
 
 extension ContactController: UINavigationControllerDelegate {}
 
 extension ContactController {
-    private func presentInfo(
-        title: String,
-        subtitle: String,
-        urlString: String = ""
-    ) {
-        let actionButton = CapsuleButton()
-        actionButton.set(
-            style: .seeThrough,
-            title: Localized.Settings.InfoDrawer.action
-        )
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
-            ),
-            DrawerLinkText(
-                text: subtitle,
-                urlString: urlString,
-                spacingAfter: 37
-            ),
-            DrawerStack(views: [
-                actionButton,
-                FlexibleSpace()
-            ])
-        ])
-
-        actionButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
-
-    private func presentDeleteInfo() {
-        let actionButton = DrawerCapsuleButton(model: .init(
-            title: Localized.Contact.Delete.Info.title,
-            style: .red
-        ))
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: Localized.Contact.Delete.Drawer.title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
-            ),
-            DrawerText(
-                text: Localized.Contact.Delete.Drawer.description(viewModel.contact.username ?? ""),
-                spacingAfter: 37,
-                customAttributes: [.font:  Fonts.Mulish.bold.font(size: 16.0)]
-            ),
-            actionButton
-        ])
-
-        actionButton.action
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                    self.viewModel.didTapDelete()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
+  private func presentInfo(
+    title: String,
+    subtitle: String,
+    urlString: String = ""
+  ) {
+    let actionButton = CapsuleButton()
+    actionButton.set(
+      style: .seeThrough,
+      title: Localized.Settings.InfoDrawer.action
+    )
+
+    actionButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: title,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 19
+      ),
+      DrawerLinkText(
+        text: subtitle,
+        urlString: urlString,
+        spacingAfter: 37
+      ),
+      DrawerStack(views: [
+        actionButton,
+        FlexibleSpace()
+      ])
+    ], isDismissable: true, from: self))
+  }
+
+  private func presentDeleteInfo() {
+    let actionButton = DrawerCapsuleButton(model: .init(
+      title: Localized.Contact.Delete.Info.title,
+      style: .red
+    ))
+
+    actionButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          self.viewModel.didTapDelete()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: Localized.Contact.Delete.Drawer.title,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 19
+      ),
+      DrawerText(
+        text: Localized.Contact.Delete.Drawer.description(viewModel.contact.username ?? ""),
+        spacingAfter: 37,
+        customAttributes: [.font:  Fonts.Mulish.bold.font(size: 16.0)]
+      ),
+      actionButton
+    ], isDismissable: true, from: self))
+  }
 }
diff --git a/Sources/ContactFeature/Controllers/NicknameController.swift b/Sources/ContactFeature/Controllers/NicknameController.swift
index 709a66998b46c59b402adbbaedb8098be57c8ae3..a36ad67dcd7f761d4df829535d17b1d140e2194f 100644
--- a/Sources/ContactFeature/Controllers/NicknameController.swift
+++ b/Sources/ContactFeature/Controllers/NicknameController.swift
@@ -5,83 +5,83 @@ import InputField
 import ScrollViewController
 
 public final class NicknameController: UIViewController {
-    lazy private var screenView = NicknameView()
-
-    private let prefilled: String
-    private let completion: StringClosure
-    private let viewModel = NicknameViewModel()
-    private var cancellables = Set<AnyCancellable>()
-    private let keyboardListener = KeyboardFrameChangeListener(notificationCenter: .default)
-
-    public init(_ prefilled: String, _ completion: @escaping StringClosure) {
-        self.prefilled = prefilled
-        self.completion = completion
-        super.init(nibName: nil, bundle: nil)
+  private lazy var screenView = NicknameView()
+
+  private let prefilled: String
+  private let completion: (String) -> Void
+  private let viewModel = NicknameViewModel()
+  private var cancellables = Set<AnyCancellable>()
+  private let keyboardListener = KeyboardFrameChangeListener(notificationCenter: .default)
+
+  public init(_ prefilled: String, _ completion: @escaping (String) -> Void) {
+    self.prefilled = prefilled
+    self.completion = completion
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func loadView() {
+    let view = UIView()
+    view.addSubview(screenView)
+
+    screenView.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview().offset(0)
     }
 
-    required init?(coder: NSCoder) { nil }
+    self.view = view
+  }
 
-    public override func loadView() {
-        let view = UIView()
-        view.addSubview(screenView)
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupKeyboard()
+    setupBindings()
 
-        screenView.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview().offset(0)
-        }
+    screenView.inputField.update(content: prefilled)
+    viewModel.didInput(prefilled)
+  }
 
-        self.view = view
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupKeyboard()
-        setupBindings()
-
-        screenView.inputField.update(content: prefilled)
-        viewModel.didInput(prefilled)
-    }
+  private func setupKeyboard() {
+    keyboardListener.keyboardFrameWillChange = { [weak self] keyboard in
+      guard let self else { return }
 
-    private func setupKeyboard() {
-        keyboardListener.keyboardFrameWillChange = { [weak self] keyboard in
-            guard let self = self else { return }
+      let inset = self.view.frame.height - self.view.convert(keyboard.frame, from: nil).minY
 
-            let inset = self.view.frame.height - self.view.convert(keyboard.frame, from: nil).minY
+      self.screenView.snp.updateConstraints {
+        $0.bottom.equalToSuperview().offset(-inset)
+      }
 
-            self.screenView.snp.updateConstraints {
-                $0.bottom.equalToSuperview().offset(-inset)
-            }
-
-            self.view.setNeedsLayout()
-
-            UIView.animate(withDuration: keyboard.animationDuration) {
-                self.view.layoutIfNeeded()
-            }
-        }
-    }
+      self.view.setNeedsLayout()
 
-    private func setupBindings() {
-        viewModel.state
-            .map(\.status)
-            .receive(on: DispatchQueue.main)
-            .sink { [weak screenView] in screenView?.update(status: $0) }
-            .store(in: &cancellables)
-
-        viewModel.done
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                dismiss(animated: true)
-                completion($0)
-            }.store(in: &cancellables)
-
-        screenView.inputField.textPublisher
-            .sink { [weak viewModel] in viewModel?.didInput($0) }
-            .store(in: &cancellables)
-
-        screenView.saveButton.publisher(for: .touchUpInside)
-            .sink { [weak viewModel] in viewModel?.didTapSave() }
-            .store(in: &cancellables)
+      UIView.animate(withDuration: keyboard.animationDuration) {
+        self.view.layoutIfNeeded()
+      }
     }
+  }
+
+  private func setupBindings() {
+    viewModel.state
+      .map(\.status)
+      .receive(on: DispatchQueue.main)
+      .sink { [weak screenView] in screenView?.update(status: $0) }
+      .store(in: &cancellables)
+
+    viewModel.done
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        dismiss(animated: true)
+        completion($0)
+      }.store(in: &cancellables)
+
+    screenView.inputField.textPublisher
+      .sink { [weak viewModel] in viewModel?.didInput($0) }
+      .store(in: &cancellables)
+
+    screenView.saveButton.publisher(for: .touchUpInside)
+      .sink { [weak viewModel] in viewModel?.didTapSave() }
+      .store(in: &cancellables)
+  }
 }
diff --git a/Sources/ContactFeature/Coordinator/ContactCoordinator.swift b/Sources/ContactFeature/Coordinator/ContactCoordinator.swift
deleted file mode 100644
index eea7f1ca94bbcace7a637abc5a1d094d53fff74b..0000000000000000000000000000000000000000
--- a/Sources/ContactFeature/Coordinator/ContactCoordinator.swift
+++ /dev/null
@@ -1,70 +0,0 @@
-import UIKit
-import Models
-import Shared
-import XXModels
-import ChatFeature
-import Presentation
-
-public protocol ContactCoordinating: AnyObject {
-    func toPhotos(from: UIViewController)
-    func toRequests(from: UIViewController)
-    func toSingleChat(with: Contact, from: UIViewController)
-    func toDrawer(_: UIViewController, from: UIViewController)
-    func toNickname(from: UIViewController, prefilled: String, _: @escaping StringClosure)
-}
-
-public final class ContactCoordinator: ContactCoordinating {
-    var pushPresenter: Presenting = PushPresenter()
-    var modalPresenter: Presenting = ModalPresenter()
-    var bottomPresenter: Presenting = BottomPresenter()
-    var replacePresenter: Presenting = ReplacePresenter(mode: .replaceBackwards(SingleChatController.self))
-
-    var requestsFactory: () -> UIViewController
-    var singleChatFactory: (Contact) -> UIViewController
-    var imagePickerFactory: () -> UIImagePickerController
-    var nicknameFactory: (String, @escaping StringClosure) -> UIViewController
-
-    public init(
-        requestsFactory: @escaping () -> UIViewController,
-        singleChatFactory: @escaping (Contact) -> UIViewController,
-        imagePickerFactory: @escaping () -> UIImagePickerController,
-        nicknameFactory: @escaping (String, @escaping StringClosure) -> UIViewController
-    ) {
-        self.requestsFactory = requestsFactory
-        self.singleChatFactory = singleChatFactory
-        self.imagePickerFactory = imagePickerFactory
-        self.nicknameFactory = nicknameFactory
-    }
-}
-
-public extension ContactCoordinator {
-    func toPhotos(from parent: UIViewController) {
-        let screen = imagePickerFactory()
-        screen.delegate = (parent as? (UIImagePickerControllerDelegate & UINavigationControllerDelegate))
-        screen.allowsEditing = true
-        modalPresenter.present(screen, from: parent)
-    }
-
-    func toNickname(
-        from parent: UIViewController,
-        prefilled: String,
-        _ completion: @escaping StringClosure
-    ) {
-        let screen = nicknameFactory(prefilled, completion)
-        bottomPresenter.present(screen, from: parent)
-    }
-
-    func toRequests(from parent: UIViewController) {
-        let screen = requestsFactory()
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toDrawer(_ drawer: UIViewController, from parent: UIViewController) {
-        bottomPresenter.present(drawer, from: parent)
-    }
-
-    func toSingleChat(with contact: Contact, from parent: UIViewController) {
-        let screen = singleChatFactory(contact)
-        replacePresenter.present(screen, from: parent)
-    }
-}
diff --git a/Sources/ContactFeature/ViewModels/ContactViewModel.swift b/Sources/ContactFeature/ViewModels/ContactViewModel.swift
index 5e6e06698cbfeb359a79ce231dde08f37c03ee6a..6e20c16e65aea219159179fcea4739f8447f858f 100644
--- a/Sources/ContactFeature/ViewModels/ContactViewModel.swift
+++ b/Sources/ContactFeature/ViewModels/ContactViewModel.swift
@@ -1,142 +1,211 @@
-import HUD
 import UIKit
-import Models
+import Shared
 import Combine
+import AppCore
 import XXModels
-import Integration
+import Defaults
+import XXClient
+import Dependencies
 import CombineSchedulers
-import DependencyInjection
+import XXMessengerClient
 
 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 private var session: SessionType
-
-    var contact: 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 backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
-
-    init(_ contact: Contact) {
-        self.contact = contact
-
-        do {
-            let email = try session.extract(fact: .email, from: contact.marshaled!)
-            let phone = try session.extract(fact: .phone, from: contact.marshaled!)
-
-            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
-            )
-        } catch {
-            print(error.localizedDescription)
-        }
+  @Dependency(\.app.dbManager) var dbManager: DBManager
+  @Dependency(\.app.messenger) var messenger: Messenger
+  @Dependency(\.app.hudManager) var hudManager: HUDManager
+  
+  @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
+
+    stateRelay.value = .init(
+      title: contact.nickname ?? contact.username,
+      email: contact.email,
+      phone: contact.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? dbManager.getDB().saveContact(contact)
+  }
+  
+  func didTapDelete() {
+    hudManager.show()
+    
+    do {
+      try messenger.e2e.get()!.deleteRequest.partnerId(contact.id)
+      try dbManager.getDB().deleteContact(contact)
+      
+      hudManager.hide()
+      popToRootRelay.send()
+    } catch {
+      hudManager.show(.init(error: error))
     }
-
-    func didChoosePhoto(_ photo: UIImage) {
-        stateRelay.value.photo = photo
-        contact.photo = photo.jpegData(compressionQuality: 0.0)
-        _ = try? session.dbManager.saveContact(contact)
-    }
-
-    func didTapDelete() {
-        hudRelay.send(.on)
-
-        do {
-            try session.deleteContact(contact)
-            hudRelay.send(.none)
-            popToRootRelay.send()
-        } catch {
-            hudRelay.send(.error(.init(with: error)))
+  }
+  
+  func didTapReject() {
+    // TODO: Reject function on the API?
+    _ = try? dbManager.getDB().deleteContact(contact)
+    popRelay.send()
+  }
+  
+  func didTapClear() {
+    _ = try? dbManager.getDB().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? dbManager.getDB().saveContact(contact)
+    
+    stateRelay.value.nickname = contact.nickname
+  }
+  
+  func didTapResend() {
+    hudManager.show()
+    contact.authStatus = .requesting
+    
+    backgroundScheduler.schedule { [weak self] in
+      guard let self else { return }
+      
+      do {
+        try self.dbManager.getDB().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() {
-        try? session.deleteContact(contact)
-        popRelay.send()
-    }
-
-    func didTapClear() {
-        _ = try? session.dbManager.deleteMessages(.init(chat: .direct(session.myId, contact.id)))
-    }
-
-    func didUpdateNickname(_ string: String) {
-        contact.nickname = string.isEmpty ? nil : string
-        stateRelay.value.title = string.isEmpty ? contact.username : string
-        _ = try? session.dbManager.saveContact(contact)
-
-        stateRelay.value.nickname = contact.nickname
-    }
-
-    func didTapResend() {
-        hudRelay.send(.on)
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.session.add(self.contact)
-                self.hudRelay.send(.none)
-                self.popRelay.send()
-            } catch {
-                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
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.session.add(self.contact)
-                self.hudRelay.send(.none)
-                self.successRelay.send()
-            } catch {
-                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.dbManager.getDB().saveContact(self.contact)
+        
+        self.hudManager.hide()
+        self.popRelay.send()
+      } catch {
+        self.contact.authStatus = .requestFailed
+        _ = try? self.dbManager.getDB().saveContact(self.contact)
+        self.hudManager.show(.init(error: error))
+      }
     }
-
-    func didTapAccept(_ nickname: String) {
-        hudRelay.send(.on)
-        contact.nickname = nickname
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.session.confirm(self.contact)
-                self.hudRelay.send(.none)
-                self.popRelay.send()
-            } catch {
-                self.hudRelay.send(.error(.init(with: error)))
-            }
+  }
+  
+  func didTapRequest(with nickname: String) {
+    hudManager.show()
+    contact.nickname = nickname
+    contact.authStatus = .requesting
+    
+    backgroundScheduler.schedule { [weak self] in
+      guard let self else { return }
+      
+      do {
+        try self.dbManager.getDB().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.dbManager.getDB().saveContact(self.contact)
+        
+        self.hudManager.hide()
+        self.successRelay.send()
+      } catch {
+        self.contact.authStatus = .requestFailed
+        _ = try? self.dbManager.getDB().saveContact(self.contact)
+        self.hudManager.show(.init(error: error))
+      }
+    }
+  }
+  
+  func didTapAccept(_ nickname: String) {
+    hudManager.show()
+    contact.nickname = nickname
+    contact.authStatus = .confirming
+    
+    backgroundScheduler.schedule { [weak self] in
+      guard let self else { return }
+      
+      do {
+        try self.dbManager.getDB().saveContact(self.contact)
+        
+        let _ = try self.messenger.e2e.get()!.confirmReceivedRequest(partner: XXClient.Contact.live(self.contact.marshaled!))
+        
+        self.contact.authStatus = .friend
+        try self.dbManager.getDB().saveContact(self.contact)
+        
+        self.hudManager.hide()
+        self.popRelay.send()
+      } catch {
+        self.contact.authStatus = .confirmationFailed
+        _ = try? self.dbManager.getDB().saveContact(self.contact)
+        self.hudManager.show(.init(error: error))
+      }
     }
+  }
 }
diff --git a/Sources/ContactFeature/ViewModels/NicknameViewModel.swift b/Sources/ContactFeature/ViewModels/NicknameViewModel.swift
index 25897d83c215e59b6cfd6233bcb28dc503feb616..6f4cd52b331188a2d10fac1ddff5e0ebaf3d5f80 100644
--- a/Sources/ContactFeature/ViewModels/NicknameViewModel.swift
+++ b/Sources/ContactFeature/ViewModels/NicknameViewModel.swift
@@ -1,6 +1,7 @@
 import Shared
 import Combine
 import InputField
+import AppResources
 
 struct NicknameViewState {
     var nickname: String = ""
diff --git a/Sources/ContactFeature/Views/ContactConfirmedView.swift b/Sources/ContactFeature/Views/ContactConfirmedView.swift
index 472513842beaf5914e6924bf56f56d1b5f2f5b34..7a99fb9f0be8995158fcf17c0f230eb424a32c2d 100644
--- a/Sources/ContactFeature/Views/ContactConfirmedView.swift
+++ b/Sources/ContactFeature/Views/ContactConfirmedView.swift
@@ -1,38 +1,39 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ContactConfirmedView: UIView {
-    let stackView = UIStackView()
-    let clearButton = CapsuleButton()
-    let buttons = SheetCardComponent()
-
-    init() {
-        super.init(frame: .zero)
-
-        clearButton.setStyle(.seeThrough)
-        clearButton.setTitle(Localized.Contact.Confirmed.clear, for: .normal)
-        
-        buttons.set(buttons: [clearButton])
-
-        stackView.axis = .vertical
-        stackView.spacing = 25
-
-        addSubview(stackView)
-        addSubview(buttons)
-        
-        stackView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(24)
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-        }
-
-        buttons.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualTo(stackView.snp.bottom).offset(24)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.equalToSuperview()
-        }
+  let stackView = UIStackView()
+  let clearButton = CapsuleButton()
+  let buttons = SheetCardComponent()
+
+  init() {
+    super.init(frame: .zero)
+
+    clearButton.setStyle(.seeThrough)
+    clearButton.setTitle(Localized.Contact.Confirmed.clear, for: .normal)
+
+    buttons.set(buttons: [clearButton])
+
+    stackView.axis = .vertical
+    stackView.spacing = 25
+
+    addSubview(stackView)
+    addSubview(buttons)
+
+    stackView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(24)
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
+    }
+
+    buttons.snp.makeConstraints {
+      $0.top.greaterThanOrEqualTo(stackView.snp.bottom).offset(24)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/ContactFeature/Views/ContactInProgressView.swift b/Sources/ContactFeature/Views/ContactInProgressView.swift
index 165600f9deb908c47de78f7e4b71e9213d3d847e..2866b6d5d586b1fbc94dfe6479e41d530448980b 100644
--- a/Sources/ContactFeature/Views/ContactInProgressView.swift
+++ b/Sources/ContactFeature/Views/ContactInProgressView.swift
@@ -1,70 +1,54 @@
 import UIKit
 import Shared
-import Models
 import XXModels
+import AppResources
 
 final class ContactAlmostView: UIView {
-    // MARK: UI
+  let stack = UIStackView()
+  let feedback = BottomFeedbackComponent()
 
-    let stack = UIStackView()
-    let feedback = BottomFeedbackComponent()
+  init() {
+    super.init(frame: .zero)
+    stack.axis = .vertical
+    stack.spacing = 25
 
-    // MARK: Lifecycle
+    addSubview(stack)
+    addSubview(feedback)
 
-    init() {
-        super.init(frame: .zero)
-        setup()
+    stack.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(24)
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
     }
 
-    required init?(coder: NSCoder) { nil }
-
-    // MARK: Public
-
-    func set(status: Contact.AuthStatus) {
-        switch status {
-        case .requestFailed, .confirmationFailed:
-            feedback.set(
-                icon: Asset.contactRequestExclamation.image,
-                title: Localized.Contact.Inprogress.failed,
-                style: .danger,
-                actionTitle: Localized.Contact.Inprogress.resend
-            )
-
-        case .confirming, .requested, .requesting:
-            feedback.set(
-                icon: Asset.contactRequestExclamation.image,
-                title: Localized.Contact.Inprogress.pending,
-                style: .chill
-            )
-        default:
-            break
-        }
-    }
-
-    // MARK: Properties
-
-    private func setup() {
-        stack.axis = .vertical
-        stack.spacing = 25
-
-        addSubview(stack)
-        addSubview(feedback)
-
-        setupConstraints()
+    feedback.snp.makeConstraints {
+      $0.top.greaterThanOrEqualTo(stack.snp.bottom).offset(24)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
     }
-
-    private func setupConstraints() {
-        stack.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(24)
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-        }
-
-        feedback.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualTo(stack.snp.bottom).offset(24)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.equalToSuperview()
-        }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  func set(status: Contact.AuthStatus) {
+    switch status {
+    case .requestFailed, .confirmationFailed:
+      feedback.set(
+        icon: Asset.contactRequestExclamation.image,
+        title: Localized.Contact.Inprogress.failed,
+        style: .danger,
+        actionTitle: Localized.Contact.Inprogress.resend
+      )
+
+    case .confirming, .requested, .requesting:
+      feedback.set(
+        icon: Asset.contactRequestExclamation.image,
+        title: Localized.Contact.Inprogress.pending,
+        style: .chill
+      )
+    default:
+      break
     }
+  }
 }
diff --git a/Sources/ContactFeature/Views/ContactReceivedView.swift b/Sources/ContactFeature/Views/ContactReceivedView.swift
index 2d29fd46123aab7b37e615fcc31c22cbbd486c06..4678fdb7eb4a2147e18db552fcf2f1705cd9db24 100644
--- a/Sources/ContactFeature/Views/ContactReceivedView.swift
+++ b/Sources/ContactFeature/Views/ContactReceivedView.swift
@@ -1,67 +1,62 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ContactReceivedView: UIView {
-    // MARK: UI
+  let title = UILabel()
+  let icon = UIImageView()
+  let stack = UIStackView()
+  let accept = CapsuleButton()
+  let reject = CapsuleButton()
 
-    let title = UILabel()
-    let icon = UIImageView()
-    let stack = UIStackView()
-    let accept = CapsuleButton()
-    let reject = CapsuleButton()
+  init() {
+    super.init(frame: .zero)
+    setup()
+  }
 
-    // MARK: Lifecycle
+  required init?(coder: NSCoder) { nil }
 
-    init() {
-        super.init(frame: .zero)
-        setup()
-    }
-
-    required init?(coder: NSCoder) { nil }
+  private func setup() {
+    icon.contentMode = .center
 
-    //  MARK: Private
+    title.textAlignment = .center
+    title.textColor = Asset.neutralBody.color
+    title.text = Localized.Contact.Received.title
+    title.font = Fonts.Mulish.bold.font(size: 24.0)
 
-    private func setup() {
-        icon.contentMode = .center
+    icon.image = Asset.contactRequestPlaceholder.image
 
-        title.textAlignment = .center
-        title.textColor = Asset.neutralBody.color
-        title.text = Localized.Contact.Received.title
-        title.font = Fonts.Mulish.bold.font(size: 24.0)
+    accept.setStyle(.brandColored)
+    accept.setTitle(Localized.Contact.Received.accept, for: .normal)
 
-        icon.image = Asset.contactRequestPlaceholder.image
+    reject.setStyle(.seeThrough)
+    reject.setTitle(Localized.Contact.Received.reject, for: .normal)
 
-        accept.setStyle(.brandColored)
-        accept.setTitle(Localized.Contact.Received.accept, for: .normal)
+    stack.axis = .vertical
+    stack.addArrangedSubview(title)
+    stack.addArrangedSubview(accept)
+    stack.addArrangedSubview(reject)
 
-        reject.setStyle(.seeThrough)
-        reject.setTitle(Localized.Contact.Received.reject, for: .normal)
+    stack.setCustomSpacing(24, after: title)
+    stack.setCustomSpacing(20, after: accept)
 
-        stack.axis = .vertical
-        stack.addArrangedSubview(title)
-        stack.addArrangedSubview(accept)
-        stack.addArrangedSubview(reject)
+    addSubview(icon)
+    addSubview(stack)
 
-        stack.setCustomSpacing(24, after: title)
-        stack.setCustomSpacing(20, after: accept)
+    setupConstraints()
+  }
 
-        addSubview(icon)
-        addSubview(stack)
-
-        setupConstraints()
+  private func setupConstraints() {
+    icon.snp.makeConstraints { make in
+      make.centerX.equalToSuperview()
+      make.bottom.equalTo(stack.snp.top).offset(-30)
     }
 
-    private func setupConstraints() {
-        icon.snp.makeConstraints { make in
-            make.centerX.equalToSuperview()
-            make.bottom.equalTo(stack.snp.top).offset(-30)
-        }
-
-        stack.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualToSuperview().offset(20)
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-            make.bottom.equalToSuperview().offset(-34)
-        }
+    stack.snp.makeConstraints { make in
+      make.top.greaterThanOrEqualToSuperview().offset(20)
+      make.left.equalToSuperview().offset(24)
+      make.right.equalToSuperview().offset(-24)
+      make.bottom.equalToSuperview().offset(-34)
     }
+  }
 }
diff --git a/Sources/ContactFeature/Views/ContactScannedView.swift b/Sources/ContactFeature/Views/ContactScannedView.swift
index 840e445cacfbc228705a5616c1b198c86bd534fe..8dd6a118c79bf2e1b736d4214d45a90d2450c5b3 100644
--- a/Sources/ContactFeature/Views/ContactScannedView.swift
+++ b/Sources/ContactFeature/Views/ContactScannedView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ContactScannedView: UIView {
     let title = UILabel()
diff --git a/Sources/ContactFeature/Views/ContactSuccessView.swift b/Sources/ContactFeature/Views/ContactSuccessView.swift
index 616d1b0621274dc6d7b04fb710cef4d4b28140d6..dc610b3ca85802443e376c37ace40c68900f024f 100644
--- a/Sources/ContactFeature/Views/ContactSuccessView.swift
+++ b/Sources/ContactFeature/Views/ContactSuccessView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ContactSuccessView: UIView {
     // MARK: UI
diff --git a/Sources/ContactFeature/Views/ContactView.swift b/Sources/ContactFeature/Views/ContactView.swift
index e5fb03927caf3de9018a382a004b7c89c8a69e99..059686511eaee49e47ccb3fb9b77156fc5a2427b 100644
--- a/Sources/ContactFeature/Views/ContactView.swift
+++ b/Sources/ContactFeature/Views/ContactView.swift
@@ -1,7 +1,7 @@
 import UIKit
 import Shared
-import Models
 import XXModels
+import AppResources
 
 final class ContactView: UIView {
     let container = UIView()
diff --git a/Sources/ContactFeature/Views/NicknameView.swift b/Sources/ContactFeature/Views/NicknameView.swift
index 731bdd70a67f940d16e570b7f25b78cd8f1e8e1e..637f5a1fa17a451cc2cc5514a6f2acf268febe64 100644
--- a/Sources/ContactFeature/Views/NicknameView.swift
+++ b/Sources/ContactFeature/Views/NicknameView.swift
@@ -1,6 +1,7 @@
 import UIKit
 import Shared
 import InputField
+import AppResources
 
 final class NicknameView: UIView {
     let titleLabel = UILabel()
diff --git a/Sources/ContactListFeature/ContactListController.swift b/Sources/ContactListFeature/ContactListController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..36d8e8b4c65c2801480e0aca20ae60528e706b77
--- /dev/null
+++ b/Sources/ContactListFeature/ContactListController.swift
@@ -0,0 +1,150 @@
+import UIKit
+import Shared
+import Combine
+import AppCore
+import Dependencies
+import AppResources
+import AppNavigation
+
+public final class ContactListController: UIViewController {
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+
+  private lazy var screenView = ContactListView()
+  private lazy var tableController = ContactListTableController(viewModel)
+
+  private let viewModel = ContactListViewModel()
+  private var cancellables = Set<AnyCancellable>()
+
+  public override func loadView() {
+    view = screenView
+  }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    statusBar.set(.darkContent)
+    navigationController?.navigationBar.customize(backgroundColor: Asset.neutralWhite.color)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupNavigationBar()
+    setupTableView()
+    setupBindings()
+  }
+
+  private func setupNavigationBar() {
+    navigationItem.backButtonTitle = " "
+
+    let titleLabel = UILabel()
+    titleLabel.text = Localized.ContactList.title
+    titleLabel.textColor = Asset.neutralActive.color
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 18.0)
+
+    let menuButton = UIButton()
+    menuButton.tintColor = Asset.neutralDark.color
+    menuButton.setImage(Asset.chatListMenu.image, for: .normal)
+    menuButton.addTarget(self, action: #selector(didTapMenu), for: .touchUpInside)
+    menuButton.snp.makeConstraints { $0.width.equalTo(50) }
+
+    navigationItem.leftBarButtonItem = UIBarButtonItem(
+      customView: UIStackView(arrangedSubviews: [menuButton, titleLabel])
+    )
+
+    let search = UIButton()
+    search.tintColor = Asset.neutralActive.color
+    search.setImage(Asset.contactListSearch.image, for: .normal)
+    search.addTarget(self, action: #selector(didTapSearch), for: .touchUpInside)
+    search.accessibilityIdentifier = Localized.Accessibility.ContactList.search
+
+    let scanButton = UIButton()
+    scanButton.setImage(Asset.sharedScan.image, for: .normal)
+    scanButton.addTarget(self, action: #selector(didTapScan), for: .touchUpInside)
+
+    let rightStack = UIStackView()
+    rightStack.spacing = 15
+    rightStack.addArrangedSubview(scanButton)
+    rightStack.addArrangedSubview(search)
+
+    navigationItem.rightBarButtonItem = UIBarButtonItem(customView: rightStack)
+
+    search.snp.makeConstraints {
+      $0.width.equalTo(40)
+    }
+  }
+
+  private func setupTableView() {
+    addChild(tableController)
+    screenView.addSubview(tableController.view)
+    tableController.view.snp.makeConstraints {
+      $0.top.equalTo(screenView.topStackView.snp.bottom)
+      $0.left.bottom.right.equalToSuperview()
+    }
+    tableController.didMove(toParent: self)
+  }
+
+  private func setupBindings() {
+    tableController
+      .didTap
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentChat(
+          contact: $0,
+          on: navigationController!
+        ))
+      }.store(in: &cancellables)
+
+    screenView
+      .requestsButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentRequests(on: navigationController!))
+      }.store(in: &cancellables)
+
+    screenView
+      .newGroupButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentGroupDraft(on: navigationController!))
+      }.store(in: &cancellables)
+
+    screenView
+      .searchButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentSearch(on: navigationController!))
+      }.store(in: &cancellables)
+
+    viewModel
+      .requestCount
+      .receive(on: DispatchQueue.main)
+      .sink { [weak screenView] in
+        screenView?.requestsButton.updateNotification($0)
+      }.store(in: &cancellables)
+
+    viewModel
+      .contacts
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.stackView.isHidden = !$0.isEmpty
+        if $0.isEmpty {
+          screenView.bringSubviewToFront(screenView.stackView)
+        }
+      }.store(in: &cancellables)
+  }
+
+  @objc private func didTapSearch() {
+    navigator.perform(PresentSearch(on: navigationController!))
+  }
+
+  @objc private func didTapScan() {
+    navigator.perform(PresentScan(on: navigationController!))
+  }
+
+  @objc private func didTapMenu() {
+    navigator.perform(PresentMenu(currentItem: .contacts, from: self))
+  }
+}
diff --git a/Sources/ContactListFeature/ContactListItemButton.swift b/Sources/ContactListFeature/ContactListItemButton.swift
new file mode 100644
index 0000000000000000000000000000000000000000..009260c7c3a11e54ca25670b56cd6aa3960c9643
--- /dev/null
+++ b/Sources/ContactListFeature/ContactListItemButton.swift
@@ -0,0 +1,61 @@
+import UIKit
+import Shared
+import AppResources
+
+final class ItemButton: UIControl {
+  let titleLabel = UILabel()
+  let iconImageView = UIImageView()
+  let separatorView = UIView()
+  let stackView = UIStackView()
+  let notificationLabel = UILabel()
+
+  init() {
+    super.init(frame: .zero)
+
+    titleLabel.textColor = Asset.brandPrimary.color
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    separatorView.backgroundColor = Asset.neutralLine.color
+
+    notificationLabel.isHidden = true
+    notificationLabel.layer.cornerRadius = 5
+    notificationLabel.layer.masksToBounds = true
+    notificationLabel.textColor = Asset.neutralWhite.color
+    notificationLabel.backgroundColor = Asset.brandPrimary.color
+    notificationLabel.font = Fonts.Mulish.bold.font(size: 12.0)
+    
+    stackView.spacing = 16
+    stackView.addArrangedSubview(iconImageView)
+    stackView.addArrangedSubview(titleLabel)
+    stackView.addArrangedSubview(notificationLabel)
+    stackView.setCustomSpacing(6, after: titleLabel)
+
+    stackView.isUserInteractionEnabled = false
+    addSubview(stackView)
+    addSubview(separatorView)
+
+    stackView.snp.makeConstraints { make in
+      make.top.equalToSuperview().offset(12)
+      make.left.equalToSuperview().offset(24)
+      make.bottom.equalTo(separatorView.snp.top).offset(-12)
+    }
+
+    separatorView.snp.makeConstraints { make in
+      make.left.equalToSuperview().offset(24)
+      make.right.equalToSuperview().offset(-24)
+      make.bottom.equalToSuperview()
+      make.height.equalTo(1)
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  func setup(title: String, image: UIImage) {
+    titleLabel.text = title
+    iconImageView.image = image
+  }
+
+  func updateNotification(_ count: Int) {
+    notificationLabel.isHidden = count < 1
+    notificationLabel.text = "  \(count)  "
+  }
+}
diff --git a/Sources/ContactListFeature/ContactListTableController.swift b/Sources/ContactListFeature/ContactListTableController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8215991d49982bf7f3780636947fcd7adf575adb
--- /dev/null
+++ b/Sources/ContactListFeature/ContactListTableController.swift
@@ -0,0 +1,83 @@
+import UIKit
+import Shared
+import Combine
+import XXModels
+import AppResources
+
+final class ContactListTableController: UITableViewController {
+  private var collation = UILocalizedIndexedCollation.current()
+  private var sections: [[Contact]] = [] {
+    didSet { self.tableView.reloadData() }
+  }
+  
+  private let viewModel: ContactListViewModel
+  private var cancellables = Set<AnyCancellable>()
+  private let tapRelay = PassthroughSubject<Contact, Never>()
+  
+  var didTap: AnyPublisher<Contact, Never> { tapRelay.eraseToAnyPublisher() }
+  
+  override func viewDidLoad() {
+    super.viewDidLoad()
+    setupTableView()
+  }
+  
+  init(_ viewModel: ContactListViewModel) {
+    self.viewModel = viewModel
+    super.init(style: .grouped)
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  private func setupTableView() {
+    tableView.separatorStyle = .none
+    tableView.register(AvatarCell.self)
+    tableView.backgroundColor = Asset.neutralWhite.color
+    tableView.sectionIndexColor = Asset.neutralDark.color
+    tableView.contentInset = UIEdgeInsets(top: -20, left: 0, bottom: 0, right: 0)
+    
+    viewModel.contacts
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        let results = IndexedListCollator().sectioned(items: $0)
+        self.collation = results.collation
+        self.sections = results.sections
+      }.store(in: &cancellables)
+  }
+  
+  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+    let cell: AvatarCell = tableView.dequeueReusableCell(forIndexPath: indexPath)
+    let contact = sections[indexPath.section][indexPath.row]
+    let name = (contact.nickname ?? contact.username) ?? "Fetching username..."
+    
+    cell.setup(title: name, image: contact.photo)
+    return cell
+  }
+  
+  override func numberOfSections(in: UITableView) -> Int {
+    sections.count
+  }
+  
+  override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
+    sections[section].count
+  }
+  
+  override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
+    tapRelay.send(sections[indexPath.section][indexPath.row])
+  }
+  
+  override func sectionIndexTitles(for: UITableView) -> [String]? {
+    collation.sectionIndexTitles
+  }
+  
+  override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
+    collation.sectionTitles[section]
+  }
+  
+  override func tableView(_: UITableView, sectionForSectionIndexTitle: String, at index: Int) -> Int {
+    collation.section(forSectionIndexTitle: index)
+  }
+  
+  override func tableView(_: UITableView, heightForRowAt: IndexPath) -> CGFloat {
+    64
+  }
+}
diff --git a/Sources/ContactListFeature/ContactListView.swift b/Sources/ContactListFeature/ContactListView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..75ce0cb079969051c4fef0598a417dfcf7279ec6
--- /dev/null
+++ b/Sources/ContactListFeature/ContactListView.swift
@@ -0,0 +1,73 @@
+import UIKit
+import Shared
+import AppResources
+
+final class ContactListView: UIView {
+  let newGroupButton = ItemButton()
+  let requestsButton = ItemButton()
+  let topStackView = UIStackView()
+  let stackView = UIStackView()
+  let emptyTitleLabel = UILabel()
+  let searchButton = CapsuleButton()
+
+  init() {
+    super.init(frame: .zero)
+    setup()
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  private func setup() {
+    backgroundColor = Asset.neutralWhite.color
+
+    requestsButton.separatorView.isHidden = true
+    requestsButton.setup(title: "Requests", image: Asset.contactListRequests.image)
+    newGroupButton.setup(title: Localized.ContactList.newGroup, image: Asset.contactListNewGroup.image)
+
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.lineHeightMultiple = 1.2
+    paragraph.alignment = .center
+
+    emptyTitleLabel.attributedText = NSAttributedString(
+      string: Localized.ContactList.Empty.title,
+      attributes: [
+        .paragraphStyle: paragraph,
+        .foregroundColor: Asset.neutralActive.color,
+        .font: Fonts.Mulish.bold.font(size: 24.0) as UIFont
+      ]
+    )
+    emptyTitleLabel.numberOfLines = 0
+
+    searchButton.setStyle(.brandColored)
+    searchButton.setTitle(Localized.ContactList.Empty.action, for: .normal)
+
+    stackView.spacing = 24
+    stackView.axis = .vertical
+    stackView.alignment = .center
+    stackView.addArrangedSubview(emptyTitleLabel)
+    stackView.addArrangedSubview(searchButton)
+
+    topStackView.axis = .vertical
+    topStackView.addArrangedSubview(newGroupButton)
+    topStackView.addArrangedSubview(requestsButton)
+
+    addSubview(topStackView)
+    addSubview(stackView)
+
+    setupConstraints()
+  }
+
+  private func setupConstraints() {
+    topStackView.snp.makeConstraints { make in
+      make.top.equalToSuperview().offset(20)
+      make.left.equalToSuperview()
+      make.right.equalToSuperview()
+    }
+
+    stackView.snp.makeConstraints { make in
+      make.centerY.equalToSuperview()
+      make.left.equalToSuperview().offset(24)
+      make.right.equalToSuperview().offset(-24)
+    }
+  }
+}
diff --git a/Sources/ContactListFeature/ContactListViewModel.swift b/Sources/ContactListFeature/ContactListViewModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..3e509accc5cfd7c71e76ec7e6dc814da5a63af9a
--- /dev/null
+++ b/Sources/ContactListFeature/ContactListViewModel.swift
@@ -0,0 +1,63 @@
+import Combine
+import XXModels
+import Defaults
+import ReportingFeature
+import XXMessengerClient
+
+import Foundation
+import XXClient
+
+import AppCore
+import Dependencies
+
+final class ContactListViewModel {
+  @Dependency(\.app.dbManager) var dbManager: DBManager
+  @Dependency(\.app.messenger) var messenger: Messenger
+  @Dependency(\.reportingStatus) var reportingStatus: ReportingStatus
+
+  var myId: Data {
+    try! messenger.e2e.get()!.getContact().getId()
+  }
+
+  var contacts: AnyPublisher<[XXModels.Contact], Never> {
+    let query = Contact.Query(
+      authStatus: [.friend],
+      isBlocked: reportingStatus.isEnabled() ? false : nil,
+      isBanned: reportingStatus.isEnabled() ? false: nil
+    )
+
+    return try! dbManager.getDB().fetchContactsPublisher(query)
+      .replaceError(with: [])
+      .map { $0.filter { $0.id != self.myId }}
+      .eraseToAnyPublisher()
+  }
+
+  var requestCount: AnyPublisher<Int, Never> {
+    let groupQuery = Group.Query(
+      authStatus: [.pending],
+      isLeaderBlocked: reportingStatus.isEnabled() ? false : nil,
+      isLeaderBanned: reportingStatus.isEnabled() ? false : nil
+    )
+
+    let contactsQuery = Contact.Query(
+      authStatus: [
+        .verified,
+        .confirming,
+        .confirmationFailed,
+        .verificationFailed,
+        .verificationInProgress
+      ],
+      isBlocked: reportingStatus.isEnabled() ? false : nil,
+      isBanned: reportingStatus.isEnabled() ? false : nil
+    )
+
+    return Publishers.CombineLatest(
+      try! dbManager.getDB().fetchContactsPublisher(contactsQuery)
+        .replaceError(with: []),
+      try! dbManager.getDB().fetchGroupsPublisher(groupQuery)
+        .replaceError(with: [])
+    )
+    .map { $0.0.count + $0.1.count }
+    .eraseToAnyPublisher()
+  }
+}
diff --git a/Sources/ContactListFeature/Controllers/ContactListController.swift b/Sources/ContactListFeature/Controllers/ContactListController.swift
deleted file mode 100644
index 1e09b9377139e9428ddb539c6ace95164f26bb1c..0000000000000000000000000000000000000000
--- a/Sources/ContactListFeature/Controllers/ContactListController.swift
+++ /dev/null
@@ -1,135 +0,0 @@
-import UIKit
-import Theme
-import Shared
-import Combine
-import DependencyInjection
-
-public final class ContactListController: UIViewController {
-    @Dependency private var coordinator: ContactListCoordinating
-    @Dependency private var statusBarController: StatusBarStyleControlling
-
-    lazy private var screenView = ContactListView()
-    lazy private var tableController = ContactListTableController(viewModel)
-
-    private let viewModel = ContactListViewModel()
-    private var cancellables = Set<AnyCancellable>()
-
-    public override func loadView() {
-        view = screenView
-    }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        statusBarController.style.send(.darkContent)
-        navigationController?.navigationBar.customize(backgroundColor: Asset.neutralWhite.color)
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupNavigationBar()
-        setupTableView()
-        setupBindings()
-    }
-
-    private func setupNavigationBar() {
-        navigationItem.backButtonTitle = " "
-
-        let titleLabel = UILabel()
-        titleLabel.text = Localized.ContactList.title
-        titleLabel.textColor = Asset.neutralActive.color
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 18.0)
-
-        let menuButton = UIButton()
-        menuButton.tintColor = Asset.neutralDark.color
-        menuButton.setImage(Asset.chatListMenu.image, for: .normal)
-        menuButton.addTarget(self, action: #selector(didTapMenu), for: .touchUpInside)
-        menuButton.snp.makeConstraints { $0.width.equalTo(50) }
-
-        navigationItem.leftBarButtonItem = UIBarButtonItem(
-            customView: UIStackView(arrangedSubviews: [menuButton, titleLabel])
-        )
-
-        let search = UIButton()
-        search.tintColor = Asset.neutralActive.color
-        search.setImage(Asset.contactListSearch.image, for: .normal)
-        search.addTarget(self, action: #selector(didTapSearch), for: .touchUpInside)
-        search.accessibilityIdentifier = Localized.Accessibility.ContactList.search
-
-        let scanButton = UIButton()
-        scanButton.setImage(Asset.sharedScan.image, for: .normal)
-        scanButton.addTarget(self, action: #selector(didTapScan), for: .touchUpInside)
-
-        let rightStack = UIStackView()
-        rightStack.spacing = 15
-        rightStack.addArrangedSubview(scanButton)
-        rightStack.addArrangedSubview(search)
-
-        navigationItem.rightBarButtonItem = UIBarButtonItem(customView: rightStack)
-
-        search.snp.makeConstraints { $0.width.equalTo(40) }
-    }
-
-    private func setupTableView() {
-        addChild(tableController)
-        screenView.addSubview(tableController.view)
-
-        tableController.view.snp.makeConstraints { make in
-            make.top.equalTo(screenView.topStackView.snp.bottom)
-            make.left.bottom.right.equalToSuperview()
-        }
-
-        tableController.didMove(toParent: self)
-    }
-
-    private func setupBindings() {
-        tableController.didTap
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toSingleChat(with: $0, from: self) }
-            .store(in: &cancellables)
-
-        screenView.requestsButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toRequests(from: self) }
-            .store(in: &cancellables)
-
-        screenView.newGroupButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toNewGroup(from: self) }
-            .store(in: &cancellables)
-
-        screenView.searchButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toSearch(from: self) }
-            .store(in: &cancellables)
-
-        viewModel.requestCount
-            .receive(on: DispatchQueue.main)
-            .sink { [weak screenView] in screenView?.requestsButton.updateNotification($0) }
-            .store(in: &cancellables)
-
-        viewModel.contacts
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                screenView.stackView.isHidden = !$0.isEmpty
-
-                if $0.isEmpty {
-                    screenView.bringSubviewToFront(screenView.stackView)
-                }
-            }.store(in: &cancellables)
-    }
-
-    @objc private func didTapSearch() {
-        coordinator.toSearch(from: self)
-    }
-
-    @objc private func didTapScan() {
-        coordinator.toScan(from: self)
-    }
-
-    @objc private func didTapMenu() {
-        coordinator.toSideMenu(from: self)
-    }
-}
diff --git a/Sources/ContactListFeature/Controllers/ContactListTableController.swift b/Sources/ContactListFeature/Controllers/ContactListTableController.swift
deleted file mode 100644
index ac7a06628045f199393f1839e2a6f700a7cb194e..0000000000000000000000000000000000000000
--- a/Sources/ContactListFeature/Controllers/ContactListTableController.swift
+++ /dev/null
@@ -1,83 +0,0 @@
-import UIKit
-import Shared
-import Models
-import Combine
-import XXModels
-
-final class ContactListTableController: UITableViewController {
-    private var collation = UILocalizedIndexedCollation.current()
-    private var sections: [[Contact]] = [] {
-        didSet { self.tableView.reloadData() }
-    }
-
-    private let viewModel: ContactListViewModel
-    private var cancellables = Set<AnyCancellable>()
-    private let tapRelay = PassthroughSubject<Contact, Never>()
-
-    var didTap: AnyPublisher<Contact, Never> { tapRelay.eraseToAnyPublisher() }
-
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        setupTableView()
-    }
-
-    init(_ viewModel: ContactListViewModel) {
-        self.viewModel = viewModel
-        super.init(style: .grouped)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    private func setupTableView() {
-        tableView.separatorStyle = .none
-        tableView.register(AvatarCell.self)
-        tableView.backgroundColor = Asset.neutralWhite.color
-        tableView.sectionIndexColor = Asset.neutralDark.color
-        tableView.contentInset = UIEdgeInsets(top: -20, left: 0, bottom: 0, right: 0)
-
-        viewModel.contacts
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                let results = IndexedListCollator().sectioned(items: $0)
-                self.collation = results.collation
-                self.sections = results.sections
-            }.store(in: &cancellables)
-    }
-
-    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-        let cell: AvatarCell = tableView.dequeueReusableCell(forIndexPath: indexPath)
-        let contact = sections[indexPath.section][indexPath.row]
-        let name = (contact.nickname ?? contact.username) ?? "Fetching username..."
-
-        cell.setup(title: name, image: contact.photo)
-        return cell
-    }
-
-    override func numberOfSections(in: UITableView) -> Int {
-        sections.count
-    }
-
-    override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
-        sections[section].count
-    }
-
-    override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
-        tapRelay.send(sections[indexPath.section][indexPath.row])
-    }
-
-    override func sectionIndexTitles(for: UITableView) -> [String]? {
-        collation.sectionIndexTitles
-    }
-
-    override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
-        collation.sectionTitles[section]
-    }
-
-    override func tableView(_: UITableView, sectionForSectionIndexTitle: String, at index: Int) -> Int {
-        collation.section(forSectionIndexTitle: index)
-    }
-
-    override func tableView(_: UITableView, heightForRowAt: IndexPath) -> CGFloat {
-        64
-    }
-}
diff --git a/Sources/ContactListFeature/Controllers/CreateDrawerController.swift b/Sources/ContactListFeature/Controllers/CreateDrawerController.swift
deleted file mode 100644
index 543a9722869b0bf6cbc1b66579f235cfa88daf26..0000000000000000000000000000000000000000
--- a/Sources/ContactListFeature/Controllers/CreateDrawerController.swift
+++ /dev/null
@@ -1,89 +0,0 @@
-import UIKit
-import Shared
-import Combine
-
-public final class CreateDrawerController: UIViewController {
-    lazy private var screenView = CreateDrawerView()
-
-    private let selectedCount: Int
-    private let viewModel = CreateDrawerViewModel()
-    private let completion: (String, String?) -> Void
-    private var cancellables = Set<AnyCancellable>()
-
-    public init(_ count: Int, _ completion: @escaping (String, String?) -> Void) {
-        self.selectedCount = count
-        self.completion = completion
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func loadView() {
-        let view = UIView()
-        view.addSubview(screenView)
-
-        screenView.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview().offset(0)
-        }
-
-        self.view = view
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        screenView.set(count: selectedCount) {
-            // TODO: ⚠️
-        }
-
-        setupBindings()
-    }
-
-    private func setupBindings() {
-        viewModel.statePublisher
-            .map(\.status)
-            .receive(on: DispatchQueue.main)
-            .sink { [weak screenView] in screenView?.update(status: $0) }
-            .store(in: &cancellables)
-
-        viewModel.donePublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                dismiss(animated: true)
-                completion($0.0, $0.1)
-            }.store(in: &cancellables)
-
-        screenView.cancelButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in dismiss(animated: true) }
-            .store(in: &cancellables)
-
-        screenView.inputField
-            .textPublisher
-            .sink { [weak viewModel] in viewModel?.didInput($0) }
-            .store(in: &cancellables)
-
-        screenView.otherInputField
-            .textPublisher
-            .sink { [weak viewModel] in viewModel?.didOtherInput($0) }
-            .store(in: &cancellables)
-
-        screenView.inputField
-            .returnPublisher
-            .sink { [unowned self] in screenView.inputField.endEditing(true) }
-            .store(in: &cancellables)
-
-        screenView.otherInputField
-            .returnPublisher
-            .sink { [unowned self] in screenView.otherInputField.endEditing(true) }
-            .store(in: &cancellables)
-
-        screenView.createButton
-            .publisher(for: .touchUpInside)
-            .sink { [weak viewModel] in viewModel?.didTapCreate() }
-            .store(in: &cancellables)
-    }
-}
diff --git a/Sources/ContactListFeature/Controllers/CreateGroupController.swift b/Sources/ContactListFeature/Controllers/CreateGroupController.swift
deleted file mode 100644
index e55860fc0a48220413dc33373c915e754351ebe5..0000000000000000000000000000000000000000
--- a/Sources/ContactListFeature/Controllers/CreateGroupController.swift
+++ /dev/null
@@ -1,186 +0,0 @@
-import HUD
-import UIKit
-import Models
-import Shared
-import Combine
-import XXModels
-import DependencyInjection
-
-public final class CreateGroupController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: ContactListCoordinating
-
-    lazy private var titleLabel = UILabel()
-    lazy private var createButton = UIButton()
-    lazy private var screenView = CreateGroupView()
-
-    private var selectedElements = [Contact]() {
-        didSet { screenView.tableView.reloadData() }
-    }
-    private let viewModel = CreateGroupViewModel()
-    private var cancellables = Set<AnyCancellable>()
-    private var tableDataSource: UITableViewDiffableDataSource<SectionId, Contact>!
-    private var collectionDataSource: UICollectionViewDiffableDataSource<SectionId, Contact>!
-
-    private var count = 0 {
-        didSet {
-            createButton.isEnabled = count >= 2 && count <= 10
-
-            let text = Localized.CreateGroup.title("\(count)")
-            let attString = NSMutableAttributedString(string: text)
-            attString.addAttribute(.font, value: Fonts.Mulish.semiBold.font(size: 18.0) as Any)
-            attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
-            attString.addAttribute(name: .foregroundColor, value: Asset.neutralDisabled.color, betweenCharacters: "#")
-
-            titleLabel.attributedText = attString
-        }
-    }
-
-    public override func loadView() {
-        view = screenView
-    }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        navigationController?.navigationBar
-            .customize(backgroundColor: Asset.neutralWhite.color)
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupNavigationBar()
-        setupTableAndCollection()
-        setupBindings()
-
-        count = 0
-    }
-
-    private func setupNavigationBar() {
-        navigationItem.leftBarButtonItem = UIBarButtonItem(customView: titleLabel)
-        navigationItem.leftItemsSupplementBackButton = true
-
-        createButton.setTitle(Localized.CreateGroup.create, for: .normal)
-        createButton.setTitleColor(Asset.brandPrimary.color, for: .normal)
-        createButton.titleLabel?.font = Fonts.Mulish.semiBold.font(size: 16.0)
-        createButton.setTitleColor(Asset.neutralDisabled.color, for: .disabled)
-        navigationItem.rightBarButtonItem = UIBarButtonItem(customView: createButton)
-    }
-
-    private func setupTableAndCollection() {
-        screenView.tableView.rowHeight = 64.0
-        screenView.tableView.register(AvatarCell.self)
-        screenView.collectionView.register(CreateGroupCollectionCell.self)
-
-        collectionDataSource = UICollectionViewDiffableDataSource<SectionId, Contact>(
-            collectionView: screenView.collectionView
-        ) { [weak viewModel] collectionView, indexPath, contact in
-            let cell: CreateGroupCollectionCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-
-            let title = (contact.nickname ?? contact.username) ?? ""
-            cell.setup(title: title, image: contact.photo)
-            cell.didTapRemove = { viewModel?.didSelect(contact: contact) }
-
-            return cell
-        }
-
-        tableDataSource = DiffEditableDataSource<SectionId, Contact>(
-            tableView: screenView.tableView
-        ) { [weak self] tableView, indexPath, contact in
-            let cell = tableView.dequeueReusableCell(forIndexPath: indexPath, ofType: AvatarCell.self)
-            let title = (contact.nickname ?? contact.username) ?? ""
-
-            cell.setup(title: title, image: contact.photo)
-
-            if let selectedElements = self?.selectedElements, selectedElements.contains(contact) {
-                tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
-            } else {
-                tableView.deselectRow(at: indexPath, animated: true)
-            }
-
-            return cell
-        }
-
-        screenView.tableView.delegate = self
-        screenView.tableView.dataSource = tableDataSource
-        screenView.collectionView.dataSource = collectionDataSource
-    }
-
-    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
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                screenView.collectionView.isHidden = $0.count < 1
-
-                count = $0.count
-                selectedElements = $0
-            }.store(in: &cancellables)
-
-        selected.map { selectedContacts in
-            var snapshot = NSDiffableDataSourceSnapshot<SectionId, Contact>()
-            let sections = [SectionId()]
-            snapshot.appendSections(sections)
-            sections.forEach { section in snapshot.appendItems(selectedContacts, toSection: section) }
-            return snapshot
-        }
-        .receive(on: DispatchQueue.main)
-        .sink { [unowned self] in collectionDataSource.apply($0) }
-        .store(in: &cancellables)
-
-        viewModel.contacts
-            .map { contacts in
-                var snapshot = NSDiffableDataSourceSnapshot<SectionId, Contact>()
-                let sections = [SectionId()]
-                snapshot.appendSections(sections)
-                sections.forEach { section in snapshot.appendItems(contacts, toSection: section) }
-                return snapshot
-            }
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                tableDataSource.apply($0, animatingDifferences: tableDataSource.snapshot().numberOfItems > 0)
-            }.store(in: &cancellables)
-
-        screenView.searchComponent.textPublisher
-            .removeDuplicates()
-            .sink { [unowned self] in viewModel.filter($0) }
-            .store(in: &cancellables)
-
-        viewModel.info
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toGroupChat(with: $0, from: self) }
-            .store(in: &cancellables)
-
-        createButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                coordinator.toGroupDrawer(
-                    with: count + 1,
-                    from: self, { (name, welcome) in
-                        viewModel.create(name: name, welcome: welcome, members: selectedElements)
-                    }
-                )
-            }.store(in: &cancellables)
-    }
-}
-
-extension CreateGroupController: UITableViewDelegate {
-    public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-        if let contact = tableDataSource.itemIdentifier(for: indexPath) {
-            viewModel.didSelect(contact: contact)
-        }
-    }
-
-    public func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
-        if let contact = tableDataSource.itemIdentifier(for: indexPath) {
-            viewModel.didSelect(contact: contact)
-        }
-    }
-}
diff --git a/Sources/ContactListFeature/Coordinator/ContactListCoordinator.swift b/Sources/ContactListFeature/Coordinator/ContactListCoordinator.swift
deleted file mode 100644
index eb967cf04231cb85c203714bb66dd9546bac3209..0000000000000000000000000000000000000000
--- a/Sources/ContactListFeature/Coordinator/ContactListCoordinator.swift
+++ /dev/null
@@ -1,129 +0,0 @@
-import UIKit
-import Shared
-import Models
-import XXModels
-import MenuFeature
-import ChatFeature
-import Presentation
-import ContactFeature
-import ScrollViewController
-
-public protocol ContactListCoordinating {
-    func toScan(from: UIViewController)
-    func toSearch(from: UIViewController)
-    func toRequests(from: UIViewController)
-    func toNewGroup(from: UIViewController)
-    func toSideMenu(from: UIViewController)
-    func toContact(_: Contact, from: UIViewController)
-    func toSingleChat(with: Contact, from: UIViewController)
-    func toGroupChat(with: GroupInfo, from: UIViewController)
-    func toGroupDrawer(with: Int, from: UIViewController, _: @escaping (String, String?) -> Void)
-}
-
-public struct ContactListCoordinator: ContactListCoordinating {
-    var pushPresenter: Presenting = PushPresenter()
-    var sidePresenter: Presenting = SideMenuPresenter()
-    var bottomPresenter: Presenting = BottomPresenter()
-    var fullscreenPresenter: Presenting = FullscreenPresenter()
-    var replacePresenter: Presenting = ReplacePresenter(mode: .replaceLast)
-
-    var scanFactory: () -> UIViewController
-    var searchFactory: (String?) -> UIViewController
-    var newGroupFactory: () -> UIViewController
-    var requestsFactory: () -> UIViewController
-    var contactFactory: (Contact) -> UIViewController
-    var singleChatFactory: (Contact) -> UIViewController
-    var groupChatFactory: (GroupInfo) -> UIViewController
-    var sideMenuFactory: (MenuItem, UIViewController) -> UIViewController
-    var groupDrawerFactory: (Int, @escaping (String, String?) -> Void) -> UIViewController
-
-    public init(
-        scanFactory: @escaping () -> UIViewController,
-        searchFactory: @escaping (String?) -> UIViewController,
-        newGroupFactory: @escaping () -> UIViewController,
-        requestsFactory: @escaping () -> UIViewController,
-        contactFactory: @escaping (Contact) -> UIViewController,
-        singleChatFactory: @escaping (Contact) -> UIViewController,
-        groupChatFactory: @escaping (GroupInfo) -> UIViewController,
-        sideMenuFactory: @escaping (MenuItem, UIViewController) -> UIViewController,
-        groupDrawerFactory: @escaping (Int, @escaping (String, String?) -> Void) -> UIViewController
-    ) {
-        self.scanFactory = scanFactory
-        self.searchFactory = searchFactory
-        self.contactFactory = contactFactory
-        self.newGroupFactory = newGroupFactory
-        self.requestsFactory = requestsFactory
-        self.sideMenuFactory = sideMenuFactory
-        self.groupChatFactory = groupChatFactory
-        self.singleChatFactory = singleChatFactory
-        self.groupDrawerFactory = groupDrawerFactory
-    }
-}
-
-public extension ContactListCoordinator {
-    func toGroupDrawer(
-        with count: Int,
-        from parent: UIViewController,
-        _ completion: @escaping (String, String?) -> Void
-    ) {
-        let screen = ScrollViewController.embedding(groupDrawerFactory(count, completion))
-        fullscreenPresenter.present(screen, from: parent)
-    }
-
-    func toSingleChat(
-        with contact: Contact,
-        from parent: UIViewController
-    ) {
-        let screen = singleChatFactory(contact)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toScan(from parent: UIViewController) {
-        let screen = scanFactory()
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toSearch(from parent: UIViewController) {
-        let screen = searchFactory(nil)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toRequests(from parent: UIViewController) {
-        let screen = requestsFactory()
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toNewGroup(from parent: UIViewController) {
-        let screen = newGroupFactory()
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toContact(_ contact: Contact, from parent: UIViewController) {
-        let screen = contactFactory(contact)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toGroupChat(with info: GroupInfo, from parent: UIViewController) {
-        let screen = groupChatFactory(info)
-        replacePresenter.present(screen, from: parent)
-    }
-
-    func toSideMenu(from parent: UIViewController) {
-        let screen = sideMenuFactory(.contacts, parent)
-        sidePresenter.present(screen, from: parent)
-    }
-}
-
-extension ScrollViewController {
-    static func embedding(_ viewController: UIViewController) -> ScrollViewController {
-        let scrollViewController = ScrollViewController()
-        scrollViewController.addChild(viewController)
-        scrollViewController.contentView = viewController.view
-        scrollViewController.wrapperView.handlesTouchesOutsideContent = false
-        scrollViewController.wrapperView.alignContentToBottom = true
-        scrollViewController.scrollView.bounces = false
-
-        viewController.didMove(toParent: scrollViewController)
-        return scrollViewController
-    }
-}
diff --git a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift b/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
deleted file mode 100644
index 830172f3b9bd298be2c8c8200e2c96f89d264325..0000000000000000000000000000000000000000
--- a/Sources/ContactListFeature/ViewModels/ContactListViewModel.swift
+++ /dev/null
@@ -1,52 +0,0 @@
-import Models
-import Combine
-import XXModels
-import Defaults
-import Integration
-import ReportingFeature
-import DependencyInjection
-
-final class ContactListViewModel {
-    @Dependency private var session: SessionType
-    @Dependency private var reportingStatus: ReportingStatus
-
-    var contacts: AnyPublisher<[Contact], Never> {
-        let query = Contact.Query(
-            authStatus: [.friend],
-            isBlocked: reportingStatus.isEnabled() ? false : nil,
-            isBanned: reportingStatus.isEnabled() ? false: nil
-        )
-
-        return session.dbManager.fetchContactsPublisher(query)
-            .assertNoFailure()
-            .map { $0.filter { $0.id != self.session.myId }}
-            .eraseToAnyPublisher()
-    }
-
-    var requestCount: AnyPublisher<Int, Never> {
-        let groupQuery = Group.Query(
-            authStatus: [.pending],
-            isLeaderBlocked: reportingStatus.isEnabled() ? false : nil,
-            isLeaderBanned: reportingStatus.isEnabled() ? false : nil
-        )
-
-        let contactsQuery = Contact.Query(
-            authStatus: [
-                .verified,
-                .confirming,
-                .confirmationFailed,
-                .verificationFailed,
-                .verificationInProgress
-            ],
-            isBlocked: reportingStatus.isEnabled() ? false : nil,
-            isBanned: reportingStatus.isEnabled() ? false : nil
-        )
-
-        return Publishers.CombineLatest(
-            session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(),
-            session.dbManager.fetchGroupsPublisher(groupQuery).assertNoFailure()
-        )
-        .map { $0.0.count + $0.1.count }
-        .eraseToAnyPublisher()
-    }
-}
diff --git a/Sources/ContactListFeature/ViewModels/CreateDrawerViewModel.swift b/Sources/ContactListFeature/ViewModels/CreateDrawerViewModel.swift
deleted file mode 100644
index 7369fe9d06625d5e59aa7666d184ea0ae4fc90dc..0000000000000000000000000000000000000000
--- a/Sources/ContactListFeature/ViewModels/CreateDrawerViewModel.swift
+++ /dev/null
@@ -1,53 +0,0 @@
-import Shared
-import Combine
-import InputField
-
-struct CreateDrawerViewState {
-    var welcome: String?
-    var groupName: String = ""
-    var status: InputField.ValidationStatus = .unknown(nil)
-}
-
-final class CreateDrawerViewModel {
-    var statePublisher: AnyPublisher<CreateDrawerViewState, Never> {
-        stateSubject.eraseToAnyPublisher()
-    }
-
-    var donePublisher: AnyPublisher<(String, String?), Never> {
-        doneSubject.eraseToAnyPublisher()
-    }
-
-    private let doneSubject = PassthroughSubject<(String, String?), Never>()
-    private let stateSubject = CurrentValueSubject<CreateDrawerViewState, Never>(.init())
-
-    func didInput(_ string: String) {
-        stateSubject.value.groupName = string
-        validate()
-    }
-
-    func didOtherInput(_ string: String) {
-        stateSubject.value.welcome = string
-    }
-
-    func didTapCreate() {
-        let name = stateSubject.value.groupName.trimmingCharacters(in: .whitespacesAndNewlines)
-        let welcome = stateSubject.value.welcome
-        doneSubject.send((name, welcome))
-    }
-
-    private func validate() {
-        let value = stateSubject.value.groupName.trimmingCharacters(in: .whitespacesAndNewlines)
-
-        guard value.count >= 4 else {
-            stateSubject.value.status = .invalid(Localized.CreateGroup.Drawer.minimum)
-            return
-        }
-
-        guard value.count < 32 else {
-            stateSubject.value.status = .invalid(Localized.CreateGroup.Drawer.maximum)
-            return
-        }
-
-        stateSubject.value.status = .valid(nil)
-    }
-}
diff --git a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift b/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
deleted file mode 100644
index 555407535ca52ab7ed00c7d1060d6f98a967030b..0000000000000000000000000000000000000000
--- a/Sources/ContactListFeature/ViewModels/CreateGroupViewModel.swift
+++ /dev/null
@@ -1,102 +0,0 @@
-import HUD
-import UIKit
-import Models
-import Combine
-import XXModels
-import Defaults
-import Integration
-import ReportingFeature
-import DependencyInjection
-
-final class CreateGroupViewModel {
-    @KeyObject(.username, defaultValue: "") var username: String
-
-    // MARK: Injected
-
-    @Dependency private var session: SessionType
-    @Dependency private var reportingStatus: ReportingStatus
-
-    // MARK: Properties
-
-    var selected: AnyPublisher<[Contact], Never> {
-        selectedContactsRelay.eraseToAnyPublisher()
-    }
-
-    var contacts: AnyPublisher<[Contact], Never> {
-        contactsRelay.eraseToAnyPublisher()
-    }
-
-    var hud: AnyPublisher<HUDStatus, Never> {
-        hudRelay.eraseToAnyPublisher()
-    }
-
-    var info: AnyPublisher<GroupInfo, Never> {
-        infoRelay.eraseToAnyPublisher()
-    }
-
-    private var allContacts = [Contact]()
-    private var cancellables = Set<AnyCancellable>()
-    private let infoRelay = PassthroughSubject<GroupInfo, Never>()
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-    private let contactsRelay = CurrentValueSubject<[Contact], Never>([])
-    private let selectedContactsRelay = CurrentValueSubject<[Contact], Never>([])
-
-    // MARK: Lifecycle
-
-    init() {
-        let query = Contact.Query(
-            authStatus: [.friend],
-            isBlocked: reportingStatus.isEnabled() ? false : nil,
-            isBanned: reportingStatus.isEnabled() ? false : nil
-        )
-
-        session.dbManager.fetchContactsPublisher(query)
-            .assertNoFailure()
-            .map { $0.filter { $0.id != self.session.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: Contact) {
-        if selectedContactsRelay.value.contains(contact) {
-            selectedContactsRelay.value.removeAll { $0.username == contact.username }
-        } else {
-            selectedContactsRelay.value.append(contact)
-        }
-    }
-
-    func filter(_ text: String) {
-        guard text.isEmpty == false else {
-            contactsRelay.send(allContacts)
-            return
-        }
-
-        contactsRelay.send(
-            allContacts.filter {
-                ($0.username ?? "").contains(text.lowercased())
-            }
-        )
-    }
-
-    func create(name: String, welcome: String?, members: [Contact]) {
-        hudRelay.send(.on)
-
-        session.createGroup(name: name, welcome: welcome, members: members) { [weak self] in
-            guard let self = self else { return }
-
-            self.hudRelay.send(.none)
-
-            switch $0 {
-            case .success(let info):
-                self.infoRelay.send(info)
-            case .failure(let error):
-                self.hudRelay.send(.error(.init(with: error)))
-            }
-        }
-    }
-}
diff --git a/Sources/ContactListFeature/Views/ContactListItemButton.swift b/Sources/ContactListFeature/Views/ContactListItemButton.swift
deleted file mode 100644
index c498f27ab9429fade4721e95b5fca6f3a73bdca7..0000000000000000000000000000000000000000
--- a/Sources/ContactListFeature/Views/ContactListItemButton.swift
+++ /dev/null
@@ -1,60 +0,0 @@
-import UIKit
-import Shared
-
-final class ItemButton: UIControl {
-    let titleLabel = UILabel()
-    let iconImageView = UIImageView()
-    let separatorView = UIView()
-    let stackView = UIStackView()
-    let notificationLabel = UILabel()
-
-    init() {
-        super.init(frame: .zero)
-
-        titleLabel.textColor = Asset.brandPrimary.color
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
-        separatorView.backgroundColor = Asset.neutralLine.color
-
-        notificationLabel.isHidden = true
-        notificationLabel.layer.cornerRadius = 5
-        notificationLabel.layer.masksToBounds = true
-        notificationLabel.textColor = Asset.neutralWhite.color
-        notificationLabel.backgroundColor = Asset.brandPrimary.color
-        notificationLabel.font = Fonts.Mulish.bold.font(size: 12.0)
-
-        stackView.spacing = 16
-        stackView.addArrangedSubview(iconImageView)
-        stackView.addArrangedSubview(titleLabel)
-        stackView.addArrangedSubview(notificationLabel)
-        stackView.setCustomSpacing(6, after: titleLabel)
-
-        stackView.isUserInteractionEnabled = false
-        addSubview(stackView)
-        addSubview(separatorView)
-
-        stackView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(12)
-            make.left.equalToSuperview().offset(24)
-            make.bottom.equalTo(separatorView.snp.top).offset(-12)
-        }
-
-        separatorView.snp.makeConstraints { make in
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-            make.bottom.equalToSuperview()
-            make.height.equalTo(1)
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    func setup(title: String, image: UIImage) {
-        titleLabel.text = title
-        iconImageView.image = image
-    }
-
-    func updateNotification(_ count: Int) {
-        notificationLabel.isHidden = count < 1
-        notificationLabel.text = "  \(count)  "
-    }
-}
diff --git a/Sources/ContactListFeature/Views/ContactListView.swift b/Sources/ContactListFeature/Views/ContactListView.swift
deleted file mode 100644
index e7a52a717346d43bb2551eac86839d796a9c78c1..0000000000000000000000000000000000000000
--- a/Sources/ContactListFeature/Views/ContactListView.swift
+++ /dev/null
@@ -1,72 +0,0 @@
-import UIKit
-import Shared
-
-final class ContactListView: UIView {
-    let newGroupButton = ItemButton()
-    let requestsButton = ItemButton()
-    let topStackView = UIStackView()
-    let stackView = UIStackView()
-    let emptyTitleLabel = UILabel()
-    let searchButton = CapsuleButton()
-
-    init() {
-        super.init(frame: .zero)
-        setup()
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    private func setup() {
-        backgroundColor = Asset.neutralWhite.color
-
-        requestsButton.separatorView.isHidden = true
-        requestsButton.setup(title: "Requests", image: Asset.contactListRequests.image)
-        newGroupButton.setup(title: Localized.ContactList.newGroup, image: Asset.contactListNewGroup.image)
-
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.lineHeightMultiple = 1.2
-        paragraph.alignment = .center
-
-        emptyTitleLabel.attributedText = NSAttributedString(
-            string: Localized.ContactList.Empty.title,
-            attributes: [
-                .paragraphStyle: paragraph,
-                .foregroundColor: Asset.neutralActive.color,
-                .font: Fonts.Mulish.bold.font(size: 24.0) as UIFont
-            ]
-        )
-        emptyTitleLabel.numberOfLines = 0
-
-        searchButton.setStyle(.brandColored)
-        searchButton.setTitle(Localized.ContactList.Empty.action, for: .normal)
-
-        stackView.spacing = 24
-        stackView.axis = .vertical
-        stackView.alignment = .center
-        stackView.addArrangedSubview(emptyTitleLabel)
-        stackView.addArrangedSubview(searchButton)
-
-        topStackView.axis = .vertical
-        topStackView.addArrangedSubview(newGroupButton)
-        topStackView.addArrangedSubview(requestsButton)
-
-        addSubview(topStackView)
-        addSubview(stackView)
-
-        setupConstraints()
-    }
-
-    private func setupConstraints() {
-        topStackView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(20)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-        }
-
-        stackView.snp.makeConstraints { make in
-            make.centerY.equalToSuperview()
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-        }
-    }
-}
diff --git a/Sources/ContactListFeature/Views/CreateDrawerView.swift b/Sources/ContactListFeature/Views/CreateDrawerView.swift
deleted file mode 100644
index 618ce7c94b4ed11109294b82bda47c8e17d98820..0000000000000000000000000000000000000000
--- a/Sources/ContactListFeature/Views/CreateDrawerView.swift
+++ /dev/null
@@ -1,106 +0,0 @@
-import UIKit
-import Shared
-import InputField
-
-final class CreateDrawerView: UIView {
-    let titleLabel = UILabel()
-    let subtitleView = TextWithInfoView()
-    let inputField = InputField()
-    let otherInputField = InputField()
-    let stackView = UIStackView()
-    let createButton = CapsuleButton()
-    let cancelButton = CapsuleButton()
-
-    var didTapInfo: (() -> Void)?
-
-    init() {
-        super.init(frame: .zero)
-
-        layer.cornerRadius = 40
-        backgroundColor = Asset.neutralWhite.color
-        layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
-
-        titleLabel.textAlignment = .left
-        titleLabel.text = Localized.CreateGroup.Drawer.title
-        titleLabel.font = Fonts.Mulish.bold.font(size: 26.0)
-        titleLabel.textColor = Asset.neutralActive.color
-
-        inputField.setup(
-            style: .regular,
-            title: Localized.CreateGroup.Drawer.input,
-            placeholder: Localized.CreateGroup.Drawer.placeholder,
-            leftView: .image(Asset.personGray.image),
-            accessibility: Localized.Accessibility.CreateGroup.Drawer.input,
-            subtitleColor: Asset.neutralDisabled.color
-        )
-
-        otherInputField.setup(
-            style: .regular,
-            title: Localized.CreateGroup.Drawer.otherInput,
-            placeholder: Localized.CreateGroup.Drawer.otherPlaceholder,
-            leftView: .image(Asset.balloon.image),
-            accessibility: Localized.Accessibility.CreateGroup.Drawer.otherInput,
-            subtitleColor: Asset.neutralDisabled.color
-        )
-
-        createButton.set(
-            style: .brandColored,
-            title: Localized.CreateGroup.Drawer.action,
-            accessibility: Localized.Accessibility.CreateGroup.Drawer.create
-        )
-
-        cancelButton.set(
-            style: .seeThrough,
-            title: Localized.CreateGroup.Drawer.cancel
-        )
-
-        stackView.spacing = 20
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(titleLabel)
-        stackView.addArrangedSubview(subtitleView)
-        stackView.addArrangedSubview(inputField)
-        stackView.addArrangedSubview(otherInputField)
-        stackView.addArrangedSubview(createButton)
-        stackView.addArrangedSubview(cancelButton)
-
-        addSubview(stackView)
-
-        stackView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(60)
-            $0.left.equalToSuperview().offset(50)
-            $0.right.equalToSuperview().offset(-50)
-            $0.bottom.equalToSuperview().offset(-70)
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    func set(count: Int, didTap: @escaping () -> Void) {
-        self.didTapInfo = didTap
-
-        let paragraphStyle = NSMutableParagraphStyle()
-        paragraphStyle.alignment = .left
-        paragraphStyle.lineHeightMultiple = 1.1
-
-        subtitleView.setup(
-            text: Localized.CreateGroup.Drawer.subtitle("\(count)"),
-            attributes: [
-                .paragraphStyle: paragraphStyle,
-                .foregroundColor: Asset.neutralBody.color,
-                .font: Fonts.Mulish.semiBold.font(size: 14.0) as Any
-            ],
-            didTapInfo: { [weak self] in self?.didTapInfo?() }
-        )
-    }
-
-    func update(status: InputField.ValidationStatus) {
-        inputField.update(status: status)
-
-        switch status {
-        case .valid:
-            createButton.isEnabled = true
-        case .invalid, .unknown:
-            createButton.isEnabled = false
-        }
-    }
-}
diff --git a/Sources/ContactListFeature/Views/CreateGroupCollectionCell.swift b/Sources/ContactListFeature/Views/CreateGroupCollectionCell.swift
deleted file mode 100644
index 1717365300ab0ae52380cc47e6073f8118684957..0000000000000000000000000000000000000000
--- a/Sources/ContactListFeature/Views/CreateGroupCollectionCell.swift
+++ /dev/null
@@ -1,80 +0,0 @@
-import UIKit
-import Shared
-import Combine
-
-final class CreateGroupCollectionCell: UICollectionViewCell {
-    let titleLabel = UILabel()
-    let removeButton = UIButton()
-    let upperView = UIView()
-    let avatarView = AvatarView()
-
-    var didTapRemove: (() -> Void)?
-    var cancellables = Set<AnyCancellable>()
-
-    override init(frame: CGRect) {
-        super.init(frame: frame)
-
-        titleLabel.numberOfLines = 2
-        titleLabel.lineBreakMode = .byWordWrapping
-        titleLabel.textAlignment = .center
-        titleLabel.textColor = Asset.neutralDark.color
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
-
-        removeButton.layer.cornerRadius = 9
-        removeButton.backgroundColor = Asset.accentDanger.color
-        removeButton.setImage(Asset.contactListAvatarRemove.image, for: .normal)
-
-        upperView.addSubview(avatarView)
-        contentView.addSubview(titleLabel)
-        contentView.addSubview(upperView)
-        contentView.addSubview(removeButton)
-
-        upperView.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-        }
-
-        avatarView.snp.makeConstraints {
-            $0.width.equalTo(48)
-            $0.height.equalTo(48)
-            $0.top.equalToSuperview().offset(4)
-            $0.left.equalToSuperview().offset(4)
-            $0.right.equalToSuperview().offset(-4)
-            $0.bottom.equalToSuperview().offset(-4)
-        }
-
-        removeButton.snp.makeConstraints {
-            $0.centerY.equalTo(avatarView.snp.top).offset(5)
-            $0.centerX.equalTo(avatarView.snp.right).offset(-5)
-            $0.width.equalTo(18)
-            $0.height.equalTo(18)
-        }
-
-        titleLabel.snp.makeConstraints {
-            $0.top.equalTo(upperView.snp.bottom)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        titleLabel.text = nil
-        avatarView.prepareForReuse()
-        cancellables.removeAll()
-    }
-
-    func setup(title: String, image: Data?) {
-        titleLabel.text = title
-        avatarView.setupProfile(title: title, image: image, size: .large)
-        cancellables.removeAll()
-
-        removeButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in didTapRemove?() }
-            .store(in: &cancellables)
-    }
-}
diff --git a/Sources/ContactListFeature/Views/CreateGroupView.swift b/Sources/ContactListFeature/Views/CreateGroupView.swift
deleted file mode 100644
index 0f7bd57962bed1fd6930bb6e1cd62fe2c618b01b..0000000000000000000000000000000000000000
--- a/Sources/ContactListFeature/Views/CreateGroupView.swift
+++ /dev/null
@@ -1,62 +0,0 @@
-import UIKit
-import Shared
-import SnapKit
-
-final class CreateGroupView: UIView {
-    let stackView = UIStackView()
-    let tableView = UITableView()
-    let searchComponent = SearchComponent()
-    lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
-
-    let layout: UICollectionViewFlowLayout = {
-        let layout = UICollectionViewFlowLayout()
-        layout.minimumInteritemSpacing = 45
-        layout.itemSize = CGSize(width: 56, height: 100)
-        layout.scrollDirection = .horizontal
-        return layout
-    }()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        tableView.separatorStyle = .none
-        tableView.tintColor = Asset.brandPrimary.color
-        tableView.backgroundColor = Asset.neutralWhite.color
-        tableView.allowsMultipleSelectionDuringEditing = true
-        tableView.setEditing(true, animated: true)
-
-        searchComponent.set(
-            placeholder: "Search connections",
-            imageAtRight: UIImage.color(.clear)
-        )
-
-        collectionView.backgroundColor = Asset.neutralWhite.color
-        collectionView.contentInset = UIEdgeInsets(top: 0, left: 30, bottom: 0, right: 30)
-
-        stackView.spacing = 31
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(collectionView)
-        stackView.addArrangedSubview(tableView)
-
-        addSubview(stackView)
-        addSubview(searchComponent)
-
-        searchComponent.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(20)
-            make.left.equalToSuperview().offset(20)
-            make.right.equalToSuperview().offset(-20)
-        }
-
-        stackView.snp.makeConstraints { make in
-            make.top.equalTo(searchComponent.snp.bottom).offset(20)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.equalToSuperview()
-        }
-
-        collectionView.snp.makeConstraints { $0.height.equalTo(100) }
-    }
-
-    required init?(coder: NSCoder) { nil }
-}
diff --git a/Sources/Countries/Country.swift b/Sources/Countries/Country.swift
deleted file mode 100644
index acb0bae1d468c7cf3abba10f20a59e7c8d803949..0000000000000000000000000000000000000000
--- a/Sources/Countries/Country.swift
+++ /dev/null
@@ -1,45 +0,0 @@
-import os
-import Foundation
-
-public struct Country {
-    public var name: String
-    public var code: String
-    public var flag: String
-    public var regex: String
-    public var prefix: String
-    public var example: String
-    public var prefixWithFlag: String { "\(flag) \(prefix)" }
-
-    public static func fromMyPhone() -> Self {
-        let all = all()
-
-        guard let country = all.filter({ $0.code == Locale.current.regionCode }).first else {
-            return all.filter { $0.code == "US" }.first!
-        }
-
-        return country
-    }
-
-    public static func all() -> [Self] {
-        guard let url = Bundle.module.url(forResource: "country_codes", withExtension: "json"),
-              let data = try? Data(contentsOf: url),
-              let countries = try? JSONDecoder().decode([Country].self, from: data) else {
-                  fatalError("Can't handle country codes json")
-              }
-
-        return countries
-    }
-
-    public static func findFrom(_ number: String) -> Self {
-        all().first { country in
-            let start = number.index(number.startIndex, offsetBy: number.count - 2)
-            let end = number.index(start, offsetBy: number.count - (number.count - 2))
-
-            return country.code == String(number[start ..< end])
-        }!
-    }
-}
-
-extension Country: Hashable {}
-extension Country: Equatable {}
-extension Country: Decodable {}
diff --git a/Sources/Countries/CountryListCell.swift b/Sources/Countries/CountryListCell.swift
deleted file mode 100644
index b3b650e5ae8daae80cc8b026fc3d242bac1b9920..0000000000000000000000000000000000000000
--- a/Sources/Countries/CountryListCell.swift
+++ /dev/null
@@ -1,62 +0,0 @@
-import UIKit
-import Shared
-
-final class CountryListCell: UITableViewCell {
-    let nameLabel = UILabel()
-    let flagLabel = UILabel()
-    let prefixLabel = UILabel()
-    let separatorView = UIView()
-
-    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        super.init(style: style, reuseIdentifier: reuseIdentifier)
-
-        selectionStyle = .none
-        backgroundColor = Asset.neutralWhite.color
-
-        nameLabel.textColor = Asset.neutralDark.color
-        prefixLabel.textColor = Asset.neutralWeak.color
-        nameLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
-        prefixLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
-
-        separatorView.backgroundColor = Asset.brandBackground.color
-        prefixLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
-
-        contentView.addSubview(nameLabel)
-        contentView.addSubview(flagLabel)
-        contentView.addSubview(prefixLabel)
-        contentView.addSubview(separatorView)
-
-        flagLabel.snp.makeConstraints {
-            $0.left.top.equalToSuperview().inset(18)
-            $0.bottom.equalToSuperview().offset(-16)
-        }
-
-        nameLabel.snp.makeConstraints {
-            $0.left.equalToSuperview().offset(55)
-            $0.centerY.equalToSuperview()
-            $0.right.lessThanOrEqualTo(prefixLabel.snp.left).offset(-10)
-        }
-
-        prefixLabel.snp.makeConstraints {
-            $0.right.equalToSuperview().offset(-18)
-            $0.centerY.equalToSuperview()
-        }
-
-        separatorView.snp.makeConstraints {
-            $0.bottom.equalToSuperview()
-            $0.left.equalToSuperview().offset(20)
-            $0.right.equalToSuperview().offset(-20)
-            $0.height.equalTo(1)
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-
-        nameLabel.text = nil
-        flagLabel.text = nil
-        prefixLabel.text = nil
-    }
-}
diff --git a/Sources/Countries/CountryListController.swift b/Sources/Countries/CountryListController.swift
deleted file mode 100644
index a11c0e5724696bc172f58cfdc7b4333288639b7f..0000000000000000000000000000000000000000
--- a/Sources/Countries/CountryListController.swift
+++ /dev/null
@@ -1,93 +0,0 @@
-import os
-import Theme
-import UIKit
-import Shared
-import Combine
-import DependencyInjection
-
-public final class CountryListController: UIViewController {
-    @Dependency private var statusBarController: StatusBarStyleControlling
-    
-    lazy private var screenView = CountryListView()
-
-    private var didChoose: ((Country) -> Void)!
-    private let viewModel = CountryListViewModel()
-    private var cancellables = Set<AnyCancellable>()
-    private var dataSource: UITableViewDiffableDataSource<SectionId, Country>!
-
-    public init(_ didChoose: @escaping (Country) -> Void) {
-        self.didChoose = didChoose
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        statusBarController.style.send(.darkContent)
-
-        navigationController?.navigationBar.customize(
-            backgroundColor: Asset.neutralWhite.color,
-            shadowColor: Asset.neutralDisabled.color
-        )
-    }
-
-    public override func loadView() {
-        view = screenView
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        screenView.tableView.register(CountryListCell.self)
-        setupNavigationBar()
-        setupBindings()
-
-        viewModel.fetchCountryList()
-    }
-    
-    private func setupNavigationBar() {
-        let title = UILabel()
-        title.text = Localized.Countries.title
-        title.textColor = Asset.neutralActive.color
-        title.font = Fonts.Mulish.semiBold.font(size: 18.0)
-
-        navigationItem.leftBarButtonItem = UIBarButtonItem(customView: title)
-        navigationItem.leftItemsSupplementBackButton = true
-    }
-
-    private func setupBindings() {
-        viewModel.countries
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in dataSource.apply($0, animatingDifferences: false) }
-            .store(in: &cancellables)
-
-        dataSource = UITableViewDiffableDataSource<SectionId, Country>(
-            tableView: screenView.tableView
-        ) { tableView, indexPath, country in
-            let cell: CountryListCell = tableView.dequeueReusableCell(forIndexPath: indexPath)
-            cell.flagLabel.text = country.flag
-            cell.nameLabel.text = country.name
-            cell.prefixLabel.text = country.prefix
-            return cell
-        }
-
-        screenView.searchComponent
-            .textPublisher
-            .removeDuplicates()
-            .sink { [unowned self] in viewModel.didSearchFor($0) }
-            .store(in: &cancellables)
-
-        screenView.tableView.delegate = self
-        screenView.tableView.dataSource = dataSource
-    }
-
-    public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-        if let country = dataSource.itemIdentifier(for: indexPath) {
-            didChoose(country)
-            navigationController?.popViewController(animated: true)
-        }
-    }
-}
-
-extension CountryListController: UITableViewDelegate {}
diff --git a/Sources/Countries/CountryListView.swift b/Sources/Countries/CountryListView.swift
deleted file mode 100644
index cf743823ace4b8b1d45fefeb1dedc0360374799a..0000000000000000000000000000000000000000
--- a/Sources/Countries/CountryListView.swift
+++ /dev/null
@@ -1,42 +0,0 @@
-import UIKit
-import Shared
-
-final class CountryListView: UIView {
-    let tableView = UITableView()
-    let searchComponent = SearchComponent()
-
-    init() {
-        super.init(frame: .zero)
-        setup()
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    private func setup() {
-        tableView.separatorStyle = .none
-        tableView.backgroundColor = .clear
-        backgroundColor = Asset.neutralWhite.color
-
-        searchComponent.set(
-            imageAtRight: UIImage.color(.clear),
-            inputAccessibility: Localized.Accessibility.Countries.Search.field,
-            rightAccessibility: Localized.Accessibility.Countries.Search.right
-        )
-
-        addSubview(tableView)
-        addSubview(searchComponent)
-
-        searchComponent.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(20)
-            make.left.equalToSuperview().offset(20)
-            make.right.equalToSuperview().offset(-20)
-        }
-
-        tableView.snp.makeConstraints { make in
-            make.top.equalTo(searchComponent.snp.bottom).offset(20)
-            make.left.equalToSuperview()
-            make.bottom.equalToSuperview()
-            make.right.equalToSuperview()
-        }
-    }
-}
diff --git a/Sources/Countries/CountryListViewModel.swift b/Sources/Countries/CountryListViewModel.swift
deleted file mode 100644
index e4157a16e1938cc506f0549113d393eb6f5b6202..0000000000000000000000000000000000000000
--- a/Sources/Countries/CountryListViewModel.swift
+++ /dev/null
@@ -1,49 +0,0 @@
-import os
-import UIKit
-import Shared
-import Combine
-import Foundation
-
-private let logger = Logger(subsystem: "logs_xxmessenger", category: "Countries.CountryListViewModel.swift")
-
-final class CountryListViewModel {
-    var countries: AnyPublisher<NSDiffableDataSourceSnapshot<SectionId, Country>, Never> {
-        countriesRelay.eraseToAnyPublisher()
-    }
-
-    private var cancellables = Set<AnyCancellable>()
-    private let searchQueryRelay = CurrentValueSubject<String, Never>("")
-    private let countriesRelay = CurrentValueSubject<NSDiffableDataSourceSnapshot<SectionId, Country>, Never>(.init())
-
-    func fetchCountryList() {
-        logger.log("fetchCountryList()")
-
-        Publishers.CombineLatest(Just(Country.all()), searchQueryRelay)
-            .map { countryList, query -> NSDiffableDataSourceSnapshot<SectionId, Country> in
-                var snapshot = NSDiffableDataSourceSnapshot<SectionId, Country>()
-                let section = SectionId()
-                snapshot.appendSections([section])
-
-                guard !query.isEmpty else {
-                    logger.log("query.isEmpty, returning all countries")
-                    snapshot.appendItems(countryList, toSection: section)
-                    return snapshot
-                }
-
-                let filtered = countryList.filter {
-                    $0.name.lowercased().contains(query.lowercased()) ||
-                    $0.prefix.lowercased().contains(query.lowercased())
-                }
-
-                snapshot.appendItems(filtered, toSection: section)
-                return snapshot
-
-            }.sink { [weak countriesRelay] in countriesRelay?.send($0) }
-            .store(in: &cancellables)
-    }
-
-    func didSearchFor(_ string: String) {
-        logger.log("didSearchFor \(string, privacy: .public)()")
-        searchQueryRelay.send(string)
-    }
-}
diff --git a/Sources/CountryListFeature/CountryListCell.swift b/Sources/CountryListFeature/CountryListCell.swift
new file mode 100644
index 0000000000000000000000000000000000000000..61dc02fcab863e5a965f6c5e589e880d3617ba12
--- /dev/null
+++ b/Sources/CountryListFeature/CountryListCell.swift
@@ -0,0 +1,63 @@
+import UIKit
+import Shared
+import AppResources
+
+final class CountryListCell: UITableViewCell {
+  let nameLabel = UILabel()
+  let flagLabel = UILabel()
+  let prefixLabel = UILabel()
+  let separatorView = UIView()
+
+  override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+    super.init(style: style, reuseIdentifier: reuseIdentifier)
+
+    selectionStyle = .none
+    backgroundColor = Asset.neutralWhite.color
+
+    nameLabel.textColor = Asset.neutralDark.color
+    prefixLabel.textColor = Asset.neutralWeak.color
+    nameLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    prefixLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
+
+    separatorView.backgroundColor = Asset.brandBackground.color
+    prefixLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
+
+    contentView.addSubview(nameLabel)
+    contentView.addSubview(flagLabel)
+    contentView.addSubview(prefixLabel)
+    contentView.addSubview(separatorView)
+
+    flagLabel.snp.makeConstraints {
+      $0.left.top.equalToSuperview().inset(18)
+      $0.bottom.equalToSuperview().offset(-16)
+    }
+
+    nameLabel.snp.makeConstraints {
+      $0.left.equalToSuperview().offset(55)
+      $0.centerY.equalToSuperview()
+      $0.right.lessThanOrEqualTo(prefixLabel.snp.left).offset(-10)
+    }
+
+    prefixLabel.snp.makeConstraints {
+      $0.right.equalToSuperview().offset(-18)
+      $0.centerY.equalToSuperview()
+    }
+
+    separatorView.snp.makeConstraints {
+      $0.bottom.equalToSuperview()
+      $0.left.equalToSuperview().offset(20)
+      $0.right.equalToSuperview().offset(-20)
+      $0.height.equalTo(1)
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  override func prepareForReuse() {
+    super.prepareForReuse()
+
+    nameLabel.text = nil
+    flagLabel.text = nil
+    prefixLabel.text = nil
+  }
+}
diff --git a/Sources/CountryListFeature/CountryListController.swift b/Sources/CountryListFeature/CountryListController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..dd6ad4e65985464e7ae3c323a30df35da6033ab9
--- /dev/null
+++ b/Sources/CountryListFeature/CountryListController.swift
@@ -0,0 +1,76 @@
+import UIKit
+import Shared
+import Combine
+import AppCore
+import AppResources
+import Dependencies
+
+public final class CountryListController: UIViewController, UITableViewDelegate {
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+
+  private lazy var screenView = CountryListView()
+
+  private let completion: (Country) -> Void
+  private let viewModel = CountryListViewModel()
+  private var cancellables = Set<AnyCancellable>()
+  private var dataSource: UITableViewDiffableDataSource<SectionId, Country>!
+
+  public init(_ completion: @escaping (Country) -> Void) {
+    self.completion = completion
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    statusBar.set(.darkContent)
+  }
+
+  public override func loadView() {
+    view = screenView
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    screenView
+      .tableView
+      .register(CountryListCell.self)
+
+    viewModel
+      .countries
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        dataSource.apply($0, animatingDifferences: false)
+      }.store(in: &cancellables)
+
+    dataSource = UITableViewDiffableDataSource<SectionId, Country>(
+      tableView: screenView.tableView
+    ) { tableView, indexPath, country in
+      let cell: CountryListCell = tableView.dequeueReusableCell(forIndexPath: indexPath)
+      cell.flagLabel.text = country.flag
+      cell.nameLabel.text = country.name
+      cell.prefixLabel.text = country.prefix
+      return cell
+    }
+
+    screenView
+      .searchComponent
+      .textPublisher
+      .removeDuplicates()
+      .sink { [unowned self] in
+        viewModel.didSearchFor($0)
+      }.store(in: &cancellables)
+
+    screenView.tableView.delegate = self
+    screenView.tableView.dataSource = dataSource
+    viewModel.fetchCountryList()
+  }
+
+  public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+    if let country = dataSource.itemIdentifier(for: indexPath) {
+      completion(country)
+      dismiss(animated: true)
+    }
+  }
+}
diff --git a/Sources/CountryListFeature/CountryListView.swift b/Sources/CountryListFeature/CountryListView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..9d5f65f297a66aa47c3db75cc97782845600a3f4
--- /dev/null
+++ b/Sources/CountryListFeature/CountryListView.swift
@@ -0,0 +1,39 @@
+import UIKit
+import Shared
+import AppResources
+
+final class CountryListView: UIView {
+  let tableView = UITableView()
+  let searchComponent = SearchComponent()
+
+  init() {
+    super.init(frame: .zero)
+
+    tableView.separatorStyle = .none
+    tableView.backgroundColor = .clear
+    backgroundColor = Asset.neutralWhite.color
+
+    searchComponent.set(
+      imageAtRight: UIImage.color(.clear),
+      inputAccessibility: Localized.Accessibility.Countries.Search.field,
+      rightAccessibility: Localized.Accessibility.Countries.Search.right
+    )
+
+    addSubview(tableView)
+    addSubview(searchComponent)
+
+    searchComponent.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(20)
+      $0.left.equalToSuperview().offset(20)
+      $0.right.equalToSuperview().offset(-20)
+    }
+    tableView.snp.makeConstraints {
+      $0.top.equalTo(searchComponent.snp.bottom).offset(20)
+      $0.left.equalToSuperview()
+      $0.bottom.equalToSuperview()
+      $0.right.equalToSuperview()
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+}
diff --git a/Sources/CountryListFeature/CountryListViewModel.swift b/Sources/CountryListFeature/CountryListViewModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..4144345c6118e05b0f2743abe8b4a92c68d53527
--- /dev/null
+++ b/Sources/CountryListFeature/CountryListViewModel.swift
@@ -0,0 +1,43 @@
+import UIKit
+import Shared
+import Combine
+
+final class CountryListViewModel {
+  var countries: AnyPublisher<NSDiffableDataSourceSnapshot<SectionId, Country>, Never> {
+    countriesRelay.eraseToAnyPublisher()
+  }
+
+  private var cancellables = Set<AnyCancellable>()
+  private let searchQueryRelay = CurrentValueSubject<String, Never>("")
+  private let countriesRelay = CurrentValueSubject<NSDiffableDataSourceSnapshot<SectionId, Country>, Never>(.init())
+
+  func fetchCountryList() {
+    Publishers
+      .CombineLatest(Just(Country.all()), searchQueryRelay)
+      .map { countryList, query -> NSDiffableDataSourceSnapshot<SectionId, Country> in
+        var snapshot = NSDiffableDataSourceSnapshot<SectionId, Country>()
+        let section = SectionId()
+        snapshot.appendSections([section])
+
+        guard !query.isEmpty else {
+          snapshot.appendItems(countryList, toSection: section)
+          return snapshot
+        }
+
+        let filtered = countryList.filter {
+          $0.name.lowercased().contains(query.lowercased()) ||
+          $0.prefix.lowercased().contains(query.lowercased())
+        }
+
+        snapshot.appendItems(filtered, toSection: section)
+        return snapshot
+
+      }.sink { [weak countriesRelay] in
+        countriesRelay?.send($0)
+      }.store(in: &cancellables)
+  }
+
+  func didSearchFor(_ string: String) {
+    searchQueryRelay.send(string)
+  }
+}
diff --git a/Sources/CrashReport/CrashReport.swift b/Sources/CrashReport/CrashReport.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8e6d2b7e4e793c70f16dfed720def386c783f1d0
--- /dev/null
+++ b/Sources/CrashReport/CrashReport.swift
@@ -0,0 +1,25 @@
+import Firebase
+import FirebaseCrashlytics
+import XCTestDynamicOverlay
+
+public struct CrashReport {
+  public var configure: () -> Void
+  public var sendError: (NSError) -> Void
+  public var setEnabled: (Bool) -> Void
+}
+
+extension CrashReport {
+  public static let live = CrashReport(
+    configure: FirebaseApp.configure,
+    sendError: Crashlytics.crashlytics().record(error:),
+    setEnabled: Crashlytics.crashlytics().setCrashlyticsCollectionEnabled
+  )
+}
+
+extension CrashReport {
+  public static let unimplemented = CrashReport(
+    configure: XCTUnimplemented("\(Self.self)"),
+    sendError: XCTUnimplemented("\(Self.self)"),
+    setEnabled: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/CrashReport/Dependency.swift b/Sources/CrashReport/Dependency.swift
new file mode 100644
index 0000000000000000000000000000000000000000..ee81581b77eac6a15c8ca9e4d5044a644a7b4c13
--- /dev/null
+++ b/Sources/CrashReport/Dependency.swift
@@ -0,0 +1,13 @@
+import Dependencies
+
+private enum CrashReportDependencyKey: DependencyKey {
+  static let liveValue: CrashReport = .live
+  static let testValue: CrashReport = .unimplemented
+}
+
+extension DependencyValues {
+  public var crashReport: CrashReport {
+    get { self[CrashReportDependencyKey.self] }
+    set { self[CrashReportDependencyKey.self] = newValue }
+  }
+}
diff --git a/Sources/CrashReporting/CrashReporter.swift b/Sources/CrashReporting/CrashReporter.swift
deleted file mode 100644
index f249d741c7baa42218d77487a26e717866ea2f9a..0000000000000000000000000000000000000000
--- a/Sources/CrashReporting/CrashReporter.swift
+++ /dev/null
@@ -1,25 +0,0 @@
-import Foundation
-
-public struct CrashReporter {
-    public var configure: () -> Void
-    public var sendError: (NSError) -> Void
-    public var setEnabled: (Bool) -> Void
-
-    public init(
-        configure: @escaping () -> Void,
-        sendError: @escaping (NSError) -> Void,
-        setEnabled: @escaping (Bool) -> Void
-    ) {
-        self.configure = configure
-        self.sendError = sendError
-        self.setEnabled = setEnabled
-    }
-}
-
-public extension CrashReporter {
-    static let noop = Self(
-        configure: {},
-        sendError: { _ in },
-        setEnabled: { _ in }
-    )
-}
diff --git a/Sources/CrashService/CrashService.swift b/Sources/CrashService/CrashService.swift
deleted file mode 100644
index 4815ed483b3177262b90089ecc2af62973208a5d..0000000000000000000000000000000000000000
--- a/Sources/CrashService/CrashService.swift
+++ /dev/null
@@ -1,11 +0,0 @@
-import Firebase
-import CrashReporting
-import FirebaseCrashlytics
-
-public extension CrashReporter {
-    static let live = Self(
-        configure: { FirebaseApp.configure() },
-        sendError: { Crashlytics.crashlytics().record(error: $0) },
-        setEnabled: { Crashlytics.crashlytics().setCrashlyticsCollectionEnabled($0) }
-    )
-}
diff --git a/Sources/CreateGroupFeature/CreateGroupController.swift b/Sources/CreateGroupFeature/CreateGroupController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..ad7303364f56d0dbd7b840f3803cb066f8be2578
--- /dev/null
+++ b/Sources/CreateGroupFeature/CreateGroupController.swift
@@ -0,0 +1,87 @@
+import UIKit
+import Combine
+import XXModels
+
+public final class CreateGroupController: UIViewController {
+  private lazy var screenView = CreateGroupView()
+
+  private let groupMembers: [Contact]
+  private let viewModel = CreateGroupViewModel()
+  private var cancellables = Set<AnyCancellable>()
+
+  public init(_ groupMembers: [Contact]) {
+    self.groupMembers = groupMembers
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func loadView() {
+    view = screenView
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    screenView.set(count: groupMembers.count, didTap: {})
+
+    viewModel
+      .statePublisher
+      .map(\.status)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.update(status: $0)
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.shouldDismiss)
+      .filter { $0 == true }
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] _ in
+        dismiss(animated: true)
+      }.store(in: &cancellables)
+
+    screenView
+      .cancelButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        dismiss(animated: true)
+      }.store(in: &cancellables)
+
+    screenView
+      .inputField
+      .textPublisher
+      .sink { [unowned self] in
+        viewModel.didInput($0)
+      }.store(in: &cancellables)
+
+    screenView
+      .otherInputField
+      .textPublisher
+      .sink { [unowned self] in
+        viewModel.didOtherInput($0)
+      }.store(in: &cancellables)
+
+    screenView
+      .inputField
+      .returnPublisher
+      .sink { [unowned self] in
+        screenView.inputField.endEditing(true)
+      }.store(in: &cancellables)
+
+    screenView
+      .otherInputField
+      .returnPublisher
+      .sink { [unowned self] in
+        screenView.otherInputField.endEditing(true)
+      }.store(in: &cancellables)
+
+    screenView
+      .createButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        viewModel.didTapCreate(groupMembers)
+      }.store(in: &cancellables)
+  }
+}
diff --git a/Sources/CreateGroupFeature/CreateGroupView.swift b/Sources/CreateGroupFeature/CreateGroupView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8d38493433607e66a2ac836e8ae8e07d473be134
--- /dev/null
+++ b/Sources/CreateGroupFeature/CreateGroupView.swift
@@ -0,0 +1,107 @@
+import UIKit
+import Shared
+import InputField
+import AppResources
+
+final class CreateGroupView: UIView {
+  let titleLabel = UILabel()
+  let subtitleView = TextWithInfoView()
+  let inputField = InputField()
+  let otherInputField = InputField()
+  let stackView = UIStackView()
+  let createButton = CapsuleButton()
+  let cancelButton = CapsuleButton()
+
+  var didTapInfo: (() -> Void)?
+
+  init() {
+    super.init(frame: .zero)
+
+    layer.cornerRadius = 40
+    backgroundColor = Asset.neutralWhite.color
+    layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
+
+    titleLabel.textAlignment = .left
+    titleLabel.text = Localized.CreateGroup.Drawer.title
+    titleLabel.font = Fonts.Mulish.bold.font(size: 26.0)
+    titleLabel.textColor = Asset.neutralActive.color
+
+    inputField.setup(
+      style: .regular,
+      title: Localized.CreateGroup.Drawer.input,
+      placeholder: Localized.CreateGroup.Drawer.placeholder,
+      leftView: .image(Asset.personGray.image),
+      accessibility: Localized.Accessibility.CreateGroup.Drawer.input,
+      subtitleColor: Asset.neutralDisabled.color
+    )
+
+    otherInputField.setup(
+      style: .regular,
+      title: Localized.CreateGroup.Drawer.otherInput,
+      placeholder: Localized.CreateGroup.Drawer.otherPlaceholder,
+      leftView: .image(Asset.balloon.image),
+      accessibility: Localized.Accessibility.CreateGroup.Drawer.otherInput,
+      subtitleColor: Asset.neutralDisabled.color
+    )
+
+    createButton.set(
+      style: .brandColored,
+      title: Localized.CreateGroup.Drawer.action,
+      accessibility: Localized.Accessibility.CreateGroup.Drawer.create
+    )
+
+    cancelButton.set(
+      style: .seeThrough,
+      title: Localized.CreateGroup.Drawer.cancel
+    )
+
+    stackView.spacing = 20
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(titleLabel)
+    stackView.addArrangedSubview(subtitleView)
+    stackView.addArrangedSubview(inputField)
+    stackView.addArrangedSubview(otherInputField)
+    stackView.addArrangedSubview(createButton)
+    stackView.addArrangedSubview(cancelButton)
+
+    addSubview(stackView)
+
+    stackView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(60)
+      $0.left.equalToSuperview().offset(50)
+      $0.right.equalToSuperview().offset(-50)
+      $0.bottom.equalToSuperview().offset(-70)
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  func set(count: Int, didTap: @escaping () -> Void) {
+    self.didTapInfo = didTap
+
+    let paragraphStyle = NSMutableParagraphStyle()
+    paragraphStyle.alignment = .left
+    paragraphStyle.lineHeightMultiple = 1.1
+
+    subtitleView.setup(
+      text: Localized.CreateGroup.Drawer.subtitle("\(count)"),
+      attributes: [
+        .paragraphStyle: paragraphStyle,
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.semiBold.font(size: 14.0) as Any
+      ],
+      didTapInfo: { [weak self] in self?.didTapInfo?() }
+    )
+  }
+
+  func update(status: InputField.ValidationStatus) {
+    inputField.update(status: status)
+
+    switch status {
+    case .valid:
+      createButton.isEnabled = true
+    case .invalid, .unknown:
+      createButton.isEnabled = false
+    }
+  }
+}
diff --git a/Sources/CreateGroupFeature/CreateGroupViewModel.swift b/Sources/CreateGroupFeature/CreateGroupViewModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..25ef6090123ff307f2f6555d56bade868c5f5ce8
--- /dev/null
+++ b/Sources/CreateGroupFeature/CreateGroupViewModel.swift
@@ -0,0 +1,99 @@
+import Shared
+import Combine
+import AppCore
+import XXModels
+import InputField
+import AppResources
+import Dependencies
+
+import Foundation // ?
+
+struct CreateGroupViewModel {
+  struct ViewState {
+    var welcome: String?
+    var groupName: String = ""
+    var status: InputField.ValidationStatus = .unknown(nil)
+    var shouldDismiss: Bool = false
+  }
+
+  @Dependency(\.app.bgQueue) var bgQueue
+  @Dependency(\.app.dbManager) var dbManager
+  @Dependency(\.app.messenger) var messenger
+  @Dependency(\.app.hudManager) var hudManager
+
+  var statePublisher: AnyPublisher<ViewState, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
+
+  private let stateSubject = CurrentValueSubject<ViewState, Never>(.init())
+
+  func didInput(_ string: String) {
+    stateSubject.value.groupName = string
+    validate()
+  }
+
+  func didOtherInput(_ string: String) {
+    stateSubject.value.welcome = string
+  }
+
+  func didTapCreate(_ members: [Contact]) {
+    hudManager.show()
+    let welcome = stateSubject.value.welcome
+    let name = stateSubject.value.groupName.trimmingCharacters(in: .whitespacesAndNewlines)
+
+    bgQueue.schedule {
+      do {
+        let report = try messenger.groupChat()!.makeGroup(
+          membership: members.map(\.id),
+          message: welcome?.data(using: .utf8),
+          name: name.data(using: .utf8)
+        )
+        let group = Group(
+          id: report.id,
+          name: name,
+          leaderId: try messenger.e2e.get()!.getContact().getId(),
+          createdAt: Date(),
+          authStatus: .participating,
+          serialized: try report.encode()
+        )
+        try dbManager.getDB().saveGroup(group)
+        if let welcome {
+          try dbManager.getDB().saveMessage(.init(
+            senderId: try messenger.e2e.get()!.getContact().getId(),
+            recipientId: nil,
+            groupId: group.id,
+            date: group.createdAt,
+            status: .sent,
+            isUnread: false,
+            text: welcome
+          ))
+        }
+        try members.map {
+          GroupMember(groupId: group.id, contactId: $0.id)
+        }.forEach {
+          try dbManager.getDB().saveGroupMember($0)
+        }
+        _ = try dbManager.getDB().fetchGroupInfos(
+          .init(groupId: group.id)
+        ).first
+        hudManager.hide()
+        stateSubject.value.shouldDismiss = true
+      } catch {
+        hudManager.show(.init(error: error))
+      }
+    }
+  }
+
+  private func validate() {
+    let value = stateSubject.value.groupName.trimmingCharacters(in: .whitespacesAndNewlines)
+    guard value.count >= 4 else {
+      stateSubject.value.status = .invalid(Localized.CreateGroup.Drawer.minimum)
+      return
+    }
+    guard value.count < 21 else {
+      stateSubject.value.status = .invalid(Localized.CreateGroup.Drawer.maximum)
+      return
+    }
+    stateSubject.value.status = .valid(nil)
+  }
+}
diff --git a/Sources/Defaults/Dependencies.swift b/Sources/Defaults/Dependencies.swift
new file mode 100644
index 0000000000000000000000000000000000000000..e0446cf6f3a8c8d952b419df86a95cb7043d52fd
--- /dev/null
+++ b/Sources/Defaults/Dependencies.swift
@@ -0,0 +1,13 @@
+import Dependencies
+
+private enum KeyObjectStoreDependencyKey: DependencyKey {
+  static let liveValue: KeyObjectStore = .live
+  static let testValue: KeyObjectStore = .unimplemented
+}
+
+extension DependencyValues {
+  public var store: KeyObjectStore {
+    get { self[KeyObjectStoreDependencyKey.self] }
+    set { self[KeyObjectStoreDependencyKey.self] = newValue }
+  }
+}
diff --git a/Sources/Defaults/KeyObject.swift b/Sources/Defaults/KeyObject.swift
index 0ade4e83639f54a5181b4292936a2d9dee049f60..99635cdab25184351ea1b3421562e185d9628870 100644
--- a/Sources/Defaults/KeyObject.swift
+++ b/Sources/Defaults/KeyObject.swift
@@ -1,110 +1,60 @@
 import Foundation
-import DependencyInjection
+import Dependencies
 
 public enum Key: String {
-    // MARK: Profile
-
-    case email
-    case phone
-    case avatar
-    case username
-
-    case sharingEmail
-    case sharingPhone
-
-    // MARK: Notifications
-
-    case requestCounter
-    case pushNotifications
-    case inappnotifications
-
-    // MARK: General
-
-    case theme
-    case acceptedTerms
-
-    // MARK: Requests
-
-    case isShowingHiddenRequests
-
-    // MARK: Backup
-
-    case backupSettings
-
-    // MARK: Settings
-
-    case biometrics
-    case hideAppList
-    case recordingLogs
-    case crashReporting
-    case icognitoKeyboard
-
-    case dummyTrafficOn
-    case askedDummyTrafficOnce
-}
-
-public struct KeyObjectStore {
-    var objectForKey: (String) -> Any?
-    var setObjectForKey: (Any?, String) -> Void
-    var removeObjectForKey: (String) -> Void
-
-    public init(
-        objectForKey: @escaping (String) -> Any?,
-        setObjectForKey: @escaping (Any?, String) -> Void,
-        removeObjectForKey: @escaping (String) -> Void
-    ) {
-        self.objectForKey = objectForKey
-        self.setObjectForKey = setObjectForKey
-        self.removeObjectForKey = removeObjectForKey
-    }
-}
-
-public extension KeyObjectStore {
-    static func mock(dictionary: NSMutableDictionary) -> Self {
-        Self(objectForKey: { dictionary[$0] },
-             setObjectForKey: { dictionary[$1] = $0 },
-             removeObjectForKey: { dictionary[$0] = nil })
-    }
-
-    static let userDefaults = Self(
-        objectForKey: UserDefaults.standard.object(forKey:),
-        setObjectForKey: UserDefaults.standard.set(_:forKey:),
-        removeObjectForKey: UserDefaults.standard.removeObject(forKey:)
-    )
+  case email
+  case phone
+  case avatar
+  case username
+  case sharingEmail
+  case sharingPhone
+  case requestCounter
+  case pushNotifications
+  case inappnotifications
+  case acceptedTerms
+  case isShowingHiddenRequests
+  case backupSettings
+  case biometrics
+  case hideAppList
+  case recordingLogs
+  case crashReporting
+  case icognitoKeyboard
+  case dummyTrafficOn
+  case askedDummyTrafficOnce
 }
 
 @propertyWrapper
 public struct KeyObject<T> {
-    let key: String
-    let defaultValue: T
+  let key: String
+  let defaultValue: T
 
-    @Dependency var store: KeyObjectStore
+  @Dependency(\.store) var store: KeyObjectStore
 
-    public init(_ key: Key, defaultValue: T) {
-        self.key = key.rawValue
-        self.defaultValue = defaultValue
-    }
+  public init(_ key: Key, defaultValue: T) {
+    self.key = key.rawValue
+    self.defaultValue = defaultValue
+  }
 
-    public var wrappedValue: T {
-        get {
-            store.objectForKey(key) as? T ?? defaultValue
-        }
-        set {
-            if let value = newValue as? OptionalProtocol, value.isNil() {
-                store.removeObjectForKey(key)
-            } else {
-                store.setObjectForKey(newValue, key)
-            }
-        }
+  public var wrappedValue: T {
+    get {
+      store.get(key) as? T ?? defaultValue
+    }
+    set {
+      if let value = newValue as? OptionalProtocol, value.isNil() {
+        store.remove(key)
+      } else {
+        store.set(newValue, for: key)
+      }
     }
+  }
 }
 
 fileprivate protocol OptionalProtocol {
-    func isNil() -> Bool
+  func isNil() -> Bool
 }
 
 extension Optional : OptionalProtocol {
-    func isNil() -> Bool {
-        return self == nil
-    }
+  func isNil() -> Bool {
+    return self == nil
+  }
 }
diff --git a/Sources/Defaults/KeyObjectStore.swift b/Sources/Defaults/KeyObjectStore.swift
new file mode 100644
index 0000000000000000000000000000000000000000..38588f92da0ba9f28ed3c389afb8f9c9469a049a
--- /dev/null
+++ b/Sources/Defaults/KeyObjectStore.swift
@@ -0,0 +1,21 @@
+public struct KeyObjectStore {
+  public var get: ObjectForKey
+  public var set: SetObjectForKey
+  public var remove: RemoveObjectForKey
+}
+
+extension KeyObjectStore {
+  public static let live = KeyObjectStore(
+    get: .live,
+    set: .live,
+    remove: .live
+  )
+}
+
+extension KeyObjectStore {
+  public static let unimplemented = KeyObjectStore(
+    get: .unimplemented,
+    set: .unimplemented,
+    remove: .unimplemented
+  )
+}
diff --git a/Sources/Defaults/ObjectForKey.swift b/Sources/Defaults/ObjectForKey.swift
new file mode 100644
index 0000000000000000000000000000000000000000..e2c51f0699a95fd32f636dc58d099f2f4afe0c2c
--- /dev/null
+++ b/Sources/Defaults/ObjectForKey.swift
@@ -0,0 +1,22 @@
+import Foundation
+import XCTestDynamicOverlay
+
+public struct ObjectForKey {
+  public var run: (String) -> Any?
+
+  public func callAsFunction(_ key: String) -> Any? {
+    run(key)
+  }
+}
+
+extension ObjectForKey {
+  public static let live = ObjectForKey {
+    UserDefaults.standard.object(forKey: $0)
+  }
+}
+
+extension ObjectForKey {
+  public static let unimplemented = ObjectForKey(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/Defaults/RemoveObjectForKey.swift b/Sources/Defaults/RemoveObjectForKey.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f8108bcffc0d978558f38b017e2cc9f0de848cc3
--- /dev/null
+++ b/Sources/Defaults/RemoveObjectForKey.swift
@@ -0,0 +1,22 @@
+import Foundation
+import XCTestDynamicOverlay
+
+public struct RemoveObjectForKey {
+  public var run: (String) -> Void
+
+  public func callAsFunction(_ key: String) -> Void {
+    run(key)
+  }
+}
+
+extension RemoveObjectForKey {
+  public static let live = RemoveObjectForKey {
+    UserDefaults.standard.removeObject(forKey: $0)
+  }
+}
+
+extension RemoveObjectForKey {
+  public static let unimplemented = RemoveObjectForKey(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/Defaults/SetObjectForKey.swift b/Sources/Defaults/SetObjectForKey.swift
new file mode 100644
index 0000000000000000000000000000000000000000..c2f7d625d607474cdb4f7ba673fe9ead94673651
--- /dev/null
+++ b/Sources/Defaults/SetObjectForKey.swift
@@ -0,0 +1,22 @@
+import Foundation
+import XCTestDynamicOverlay
+
+public struct SetObjectForKey {
+  public var run: (Any?, String) -> Void
+
+  public func callAsFunction(_ value: Any?, for key: String) -> Void {
+    run(value, key)
+  }
+}
+
+extension SetObjectForKey {
+  public static let live = SetObjectForKey { value, key in
+    UserDefaults.standard.set(value, forKey: key)
+  }
+}
+
+extension SetObjectForKey {
+  public static let unimplemented = SetObjectForKey(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/DependencyInjection/Container.swift b/Sources/DependencyInjection/Container.swift
deleted file mode 100644
index cd7bf2fcb3ce6515f63355f71163b30d83f67dd5..0000000000000000000000000000000000000000
--- a/Sources/DependencyInjection/Container.swift
+++ /dev/null
@@ -1,27 +0,0 @@
-public final class Container {
-    public static let shared = Container()
-
-    public init() {}
-
-    public func register<T>(_ dependency: T) {
-        dependencies[key(for: T.self)] = dependency
-    }
-
-    public func unregister<T>(_ dependencyType: T.Type) {
-        dependencies.removeValue(forKey: String(describing: dependencyType))
-    }
-
-    public func resolve<T>() throws -> T {
-        let key = self.key(for: T.self)
-        guard let dependency = dependencies[key] as? T else {
-            throw UnregisteredDependencyError(type: key)
-        }
-        return dependency
-    }
-
-    var dependencies = [String: Any]()
-
-    func key<T>(for dependencyType: T.Type) -> String {
-        String(describing: dependencyType)
-    }
-}
diff --git a/Sources/DependencyInjection/DependencyPropertyWrapper.swift b/Sources/DependencyInjection/DependencyPropertyWrapper.swift
deleted file mode 100644
index ef7067accdd81d40f96cc862aab22d213efafda1..0000000000000000000000000000000000000000
--- a/Sources/DependencyInjection/DependencyPropertyWrapper.swift
+++ /dev/null
@@ -1,20 +0,0 @@
-@propertyWrapper
-public struct Dependency<T> {
-    public init(container: Container = .shared, file: StaticString = #file, line: UInt = #line) {
-        self.container = container
-        self.file = file
-        self.line = line
-    }
-
-    public var wrappedValue: T {
-        do {
-            return try container.resolve()
-        } catch {
-            fatalError(error.localizedDescription, file: file, line: line)
-        }
-    }
-
-    let container: Container
-    let file: StaticString
-    let line: UInt
-}
diff --git a/Sources/DependencyInjection/UnregisteredDependencyError.swift b/Sources/DependencyInjection/UnregisteredDependencyError.swift
deleted file mode 100644
index 8ad955e63840a51fbd4da4df87dc2970728e3824..0000000000000000000000000000000000000000
--- a/Sources/DependencyInjection/UnregisteredDependencyError.swift
+++ /dev/null
@@ -1,11 +0,0 @@
-import Foundation
-
-public struct UnregisteredDependencyError: Error, Equatable {
-    public var type: String
-}
-
-extension UnregisteredDependencyError: LocalizedError {
-    public var errorDescription: String? {
-        "Resolving unregistered dependency <\(type)>"
-    }
-}
diff --git a/Sources/DrawerFeature/DrawerController.swift b/Sources/DrawerFeature/DrawerController.swift
index d907c26ba78956a919bcf58034b6e50720c0456a..c7503f8c68382be1494104ff483abcacb11ca542 100644
--- a/Sources/DrawerFeature/DrawerController.swift
+++ b/Sources/DrawerFeature/DrawerController.swift
@@ -2,26 +2,26 @@ import UIKit
 import Combine
 
 public final class DrawerController: UIViewController {
-    lazy private var screenView = DrawerView()
-    private let content: [DrawerItem]
-    public var cancellables = Set<AnyCancellable>()
+  private lazy var screenView = DrawerView()
+  private let content: [DrawerItem]
+  public var cancellables = Set<AnyCancellable>()
 
-    public init(with content: [DrawerItem]) {
-        self.content = content
-        super.init(nibName: nil, bundle: nil)
+  public init(_ items: [Any]) {
+    self.content = items as! [DrawerItem]
+    super.init(nibName: nil, bundle: nil)
 
-        let views = content.map { $0.makeView() }
-        views.forEach { screenView.stackView.addArrangedSubview($0) }
+    let views = content.map { $0.makeView() }
+    views.forEach { screenView.stackView.addArrangedSubview($0) }
 
-        content.enumerated().forEach { item in
-            guard let spacing = item.element.spacingAfter else { return }
-            screenView.stackView.setCustomSpacing(spacing, after: views[item.offset])
-        }
+    content.enumerated().forEach { item in
+      guard let spacing = item.element.spacingAfter else { return }
+      screenView.stackView.setCustomSpacing(spacing, after: views[item.offset])
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 
-    public override func loadView() {
-        view = screenView
-    }
+  public override func loadView() {
+    view = screenView
+  }
 }
diff --git a/Sources/DrawerFeature/DrawerView.swift b/Sources/DrawerFeature/DrawerView.swift
index e03282d5306ef66399d5ee0e96915f38d3e895de..95b711c3280ccdd40502d6cc4421eb98f058c672 100644
--- a/Sources/DrawerFeature/DrawerView.swift
+++ b/Sources/DrawerFeature/DrawerView.swift
@@ -1,26 +1,27 @@
 import UIKit
 import Shared
+import AppResources
 
 final class DrawerView: UIView {
-    let stackView = UIStackView()
+  let stackView = UIStackView()
 
-    init() {
-        super.init(frame: .zero)
+  init() {
+    super.init(frame: .zero)
 
-        layer.cornerRadius = 40
-        backgroundColor = Asset.neutralWhite.color
-        layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
+    layer.cornerRadius = 40
+    backgroundColor = Asset.neutralWhite.color
+    layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
 
-        stackView.axis = .vertical
-        addSubview(stackView)
+    stackView.axis = .vertical
+    addSubview(stackView)
 
-        stackView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(40)
-            $0.left.equalToSuperview().offset(50)
-            $0.right.equalToSuperview().offset(-50)
-            $0.bottom.equalToSuperview().offset(-50)
-        }
+    stackView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(40)
+      $0.left.equalToSuperview().offset(50)
+      $0.right.equalToSuperview().offset(-50)
+      $0.bottom.equalToSuperview().offset(-50)
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/DrawerFeature/Items/DrawerLinkText.swift b/Sources/DrawerFeature/Items/DrawerLinkText.swift
index 428acbaa89e295fb8f95f4bc3a131f0955e571fc..313e98d08ae67f511bc0d6c9ae34d527a2a12f8a 100644
--- a/Sources/DrawerFeature/Items/DrawerLinkText.swift
+++ b/Sources/DrawerFeature/Items/DrawerLinkText.swift
@@ -1,63 +1,64 @@
 import UIKit
 import Shared
+import AppResources
 
 public final class DrawerLinkText: NSObject, DrawerItem {
-    let text: String
-    let urlString: String
-
-    public var spacingAfter: CGFloat? = 0
-
-    public init(
-        text: String,
-        urlString: String,
-        spacingAfter: CGFloat = 10
-    ) {
-        self.text = text
-        self.urlString = urlString
-        self.spacingAfter = spacingAfter
-    }
+  let text: String
+  let urlString: String
+
+  public var spacingAfter: CGFloat? = 0
+
+  public init(
+    text: String,
+    urlString: String,
+    spacingAfter: CGFloat = 10
+  ) {
+    self.text = text
+    self.urlString = urlString
+    self.spacingAfter = spacingAfter
+  }
+
+  public func makeView() -> UIView {
+    let textView = UnselectableTextView()
+    textView.delegate = self
+    textView.isEditable = false
+    textView.isSelectable = true
+    textView.isScrollEnabled = false
+    textView.backgroundColor = .clear
+    textView.isUserInteractionEnabled = true
+
+    let paragraphStyle = NSMutableParagraphStyle()
+    paragraphStyle.alignment = .left
+    paragraphStyle.lineHeightMultiple = 1.1
 
-    public func makeView() -> UIView {
-        let textView = UnselectableTextView()
-        textView.delegate = self
-        textView.isEditable = false
-        textView.isSelectable = true
-        textView.isScrollEnabled = false
-        textView.backgroundColor = .clear
-        textView.isUserInteractionEnabled = true
-
-        let paragraphStyle = NSMutableParagraphStyle()
-        paragraphStyle.alignment = .left
-        paragraphStyle.lineHeightMultiple = 1.1
-
-        let attrString = NSMutableAttributedString(string: text)
-        attrString.addAttributes([
-            .paragraphStyle: paragraphStyle,
-            .foregroundColor: Asset.neutralDark.color,
-            .font: Fonts.Mulish.regular.font(size: 16.0) as Any
-        ])
-
-        if let url = URL(string: urlString) {
-            attrString.addAttribute(name: .link, value: url, betweenCharacters: "#")
-
-            textView.linkTextAttributes = [
-                .paragraphStyle: paragraphStyle,
-                .foregroundColor: Asset.brandPrimary.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0) as Any
-            ]
-        }
-
-        textView.attributedText = attrString
-
-        return textView
+    let attrString = NSMutableAttributedString(string: text)
+    attrString.addAttributes([
+      .paragraphStyle: paragraphStyle,
+      .foregroundColor: Asset.neutralDark.color,
+      .font: Fonts.Mulish.regular.font(size: 16.0) as Any
+    ])
+
+    if let url = URL(string: urlString) {
+      attrString.addAttribute(name: .link, value: url, betweenCharacters: "#")
+
+      textView.linkTextAttributes = [
+        .paragraphStyle: paragraphStyle,
+        .foregroundColor: Asset.brandPrimary.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any
+      ]
     }
 
-    public func textView(
-        _: UITextView,
-        shouldInteractWith: URL,
-        in: NSRange,
-        interaction: UITextItemInteraction
-    ) -> Bool { true }
+    textView.attributedText = attrString
+
+    return textView
+  }
+
+  public func textView(
+    _: UITextView,
+    shouldInteractWith: URL,
+    in: NSRange,
+    interaction: UITextItemInteraction
+  ) -> Bool { true }
 }
 
 extension DrawerLinkText: UITextViewDelegate {}
diff --git a/Sources/DrawerFeature/Items/DrawerLoadingRetry.swift b/Sources/DrawerFeature/Items/DrawerLoadingRetry.swift
index dcf46a4c812ef1698972c1f5337c1963279a7122..552cb10f94f06161c7a6a6eb6f84aef0c1cb88d0 100644
--- a/Sources/DrawerFeature/Items/DrawerLoadingRetry.swift
+++ b/Sources/DrawerFeature/Items/DrawerLoadingRetry.swift
@@ -1,57 +1,58 @@
 import UIKit
 import Shared
 import Combine
+import AppResources
 
 public final class DrawerLoadingRetry: DrawerItem {
-    public var retryPublisher: AnyPublisher<Void, Never> {
-        retrySubject.eraseToAnyPublisher()
-    }
-
-    private let view = UIView()
-    private let retryButton = UIButton()
-    private let stackView = UIStackView()
-    private var cancellables = Set<AnyCancellable>()
-    private let activityIndicator = UIActivityIndicatorView()
-    private let retrySubject = PassthroughSubject<Void, Never>()
-
-    public var spacingAfter: CGFloat? = 0
-
-    public init(spacingAfter: CGFloat? = 10) {
-        self.spacingAfter = spacingAfter
-        self.activityIndicator.style = .large
-        self.activityIndicator.hidesWhenStopped = true
-    }
-
-    public func startSpinning() {
-        activityIndicator.startAnimating()
-        retryButton.isHidden = true
-    }
-
-    public func stopSpinning(withRetry retry: Bool) {
-        guard retry else { view.isHidden = true; return }
-
-        retryButton.isHidden = false
-        activityIndicator.stopAnimating()
-        retryButton.setTitle("Retry", for: .normal)
-        retryButton.setTitleColor(.red, for: .normal)
-
-        retryButton.titleLabel?.numberOfLines = 0
-        retryButton.titleLabel?.textAlignment = .center
-        retryButton.titleLabel?.font = Fonts.Mulish.bold.font(size: 16.0)
-    }
-
-    public func makeView() -> UIView {
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(activityIndicator)
-        stackView.addArrangedSubview(retryButton)
-
-        retryButton
-            .publisher(for: .touchUpInside)
-            .sink { [weak retrySubject] in retrySubject?.send() }
-            .store(in: &cancellables)
-
-        view.addSubview(stackView)
-        stackView.snp.makeConstraints { $0.edges.equalToSuperview() }
-        return view
-    }
+  public var retryPublisher: AnyPublisher<Void, Never> {
+    retrySubject.eraseToAnyPublisher()
+  }
+
+  private let view = UIView()
+  private let retryButton = UIButton()
+  private let stackView = UIStackView()
+  private var cancellables = Set<AnyCancellable>()
+  private let activityIndicator = UIActivityIndicatorView()
+  private let retrySubject = PassthroughSubject<Void, Never>()
+
+  public var spacingAfter: CGFloat? = 0
+
+  public init(spacingAfter: CGFloat? = 10) {
+    self.spacingAfter = spacingAfter
+    self.activityIndicator.style = .large
+    self.activityIndicator.hidesWhenStopped = true
+  }
+
+  public func startSpinning() {
+    activityIndicator.startAnimating()
+    retryButton.isHidden = true
+  }
+
+  public func stopSpinning(withRetry retry: Bool) {
+    guard retry else { view.isHidden = true; return }
+
+    retryButton.isHidden = false
+    activityIndicator.stopAnimating()
+    retryButton.setTitle("Retry", for: .normal)
+    retryButton.setTitleColor(.red, for: .normal)
+
+    retryButton.titleLabel?.numberOfLines = 0
+    retryButton.titleLabel?.textAlignment = .center
+    retryButton.titleLabel?.font = Fonts.Mulish.bold.font(size: 16.0)
+  }
+
+  public func makeView() -> UIView {
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(activityIndicator)
+    stackView.addArrangedSubview(retryButton)
+
+    retryButton
+      .publisher(for: .touchUpInside)
+      .sink { [weak retrySubject] in retrySubject?.send() }
+      .store(in: &cancellables)
+
+    view.addSubview(stackView)
+    stackView.snp.makeConstraints { $0.edges.equalToSuperview() }
+    return view
+  }
 }
diff --git a/Sources/DrawerFeature/Items/DrawerRadio.swift b/Sources/DrawerFeature/Items/DrawerRadio.swift
index de3b764fe403ef3f2c6d80c0e1f91d57ac905382..a3251cf81e9f164ebd4ab6a6ae0317dca0f81872 100644
--- a/Sources/DrawerFeature/Items/DrawerRadio.swift
+++ b/Sources/DrawerFeature/Items/DrawerRadio.swift
@@ -1,79 +1,80 @@
 import UIKit
 import Shared
 import Combine
+import AppResources
 
 public final class DrawerRadio: DrawerItem {
-    private let title: String
-    private let isSelected: Bool
-    private var cancellables = Set<AnyCancellable>()
-    private let actionSubject = PassthroughSubject<Void, Never>()
+  private let title: String
+  private let isSelected: Bool
+  private var cancellables = Set<AnyCancellable>()
+  private let actionSubject = PassthroughSubject<Void, Never>()
 
-    public var spacingAfter: CGFloat? = 0
-    public var action: AnyPublisher<Void, Never> { actionSubject.eraseToAnyPublisher() }
+  public var spacingAfter: CGFloat? = 0
+  public var action: AnyPublisher<Void, Never> { actionSubject.eraseToAnyPublisher() }
 
-    public init(
-        title: String,
-        isSelected: Bool,
-        spacingAfter: CGFloat = 10
-    ) {
-        self.title = title
-        self.isSelected = isSelected
-        self.spacingAfter = spacingAfter
-    }
+  public init(
+    title: String,
+    isSelected: Bool,
+    spacingAfter: CGFloat = 10
+  ) {
+    self.title = title
+    self.isSelected = isSelected
+    self.spacingAfter = spacingAfter
+  }
 
-    public func makeView() -> UIView {
-        cancellables.removeAll()
+  public func makeView() -> UIView {
+    cancellables.removeAll()
 
-        let radioView = UIView()
-        let titleLabel = UILabel()
-        let radioInnerView = UIView()
+    let radioView = UIView()
+    let titleLabel = UILabel()
+    let radioInnerView = UIView()
 
-        let view = UIControl()
-        view.addSubview(titleLabel)
-        view.addSubview(radioView)
-        radioView.addSubview(radioInnerView)
+    let view = UIControl()
+    view.addSubview(titleLabel)
+    view.addSubview(radioView)
+    radioView.addSubview(radioInnerView)
 
-        titleLabel.text = title
-        titleLabel.textColor = Asset.neutralDark.color
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    titleLabel.text = title
+    titleLabel.textColor = Asset.neutralDark.color
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
 
-        radioView.layer.cornerRadius = 11.0
-        radioInnerView.layer.cornerRadius = 3
-        radioView.isUserInteractionEnabled = false
+    radioView.layer.cornerRadius = 11.0
+    radioInnerView.layer.cornerRadius = 3
+    radioView.isUserInteractionEnabled = false
 
-        if isSelected {
-            radioView.layer.borderWidth = 0.0
-            radioView.backgroundColor = Asset.brandLight.color
-            radioView.layer.borderColor = Asset.brandLight.color.cgColor
-            radioInnerView.backgroundColor = Asset.neutralWhite.color
-        } else {
-            radioView.layer.borderWidth = 1.0
-            radioView.backgroundColor = Asset.neutralSecondary.color
-            radioView.layer.borderColor = Asset.neutralLine.color.cgColor
-            radioInnerView.backgroundColor = .clear
-        }
+    if isSelected {
+      radioView.layer.borderWidth = 0.0
+      radioView.backgroundColor = Asset.brandLight.color
+      radioView.layer.borderColor = Asset.brandLight.color.cgColor
+      radioInnerView.backgroundColor = Asset.neutralWhite.color
+    } else {
+      radioView.layer.borderWidth = 1.0
+      radioView.backgroundColor = Asset.neutralSecondary.color
+      radioView.layer.borderColor = Asset.neutralLine.color.cgColor
+      radioInnerView.backgroundColor = .clear
+    }
 
-        titleLabel.snp.makeConstraints {
-            $0.left.equalToSuperview().offset(42)
-            $0.centerY.equalToSuperview()
-        }
+    titleLabel.snp.makeConstraints {
+      $0.left.equalToSuperview().offset(42)
+      $0.centerY.equalToSuperview()
+    }
 
-        radioView.snp.makeConstraints {
-            $0.right.equalTo(titleLabel.snp.left).offset(-12)
-            $0.width.height.equalTo(20)
-            $0.centerY.equalToSuperview()
-            $0.bottom.equalToSuperview().offset(-5)
-        }
+    radioView.snp.makeConstraints {
+      $0.right.equalTo(titleLabel.snp.left).offset(-12)
+      $0.width.height.equalTo(20)
+      $0.centerY.equalToSuperview()
+      $0.bottom.equalToSuperview().offset(-5)
+    }
 
-        radioInnerView.snp.makeConstraints {
-            $0.width.height.equalTo(6)
-            $0.center.equalToSuperview()
-        }
+    radioInnerView.snp.makeConstraints {
+      $0.width.height.equalTo(6)
+      $0.center.equalToSuperview()
+    }
 
-        view.publisher(for: .touchUpInside)
-            .sink { [weak self] in self?.actionSubject.send() }
-            .store(in: &cancellables)
+    view.publisher(for: .touchUpInside)
+      .sink { [weak self] in self?.actionSubject.send() }
+      .store(in: &cancellables)
 
-        return view
-    }
+    return view
+  }
 }
diff --git a/Sources/DrawerFeature/Items/DrawerSwitch.swift b/Sources/DrawerFeature/Items/DrawerSwitch.swift
index 449261487789f2685bc43b861ce313b8cf8aaa88..5db2555ed0a36ed68efc9042cdc9635030af4d3d 100644
--- a/Sources/DrawerFeature/Items/DrawerSwitch.swift
+++ b/Sources/DrawerFeature/Items/DrawerSwitch.swift
@@ -1,80 +1,81 @@
 import UIKit
 import Shared
 import Combine
+import AppResources
 
 public final class DrawerSwitch: DrawerItem {
-    public var isOnPublisher: AnyPublisher<Bool, Never> {
-        isOnSubject.eraseToAnyPublisher()
-    }
+  public var isOnPublisher: AnyPublisher<Bool, Never> {
+    isOnSubject.eraseToAnyPublisher()
+  }
 
-    private let title: String
-    private let content: String
-    private let isEnabled: Bool
-    private let isInitiallyOn: Bool
-    private var cancellables = Set<AnyCancellable>()
-    private let isOnSubject: CurrentValueSubject<Bool, Never>
+  private let title: String
+  private let content: String
+  private let isEnabled: Bool
+  private let isInitiallyOn: Bool
+  private var cancellables = Set<AnyCancellable>()
+  private let isOnSubject: CurrentValueSubject<Bool, Never>
 
-    public var spacingAfter: CGFloat? = 0
+  public var spacingAfter: CGFloat? = 0
 
-    public init(
-        title: String,
-        content: String,
-        isEnabled: Bool = true,
-        spacingAfter: CGFloat = 10,
-        isInitiallyOn: Bool = false
-    ) {
-        self.title = title
-        self.content = content
-        self.isEnabled = isEnabled
-        self.spacingAfter = spacingAfter
-        self.isInitiallyOn = isInitiallyOn
-        self.isOnSubject = .init(isInitiallyOn)
-    }
+  public init(
+    title: String,
+    content: String,
+    isEnabled: Bool = true,
+    spacingAfter: CGFloat = 10,
+    isInitiallyOn: Bool = false
+  ) {
+    self.title = title
+    self.content = content
+    self.isEnabled = isEnabled
+    self.spacingAfter = spacingAfter
+    self.isInitiallyOn = isInitiallyOn
+    self.isOnSubject = .init(isInitiallyOn)
+  }
 
-    public func makeView() -> UIView {
-        let view = UIView()
-        let titleLabel = UILabel()
-        let contentLabel = UILabel()
-        let switcherView = UISwitch()
+  public func makeView() -> UIView {
+    let view = UIView()
+    let titleLabel = UILabel()
+    let contentLabel = UILabel()
+    let switcherView = UISwitch()
 
-        titleLabel.text = title
-        contentLabel.text = content
+    titleLabel.text = title
+    contentLabel.text = content
 
-        switcherView.isOn = isInitiallyOn
-        switcherView.isEnabled = isEnabled
-        switcherView.onTintColor = Asset.brandPrimary.color
+    switcherView.isOn = isInitiallyOn
+    switcherView.isEnabled = isEnabled
+    switcherView.onTintColor = Asset.brandPrimary.color
 
-        titleLabel.textColor = Asset.neutralWeak.color
-        contentLabel.textColor = Asset.neutralActive.color
+    titleLabel.textColor = Asset.neutralWeak.color
+    contentLabel.textColor = Asset.neutralActive.color
 
-        titleLabel.font = Fonts.Mulish.bold.font(size: 12.0)
-        contentLabel.font = Fonts.Mulish.regular.font(size: 16.0)
+    titleLabel.font = Fonts.Mulish.bold.font(size: 12.0)
+    contentLabel.font = Fonts.Mulish.regular.font(size: 16.0)
 
-        view.addSubview(titleLabel)
-        view.addSubview(contentLabel)
-        view.addSubview(switcherView)
+    view.addSubview(titleLabel)
+    view.addSubview(contentLabel)
+    view.addSubview(switcherView)
 
-        titleLabel.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.left.equalToSuperview()
-        }
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview()
+    }
 
-        contentLabel.snp.makeConstraints {
-            $0.top.equalTo(titleLabel.snp.bottom).offset(5)
-            $0.left.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
+    contentLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(5)
+      $0.left.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
 
-        switcherView.snp.makeConstraints {
-            $0.right.equalToSuperview()
-            $0.centerY.equalToSuperview()
-        }
+    switcherView.snp.makeConstraints {
+      $0.right.equalToSuperview()
+      $0.centerY.equalToSuperview()
+    }
 
-        switcherView.publisher(for: .valueChanged)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in isOnSubject.send(switcherView.isOn) }
-            .store(in: &cancellables)
+    switcherView.publisher(for: .valueChanged)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in isOnSubject.send(switcherView.isOn) }
+      .store(in: &cancellables)
 
-        return view
-    }
+    return view
+  }
 }
diff --git a/Sources/DrawerFeature/Items/DrawerTable.swift b/Sources/DrawerFeature/Items/DrawerTable.swift
index 726dae9fe28ccd5720167a7b4ed1944eebf419e5..78f2b9299ca078c20fdbf426159d732a1af3366e 100644
--- a/Sources/DrawerFeature/Items/DrawerTable.swift
+++ b/Sources/DrawerFeature/Items/DrawerTable.swift
@@ -1,146 +1,148 @@
 import UIKit
 import Shared
 import SnapKit
+import AppResources
 
 enum DrawerTableSection {
-    case main
+  case main
 }
 
 public final class DrawerTable: DrawerItem {
-    private let view = UIView()
-    private let tableView = UITableView()
-    private var heightConstraint: Constraint?
-    private let dataSource: UITableViewDiffableDataSource<DrawerTableSection, DrawerTableCellModel>
-
-    public var spacingAfter: CGFloat? = 0
-
-    public init(spacingAfter: CGFloat? = 10) {
-        self.dataSource = .init(
-            tableView: tableView,
-            cellProvider: { tableView, indexPath, model in
-                let cell: DrawerTableCell = tableView.dequeueReusableCell(forIndexPath: indexPath)
-
-                cell.titleLabel.text = model.title
-                cell.avatarView.setupProfile(
-                    title: model.title,
-                    image: model.image,
-                    size: .medium
-                )
-
-                if model.isCreator {
-                    cell.subtitleLabel.text = "Creator"
-                    cell.subtitleLabel.isHidden = false
-                    cell.subtitleLabel.textColor = Asset.accentSafe.color
-                } else if !model.isConnection {
-                    cell.subtitleLabel.text = "Not a connection"
-                    cell.subtitleLabel.isHidden = false
-                    cell.subtitleLabel.textColor = Asset.neutralSecondaryAlternative.color
-                } else {
-                    cell.subtitleLabel.isHidden = true
-                }
-
-                return cell
-            })
-
-        self.spacingAfter = spacingAfter
-    }
+  private let view = UIView()
+  private let tableView = UITableView()
+  private var heightConstraint: Constraint?
+  private let dataSource: UITableViewDiffableDataSource<DrawerTableSection, DrawerTableCellModel>
+
+  public var spacingAfter: CGFloat? = 0
+
+  public init(spacingAfter: CGFloat? = 10) {
+    self.dataSource = .init(
+      tableView: tableView,
+      cellProvider: { tableView, indexPath, model in
+        let cell: DrawerTableCell = tableView.dequeueReusableCell(forIndexPath: indexPath)
+
+        cell.titleLabel.text = model.title
+        cell.avatarView.setupProfile(
+          title: model.title,
+          image: model.image,
+          size: .medium
+        )
+
+        if model.isCreator {
+          cell.subtitleLabel.text = "Creator"
+          cell.subtitleLabel.isHidden = false
+          cell.subtitleLabel.textColor = Asset.accentSafe.color
+        } else if !model.isConnection {
+          cell.subtitleLabel.text = "Not a connection"
+          cell.subtitleLabel.isHidden = false
+          cell.subtitleLabel.textColor = Asset.neutralSecondaryAlternative.color
+        } else {
+          cell.subtitleLabel.isHidden = true
+        }
 
-    public func makeView() -> UIView {
-        tableView.register(DrawerTableCell.self)
-        tableView.dataSource = dataSource
-        tableView.separatorStyle = .none
+        return cell
+      })
 
-        view.addSubview(tableView)
+    self.spacingAfter = spacingAfter
+  }
 
-        tableView.snp.makeConstraints {
-            $0.edges.equalToSuperview()
-            heightConstraint = $0.height.equalTo(1).priority(.low).constraint
-        }
+  public func makeView() -> UIView {
+    tableView.register(DrawerTableCell.self)
+    tableView.dataSource = dataSource
+    tableView.separatorStyle = .none
+    tableView.backgroundColor = UIColor.white
 
-        return view
+    view.addSubview(tableView)
+
+    tableView.snp.makeConstraints {
+      $0.edges.equalToSuperview()
+      heightConstraint = $0.height.equalTo(1).priority(.low).constraint
     }
 
-    public func update(models: [DrawerTableCellModel]) {
-        let cellHeight = 56
-        self.heightConstraint?.update(offset: cellHeight * models.count)
+    return view
+  }
 
-        var snapshot = NSDiffableDataSourceSnapshot<DrawerTableSection, DrawerTableCellModel>()
-        snapshot.appendSections([.main])
-        snapshot.appendItems(models, toSection: .main)
-        dataSource.apply(snapshot, animatingDifferences: false) { [self] in
-            tableView.isScrollEnabled = tableView.contentSize.height > tableView.frame.height
-        }
+  public func update(models: [DrawerTableCellModel]) {
+    let cellHeight = 56
+    self.heightConstraint?.update(offset: cellHeight * models.count)
+
+    var snapshot = NSDiffableDataSourceSnapshot<DrawerTableSection, DrawerTableCellModel>()
+    snapshot.appendSections([.main])
+    snapshot.appendItems(models, toSection: .main)
+    dataSource.apply(snapshot, animatingDifferences: false) { [self] in
+      tableView.isScrollEnabled = tableView.contentSize.height > tableView.frame.height
     }
+  }
 }
 
 public struct DrawerTableCellModel: Hashable {
-    let id: Data
-    let title: String
-    let image: Data?
-    let isCreator: Bool
-    let isConnection: Bool
-
-    public init(
-        id: Data,
-        title: String,
-        image: Data? = nil,
-        isCreator: Bool = false,
-        isConnection: Bool = true
-    ) {
-        self.id = id
-        self.title = title
-        self.image = image
-        self.isCreator = isCreator
-        self.isConnection = isConnection
-    }
+  let id: Data
+  let title: String
+  let image: Data?
+  let isCreator: Bool
+  let isConnection: Bool
+
+  public init(
+    id: Data,
+    title: String,
+    image: Data? = nil,
+    isCreator: Bool = false,
+    isConnection: Bool = true
+  ) {
+    self.id = id
+    self.title = title
+    self.image = image
+    self.isCreator = isCreator
+    self.isConnection = isConnection
+  }
 }
 
 final class DrawerTableCell: UITableViewCell {
-    let titleLabel = UILabel()
-    let subtitleLabel = UILabel()
-    let avatarView = AvatarView()
-    let stackView = UIStackView()
-
-    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        super.init(style: style, reuseIdentifier: reuseIdentifier)
-
-        selectionStyle = .none
-        backgroundColor = Asset.neutralWhite.color
-
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 16.0)
-        subtitleLabel.font = Fonts.Mulish.regular.font(size: 14.0)
-        titleLabel.textColor = Asset.neutralActive.color
-
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(titleLabel)
-        stackView.addArrangedSubview(subtitleLabel)
-
-        contentView.addSubview(avatarView)
-        contentView.addSubview(stackView)
-
-        avatarView.snp.makeConstraints {
-            $0.width.equalTo(36)
-            $0.height.equalTo(36)
-            $0.top.equalToSuperview().offset(10)
-            $0.left.equalToSuperview()
-            $0.bottom.equalToSuperview().offset(-10)
-        }
+  let titleLabel = UILabel()
+  let subtitleLabel = UILabel()
+  let avatarView = AvatarView()
+  let stackView = UIStackView()
+
+  override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+    super.init(style: style, reuseIdentifier: reuseIdentifier)
+
+    selectionStyle = .none
+    backgroundColor = Asset.neutralWhite.color
+
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 16.0)
+    subtitleLabel.font = Fonts.Mulish.regular.font(size: 14.0)
+    titleLabel.textColor = Asset.neutralActive.color
+
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(titleLabel)
+    stackView.addArrangedSubview(subtitleLabel)
+
+    contentView.addSubview(avatarView)
+    contentView.addSubview(stackView)
+
+    avatarView.snp.makeConstraints {
+      $0.width.equalTo(36)
+      $0.height.equalTo(36)
+      $0.top.equalToSuperview().offset(10)
+      $0.left.equalToSuperview()
+      $0.bottom.equalToSuperview().offset(-10)
+    }
 
-        stackView.snp.makeConstraints {
-            $0.left.equalTo(avatarView.snp.right).offset(15)
-            $0.top.equalTo(avatarView)
-            $0.bottom.equalTo(avatarView)
-            $0.right.equalToSuperview()
-        }
+    stackView.snp.makeConstraints {
+      $0.left.equalTo(avatarView.snp.right).offset(15)
+      $0.top.equalTo(avatarView)
+      $0.bottom.equalTo(avatarView)
+      $0.right.equalToSuperview()
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 
-    override func prepareForReuse() {
-        super.prepareForReuse()
+  override func prepareForReuse() {
+    super.prepareForReuse()
 
-        titleLabel.text = nil
-        subtitleLabel.text = nil
-        avatarView.prepareForReuse()
-    }
+    titleLabel.text = nil
+    subtitleLabel.text = nil
+    avatarView.prepareForReuse()
+  }
 }
diff --git a/Sources/DrawerFeature/Items/DrawerText.swift b/Sources/DrawerFeature/Items/DrawerText.swift
index 8cfeaffa7487d0fe644b7538fd245648ef4cd21c..beac93e6951e417a42e5f2844f730c228af11dbf 100644
--- a/Sources/DrawerFeature/Items/DrawerText.swift
+++ b/Sources/DrawerFeature/Items/DrawerText.swift
@@ -1,70 +1,71 @@
 import UIKit
 import Shared
+import AppResources
 
 public final class DrawerText: DrawerItem {
-    private let font: UIFont
-    private let text: String
-    private let color: UIColor
-    private let leftImage: UIImage?
-    private let alignment: NSTextAlignment
-    private let lineHeightMultiple: CGFloat
-    private let customAttributes: [NSAttributedString.Key: Any]?
-    private let stackView = UIStackView()
+  private let font: UIFont
+  private let text: String
+  private let color: UIColor
+  private let leftImage: UIImage?
+  private let alignment: NSTextAlignment
+  private let lineHeightMultiple: CGFloat
+  private let customAttributes: [NSAttributedString.Key: Any]?
+  private let stackView = UIStackView()
 
-    public var spacingAfter: CGFloat? = 0
+  public var spacingAfter: CGFloat? = 0
 
-    public init(
-        font: UIFont = Fonts.Mulish.regular.font(size: 16.0),
-        text: String,
-        color: UIColor = Asset.neutralActive.color,
-        alignment: NSTextAlignment = .left,
-        lineHeightMultiple: CGFloat = 1.1,
-        spacingAfter: CGFloat = 10,
-        customAttributes: [NSAttributedString.Key: Any]? = nil,
-        leftImage: UIImage? = nil
-    ) {
-        self.font = font
-        self.text = text
-        self.color = color
-        self.leftImage = leftImage
-        self.alignment = alignment
-        self.spacingAfter = spacingAfter
-        self.customAttributes = customAttributes
-        self.lineHeightMultiple = lineHeightMultiple
-    }
-
-    public func makeView() -> UIView {
-        let label = UILabel()
-        label.numberOfLines = 0
+  public init(
+    font: UIFont = Fonts.Mulish.regular.font(size: 16.0),
+    text: String,
+    color: UIColor = Asset.neutralActive.color,
+    alignment: NSTextAlignment = .left,
+    lineHeightMultiple: CGFloat = 1.1,
+    spacingAfter: CGFloat = 10,
+    customAttributes: [NSAttributedString.Key: Any]? = nil,
+    leftImage: UIImage? = nil
+  ) {
+    self.font = font
+    self.text = text
+    self.color = color
+    self.leftImage = leftImage
+    self.alignment = alignment
+    self.spacingAfter = spacingAfter
+    self.customAttributes = customAttributes
+    self.lineHeightMultiple = lineHeightMultiple
+  }
 
-        let paragraphStyle = NSMutableParagraphStyle()
-        paragraphStyle.alignment = alignment
-        paragraphStyle.lineHeightMultiple = lineHeightMultiple
+  public func makeView() -> UIView {
+    let label = UILabel()
+    label.numberOfLines = 0
 
-        let attrString = NSMutableAttributedString(string: text)
-        attrString.addAttributes([
-            .paragraphStyle: paragraphStyle,
-            .foregroundColor: color,
-            .font: font as Any
-        ])
+    let paragraphStyle = NSMutableParagraphStyle()
+    paragraphStyle.alignment = alignment
+    paragraphStyle.lineHeightMultiple = lineHeightMultiple
 
-        if let customAttributes = customAttributes {
-            attrString.addAttributes(
-                attributes: customAttributes,
-                betweenCharacters: "#"
-            )
-        }
+    let attrString = NSMutableAttributedString(string: text)
+    attrString.addAttributes([
+      .paragraphStyle: paragraphStyle,
+      .foregroundColor: color,
+      .font: font as Any
+    ])
 
-        label.attributedText = attrString
+    if let customAttributes = customAttributes {
+      attrString.addAttributes(
+        attributes: customAttributes,
+        betweenCharacters: "#"
+      )
+    }
 
-        if let image = leftImage {
-            let imageView = UIImageView()
-            imageView.image = image
-            stackView.addArrangedSubview(imageView)
-        }
+    label.attributedText = attrString
 
-        stackView.addArrangedSubview(label)
-        stackView.spacing = 5
-        return stackView
+    if let image = leftImage {
+      let imageView = UIImageView()
+      imageView.image = image
+      stackView.addArrangedSubview(imageView)
     }
+
+    stackView.addArrangedSubview(label)
+    stackView.spacing = 5
+    return stackView
+  }
 }
diff --git a/Sources/DropboxFeature/DropboxInterface.swift b/Sources/DropboxFeature/DropboxInterface.swift
deleted file mode 100644
index 1a5aa02a054ad29816530447c3ed5d725573160b..0000000000000000000000000000000000000000
--- a/Sources/DropboxFeature/DropboxInterface.swift
+++ /dev/null
@@ -1,18 +0,0 @@
-import UIKit
-import Combine
-
-public protocol DropboxInterface {
-    func isAuthorized() -> Bool
-
-    func unlink()
-
-    func handleOpenUrl(_ url: URL) -> Bool
-
-    func downloadBackup(_: String, _: @escaping (Result<Data, Error>) -> Void)
-
-    func uploadBackup(_: URL, _: @escaping (Result<DropboxMetadata, Error>) -> Void)
-
-    func downloadMetadata(_: @escaping (Result<DropboxMetadata?, Error>) -> Void)
-
-    func authorize(presenting: UIViewController) -> AnyPublisher<Result<Bool, Error>, Never>
-}
diff --git a/Sources/DropboxFeature/DropboxMetadata.swift b/Sources/DropboxFeature/DropboxMetadata.swift
deleted file mode 100644
index ceb4fe195242bf13ffb4e2a65a48d58aea282756..0000000000000000000000000000000000000000
--- a/Sources/DropboxFeature/DropboxMetadata.swift
+++ /dev/null
@@ -1,18 +0,0 @@
-import Foundation
-import SwiftyDropbox
-
-public struct DropboxMetadata: Equatable {
-    public var size: Float
-    public var path: String
-    public var modifiedDate: Date
-
-    public init(
-        size: Float,
-        path: String,
-        modifiedDate: Date
-    ) {
-        self.size = size
-        self.path = path
-        self.modifiedDate = modifiedDate
-    }
-}
diff --git a/Sources/DropboxFeature/DropboxService.swift b/Sources/DropboxFeature/DropboxService.swift
deleted file mode 100644
index e90ef79721443c67ae452e1a0105ab0b86c2e6ca..0000000000000000000000000000000000000000
--- a/Sources/DropboxFeature/DropboxService.swift
+++ /dev/null
@@ -1,209 +0,0 @@
-import UIKit
-import Combine
-import SwiftyDropbox
-
-public struct DropboxService: DropboxInterface {
-    private let didAuthorizeSubject = PassthroughSubject<Result<Bool, Error>, Never>()
-
-    public init() {
-        let path = Bundle.module.path(forResource: "Dropbox-Keys", ofType: "plist")
-        let url = URL(fileURLWithPath: path!)
-        let keys = try! NSDictionary(contentsOf: url, error: ())
-
-        DropboxClientsManager.setupWithAppKey(keys["DROPBOX_APP_KEY"] as! String)
-    }
-
-    public func unlink() {
-        DropboxClientsManager.unlinkClients()
-    }
-
-    public func isAuthorized() -> Bool {
-        DropboxClientsManager.authorizedClient != nil
-    }
-
-    public func authorize(presenting controller: UIViewController) -> AnyPublisher<Result<Bool, Error>, Never> {
-        let scopes = ["files.metadata.read", "files.content.read", "files.content.write"]
-
-        return didAuthorizeSubject.handleEvents(receiveSubscription: { _ in
-            let scopeRequest = ScopeRequest(scopeType: .user, scopes: scopes, includeGrantedScopes: false)
-
-            DropboxClientsManager.authorizeFromControllerV2(
-                UIApplication.shared,
-                controller: controller,
-                loadingStatusDelegate: nil,
-                openURL: { (url: URL) -> Void in UIApplication.shared.open(url, options: [:], completionHandler: nil) },
-                scopeRequest: scopeRequest
-            )
-        }).first().eraseToAnyPublisher()
-    }
-
-    public func handleOpenUrl(_ url: URL) -> Bool {
-        DropboxClientsManager.handleRedirectURL(url) {
-            switch $0 {
-            case .none:
-                didAuthorizeSubject.send(.success(false))
-            case .error(let oAuthError, _):
-                didAuthorizeSubject.send(.failure(oAuthError))
-            case .success:
-                didAuthorizeSubject.send(.success(true))
-            case .cancel:
-                didAuthorizeSubject.send(.success(false))
-            }
-        }
-    }
-
-    public func downloadBackup(_ path: String, _ completion: @escaping (Result<Data, Error>) -> Void) {
-        Task {
-            do {
-                guard try await folderExists() else { fatalError() }
-
-                let data = try await fetchBackup()
-                completion(.success(data))
-            } catch {
-                completion(.failure(error))
-            }
-        }
-    }
-
-    public func uploadBackup(_ url: URL, _ completion: @escaping (Result<DropboxMetadata, Error>) -> Void) {
-        Task {
-            do {
-                if try await !folderExists() {
-                    try await createFolder()
-                }
-
-                let data = try Data(contentsOf: url)
-                let metadata = try await upload(data: data)
-                completion(.success(metadata))
-            } catch {
-                completion(.failure(error))
-            }
-        }
-    }
-
-    public func downloadMetadata(_ completion: @escaping (Result<DropboxMetadata?, Error>) -> Void) {
-        Task {
-            do {
-                guard try await folderExists() else {
-                    completion(.success(nil))
-                    return
-                }
-
-                let metadata = try await fetchMetadata()
-                completion(.success(metadata))
-            } catch {
-                completion(.failure(error))
-            }
-        }
-    }
-}
-
-extension DropboxService {
-    private func folderExists() async throws -> Bool {
-        guard let client = DropboxClientsManager.authorizedClient else { fatalError() }
-
-        return try await withCheckedThrowingContinuation { continuation in
-            client.files.listFolder(path: "/backup")
-                .response { result, error in
-                if let error = error {
-                    if case .routeError(_, _, _, _) = error as CallError {
-                        continuation.resume(returning: false)
-                        return
-                    }
-
-                    let err = NSError(domain: error.description, code: 0)
-                    continuation.resume(throwing: err)
-                    return
-                }
-
-                continuation.resume(returning: result != nil)
-            }
-        }
-    }
-
-    private func createFolder() async throws {
-        guard let client = DropboxClientsManager.authorizedClient else { fatalError() }
-
-        return try await withCheckedThrowingContinuation { continuation in
-            client.files.createFolderV2(path: "/backup")
-                .response { _, error in
-                if let error = error {
-                    let err = NSError(domain: error.description, code: 0)
-                    continuation.resume(throwing: err)
-                    return
-                }
-
-                continuation.resume(returning: ())
-            }
-        }
-    }
-
-    private func fetchMetadata() async throws -> DropboxMetadata? {
-        guard let client = DropboxClientsManager.authorizedClient else { fatalError() }
-
-        return try await withCheckedThrowingContinuation { continuation in
-            client.files.getMetadata(path: "/backup/backup.xxm")
-                .response { response, error in
-                if let error = error {
-                    let err = NSError(domain: error.description, code: 0)
-                    continuation.resume(throwing: err)
-                    return
-                }
-
-                if let result = response as? Files.FileMetadata {
-                    let size = Float(result.size)
-                    let modifiedDate = result.serverModified
-                    continuation.resume(returning: .init(
-                        size: size,
-                        path: "/backup/backup.xxm",
-                        modifiedDate: modifiedDate
-                    ))
-                } else {
-                    continuation.resume(returning: nil)
-                }
-            }
-        }
-    }
-
-    private func fetchBackup() async throws -> Data {
-        guard let client = DropboxClientsManager.authorizedClient else { fatalError() }
-
-        return try await withCheckedThrowingContinuation { continuation in
-            client.files.download(path: "/backup/backup.xxm")
-                .response(completionHandler: { response, error in
-                if let error = error {
-                    let err = NSError(domain: error.description, code: 0)
-                    continuation.resume(throwing: err)
-                    return
-                }
-
-                if let response = response {
-                    continuation.resume(returning: response.1)
-                }
-            })
-        }
-    }
-
-    private func upload(data: Data) async throws -> DropboxMetadata {
-        guard let client = DropboxClientsManager.authorizedClient else { fatalError() }
-
-        return try await withCheckedThrowingContinuation { continuation in
-            client.files.upload(path: "/backup/backup.xxm", mode: .overwrite, input: data)
-                .response { response, error in
-                    if let error = error {
-                        let err = NSError(domain: error.description, code: 0)
-                        continuation.resume(throwing: err)
-                        return
-                    }
-
-                    if let response = response {
-                        continuation.resume(returning: .init(
-                            size: Float(response.size),
-                            path: response.pathLower!,
-                            modifiedDate: response.serverModified
-                        ))
-                    }
-                }
-        }
-    }
-}
diff --git a/Sources/DropboxFeature/DropboxServiceMock.swift b/Sources/DropboxFeature/DropboxServiceMock.swift
deleted file mode 100644
index ba9654b69f870fb28b49cd8414676375fcb866ba..0000000000000000000000000000000000000000
--- a/Sources/DropboxFeature/DropboxServiceMock.swift
+++ /dev/null
@@ -1,22 +0,0 @@
-import UIKit
-import Combine
-
-public struct DropboxServiceMock: DropboxInterface {
-    public init() {}
-
-    public func unlink() {}
-
-    public func isAuthorized() -> Bool { true }
-
-    public func handleOpenUrl(_ url: URL) -> Bool { true }
-
-    public func didFinishAuthFlow(withError: String?) {}
-
-    public func downloadBackup(_: String, _: @escaping (Result<Data, Error>) -> Void) {}
-
-    public func uploadBackup(_: URL, _: @escaping (Result<DropboxMetadata, Error>) -> Void) {}
-
-    public func downloadMetadata(_: @escaping (Result<DropboxMetadata?, Error>) -> Void) {}
-
-    public func authorize(presenting: UIViewController) -> AnyPublisher<Result<Bool, Error>, Never> { fatalError() }
-}
diff --git a/Sources/DropboxFeature/Resources/Dropbox-Keys.plist b/Sources/DropboxFeature/Resources/Dropbox-Keys.plist
deleted file mode 100644
index ecf15d0188386cac204a2e243e59320620a6c9c5..0000000000000000000000000000000000000000
--- a/Sources/DropboxFeature/Resources/Dropbox-Keys.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>DROPBOX_APP_KEY</key>
-	<string></string>
-</dict>
-</plist>
diff --git a/Sources/FetchBannedList/Dependency.swift b/Sources/FetchBannedList/Dependency.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8eb9ad1696a663a47bcb257e3b71d09fc6225e11
--- /dev/null
+++ b/Sources/FetchBannedList/Dependency.swift
@@ -0,0 +1,14 @@
+import Dependencies
+
+private enum FetchBannedListDependencyKey: DependencyKey {
+  static let liveValue: FetchBannedList = .live
+  static let testValue: FetchBannedList = .unimplemented
+}
+
+extension DependencyValues {
+  public var fetchBannedList: FetchBannedList {
+    get { self[FetchBannedListDependencyKey.self] }
+    set { self[FetchBannedListDependencyKey.self] = newValue }
+  }
+}
+
diff --git a/Sources/FetchBannedList/FetchBannedList.swift b/Sources/FetchBannedList/FetchBannedList.swift
new file mode 100644
index 0000000000000000000000000000000000000000..9315cd589c431e3ed010d34e7c321f2dc9c5c787
--- /dev/null
+++ b/Sources/FetchBannedList/FetchBannedList.swift
@@ -0,0 +1,46 @@
+import Foundation
+import XCTestDynamicOverlay
+
+public struct FetchBannedList {
+  public enum Error: Swift.Error, Equatable {
+    case network(URLError)
+    case invalidResponse
+  }
+
+  public typealias Completion = (Result<Data, Error>) -> Void
+
+  public var run: (@escaping Completion) -> Void
+
+  public func callAsFunction(completion: @escaping Completion) {
+    run(completion)
+  }
+}
+
+extension FetchBannedList {
+  public static let live = FetchBannedList { completion in
+    let url = URL(string: "https://elixxir-bins.s3.us-west-1.amazonaws.com/client/bannedUsers/banned.csv")!
+    let session = URLSession.shared
+    let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData)
+    let task = session.dataTask(with: request) { data, response, error in
+      if let error = error {
+        completion(.failure(.network(error as! URLError)))
+        return
+      }
+      guard let response = response as? HTTPURLResponse,
+            (200..<300).contains(response.statusCode),
+            let data = data
+      else {
+        completion(.failure(.invalidResponse))
+        return
+      }
+      completion(.success(data))
+    }
+    task.resume()
+  }
+}
+
+extension FetchBannedList {
+  public static let unimplemented = FetchBannedList(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/GoogleDriveFeature/GoogleDriveInterface.swift b/Sources/GoogleDriveFeature/GoogleDriveInterface.swift
deleted file mode 100644
index c6710b8610fc9081e211fe0363f10f40584ea0e6..0000000000000000000000000000000000000000
--- a/Sources/GoogleDriveFeature/GoogleDriveInterface.swift
+++ /dev/null
@@ -1,13 +0,0 @@
-import UIKit
-
-public protocol GoogleDriveInterface {
-    func isAuthorized(_: @escaping (Bool) -> Void)
-
-    func downloadMetadata(_: @escaping (Result<GoogleDriveMetadata?, Error>) -> Void)
-
-    func uploadBackup(_: URL, _: @escaping (Result<GoogleDriveMetadata, Error>) -> Void)
-
-    func authorize(presenting: UIViewController, _: @escaping (Result<Void, Error>) -> Void)
-
-    func downloadBackup(_: String, progressCallback: @escaping (Float) -> Void, _: @escaping (Result<Data, Error>) -> Void)
-}
diff --git a/Sources/GoogleDriveFeature/GoogleDriveMetadata.swift b/Sources/GoogleDriveFeature/GoogleDriveMetadata.swift
deleted file mode 100644
index f4179db86b943af99a0eaacdcae32ea987bdd73f..0000000000000000000000000000000000000000
--- a/Sources/GoogleDriveFeature/GoogleDriveMetadata.swift
+++ /dev/null
@@ -1,27 +0,0 @@
-import GoogleAPIClientForREST_Drive
-
-public struct GoogleDriveMetadata: Equatable {
-    public var size: Float
-    public var identifier: String
-    public var modifiedDate: Date
-
-    public init(
-        size: Float,
-        identifier: String,
-        modifiedDate: Date
-    ) {
-        self.size = size
-        self.identifier = identifier
-        self.modifiedDate = modifiedDate
-    }
-}
-
-extension GoogleDriveMetadata {
-    init?(withDriveFile file: GTLRDrive_File) {
-        guard let size = file.size?.floatValue,
-              let identifier = file.identifier,
-              let modifiedDate = file.modifiedTime?.date else { return nil }
-
-        self.init(size: size, identifier: identifier, modifiedDate: modifiedDate)
-    }
-}
diff --git a/Sources/GoogleDriveFeature/GoogleDriveService.swift b/Sources/GoogleDriveFeature/GoogleDriveService.swift
deleted file mode 100644
index 4ae4ac7457058d11ab3777cd71a4e8843f33458c..0000000000000000000000000000000000000000
--- a/Sources/GoogleDriveFeature/GoogleDriveService.swift
+++ /dev/null
@@ -1,335 +0,0 @@
-import UIKit
-import GoogleSignIn
-import GTMSessionFetcherFull
-import GTMSessionFetcherCore
-import GoogleAPIClientForREST_Drive
-
-public final class GoogleDriveService: GoogleDriveInterface {
-    private static let scopeFile = "https://www.googleapis.com/auth/drive.file"
-    private static let scopeAppData = "https://www.googleapis.com/auth/drive.appdata"
-
-    var user: GIDGoogleUser?
-
-    let service: GTLRDriveService = {
-        let service = GTLRDriveService()
-
-        let path = Bundle.module.path(forResource: "GoogleDrive-Keys", ofType: "plist")
-        let url = URL(fileURLWithPath: path!)
-        let keys = try! NSDictionary(contentsOf: url, error: ())
-
-        service.apiKey = keys["DRIVE_API_KEY"] as? String
-        return service
-    }()
-
-    public init() {}
-
-    public func isAuthorized(_ completion: @escaping (Bool) -> Void) {
-        guard GIDSignIn.sharedInstance.hasPreviousSignIn() else {
-            return completion(false)
-        }
-
-        GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
-            guard let user = user, let scopes = user.grantedScopes, error == nil else {
-                return completion(false)
-            }
-
-            self.user = user
-            self.service.authorizer = user.authentication.fetcherAuthorizer()
-            completion(scopes.contains(GoogleDriveService.scopeFile) && scopes.contains(GoogleDriveService.scopeAppData))
-        }
-    }
-
-    public func authorize(
-        presenting controller: UIViewController,
-        _ completion: @escaping (Result<Void, Error>) -> Void
-    ) {
-        GIDSignIn.sharedInstance.restorePreviousSignIn { [weak self] user, error in
-            guard let self = self else { return }
-
-            guard error == nil else {
-                self.signIn(presenting: controller) {
-                    switch $0 {
-                    case .success:
-                        self.authorizeDrive(controller: controller, completion: completion)
-                    case .failure(let error):
-                        completion(.failure(error))
-                    }
-                }
-
-                return
-            }
-
-            guard let user = user else { fatalError() }
-
-            self.user = user
-            self.service.authorizer = user.authentication.fetcherAuthorizer()
-            self.authorizeDrive(controller: controller, completion: completion)
-        }
-    }
-
-    public func downloadMetadata(_ completion: @escaping (Result<GoogleDriveMetadata?, Error>) -> Void) {
-        Task {
-            do {
-                guard let folder = try await fetchFolder() else {
-                    completion(.success(nil))
-                    return
-                }
-
-                _ = try await listFiles(on: folder)
-
-                let backup = try await fetchBackup(at: folder)
-                completion(.success(backup))
-            } catch {
-                completion(.failure(error))
-            }
-        }
-    }
-
-    public func downloadBackup(
-        _ backup: String,
-        progressCallback: @escaping (Float) -> Void,
-        _ completion: @escaping (Result<Data, Error>) -> Void
-    ) {
-        let query = GTLRDriveQuery_FilesGet.queryForMedia(withFileId: backup)
-        service.executeQuery(query) { _, file, error in
-            guard error == nil else {
-                print("Error on line #\(#line): \(error!.localizedDescription)")
-                return completion(.failure(error!))
-            }
-
-            guard let data = (file as? GTLRDataObject)?.data else {
-                print("Error on line #\(#line)")
-                return completion(.failure(NSError()))
-            }
-
-            completion(.success(data))
-        }
-    }
-
-    public func uploadBackup(
-        _ file: URL,
-        _ completion: @escaping (Result<GoogleDriveMetadata, Error>) -> Void
-    ) {
-        Task {
-            do {
-                var folder = try await fetchFolder()
-                if folder == nil { folder = try await createFolder() }
-                let metadata = try await uploadFile(file, to: folder!)
-                let listMetadata = try await listFiles(on: folder!)
-                try await cleanup(listMetadata)
-                completion(.success(metadata))
-            } catch {
-                print("Error on line #\(#line): \(error.localizedDescription)")
-                completion(.failure(error))
-            }
-        }
-    }
-}
-
-extension GoogleDriveService {
-    private func authorizeDrive(
-        controller: UIViewController,
-        completion: @escaping (Result<Void, Error>) -> Void
-    ) {
-        if let user = user,
-           let scopes = user.grantedScopes,
-           scopes.contains(GoogleDriveService.scopeFile),
-           scopes.contains(GoogleDriveService.scopeAppData) {
-            return completion(.success(()))
-        }
-
-        GIDSignIn.sharedInstance.addScopes(
-            [GoogleDriveService.scopeFile, GoogleDriveService.scopeAppData],
-            presenting: controller, callback: { user, error in
-                guard error == nil else {
-                    print("Error on line #\(#line): \(error!.localizedDescription)")
-                    return completion(.failure(error!))
-                }
-
-                guard let user = user else { fatalError() }
-                self.user = user
-                completion(.success(()))
-            }
-        )
-    }
-
-    private func signIn(
-        presenting controller: UIViewController,
-        completion: @escaping (Result<Void, Error>) -> Void
-    ) {
-        GIDSignIn.sharedInstance.signIn(
-            with: GIDConfiguration(clientID: "662236151640-30i07ubg6ukodg15u0bnpk322p030u3j.apps.googleusercontent.com"),
-            presenting: controller,
-            callback: { user, error in
-                guard error == nil else {
-                    print("Error on line #\(#line): \(error!.localizedDescription)")
-                    return completion(.failure(error!))
-                }
-
-                guard let user = user else { fatalError() }
-
-                self.user = user
-                self.service.authorizer = user.authentication.fetcherAuthorizer()
-                completion(.success(()))
-            }
-        )
-    }
-
-    private func fetchFolder() async throws -> String? {
-        let query = GTLRDriveQuery_FilesList.query()
-        query.q = "mimeType = 'application/vnd.google-apps.folder' and name = 'backup'"
-        query.spaces = "appDataFolder"
-        query.fields = "nextPageToken, files(id, name)"
-
-        return try await withCheckedThrowingContinuation { continuation in
-            service.executeQuery(query) { _, result, error in
-                if let error = error {
-                    print("Error on line #\(#line): \(error.localizedDescription)")
-                    continuation.resume(throwing: error)
-                    return
-                }
-
-                let item = (result as? GTLRDrive_FileList)?.files?.first
-                continuation.resume(returning: item?.identifier)
-            }
-        }
-    }
-
-    private func fetchBackup(at folder: String) async throws -> GoogleDriveMetadata? {
-        let query = GTLRDriveQuery_FilesList.query()
-        query.q = "'\(folder)' in parents and name = 'backup.xxm'"
-        query.spaces = "appDataFolder"
-        query.fields = "nextPageToken, files(id, size, name, modifiedTime)"
-
-        return try await withCheckedThrowingContinuation { continuation in
-            service.executeQuery(query) { _, result, error in
-                if let error = error {
-                    print("Error on line #\(#line): \(error.localizedDescription)")
-                    continuation.resume(throwing: error)
-                    return
-                }
-
-                var metadata: GoogleDriveMetadata? = nil
-
-                if let file = (result as? GTLRDrive_FileList)?.files?.first,
-                   let size = file.size,
-                   let id = file.identifier,
-                   let date = file.modifiedTime?.date {
-                    metadata = GoogleDriveMetadata(size: size.floatValue, identifier: id, modifiedDate: date)
-                }
-
-                continuation.resume(returning: metadata)
-            }
-        }
-    }
-
-    private func createFolder() async throws -> String {
-        let file = GTLRDrive_File()
-        file.name = "backup"
-        file.parents = ["appDataFolder"]
-        file.mimeType = "application/vnd.google-apps.folder"
-
-        let query = GTLRDriveQuery_FilesCreate.query(withObject: file, uploadParameters: nil)
-
-        return try await withCheckedThrowingContinuation { continuation in
-            service.executeQuery(query) { _, result, error in
-                if let error = error {
-                    print("Error on line #\(#line): \(error.localizedDescription)")
-                    continuation.resume(throwing: error)
-                    return
-                }
-
-                guard let identifier = (result as? GTLRDrive_File)?.identifier else {
-                    let errorTitle = "Couldn't create backup folder but no error was passed (?)"
-                    let error = NSError(domain: errorTitle, code: 0, userInfo: [NSLocalizedDescriptionKey: errorTitle])
-                    print("Error on line #\(#line): \(error.localizedDescription)")
-                    continuation.resume(throwing: error)
-                    return
-                }
-
-                continuation.resume(returning: identifier)
-            }
-        }
-    }
-
-    private func uploadFile(
-        _ fileURL: URL,
-        to folder: String
-    ) async throws -> GoogleDriveMetadata {
-
-        let file = GTLRDrive_File()
-        file.name = "backup.xxm"
-        file.parents = [folder]
-        file.mimeType = "application/octet-stream"
-
-        let params = GTLRUploadParameters(fileURL: fileURL, mimeType: file.mimeType!)
-        let query = GTLRDriveQuery_FilesCreate.query(withObject: file, uploadParameters: params)
-        query.fields = "id, size, modifiedTime"
-
-        return try await withCheckedThrowingContinuation { continuation in
-            service.executeQuery(query) { _, result, error in
-                if let error = error {
-                    print("Error on line #\(#line): \(error.localizedDescription)")
-                    continuation.resume(throwing: error)
-                    return
-                }
-
-                guard let driveFile = (result as? GTLRDrive_File),
-                      let size = driveFile.size,
-                      let id = driveFile.identifier,
-                      let date = driveFile.modifiedTime?.date else {
-                    let errorTitle = "Couldn't upload file but no error was passed (?)"
-                    let error = NSError(domain: errorTitle, code: 0, userInfo: [NSLocalizedDescriptionKey: errorTitle])
-                    print("Error on line #\(#line): \(error.localizedDescription)")
-                    continuation.resume(throwing: error)
-                    return
-                }
-
-                continuation.resume(returning: .init(size: size.floatValue, identifier: id, modifiedDate: date))
-            }
-        }
-    }
-
-    private func listFiles(on folder: String) async throws -> [GoogleDriveMetadata] {
-        let query = GTLRDriveQuery_FilesList.query()
-        query.q = "'\(folder)' in parents"
-        query.spaces = "appDataFolder"
-        query.fields = "nextPageToken, files(id, modifiedTime, size, name)"
-
-        return try await withCheckedThrowingContinuation { continuation in
-            service.executeQuery(query) { _, result, error in
-                if let error = error {
-                    print("Error on line #\(#line): \(error.localizedDescription)")
-                    continuation.resume(throwing: error)
-                    return
-                }
-
-                guard let files = (result as? GTLRDrive_FileList)?.files else {
-                    continuation.resume(returning: [])
-                    return
-                }
-
-                let metadataList = files.compactMap(GoogleDriveMetadata.init(withDriveFile:))
-                continuation.resume(returning: metadataList)
-            }
-        }
-    }
-
-    private func cleanup(_ files: [GoogleDriveMetadata]) async throws {
-        let latestBackup = files.max { $0.modifiedDate < $1.modifiedDate }
-        let identifiers = files.filter { $0 != latestBackup }.map(\.identifier)
-        let query = GTLRBatchQuery(queries: identifiers.map(GTLRDriveQuery_FilesDelete.query(withFileId:)))
-
-        return try await withCheckedThrowingContinuation { continuation in
-            service.executeQuery(query) { _, _, error in
-                if let error = error {
-                    print("Error on line #\(#line): \(error.localizedDescription)")
-                    continuation.resume(throwing: error)
-                    return
-                }
-
-                continuation.resume(returning: ())
-            }
-        }
-    }
-}
diff --git a/Sources/GoogleDriveFeature/GoogleDriveServiceMock.swift b/Sources/GoogleDriveFeature/GoogleDriveServiceMock.swift
deleted file mode 100644
index cf7625355cbc778fca29eb245586079b1601db6f..0000000000000000000000000000000000000000
--- a/Sources/GoogleDriveFeature/GoogleDriveServiceMock.swift
+++ /dev/null
@@ -1,40 +0,0 @@
-import UIKit
-
-public final class GoogleDriveServiceMock: GoogleDriveInterface {
-    public init() {}
-
-    public func isAuthorized(_ completion: @escaping (Bool) -> Void) {
-        completion(true)
-    }
-
-    public func uploadBackup(_: URL, _ completion: @escaping (Result<GoogleDriveMetadata, Error>) -> Void) {
-        completion(.success(.init(size: 23.toBytes(), identifier: "", modifiedDate: Date())))
-    }
-
-    public func downloadMetadata(_ completion: @escaping (Result<GoogleDriveMetadata?, Error>) -> Void) {
-        completion(.success(.init(size: 23.toBytes(), identifier: "", modifiedDate: Date())))
-    }
-
-    public func authorize(presenting: UIViewController, _ completion: @escaping (Result<Void, Error>) -> Void) {
-        completion(.success(()))
-    }
-
-    public func downloadBackup(
-        _: String,
-        progressCallback: @escaping (Float) -> Void,
-        _ completion: @escaping (Result<Data, Error>) -> Void
-    ) {
-        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { progressCallback(3.toBytes()) }
-        DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) { progressCallback(7.toBytes()) }
-        DispatchQueue.main.asyncAfter(deadline: .now() + 0.9) { progressCallback(12.toBytes()) }
-        DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) { progressCallback(15.toBytes()) }
-        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { progressCallback(16.toBytes()) }
-        DispatchQueue.main.asyncAfter(deadline: .now() + 1.8) { progressCallback(19.toBytes()) }
-        DispatchQueue.main.asyncAfter(deadline: .now() + 2.1) { progressCallback(22.toBytes()) }
-        DispatchQueue.main.asyncAfter(deadline: .now() + 2.4) { completion(.success(Data())) }
-    }
-}
-
-private extension Int {
-    func toBytes() -> Float { Float(self) * 1000000.0 }
-}
diff --git a/Sources/GoogleDriveFeature/Resources/GoogleDrive-Keys.plist b/Sources/GoogleDriveFeature/Resources/GoogleDrive-Keys.plist
deleted file mode 100644
index 614bac60d3fd5014a33613ee3e83c72656d0854d..0000000000000000000000000000000000000000
--- a/Sources/GoogleDriveFeature/Resources/GoogleDrive-Keys.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>DRIVE_API_KEY</key>
-	<string></string>
-</dict>
-</plist>
diff --git a/Sources/GroupDraftFeature/GroupDraftCollectionCell.swift b/Sources/GroupDraftFeature/GroupDraftCollectionCell.swift
new file mode 100644
index 0000000000000000000000000000000000000000..cdcb1393c7460df0410e352ca7ab3347d359dbaf
--- /dev/null
+++ b/Sources/GroupDraftFeature/GroupDraftCollectionCell.swift
@@ -0,0 +1,83 @@
+import UIKit
+import Shared
+import Combine
+import AppResources
+
+final class GroupDraftCollectionCell: UICollectionViewCell {
+  let titleLabel = UILabel()
+  let removeButton = UIButton()
+  let upperView = UIView()
+  let avatarView = AvatarView()
+  
+  var didTapRemove: (() -> Void)?
+  var cancellables = Set<AnyCancellable>()
+  
+  override init(frame: CGRect) {
+    super.init(frame: frame)
+    
+    titleLabel.numberOfLines = 2
+    titleLabel.lineBreakMode = .byWordWrapping
+    titleLabel.textAlignment = .center
+    titleLabel.textColor = Asset.neutralDark.color
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    
+    removeButton.layer.cornerRadius = 9
+    removeButton.backgroundColor = Asset.accentDanger.color
+    removeButton.setImage(Asset.contactListAvatarRemove.image, for: .normal)
+    
+    upperView.addSubview(avatarView)
+    contentView.addSubview(titleLabel)
+    contentView.addSubview(upperView)
+    contentView.addSubview(removeButton)
+    
+    upperView.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+    }
+    
+    avatarView.snp.makeConstraints {
+      $0.width.equalTo(48)
+      $0.height.equalTo(48)
+      $0.top.equalToSuperview().offset(4)
+      $0.left.equalToSuperview().offset(4)
+      $0.right.equalToSuperview().offset(-4)
+      $0.bottom.equalToSuperview().offset(-4)
+    }
+    
+    removeButton.snp.makeConstraints {
+      $0.centerY.equalTo(avatarView.snp.top).offset(5)
+      $0.centerX.equalTo(avatarView.snp.right).offset(-5)
+      $0.width.equalTo(18)
+      $0.height.equalTo(18)
+    }
+    
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(upperView.snp.bottom)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  override func prepareForReuse() {
+    super.prepareForReuse()
+    titleLabel.text = nil
+    avatarView.prepareForReuse()
+    cancellables.removeAll()
+  }
+  
+  func setup(title: String, image: Data?) {
+    titleLabel.text = title
+    avatarView.setupProfile(title: title, image: image, size: .large)
+    cancellables.removeAll()
+    
+    removeButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        didTapRemove?()
+      }.store(in: &cancellables)
+  }
+}
diff --git a/Sources/GroupDraftFeature/GroupDraftController.swift b/Sources/GroupDraftFeature/GroupDraftController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..27dd858fa3abc733e288f4495d53f0aaad629741
--- /dev/null
+++ b/Sources/GroupDraftFeature/GroupDraftController.swift
@@ -0,0 +1,191 @@
+import UIKit
+import Shared
+import Combine
+import XXModels
+import AppResources
+import Dependencies
+import AppNavigation
+
+public final class GroupDraftController: UIViewController {
+  @Dependency(\.navigator) var navigator: Navigator
+
+  private lazy var titleLabel = UILabel()
+  private lazy var createButton = UIButton()
+  private lazy var screenView = GroupDraftView()
+
+  private var selectedElements = [Contact]() {
+    didSet { screenView.tableView.reloadData() }
+  }
+  private let viewModel = GroupDraftViewModel()
+  private var cancellables = Set<AnyCancellable>()
+  private var tableDataSource: UITableViewDiffableDataSource<SectionId, Contact>!
+  private var collectionDataSource: UICollectionViewDiffableDataSource<SectionId, Contact>!
+
+  private var count = 0 {
+    didSet {
+      createButton.isEnabled = count >= 2 && count <= 10
+
+      let text = Localized.CreateGroup.title("\(count)")
+      let attString = NSMutableAttributedString(string: text)
+      attString.addAttribute(.font, value: Fonts.Mulish.semiBold.font(size: 18.0) as Any)
+      attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
+      attString.addAttributes(attributes: [
+        .foregroundColor: Asset.neutralDisabled.color,
+        .font: Fonts.Mulish.regular.font(size: 14.0) as Any
+      ], betweenCharacters: "#")
+
+      titleLabel.attributedText = attString
+      titleLabel.sizeToFit()
+    }
+  }
+
+  public override func loadView() {
+    view = screenView
+  }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    navigationController?.navigationBar
+      .customize(backgroundColor: Asset.neutralWhite.color)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupNavigationBar()
+    setupTableAndCollection()
+    setupBindings()
+
+    count = 0
+  }
+
+  private func setupNavigationBar() {
+    navigationItem.leftBarButtonItem = UIBarButtonItem(customView: titleLabel)
+    navigationItem.leftItemsSupplementBackButton = true
+
+    createButton.setTitle(Localized.CreateGroup.create, for: .normal)
+    createButton.setTitleColor(Asset.brandPrimary.color, for: .normal)
+    createButton.titleLabel?.font = Fonts.Mulish.semiBold.font(size: 16.0)
+    createButton.setTitleColor(Asset.neutralDisabled.color, for: .disabled)
+    navigationItem.rightBarButtonItem = UIBarButtonItem(customView: createButton)
+  }
+
+  private func setupTableAndCollection() {
+    screenView.tableView.rowHeight = 64.0
+    screenView.tableView.register(AvatarCell.self)
+    screenView.collectionView.register(GroupDraftCollectionCell.self)
+
+    collectionDataSource = UICollectionViewDiffableDataSource<SectionId, Contact>(
+      collectionView: screenView.collectionView
+    ) { [weak viewModel] collectionView, indexPath, contact in
+      let cell: GroupDraftCollectionCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+
+      let title = (contact.nickname ?? contact.username) ?? ""
+      cell.setup(title: title, image: contact.photo)
+      cell.didTapRemove = { viewModel?.didSelect(contact: contact) }
+
+      return cell
+    }
+
+    tableDataSource = DiffEditableDataSource<SectionId, Contact>(
+      tableView: screenView.tableView
+    ) { [weak self] tableView, indexPath, contact in
+      let cell = tableView.dequeueReusableCell(forIndexPath: indexPath, ofType: AvatarCell.self)
+      let title = (contact.nickname ?? contact.username) ?? ""
+
+      cell.setup(title: title, image: contact.photo)
+
+      if let selectedElements = self?.selectedElements, selectedElements.contains(contact) {
+        tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
+      } else {
+        tableView.deselectRow(at: indexPath, animated: true)
+      }
+
+      return cell
+    }
+
+    screenView.tableView.delegate = self
+    screenView.tableView.dataSource = tableDataSource
+    screenView.collectionView.dataSource = collectionDataSource
+  }
+
+  private func setupBindings() {
+    let selected = viewModel.selected.share()
+
+    selected
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.collectionView.isHidden = $0.count < 1
+
+        count = $0.count
+        selectedElements = $0
+      }.store(in: &cancellables)
+
+    selected.map { selectedContacts in
+      var snapshot = NSDiffableDataSourceSnapshot<SectionId, Contact>()
+      let sections = [SectionId()]
+      snapshot.appendSections(sections)
+      sections.forEach { section in snapshot.appendItems(selectedContacts, toSection: section) }
+      return snapshot
+    }
+    .receive(on: DispatchQueue.main)
+    .sink { [unowned self] in collectionDataSource.apply($0) }
+    .store(in: &cancellables)
+
+    viewModel
+      .contacts
+      .map { contacts in
+        var snapshot = NSDiffableDataSourceSnapshot<SectionId, Contact>()
+        let sections = [SectionId()]
+        snapshot.appendSections(sections)
+        sections.forEach { section in snapshot.appendItems(contacts, toSection: section) }
+        return snapshot
+      }
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        tableDataSource.apply($0, animatingDifferences: tableDataSource.snapshot().numberOfItems > 0)
+      }.store(in: &cancellables)
+
+    screenView
+      .searchComponent
+      .textPublisher
+      .removeDuplicates()
+      .sink { [unowned self] in
+        viewModel.filter($0)
+      }.store(in: &cancellables)
+
+    viewModel
+      .info
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentGroupChat(
+          groupInfo: $0,
+          on: navigationController!
+        ))
+      }.store(in: &cancellables)
+
+    createButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentCreateGroup(
+          members: selectedElements,
+          from: self
+        ))
+      }.store(in: &cancellables)
+  }
+}
+
+extension GroupDraftController: UITableViewDelegate {
+  public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+    if let contact = tableDataSource.itemIdentifier(for: indexPath) {
+      viewModel.didSelect(contact: contact)
+    }
+  }
+
+  public func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
+    if let contact = tableDataSource.itemIdentifier(for: indexPath) {
+      viewModel.didSelect(contact: contact)
+    }
+  }
+}
diff --git a/Sources/GroupDraftFeature/GroupDraftView.swift b/Sources/GroupDraftFeature/GroupDraftView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..972cd50602ddac171f203a01b4fef82d04ab8a37
--- /dev/null
+++ b/Sources/GroupDraftFeature/GroupDraftView.swift
@@ -0,0 +1,65 @@
+import UIKit
+import Shared
+import SnapKit
+import AppResources
+
+final class GroupDraftView: UIView {
+  let stackView = UIStackView()
+  let tableView = UITableView()
+  let searchComponent = SearchComponent()
+  lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
+  
+  let layout: UICollectionViewFlowLayout = {
+    let layout = UICollectionViewFlowLayout()
+    layout.minimumInteritemSpacing = 45
+    layout.itemSize = CGSize(width: 56, height: 100)
+    layout.scrollDirection = .horizontal
+    return layout
+  }()
+  
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+    
+    tableView.separatorStyle = .none
+    tableView.tintColor = Asset.brandPrimary.color
+    tableView.backgroundColor = Asset.neutralWhite.color
+    tableView.allowsMultipleSelectionDuringEditing = true
+    tableView.setEditing(true, animated: true)
+    
+    searchComponent.set(
+      placeholder: "Search connections",
+      imageAtRight: UIImage.color(.clear)
+    )
+    
+    collectionView.backgroundColor = Asset.neutralWhite.color
+    collectionView.contentInset = UIEdgeInsets(top: 0, left: 30, bottom: 0, right: 30)
+    
+    stackView.spacing = 31
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(collectionView)
+    stackView.addArrangedSubview(tableView)
+    
+    addSubview(stackView)
+    addSubview(searchComponent)
+    
+    searchComponent.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(20)
+      $0.left.equalToSuperview().offset(20)
+      $0.right.equalToSuperview().offset(-20)
+    }
+    
+    stackView.snp.makeConstraints {
+      $0.top.equalTo(searchComponent.snp.bottom).offset(20)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
+    
+    collectionView.snp.makeConstraints {
+      $0.height.equalTo(100)
+    }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+}
diff --git a/Sources/GroupDraftFeature/GroupDraftViewModel.swift b/Sources/GroupDraftFeature/GroupDraftViewModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..476dd69ad26eaab2de5a17ed501289f830799374
--- /dev/null
+++ b/Sources/GroupDraftFeature/GroupDraftViewModel.swift
@@ -0,0 +1,76 @@
+import Combine
+import AppCore
+import XXModels
+import Defaults
+import Foundation
+import Dependencies
+import ReportingFeature
+
+final class GroupDraftViewModel {
+  @Dependency(\.app.bgQueue) var bgQueue
+  @Dependency(\.app.dbManager) var dbManager
+  @Dependency(\.app.messenger) var messenger
+  @Dependency(\.reportingStatus) var reportingStatus
+
+  @KeyObject(.username, defaultValue: "") var username: String
+
+  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()
+  }
+
+  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
+    )
+
+    try! dbManager.getDB().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)
+  }
+
+  func didSelect(contact: XXModels.Contact) {
+    if selectedContactsRelay.value.contains(contact) {
+      selectedContactsRelay.value.removeAll { $0.username == contact.username }
+    } else {
+      selectedContactsRelay.value.append(contact)
+    }
+  }
+
+  func filter(_ text: String) {
+    guard text.isEmpty == false else {
+      contactsRelay.send(allContacts)
+      return
+    }
+
+    contactsRelay.send(
+      allContacts.filter {
+        ($0.username ?? "").contains(text.lowercased())
+      }
+    )
+  }
+}
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 207f3473c9173a3a64c8636a8373d734d207807b..0000000000000000000000000000000000000000
--- a/Sources/HUD/HUD.swift
+++ /dev/null
@@ -1,195 +0,0 @@
-import UIKit
-import Theme
-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 = Window()
-        window?.backgroundColor = UIColor.black.withAlphaComponent(0.8)
-        window?.rootViewController = StatusBarViewController(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/InputField/InputField.swift b/Sources/InputField/InputField.swift
index e041db906a504c4a1e6295c84e14a456a18b357c..f0be951d47dbeeecebeb8abd5a3d56eb13c6e31c 100644
--- a/Sources/InputField/InputField.swift
+++ b/Sources/InputField/InputField.swift
@@ -1,351 +1,350 @@
 import UIKit
 import Shared
 import Combine
+import AppResources
 
 public final class InputField: UIView {
-    public enum Style {
-        case phone
-        case regular
+  public enum Style {
+    case phone
+    case regular
+  }
+
+  public enum LeftView {
+    case image(UIImage)
+  }
+
+  public enum RightView {
+    case image(UIImage)
+    case toggleSecureEntry
+  }
+
+  public enum ValidationStatus: Equatable {
+    case valid(String?)
+    case invalid(String)
+    case unknown(String?)
+  }
+
+  let title = UILabel()
+  let hide = UIButton()
+  let clear = UIButton()
+  let subtitle = UILabel()
+
+  let outerStack = UIStackView()
+  let codeContainer = UIView()
+  let code = PhoneCodeField()
+
+  let container = UIView()
+  let innerStack = UIStackView()
+  let left = UIImageView()
+  let field = UITextField()
+
+  let toolbar = UIToolbar()
+  let toolbarButton = UIButton()
+
+  var isPhone: Bool = false
+
+  // MARK: Properties
+
+  private var rightView: RightView? = .none {
+    didSet { set(rightView: rightView) }
+  }
+
+  private var clearable: Bool = false
+  private var allowsEmptySpace: Bool = true
+  private var cancellables = Set<AnyCancellable>()
+
+  private let codeSubject = PassthroughSubject<Void, Never>()
+  private let returnSubject = PassthroughSubject<Void, Never>()
+  private let textSubject = PassthroughSubject<String, Never>()
+
+  public var codePublisher: AnyPublisher<Void, Never> { codeSubject.eraseToAnyPublisher() }
+  public var textPublisher: AnyPublisher<String, Never> { textSubject.eraseToAnyPublisher() }
+  public var returnPublisher: AnyPublisher<Void, Never> { returnSubject.eraseToAnyPublisher() }
+
+  public init() {
+    super.init(frame: .zero)
+    setup()
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public func makeFirstResponder() {
+    field.becomeFirstResponder()
+  }
+
+  public func setup(
+    style: Style = .regular,
+    title: String? = nil,
+    placeholder: String? = nil,
+    leftView: LeftView? = nil,
+    rightView: RightView? = nil,
+    accessibility: String? = nil,
+    subtitleAccessibility: String? = nil,
+    subtitleColor: UIColor = Asset.neutralWhite.color,
+    allowsEmptySpace: Bool = true,
+    keyboardType: UIKeyboardType = .default,
+    autocapitalization: UITextAutocapitalizationType = .sentences,
+    autoCorrect: UITextAutocorrectionType = .no,
+    contentType: UITextContentType? = nil,
+    returnKeyType: UIReturnKeyType = .done,
+    toolbarButtonTitle: String = Localized.Shared.done,
+    codeAccessibility: String? = nil,
+    clearable: Bool = false
+  ) {
+    self.title.text = title
+    self.set(leftView: leftView)
+
+    self.rightView = rightView
+    self.field.attributedPlaceholder = NSAttributedString(
+      string: placeholder ?? "",
+      attributes: [
+        .font: Fonts.Mulish.semiBold.font(size: 14.0),
+        .foregroundColor: Asset.neutralDisabled.color
+      ])
+
+    if contentType == .telephoneNumber {
+      isPhone = true
+    } else {
+      self.field.textContentType = contentType
     }
 
-    public enum LeftView {
-        case image(UIImage)
+    self.field.returnKeyType = returnKeyType
+    self.field.keyboardType = keyboardType
+    self.subtitle.textColor = subtitleColor
+    self.allowsEmptySpace = allowsEmptySpace
+    self.field.autocorrectionType = autoCorrect
+    self.field.accessibilityIdentifier = accessibility
+    self.field.autocapitalizationType = autocapitalization
+    self.subtitle.accessibilityIdentifier = subtitleAccessibility
+    self.clearable = clearable
+
+    if style == .phone {
+      codeContainer.addSubview(code)
+      code.accessibilityIdentifier = codeAccessibility
+      code.snp.makeConstraints { $0.edges.equalToSuperview() }
+      outerStack.insertArrangedSubview(codeContainer, at: 0)
+
+      code.publisher(for: .touchUpInside)
+        .sink { [weak codeSubject] in codeSubject?.send() }
+        .store(in: &cancellables)
+
+      self.field.keyboardType = .numberPad
+      self.allowsEmptySpace = false
+
+      toolbar.barTintColor = Asset.neutralWhite.color
+      toolbarButton.setTitle(toolbarButtonTitle, for: .normal)
+      toolbarButton.setTitleColor(Asset.brandPrimary.color, for: .normal)
+      toolbarButton.titleLabel?.font = Fonts.Mulish.bold.font(size: 17.0)
+      toolbar.setShadowImage(.color(Asset.neutralLine.color), forToolbarPosition: .any)
+      toolbarButton.addTarget(self, action: #selector(didTapDone), for: .touchUpInside)
+      toolbar.items = [UIBarButtonItem(customView: toolbarButton.pinning(at: .right(0)))]
+
+      toolbar.sizeToFit()
+      self.field.inputAccessoryView = toolbar
     }
-
-    public enum RightView {
-        case image(UIImage)
-        case toggleSecureEntry
-    }
-
-    public enum ValidationStatus: Equatable {
-        case valid(String?)
-        case invalid(String)
-        case unknown(String?)
-    }
-
-    let title = UILabel()
-    let hide = UIButton()
-    let clear = UIButton()
-    let subtitle = UILabel()
-
-    let outerStack = UIStackView()
-    let codeContainer = UIView()
-    let code = PhoneCodeField()
-
-    let container = UIView()
-    let innerStack = UIStackView()
-    let left = UIImageView()
-    let field = UITextField()
-
-    let toolbar = UIToolbar()
-    let toolbarButton = UIButton()
-
-    var isPhone: Bool = false
-
-    // MARK: Properties
-
-    private var rightView: RightView? = .none {
-        didSet { set(rightView: rightView) }
+  }
+
+  public func set(prefix: String) {
+    code.content.text = prefix
+  }
+
+  public func update(content: String) {
+    field.text = content
+  }
+
+  public func update(placeholder: String) {
+    field.placeholder = placeholder
+  }
+
+  public func update(status: ValidationStatus) {
+    switch status {
+    case .unknown(let text):
+      set(rightView: nil)
+      subtitle.text = text ?? " "
+    case .invalid(let text):
+      set(rightView: .image(Asset.sharedError.image))
+      subtitle.text = text
+    case .valid(let text):
+      set(rightView: .image(Asset.sharedSuccess.image))
+      subtitle.text = text ?? " "
     }
+  }
 
-    private var clearable: Bool = false
-    private var allowsEmptySpace: Bool = true
-    private var cancellables = Set<AnyCancellable>()
-
-    private let codeSubject = PassthroughSubject<Void, Never>()
-    private let returnSubject = PassthroughSubject<Void, Never>()
-    private let textSubject = PassthroughSubject<String, Never>()
+  // MARK: Private
 
-    public var codePublisher: AnyPublisher<Void, Never> { codeSubject.eraseToAnyPublisher() }
-    public var textPublisher: AnyPublisher<String, Never> { textSubject.eraseToAnyPublisher() }
-    public var returnPublisher: AnyPublisher<Void, Never> { returnSubject.eraseToAnyPublisher() }
-
-    public init() {
-        super.init(frame: .zero)
-        setup()
+  private func set(leftView: LeftView?) {
+    switch leftView {
+    case .image(let image):
+      left.image = image
+      left.tintColor = Asset.neutralDisabled.color
+    case .none:
+      innerStack.removeArrangedSubview(left)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    // MARK: Public
-
-    public func makeFirstResponder() {
-        field.becomeFirstResponder()
+  }
+
+  public func set(rightView: RightView?) {
+    switch rightView {
+    case.image(let image):
+      field.rightView = UIImageView(image: image)
+    case .toggleSecureEntry:
+      field.rightView = hide
+      field.isSecureTextEntry = true
+      hide.setImage(hideButtonImage(isSecureEntry: field.isSecureTextEntry), for: .normal)
+    case .none:
+      field.rightView = nil
     }
-
-    public func setup(
-        style: Style = .regular,
-        title: String? = nil,
-        placeholder: String? = nil,
-        leftView: LeftView? = nil,
-        rightView: RightView? = nil,
-        accessibility: String? = nil,
-        subtitleAccessibility: String? = nil,
-        subtitleColor: UIColor = Asset.neutralWhite.color,
-        allowsEmptySpace: Bool = true,
-        keyboardType: UIKeyboardType = .default,
-        autocapitalization: UITextAutocapitalizationType = .sentences,
-        autoCorrect: UITextAutocorrectionType = .no,
-        contentType: UITextContentType? = nil,
-        returnKeyType: UIReturnKeyType = .done,
-        toolbarButtonTitle: String = Localized.Shared.done,
-        codeAccessibility: String? = nil,
-        clearable: Bool = false
-    ) {
-        self.title.text = title
-        self.set(leftView: leftView)
-
-        self.rightView = rightView
-        self.field.attributedPlaceholder = NSAttributedString(
-            string: placeholder ?? "",
-            attributes: [
-                .font: Fonts.Mulish.semiBold.font(size: 14.0),
-                .foregroundColor: Asset.neutralDisabled.color
-            ])
-
-        if contentType == .telephoneNumber {
-            isPhone = true
-        } else {
-            self.field.textContentType = contentType
-        }
-
-        self.field.returnKeyType = returnKeyType
-        self.field.keyboardType = keyboardType
-        self.subtitle.textColor = subtitleColor
-        self.allowsEmptySpace = allowsEmptySpace
-        self.field.autocorrectionType = autoCorrect
-        self.field.accessibilityIdentifier = accessibility
-        self.field.autocapitalizationType = autocapitalization
-        self.subtitle.accessibilityIdentifier = subtitleAccessibility
-        self.clearable = clearable
-
-        if style == .phone {
-            codeContainer.addSubview(code)
-            code.accessibilityIdentifier = codeAccessibility
-            code.snp.makeConstraints { $0.edges.equalToSuperview() }
-            outerStack.insertArrangedSubview(codeContainer, at: 0)
-
-            code.publisher(for: .touchUpInside)
-                .sink { [weak codeSubject] in codeSubject?.send() }
-                .store(in: &cancellables)
-
-            self.field.keyboardType = .numberPad
-            self.allowsEmptySpace = false
-
-            toolbar.barTintColor = Asset.neutralWhite.color
-            toolbarButton.setTitle(toolbarButtonTitle, for: .normal)
-            toolbarButton.setTitleColor(Asset.brandPrimary.color, for: .normal)
-            toolbarButton.titleLabel?.font = Fonts.Mulish.bold.font(size: 17.0)
-            toolbar.setShadowImage(.color(Asset.neutralLine.color), forToolbarPosition: .any)
-            toolbarButton.addTarget(self, action: #selector(didTapDone), for: .touchUpInside)
-            toolbar.items = [UIBarButtonItem(customView: toolbarButton.pinning(at: .right(0)))]
-
-            toolbar.sizeToFit()
-            self.field.inputAccessoryView = toolbar
-        }
-    }
-
-    public func set(prefix: String) {
-        code.content.text = prefix
-    }
-
-    public func update(content: String) {
-        field.text = content
-    }
-
-    public func update(placeholder: String) {
-        field.placeholder = placeholder
-    }
-
-    public func update(status: ValidationStatus) {
-        switch status {
-        case .unknown(let text):
-            set(rightView: nil)
-            subtitle.text = text ?? " "
-        case .invalid(let text):
-            set(rightView: .image(Asset.sharedError.image))
-            subtitle.text = text
-        case .valid(let text):
-            set(rightView: .image(Asset.sharedSuccess.image))
-            subtitle.text = text ?? " "
-        }
-    }
-
-    // MARK: Private
-
-    private func set(leftView: LeftView?) {
-        switch leftView {
-        case .image(let image):
-            left.image = image
-            left.tintColor = Asset.neutralDisabled.color
-        case .none:
-            innerStack.removeArrangedSubview(left)
-        }
+  }
+
+  private func hideButtonImage(isSecureEntry: Bool) -> UIImage? {
+    let openImage = Asset.eyeOpen.image.withTintColor(Asset.neutralWeak.color)
+    let closedImage = Asset.eyeClosed.image.withTintColor(Asset.neutralWeak.color)
+    return isSecureEntry ? closedImage : openImage
+  }
+
+  private func setup() {
+    subtitle.textAlignment = .right
+    subtitle.numberOfLines = 0
+    container.layer.cornerRadius = 4
+    container.backgroundColor = Asset.neutralSecondary.color
+
+    codeContainer.layer.cornerRadius = 4
+    codeContainer.backgroundColor = Asset.neutralSecondary.color
+
+    title.textColor = Asset.neutralWeak.color
+    field.textColor = Asset.neutralActive.color
+    subtitle.textColor = Asset.neutralWhite.color
+
+    title.font = Fonts.Mulish.regular.font(size: 12.0)
+    field.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    subtitle.font = Fonts.Mulish.regular.font(size: 12.0)
+
+    clear.setImage(Asset.sharedCross.image, for: .normal)
+
+    field.textPublisher
+      .sink { [weak textSubject] in textSubject?.send($0) }
+      .store(in: &cancellables)
+
+    hide.publisher(for: .touchUpInside)
+      .sink { [unowned self] _ in
+        field.isSecureTextEntry.toggle()
+        hide.setImage(hideButtonImage(isSecureEntry: field.isSecureTextEntry), for: .normal)
+      }.store(in: &cancellables)
+
+    clear.publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        field.text = ""
+        textSubject.send("")
+        field.resignFirstResponder()
+      }.store(in: &cancellables)
+
+    field.delegate = self
+    field.rightViewMode = .always
+
+    left.contentMode = .center
+    left.setContentHuggingPriority(.required, for: .horizontal)
+
+    innerStack.spacing = 12
+    innerStack.addArrangedSubview(left)
+    innerStack.addArrangedSubview(field)
+
+    outerStack.spacing = 8
+    container.addSubview(innerStack)
+    outerStack.addArrangedSubview(container)
+
+    addSubview(title)
+    addSubview(outerStack)
+    addSubview(subtitle)
+
+    setupConstraints()
+  }
+
+  private func setupConstraints() {
+    title.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview().offset(8)
     }
 
-    public func set(rightView: RightView?) {
-        switch rightView {
-        case.image(let image):
-            field.rightView = UIImageView(image: image)
-        case .toggleSecureEntry:
-            field.rightView = hide
-            field.isSecureTextEntry = true
-            hide.setImage(hideButtonImage(isSecureEntry: field.isSecureTextEntry), for: .normal)
-        case .none:
-            field.rightView = nil
-        }
+    outerStack.snp.makeConstraints {
+      $0.top.equalTo(title.snp.bottom).offset(10)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.height.equalTo(36)
     }
 
-    private func hideButtonImage(isSecureEntry: Bool) -> UIImage? {
-        let openImage = Asset.eyeOpen.image.withTintColor(Asset.neutralWeak.color)
-        let closedImage = Asset.eyeClosed.image.withTintColor(Asset.neutralWeak.color)
-        return isSecureEntry ? closedImage : openImage
+    innerStack.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview().offset(11)
+      $0.right.equalToSuperview().offset(-11)
+      $0.bottom.equalToSuperview()
     }
 
-    private func setup() {
-        subtitle.textAlignment = .right
-        subtitle.numberOfLines = 0
-        container.layer.cornerRadius = 4
-        container.backgroundColor = Asset.neutralSecondary.color
-
-        codeContainer.layer.cornerRadius = 4
-        codeContainer.backgroundColor = Asset.neutralSecondary.color
-
-        title.textColor = Asset.neutralWeak.color
-        field.textColor = Asset.neutralActive.color
-        subtitle.textColor = Asset.neutralWhite.color
-
-        title.font = Fonts.Mulish.regular.font(size: 12.0)
-        field.font = Fonts.Mulish.semiBold.font(size: 14.0)
-        subtitle.font = Fonts.Mulish.regular.font(size: 12.0)
-
-        clear.setImage(Asset.sharedCross.image, for: .normal)
-
-        field.textPublisher
-            .sink { [weak textSubject] in textSubject?.send($0) }
-            .store(in: &cancellables)
-
-        hide.publisher(for: .touchUpInside)
-            .sink { [unowned self] _ in
-                field.isSecureTextEntry.toggle()
-                hide.setImage(hideButtonImage(isSecureEntry: field.isSecureTextEntry), for: .normal)
-            }.store(in: &cancellables)
-
-        clear.publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                field.text = ""
-                textSubject.send("")
-                field.resignFirstResponder()
-            }.store(in: &cancellables)
-
-        field.delegate = self
-        field.rightViewMode = .always
-
-        left.contentMode = .center
-        left.setContentHuggingPriority(.required, for: .horizontal)
-
-        innerStack.spacing = 12
-        innerStack.addArrangedSubview(left)
-        innerStack.addArrangedSubview(field)
-
-        outerStack.spacing = 8
-        container.addSubview(innerStack)
-        outerStack.addArrangedSubview(container)
-
-        addSubview(title)
-        addSubview(outerStack)
-        addSubview(subtitle)
-
-        setupConstraints()
+    subtitle.snp.makeConstraints {
+      $0.top.equalTo(outerStack.snp.bottom).offset(8)
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+      $0.left.greaterThanOrEqualToSuperview()
     }
+  }
 
-    private func setupConstraints() {
-        title.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview().offset(8)
-        }
-
-        outerStack.snp.makeConstraints { make in
-            make.top.equalTo(title.snp.bottom).offset(10)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.height.equalTo(36)
-        }
-
-        innerStack.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview().offset(11)
-            make.right.equalToSuperview().offset(-11)
-            make.bottom.equalToSuperview()
-        }
+  @objc private func didTapDone() {
+    returnSubject.send()
+  }
 
-        subtitle.snp.makeConstraints { make in
-            make.top.equalTo(outerStack.snp.bottom).offset(8)
-            make.right.equalToSuperview()
-            make.bottom.equalToSuperview()
-            make.left.greaterThanOrEqualToSuperview()
-        }
+  public func textFieldDidBeginEditing(_ textField: UITextField) {
+    if clearable {
+      field.rightView = clear
     }
+  }
 
-    @objc private func didTapDone() {
-        returnSubject.send()
+  public func textFieldDidEndEditing(_ textField: UITextField) {
+    if clearable {
+      set(rightView: rightView)
     }
-
-    public func textFieldDidBeginEditing(_ textField: UITextField) {
-        if clearable {
-            field.rightView = clear
-        }
+  }
+
+  public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+    returnSubject.send()
+    return true
+  }
+
+  public func textField(
+    _ textField: UITextField,
+    shouldChangeCharactersIn range: NSRange,
+    replacementString string: String
+  ) -> Bool {
+    if isPhone {
+      if string.count > 1 {
+        textField.text = string.replaceCharactersFromSet(characterSet: .decimalDigits.inverted)
+        textSubject.send(textField.text ?? "")
+        return false
+      } else {
+        return string.rangeOfCharacter(from: .decimalDigits) != nil || string == ""
+      }
     }
 
-    public func textFieldDidEndEditing(_ textField: UITextField) {
-        if clearable {
-            set(rightView: rightView)
+    if !allowsEmptySpace {
+      if string.count > 1 {
+        if textField.textContentType == .emailAddress && [".us", ".net", ".edu", ".org", ".com"].contains(string) {
+          textSubject.send(textField.text ?? "")
+          return true
         }
-    }
 
-    public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
-        returnSubject.send()
-        return true
+        textField.text = string.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines)
+        textSubject.send(textField.text ?? "")
+        return false
+      } else {
+        return string != " "
+      }
     }
 
-    public func textField(
-        _ textField: UITextField,
-        shouldChangeCharactersIn range: NSRange,
-        replacementString string: String
-    ) -> Bool {
-        if isPhone {
-            if string.count > 1 {
-                textField.text = string.replaceCharactersFromSet(characterSet: .decimalDigits.inverted)
-                textSubject.send(textField.text ?? "")
-                return false
-            } else {
-                return string.rangeOfCharacter(from: .decimalDigits) != nil || string == ""
-            }
-        }
-
-        if !allowsEmptySpace {
-            if string.count > 1 {
-                if textField.textContentType == .emailAddress && [".us", ".net", ".edu", ".org", ".com"].contains(string) {
-                    textSubject.send(textField.text ?? "")
-                    return true
-                }
-
-                textField.text = string.replaceCharactersFromSet(characterSet: .whitespacesAndNewlines)
-                textSubject.send(textField.text ?? "")
-                return false
-            } else {
-                return string != " "
-            }
-        }
-
-        return true
-    }
+    return true
+  }
 }
 
 extension InputField: UITextFieldDelegate {}
 
 private extension String {
-    func replaceCharactersFromSet(characterSet: CharacterSet, replacementString: String = "") -> String {
-        return components(separatedBy: characterSet).joined(separator: replacementString)
-    }
+  func replaceCharactersFromSet(characterSet: CharacterSet, replacementString: String = "") -> String {
+    return components(separatedBy: characterSet).joined(separator: replacementString)
+  }
 }
diff --git a/Sources/InputField/OutlinedInputField.swift b/Sources/InputField/OutlinedInputField.swift
index 3bb8ad3005167b9e9e99511243da912f7082d434..9ae881bbf855cbff43b463e0e0cf2425568b9c2a 100644
--- a/Sources/InputField/OutlinedInputField.swift
+++ b/Sources/InputField/OutlinedInputField.swift
@@ -1,85 +1,86 @@
 import UIKit
 import Shared
 import Combine
+import AppResources
 
 public final class OutlinedInputField: UIView {
-    private let stackView = UIStackView()
-    private let textField = UITextField()
-    private let placeholderLabel = UILabel()
-    private let inputContainerView = UIView()
+  private let stackView = UIStackView()
+  private let textField = UITextField()
+  private let placeholderLabel = UILabel()
+  private let inputContainerView = UIView()
 
-    private let secureInputButton = SecureInputButton()
+  private let secureInputButton = SecureInputButton()
 
-    public var textPublisher: AnyPublisher<String, Never> {
-        textField.textPublisher
-    }
+  public var textPublisher: AnyPublisher<String, Never> {
+    textField.textPublisher
+  }
 
-    public init() {
-        super.init(frame: .zero)
+  public init() {
+    super.init(frame: .zero)
 
-        layer.borderWidth = 1.0
-        layer.cornerRadius = 4.0
-        layer.masksToBounds = true
-        layer.borderColor = Asset.neutralWeak.color.cgColor
+    layer.borderWidth = 1.0
+    layer.cornerRadius = 4.0
+    layer.masksToBounds = true
+    layer.borderColor = Asset.neutralWeak.color.cgColor
 
-        textField.delegate = self
-        textField.backgroundColor = .clear
-        textField.textColor = Asset.neutralDark.color
-        placeholderLabel.textColor = Asset.neutralWeak.color
-        placeholderLabel.font = Fonts.Mulish.regular.font(size: 16.0)
+    textField.delegate = self
+    textField.backgroundColor = .clear
+    textField.textColor = Asset.neutralDark.color
+    placeholderLabel.textColor = Asset.neutralWeak.color
+    placeholderLabel.font = Fonts.Mulish.regular.font(size: 16.0)
 
-        secureInputButton.button.addTarget(self, action: #selector(didTapRight), for: .touchUpInside)
+    secureInputButton.button.addTarget(self, action: #selector(didTapRight), for: .touchUpInside)
 
-        inputContainerView.addSubview(placeholderLabel)
-        inputContainerView.addSubview(textField)
+    inputContainerView.addSubview(placeholderLabel)
+    inputContainerView.addSubview(textField)
 
-        stackView.addArrangedSubview(inputContainerView)
-        stackView.addArrangedSubview(secureInputButton)
+    stackView.addArrangedSubview(inputContainerView)
+    stackView.addArrangedSubview(secureInputButton)
 
-        addSubview(stackView)
+    addSubview(stackView)
 
-        placeholderLabel.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(15)
-            $0.left.equalToSuperview().offset(15)
-            $0.right.lessThanOrEqualToSuperview().offset(-15)
-            $0.bottom.equalToSuperview().offset(-18)
-        }
+    placeholderLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(15)
+      $0.left.equalToSuperview().offset(15)
+      $0.right.lessThanOrEqualToSuperview().offset(-15)
+      $0.bottom.equalToSuperview().offset(-18)
+    }
 
-        textField.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(15)
-            $0.left.equalToSuperview().offset(15)
-            $0.right.equalToSuperview().offset(-15)
-            $0.bottom.equalToSuperview().offset(-18)
-        }
+    textField.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(15)
+      $0.left.equalToSuperview().offset(15)
+      $0.right.equalToSuperview().offset(-15)
+      $0.bottom.equalToSuperview().offset(-18)
+    }
 
-        stackView.snp.makeConstraints {
-            $0.edges.equalToSuperview()
-        }
+    stackView.snp.makeConstraints {
+      $0.edges.equalToSuperview()
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 
-    public func setup(title: String, sensitive: Bool = false) {
-        placeholderLabel.text = title
-        textField.isSecureTextEntry = sensitive
-        secureInputButton.isHidden = !sensitive
-    }
+  public func setup(title: String, sensitive: Bool = false) {
+    placeholderLabel.text = title
+    textField.isSecureTextEntry = sensitive
+    secureInputButton.isHidden = !sensitive
+  }
 
-    @objc private func didTapRight() {
-        textField.isSecureTextEntry.toggle()
-        secureInputButton.setSecure(textField.isSecureTextEntry)
-    }
+  @objc private func didTapRight() {
+    textField.isSecureTextEntry.toggle()
+    secureInputButton.setSecure(textField.isSecureTextEntry)
+  }
 }
 
 extension OutlinedInputField: UITextFieldDelegate {
-    public func textField(
-        _ textField: UITextField,
-        shouldChangeCharactersIn range: NSRange,
-        replacementString string: String
-    ) -> Bool {
-        placeholderLabel.alpha = (textField.text! as NSString)
-            .replacingCharacters(in: range, with: string)
-            .count > 0 ? 0.0 : 1.0
-        return true
-    }
+  public func textField(
+    _ textField: UITextField,
+    shouldChangeCharactersIn range: NSRange,
+    replacementString string: String
+  ) -> Bool {
+    placeholderLabel.alpha = (textField.text! as NSString)
+      .replacingCharacters(in: range, with: string)
+      .count > 0 ? 0.0 : 1.0
+    return true
+  }
 }
diff --git a/Sources/InputField/PhoneCodeField.swift b/Sources/InputField/PhoneCodeField.swift
index 2bb6b4343a94f2f93736df04bfcbe7883cbdd88d..e0504384c1873517e43ddaa43db422894846b7e3 100644
--- a/Sources/InputField/PhoneCodeField.swift
+++ b/Sources/InputField/PhoneCodeField.swift
@@ -1,34 +1,26 @@
 import UIKit
 import Shared
+import AppResources
 
 final class PhoneCodeField: UIButton {
-    // MARK: UI
+  public let content = UILabel()
 
-    public let content = UILabel()
+  public init() {
+    super.init(frame: .zero)
 
-    // MARK: Lifecycle
+    content.textColor = Asset.neutralActive.color
+    content.font = Fonts.Mulish.semiBold.font(size: 14.0)
 
-    public init() {
-        super.init(frame: .zero)
-        setup()
-    }
-
-    public required init?(coder: NSCoder) { nil }
-
-    // MARK: Private
+    addSubview(content)
 
-    private func setup() {
-        content.textColor = Asset.neutralActive.color
-        content.font = Fonts.Mulish.semiBold.font(size: 14.0)
-
-        addSubview(content)
-
-        content.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview().offset(11)
-            make.right.equalToSuperview().offset(-11)
-            make.width.equalTo(60)
-            make.bottom.equalToSuperview()
-        }
+    content.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview().offset(11)
+      $0.right.equalToSuperview().offset(-11)
+      $0.width.equalTo(60)
+      $0.bottom.equalToSuperview()
     }
+  }
+
+  public required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/InputField/SecureInputButton.swift b/Sources/InputField/SecureInputButton.swift
index 1f2e6b20751370755ec23b966c153ca5440c9f32..d40aa67be4143483570abb59750c528b9172d500 100644
--- a/Sources/InputField/SecureInputButton.swift
+++ b/Sources/InputField/SecureInputButton.swift
@@ -1,31 +1,32 @@
 import UIKit
 import Shared
+import AppResources
 
 final class SecureInputButton: UIView {
-    private(set) var button = UIButton()
-    private let color = Asset.neutralSecondaryAlternative.color
-    private lazy var openedImage = Asset.eyeOpen.image.withTintColor(color)
-    private lazy var closedImage = Asset.eyeClosed.image.withTintColor(color)
+  private(set) var button = UIButton()
+  private let color = Asset.neutralSecondaryAlternative.color
+  private lazy var openedImage = Asset.eyeOpen.image.withTintColor(color)
+  private lazy var closedImage = Asset.eyeClosed.image.withTintColor(color)
 
-    init() {
-        super.init(frame: .zero)
+  init() {
+    super.init(frame: .zero)
 
-        button.setContentCompressionResistancePriority(.required, for: .horizontal)
-        button.setImage(Asset.eyeClosed.image.withTintColor(color), for: .normal)
+    button.setContentCompressionResistancePriority(.required, for: .horizontal)
+    button.setImage(Asset.eyeClosed.image.withTintColor(color), for: .normal)
 
-        addSubview(button)
+    addSubview(button)
 
-        button.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.left.equalToSuperview().offset(10)
-            $0.right.equalToSuperview().offset(-10)
-            $0.bottom.equalToSuperview()
-        }
+    button.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview().offset(10)
+      $0.right.equalToSuperview().offset(-10)
+      $0.bottom.equalToSuperview()
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 
-    func setSecure(_ bool: Bool) {
-        button.setImage(bool ? closedImage : openedImage, for: .normal)
-    }
+  func setSecure(_ bool: Bool) {
+    button.setImage(bool ? closedImage : openedImage, for: .normal)
+  }
 }
diff --git a/Sources/InputField/Validator.swift b/Sources/InputField/Validator.swift
index 042916f36061b3b111e3d49cd1deb9e6079c2ed3..8d77034eec63a0178f0bccdfd4737cd062ed93e0 100644
--- a/Sources/InputField/Validator.swift
+++ b/Sources/InputField/Validator.swift
@@ -1,5 +1,6 @@
 import Shared
 import Foundation
+import AppResources
 
 private enum Constants {
     static let codeMinimum = Localized.Validator.Code.minimum
@@ -58,7 +59,7 @@ public extension Validator where T == String {
                 return .failure("")
             }
 
-            let regex = try? NSRegularExpression(pattern: "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d!@#$%^&*]{8,}$")
+            let regex = try? NSRegularExpression(pattern: "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d\\W_]{8,}$")
 
             guard let regex = regex, regex.firstMatch(in: passphrase, options: [], range: passphrase.fullRange()) != nil else {
                 return .failure("")
diff --git a/Sources/Integration/Callbacks.swift b/Sources/Integration/Callbacks.swift
deleted file mode 100644
index ed9d917cf8ff0469015a0dfa4db869a450666bfe..0000000000000000000000000000000000000000
--- a/Sources/Integration/Callbacks.swift
+++ /dev/null
@@ -1,295 +0,0 @@
-import Bindings
-
-final class TextListener: NSObject, BindingsListenerProtocol {
-    let callback: (BindingsMessage?) -> ()
-
-    init(_ callback: @escaping (BindingsMessage?) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func hear(_ message: BindingsMessage?) {
-        callback(message)
-    }
-
-    func name() -> String { "TEXT_LISTENER" }
-}
-
-final class ConfirmationCallback: NSObject, BindingsAuthConfirmCallbackProtocol {
-    let callback: (_ partner: BindingsContact) -> ()
-
-    init(_ callback: @escaping (_ partner: BindingsContact) -> ()) {
-        self.callback = callback
-        super.init()
-    }
-
-    func callback(_ partner: BindingsContact?) {
-        guard let partner = partner else { return }
-        callback(partner)
-    }
-}
-
-final class RequestCallback: NSObject, BindingsAuthRequestCallbackProtocol {
-    let callback: (_ requestor: BindingsContact) -> ()
-
-    init(_ callback: @escaping (_ requestor: BindingsContact) -> ()) {
-        self.callback = callback
-        super.init()
-    }
-
-    func callback(_ requestor: BindingsContact?) {
-        guard let requestor = requestor else { return }
-        callback(requestor)
-    }
-}
-
-final class HealthCallback: NSObject, BindingsNetworkHealthCallbackProtocol {
-    let callback: (Bool) -> Void
-
-    init(_ callback: @escaping (Bool) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func callback(_ p0: Bool) {
-        callback(p0)
-    }
-}
-
-final class LogCallback: NSObject, BindingsLogWriterProtocol {
-    let callback: (String?) -> Void
-
-    init(_ callback: @escaping (String?) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func log(_ p0: String?) {
-        callback(p0)
-    }
-}
-
-final class DeliveryCallback: NSObject, BindingsMessageDeliveryCallbackProtocol {
-    let callback: (DeliveryResult) -> Void
-
-    init(_ callback: @escaping (DeliveryResult) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func eventCallback(_ msgID: Data?, delivered: Bool, timedOut: Bool, roundResults: Data?) {
-
-        let content =
-        """
-        "Delivery Callback:
-        - Timed out: \(timedOut)
-        - Delivered: \(delivered)
-        - Message ID in base64: \(String(describing: msgID?.base64EncodedString()))
-        - Round results in base64: \(String(describing: roundResults?.base64EncodedString()))"
-        """
-
-        log(string: content, type: .info)
-        callback((msgID, delivered, timedOut, roundResults))
-    }
-}
-
-final class RoundCallback: NSObject, BindingsRoundCompletionCallbackProtocol {
-    let callback: (Bool) -> Void
-
-    init(_ callback: @escaping (Bool) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func eventCallback(_ rid: Int, success: Bool, timedOut: Bool) {
-        log(string: ">>> Add/Confirm RoundCallback:\nid: \(rid)\nSuccessfull: \(success)\nTimed out: \(timedOut)", type: .info)
-        callback(success && !timedOut)
-    }
-}
-
-final class SearchCallback: NSObject, BindingsSingleSearchCallbackProtocol {
-    let callback: (Result<BindingsContact, Error>) -> Void
-
-    init(_ callback: @escaping (Result<BindingsContact, Error>) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func callback(_ contact: BindingsContact?, error: String?) {
-        if let error = error, error.count > 0 {
-            callback(.failure(NSError.create(error).friendly()))
-            return
-        }
-
-        if let contact = contact {
-            callback(.success(contact))
-        }
-    }
-}
-
-final class EventCallback: NSObject, BindingsEventCallbackFunctionObjectProtocol {
-    let callback: (BackendEvent) -> Void
-
-    init(_ callback: @escaping (BackendEvent) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func reportEvent(_ priority: Int, category: String?, evtType: String?, details: String?) {
-        callback((priority, category, evtType, details))
-    }
-}
-
-final class GroupRequestCallback: NSObject, BindingsGroupRequestFuncProtocol {
-    let callback: (BindingsGroup) -> Void
-
-    init(_ callback: @escaping (BindingsGroup) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func groupRequestCallback(_ g: BindingsGroup?) {
-        guard let group = g else { return }
-        callback(group)
-    }
-}
-
-final class GroupMessageCallback: NSObject, BindingsGroupReceiveFuncProtocol {
-    let callback: (BindingsGroupMessageReceive) -> Void
-
-    init(_ callback: @escaping (BindingsGroupMessageReceive) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func groupReceiveCallback(_ msg: BindingsGroupMessageReceive?) {
-        guard let message = msg else { return }
-        callback(message)
-    }
-}
-
-final class MultiLookupCallback: NSObject, BindingsMultiLookupCallbackProtocol {
-    let thisCallback: (BindingsContactList?, BindingsIdList?, String?) -> Void
-
-    init(_ callback: @escaping (BindingsContactList?, BindingsIdList?, String?) -> Void) {
-        self.thisCallback = callback
-        super.init()
-    }
-
-    func callback(_ Succeeded: BindingsContactList?, failed: BindingsIdList?, errors: String?) {
-        thisCallback(Succeeded, failed, errors)
-    }
-}
-
-final class PreImageCallback: NSObject, BindingsPreimageNotificationProtocol {
-    let callback: (Data?, Bool) -> Void
-
-    init(_ callback: @escaping (Data?, Bool) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func notify(_ identity: Data?, deleted: Bool) {
-        callback(identity, deleted)
-    }
-}
-
-final class LookupCallback: NSObject, BindingsLookupCallbackProtocol {
-    let callback: (Result<BindingsContact, Error>) -> Void
-
-    init(_ callback: @escaping (Result<BindingsContact, Error>) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func callback(_ contact: BindingsContact?, error: String?) {
-        if let error = error, !error.isEmpty {
-            callback(.failure(NSError.create(error).friendly()))
-            return
-        }
-
-        if let contact = contact {
-            callback(.success(contact))
-        }
-    }
-}
-
-final class IncomingTransferCallback: NSObject, BindingsFileTransferReceiveFuncProtocol {
-    let callback: (Data?, String?, String?, Data?, Int, Data?) -> Void
-
-    init(_ callback: @escaping (Data?, String?, String?, Data?, Int, Data?) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func receiveCallback(_ tid: Data?, fileName: String?, fileType: String?, sender: Data?, size: Int, preview: Data?) {
-        callback(tid, fileName, fileType, sender, size, preview)
-    }
-}
-
-final class IncomingTransferProgressCallback: NSObject, BindingsFileTransferReceivedProgressFuncProtocol {
-    let callback: (Bool, Int, Int, Error?) -> Void
-
-    init(_ callback: @escaping (Bool, Int, Int, Error?) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func receivedProgressCallback(_ completed: Bool, received: Int, total: Int, t: BindingsFilePartTracker?, err: Error?) {
-        callback(completed, received, total, err)
-    }
-}
-
-final class OutgoingTransferProgressCallback: NSObject, BindingsFileTransferSentProgressFuncProtocol {
-    let callback: (Bool, Int, Int, Int, Error?) -> Void
-
-    init(_ callback: @escaping (Bool, Int, Int, Int, Error?) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func sentProgressCallback(_ completed: Bool, sent: Int, arrived: Int, total: Int, t: BindingsFilePartTracker?, err: Error?) {
-        callback(completed, sent, arrived, total, err)
-    }
-}
-
-final class UpdateBackupCallback: NSObject, BindingsUpdateBackupFuncProtocol {
-    let callback: (Data) -> Void
-
-    init(_ callback: @escaping (Data) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func updateBackup(_ encryptedBackup: Data?) {
-        guard let data = encryptedBackup else { return }
-        callback(data)
-    }
-}
-
-final class ResetCallback: NSObject, BindingsAuthResetNotificationCallbackProtocol {
-    let callback: (BindingsContact) -> Void
-
-    init(_ callback: @escaping (BindingsContact) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func callback(_ requestor: BindingsContact?) {
-        guard let requestor = requestor else { return }
-        callback(requestor)
-    }
-}
-
-final class RestoreContactsCallback: NSObject, BindingsRestoreContactsUpdaterProtocol {
-    let callback: (Int, Int, Int, String?) -> Void
-
-    init(_ callback: @escaping (Int, Int, Int, String?) -> Void) {
-        self.callback = callback
-        super.init()
-    }
-
-    func restoreContactsCallback(_ numFound: Int, numRestored: Int, total: Int, err: String?) {
-        callback(numFound, numRestored, total, err)
-    }
-}
diff --git a/Sources/Integration/Client.swift b/Sources/Integration/Client.swift
deleted file mode 100644
index 8fc201af93fe681852c550f62965029316e00811..0000000000000000000000000000000000000000
--- a/Sources/Integration/Client.swift
+++ /dev/null
@@ -1,236 +0,0 @@
-import Retry
-import Models
-import Combine
-import Defaults
-import Bindings
-import XXModels
-import Foundation
-
-public class Client {
-    @KeyObject(.inappnotifications, defaultValue: true) var inappnotifications: Bool
-
-    let bindings: BindingsInterface
-    var backupManager: BackupInterface?
-    var dummyManager: DummyTrafficManaging?
-    var groupManager: GroupManagerInterface?
-    var userDiscovery: UserDiscoveryInterface?
-    var transferManager: TransferManagerInterface?
-
-    var backup: AnyPublisher<Data, Never> { backupSubject.eraseToAnyPublisher() }
-    var network: AnyPublisher<Bool, Never> { networkSubject.eraseToAnyPublisher() }
-    var resets: AnyPublisher<Contact, Never> { resetsSubject.eraseToAnyPublisher() }
-    var messages: AnyPublisher<Message, Never> { messagesSubject.eraseToAnyPublisher() }
-    var requests: AnyPublisher<Contact, Never> { requestsSubject.eraseToAnyPublisher() }
-    var events: AnyPublisher<BackendEvent, Never> { eventsSubject.eraseToAnyPublisher() }
-    var transfers: AnyPublisher<FileTransfer, Never> { transfersSubject.eraseToAnyPublisher() }
-    var requestsSent: AnyPublisher<Contact, Never> { requestsSentSubject.eraseToAnyPublisher() }
-    var confirmations: AnyPublisher<Contact, Never> { confirmationsSubject.eraseToAnyPublisher() }
-    var groupRequests: AnyPublisher<(Group, [Data], String?), Never> { groupRequestsSubject.eraseToAnyPublisher() }
-
-    private let backupSubject = PassthroughSubject<Data, Never>()
-    private let networkSubject = PassthroughSubject<Bool, Never>()
-    private let resetsSubject = PassthroughSubject<Contact, Never>()
-    private let requestsSubject = PassthroughSubject<Contact, Never>()
-    private let messagesSubject = PassthroughSubject<Message, Never>()
-    private let eventsSubject = PassthroughSubject<BackendEvent, Never>()
-    private let requestsSentSubject = PassthroughSubject<Contact, Never>()
-    private let confirmationsSubject = PassthroughSubject<Contact, Never>()
-    private let transfersSubject = PassthroughSubject<FileTransfer, Never>()
-    private let groupRequestsSubject = PassthroughSubject<(Group, [Data], String?), Never>()
-
-    private var isBackupInitialization = false
-    private var isBackupInitializationCompleted = false
-
-    // MARK: Lifecycle
-
-    init(
-        _ bindings: BindingsInterface,
-        fromBackup: Bool,
-        email: String?,
-        phone: String?
-    ) {
-        self.bindings = bindings
-        self.isBackupInitialization = fromBackup
-
-        do {
-            try registerListenersAndStart()
-
-            if fromBackup {
-                try instantiateUserDiscoveryFromBackup(email: email, phone: phone)
-            } else {
-                try instantiateUserDiscovery()
-            }
-
-            try instantiateTransferManager()
-            try instantiateDummyTrafficManager()
-            updatePreImage()
-        } catch {
-            log(string: error.localizedDescription, type: .error)
-        }
-    }
-
-    public func initializeBackup(passphrase: String) {
-        backupManager = nil
-        backupManager = bindings.initializeBackup(passphrase: passphrase) { [weak backupSubject] in
-            backupSubject?.send($0)
-        }
-    }
-
-    public func resumeBackup() {
-        backupManager = nil
-        backupManager = bindings.resumeBackup { [weak backupSubject] in
-            backupSubject?.send($0)
-        }
-    }
-
-    //    public func isBackupRunning() -> Bool {
-    //        guard let backupManager = backupManager else { return false }
-    //        return backupManager.isBackupRunning()
-    //    }
-
-    public func addJson(_ string: String) {
-        guard let backupManager = backupManager else {
-            fatalError("Trying to add json parameters to backup but no backup manager created yet")
-        }
-
-        backupManager.addJson(string)
-    }
-
-    public func stopListeningBackup() {
-        guard let backupManager = backupManager else { return }
-        try? backupManager.stop()
-        self.backupManager = nil
-    }
-
-    public func restoreContacts(fromBackup backup: Data) {
-        var totalPendingRestoration: Int = 0
-
-        let report = bindings.restore(
-            ids: backup,
-            using: userDiscovery!) { [weak self] in
-                guard let self = self else { return }
-
-                switch $0 {
-                case .success(var contact):
-                    contact.authStatus = .requested
-                    self.requestsSentSubject.send(contact)
-                    print(">>> Restored \(contact.username). Setting status as requested")
-                case .failure(let error):
-                    print(">>> \(error.localizedDescription)")
-                }
-            } restoreCallback: { numFound, numRestored, total, errorString in
-                totalPendingRestoration = total
-                let results =
-            """
-            >>> Results from within closure of RestoreContacts:
-            - numFound: \(numFound)
-            - numRestored: \(numRestored)
-            - total: \(total)
-            - errorString: \(errorString)
-            """
-                print(results)
-            }
-
-        guard totalPendingRestoration > 0 else { fatalError("Total is zero, why called restore contacts?") }
-
-        guard report.lenRestored() == totalPendingRestoration else {
-            print(">>> numRestored \(report.lenRestored()) is != than the total (\(totalPendingRestoration)). Going on recursion...\nnumFailed: \(report.lenFailed())\n\(report.getRestoreContactsError())")
-            restoreContacts(fromBackup: backup)
-            return
-        }
-
-        isBackupInitializationCompleted = true
-    }
-
-    private func registerListenersAndStart() throws {
-        bindings.listenNetworkUpdates { [weak networkSubject] in networkSubject?.send($0) }
-
-        bindings.listenRequests { [weak self] in
-            guard let self = self else { return }
-
-            if self.isBackupInitialization {
-                if self.isBackupInitializationCompleted {
-                    self.requestsSubject.send($0)
-                }
-            } else {
-                self.requestsSubject.send($0)
-            }
-        } _: { [weak confirmationsSubject] in
-            confirmationsSubject?.send($0)
-        } _: { [weak resetsSubject] in
-            resetsSubject?.send($0)
-        }
-
-        bindings.listenEvents { [weak eventsSubject] in
-            eventsSubject?.send($0)
-        }
-
-        groupManager = try bindings.listenGroupRequests { [weak groupRequestsSubject] request, members, welcome in
-            groupRequestsSubject?.send((request, members, welcome))
-        } groupMessages: { [weak messagesSubject] in
-            messagesSubject?.send($0)
-        }
-
-        bindings.listenPreImageUpdates()
-
-        try bindings.listenMessages { [weak messagesSubject] in
-            messagesSubject?.send($0)
-        }
-
-        bindings.startNetwork()
-    }
-
-    private func instantiateTransferManager() throws {
-        transferManager = try bindings.generateTransferManager { [weak transfersSubject] tid, name, type, sender in
-
-            /// Someone transfered something to me
-            /// but I haven't received yet. I'll store an
-            /// IncomingTransfer object so later on I can
-            /// pull up whatever this contact has sent me.
-            ///
-            guard let name = name,
-                  let type = type,
-                  let contactId = sender else {
-                      log(string: "Transfer of \(name ?? "nil").\(type ?? "nil") is being dismissed", type: .error)
-                      return
-                  }
-
-            transfersSubject?.send(
-                FileTransfer(
-                    id: tid,
-                    contactId: contactId,
-                    name: name,
-                    type: type,
-                    data: nil,
-                    progress: 0.0,
-                    isIncoming: true,
-                    createdAt: Date()
-                )
-            )
-        }
-    }
-
-    private func instantiateUserDiscovery() throws {
-        retry(max: 4, retryStrategy: .delay(seconds: 1)) { [weak self] in
-            guard let self = self else { return }
-            self.userDiscovery = try self.bindings.generateUD()
-        }
-    }
-
-    private func instantiateUserDiscoveryFromBackup(email: String?, phone: String?) throws {
-        retry(max: 4, retryStrategy: .delay(seconds: 1)) { [weak self] in
-            guard let self = self else { return }
-            self.userDiscovery = try self.bindings.generateUDFromBackup(email: email, phone: phone)
-        }
-    }
-
-    private func instantiateDummyTrafficManager() throws {
-        dummyManager = try bindings.generateDummyTraficManager()
-    }
-
-    private func updatePreImage() {
-        if let defaults = UserDefaults(suiteName: "group.elixxir.messenger") {
-            defaults.set(bindings.getPreImages(), forKey: "preImage")
-        }
-    }
-}
diff --git a/Sources/Integration/Extensions.swift b/Sources/Integration/Extensions.swift
deleted file mode 100644
index f2afdcb99e46f87141081f94e657b7ef5e14721e..0000000000000000000000000000000000000000
--- a/Sources/Integration/Extensions.swift
+++ /dev/null
@@ -1,58 +0,0 @@
-import Models
-import XXModels
-import Bindings
-
-extension Contact {
-    init(with contact: BindingsContact, status: Contact.AuthStatus) {
-        self.init(
-            id: contact.getID()!,
-            marshaled: try! contact.marshal(),
-            username: contact.retrieve(fact: .username) ?? "",
-            email: contact.retrieve(fact: .email),
-            phone: contact.retrieve(fact: .phone),
-            nickname: nil,
-            photo: nil,
-            authStatus: status,
-            isRecent: false,
-            createdAt: Date()
-        )
-    }
-}
-
-extension Message {
-    init(with message: BindingsMessage, myId: Data) {
-        guard let payload = try? Payload(with: message.getPayload()!) else { fatalError() }
-
-        self.init(
-            networkId: message.getID()!,
-            senderId: message.getSender()!,
-            recipientId: myId,
-            groupId: nil,
-            date: Date.fromTimestamp(Int(message.getTimestampNano())),
-            status: .received,
-            isUnread: true,
-            text: payload.text,
-            replyMessageId: payload.reply?.messageId,
-            roundURL: message.getRoundURL(),
-            fileTransferId: nil
-        )
-    }
-
-    init(with message: BindingsGroupMessageReceive) {
-        guard let payload = try? Payload(with: message.getPayload()!) else { fatalError() }
-
-        self.init(
-            networkId: message.getMessageID()!,
-            senderId: message.getSenderID()!,
-            recipientId: nil,
-            groupId: message.getGroupID()!,
-            date: Date.fromTimestamp(Int(message.getTimestampNano())),
-            status: .received,
-            isUnread: true,
-            text: payload.text,
-            replyMessageId: payload.reply?.messageId,
-            roundURL: message.getRoundURL(),
-            fileTransferId: nil
-        )
-    }
-}
diff --git a/Sources/Integration/FeedbackPlayer.swift b/Sources/Integration/FeedbackPlayer.swift
deleted file mode 100644
index 09d4773d68576b4fd625bdc741e9e086c9a5d7ff..0000000000000000000000000000000000000000
--- a/Sources/Integration/FeedbackPlayer.swift
+++ /dev/null
@@ -1,38 +0,0 @@
-import AVFoundation
-import AudioToolbox
-
-struct DeviceFeedback {
-    enum Haptic: UInt32 {
-        case impact = 1520
-        case notification = 1521
-        case selection = 1519
-    }
-
-    enum Alert: UInt32 {
-        case smsSent = 1004
-        case smsReceived = 1003
-        case contactAdded = 1117
-    }
-
-    // MARK: Lifecycle
-
-    private init() {}
-
-    // MARK: Static
-
-    static func sound(_ alert: Alert) {
-        try? AVAudioSession
-            .sharedInstance()
-            .setCategory(.ambient, mode: .default, options: .mixWithOthers)
-
-        AudioServicesPlaySystemSound(alert.rawValue)
-    }
-
-    static func shake(_ haptic: Haptic) {
-        try? AVAudioSession
-            .sharedInstance()
-            .setCategory(.ambient, mode: .default, options: .mixWithOthers)
-
-        AudioServicesPlaySystemSound(haptic.rawValue)
-    }
-}
diff --git a/Sources/Integration/Implementations/Bindings.swift b/Sources/Integration/Implementations/Bindings.swift
deleted file mode 100644
index a05387dda2653644a26a3bc28b40e011c6eb5e03..0000000000000000000000000000000000000000
--- a/Sources/Integration/Implementations/Bindings.swift
+++ /dev/null
@@ -1,613 +0,0 @@
-import Shared
-import Models
-import Bindings
-import XXModels
-import Foundation
-import DependencyInjection
-
-public let evaluateNotification: NotificationEvaluation = BindingsNotificationsForMe
-
-public protocol NotificationReportProtocol {
-    func forMe() -> Bool
-    func type() -> String
-    func source() -> Data?
-}
-
-public protocol NotificationManyReportProtocol {
-    func len() -> Int
-    func get(index: Int) throws -> NotificationReportProtocol
-}
-
-extension BindingsNotificationForMeReport: NotificationReportProtocol {}
-
-extension BindingsManyNotificationForMeReport: NotificationManyReportProtocol {
-    public func get(index: Int) throws -> NotificationReportProtocol {
-        try get(index)
-    }
-}
-
-extension BindingsClient: BindingsInterface {
-    public func removeContact(_ data: Data) throws {
-        do {
-            try deleteContact(data)
-            log(string: "Deleted a contact", type: .info)
-        } catch {
-            log(string: "Failed to delete a contact: \(error.localizedDescription)", type: .error)
-            throw error.friendly()
-        }
-    }
-
-    func dumpThreads() {
-        log(type: .crumbs)
-
-        var error: NSError?
-        let string = BindingsDumpStack(&error)
-
-        if let error = error {
-            log(string: error.localizedDescription, type: .error)
-            return
-        }
-
-        log(string: string, type: .bindings)
-    }
-
-    public func resetSessionWith(_ recipient: Data) {
-        var int: Int = 0
-
-        do {
-            try resetSession(recipient, meMarshaled: meMarshalled, message: "", ret0_: &int)
-        } catch {
-            print(">>> \(error.localizedDescription)")
-        }
-    }
-
-    public func verify(marshaled: Data, verifiedMarshaled: Data) throws -> Bool {
-        var bool: ObjCBool = false
-        try verifyOwnership(marshaled, verifiedMarshaled: verifiedMarshaled, ret0_: &bool)
-        log(string: "Onwership verification: \(bool.boolValue)", type: bool.boolValue ? .info : .error)
-        return bool.boolValue
-    }
-
-    public func compress(
-        image: Data,
-        _ completion: @escaping(Result<Data, Error>) -> Void
-    ) {
-        var error: NSError?
-        let compressed = BindingsCompressJpeg(image, &error)
-
-        guard error == nil else {
-            log(string: "Error when compressing jpeg: \(error!.localizedDescription)", type: .error)
-            completion(.failure(error!.friendly()))
-            return
-        }
-
-        guard let compressed = compressed else {
-            completion(.failure(NSError.create("Image compression failed without error")))
-            return
-        }
-
-        let compressionRate = String(format: "%.4f", Float(compressed.count)/Float(image.count))
-        log(string: "Compressed image x\(compressionRate) (\(image.count) -> \(compressed.count))", type: .info)
-        completion(.success(compressed))
-    }
-
-    public var hasRunningTasks: Bool {
-        hasRunningProcessies()
-    }
-
-    public var myId: Data {
-        guard let user = getUser(), let contact = user.getContact(), let id = contact.getID() else {
-            fatalError("Couldn't get my ID")
-        }
-
-        return id
-    }
-
-    public var meMarshalled: Data {
-        guard let user = getUser(), let contact = user.getContact(), let marshal = try? contact.marshal() else {
-            fatalError("Couldn't get my own contact marshalled")
-        }
-
-        return marshal
-    }
-
-    public func getPreImages() -> String {
-        getPreimages(receptionId)
-    }
-
-    public func meMarshalled(_ username: String, email: String?, phone: String?) -> Data {
-        guard let user = getUser(),
-              let contact = user.getContact(),
-              let factList = contact.getFactList() else { fatalError() }
-
-        try! factList.add(username, factType: FactType.username.rawValue)
-
-        if let email = email {
-            try! factList.add(email, factType: FactType.email.rawValue)
-        }
-
-        if let phone = phone {
-            try! factList.add(phone, factType: FactType.phone.rawValue)
-        }
-
-        return try! contact.marshal()
-    }
-
-    public var receptionId: Data {
-        guard let user = getUser(), let recId = user.getReceptionID() else { fatalError() }
-        return recId
-    }
-
-    public static let version: String = {
-        return BindingsGetVersion()
-    }()
-
-    public static let new: ClientNew = BindingsNewClient
-
-    public static let fromBackup: ClientFromBackup = BindingsNewClientFromBackup
-
-    public static let secret: (Int) -> Data? = BindingsGenerateSecret
-
-    public static let login: (String?, Data?, String?, NSErrorPointer) -> BindingsInterface? = BindingsLogin
-
-    public static func updateNDF(
-        for env: NetworkEnvironment,
-        _ completion: @escaping (Result<Data?, Error>) -> Void
-    ) {
-        var error: NSError?
-        let ndf = BindingsDownloadAndVerifySignedNdfWithUrl(env.url, env.cert, &error)
-
-        guard error == nil else {
-            Self.updateNDF(for: env, completion)
-            return
-        }
-
-        completion(.success(ndf))
-    }
-
-    /// Fetches a JSON with up-to-date error descriptions
-    /// then passes it to the bindings that will emit cleaner
-    /// errors
-    ///
-    /// - ToDo: Request status codes for errors
-    ///
-    public static func updateErrors() {
-        log(type: .crumbs)
-
-        var error: NSError?
-        if let dbErrors = BindingsDownloadErrorDB(&error) {
-            var otherError: NSError?
-            BindingsUpdateCommonErrors(String(data: dbErrors, encoding: .utf8), &otherError)
-
-            if let otherError = otherError {
-                log(string: otherError.localizedDescription, type: .error)
-            }
-        }
-
-        if let error = error {
-            log(string: error.localizedDescription, type: .error)
-        }
-    }
-
-    /// Starts the network
-    ///
-    /// If network status is != 0 it means the network is
-    /// not ready yet or the device is not ready. A recursion was applied
-    /// as a temporary solution in order to retry indefinitely
-    ///
-    /// - ToDo: Split function into smaller functions
-    ///
-    public func startNetwork() {
-        log(type: .crumbs)
-
-        var error: NSError?
-        let status = networkFollowerStatus()
-
-        BindingsLogLevel(1, &error)
-        registerErrorCallback(BindingsError())
-
-        guard status == 0 else {
-            log(string: ">>> Network is not ready yet. Let's give it a second...", type: .error)
-            sleep(1)
-            startNetwork()
-            return
-        }
-
-        try! startNetworkFollower(10000)
-        log(string: ">>> Starting the network...", type: .info)
-    }
-
-    /// (Tries) to stop the network
-    ///
-    /// - Warning: This function tries to stop several
-    ///            threads and it may take some time.
-    ///            That's why we register a background
-    ///            task on AppDelegate.swift
-    ///
-    public func stopNetwork() {
-        log(type: .crumbs)
-
-        try! stopNetworkFollower()
-        log(string: "Stopping the network...", type: .info)
-    }
-
-    /// Extracts *user id* from a contact
-    ///
-    /// - Parameters:
-    ///   - from: Byte array containing contact object
-    ///
-    /// - Returns: Optional byte array, if *user id* could be retrieved
-    ///
-    public func getId(from marshaled: Data) -> Data? {
-        log(type: .crumbs)
-
-        var error: NSError?
-        let contact = BindingsUnmarshalContact(marshaled, &error)
-
-        if let error = error {
-            log(string: error.localizedDescription, type: .error)
-            return nil
-        }
-
-        return contact?.getID()
-    }
-
-    public func add(_ contact: Data, from me: Data, _ completion: @escaping (Result<Bool, Error>) -> Void) {
-        log(type: .crumbs)
-
-        do {
-            var roundId = Int()
-            try requestAuthenticatedChannel(contact, meMarshaled: me, message: nil, ret0_: &roundId)
-            completion(.success(true))
-        } catch {
-            log(string: error.localizedDescription, type: .error)
-            completion(.failure(error.friendly()))
-        }
-    }
-
-    /// Confirms a contact request
-    ///
-    /// - Parameters:
-    ///   - contact: Byte array containing *contact object*
-    ///   - completion: Result callback with associated
-    ///                 values *boolean* = success &&
-    ///                 !timedOut or *Error* upon throwing
-    ///
-    public func confirm(_ contact: Data, _ completion: @escaping (Result<Bool, Error>) -> Void) {
-        log(type: .crumbs)
-
-        do {
-            var roundId = Int()
-            try confirmAuthenticatedChannel(contact, ret0_: &roundId)
-            completion(.success(true))
-        } catch {
-            log(string: error.localizedDescription, type: .error)
-            completion(.failure(error.friendly()))
-        }
-    }
-
-    /// Sends a message over CMIX
-    ///
-    /// - Parameters:
-    ///   - recipient: Byte array containing *user id*
-    ///   - payload: Byte array containing *message payload*
-    ///
-    /// - Returns: Result w/ associated values
-    ///            byte array containing *SentReport*
-    ///            or *Error* upon throwing
-    ///
-    public func send(_ payload: Data, to recipient: Data) -> Result<E2ESendReportType, Error> {
-        log(type: .crumbs)
-
-        do {
-            let report = try sendE2E(recipient, payload: payload, messageType: 2, parameters: nil)
-
-            var roundIds = [Int]()
-
-            if let roundList = report.getRoundList(), let payloadUnwrapped = try? Payload(with: payload) {
-                let length = roundList.len()
-                for index in 0..<length {
-                    var integer: Int = 0
-                    do {
-                        try roundList.get(index, ret0_: &integer)
-                        roundIds.append(integer)
-                    } catch {
-                        log(string: "Error trying to inspect round list: \(error.localizedDescription)", type: .error)
-                    }
-                }
-
-                log(string: "Round ids for \(payloadUnwrapped.text.prefix(5))... = \(roundIds)", type: .info)
-            }
-
-            return .success(report)
-        } catch {
-            log(string: error.localizedDescription, type: .error)
-            return .failure(error)
-        }
-    }
-
-    /// Listens to the delivery of a message through a report
-    ///
-    /// - Note: Delivery actually refers to the
-    ///         gateway, not necessarily the other end
-    ///         received/read this message yet.
-    ///
-    /// - Parameters:
-    ///   - report: SentReport marshalled
-    ///   - completion: Result callback w/ associated
-    ///                 values *completed* or *Error*
-    ///                 upon throwing
-    ///
-    public func listen(report: Data, _ completion: @escaping (Result<MessageDeliveryStatus, Error>) -> Void) {
-        do {
-            try listenDelivery(of: report) { msgId, delivered, timedOut, roundResults in
-                let status: MessageDeliveryStatus
-
-                if delivered == false {
-                    let extendedLogs =
-                    """
-                    Round delivery callback from wait(forMessageDelivery:)
-                    - timedOut = \(timedOut)
-                    - delivered = \(delivered)
-                    """
-                    log(string: extendedLogs, type: .error)
-                    log(string: extendedLogs, type: .error)
-
-                    if timedOut == true {
-                        status = .timedout
-                    } else {
-                        status = .failed
-                    }
-                } else {
-                    status = .sent
-                }
-
-                completion(.success(status))
-            }
-        } catch {
-            completion(.failure(error))
-        }
-    }
-
-    public func registerNotifications(_ token: Data) throws {
-        let tokenString = token.map { String(format: "%02hhx", $0) }.joined()
-
-        do {
-            try register(forNotifications: tokenString)
-        } catch {
-            throw error.friendly()
-        }
-    }
-
-    /// Unregisters device token on backend
-    ///
-    /// - Throws: If when trying to unregister
-    ///           some exception come up such as
-    ///           timing out or user is not registered
-    ///
-    public func unregisterNotifications() throws {
-        log(type: .crumbs)
-
-        do {
-            try unregisterForNotifications()
-            log(string: "Unregistered notifications", type: .info)
-        } catch {
-            log(string: error.localizedDescription, type: .error)
-            throw error.friendly()
-        }
-    }
-
-    /// Checks if number of nodes already registered is enough
-    ///
-    /// Whenever the user wants to do an operation that involves
-    /// *User Discovery*, the app should make sure that a minimum
-    /// amount of nodes already know about this user
-    ///
-    /// - Throws: `NodeRegistrationError.amountIsTooLow` if
-    ///            the ratio is below minimum (currently 85%).
-    ///            `NodeRegistrationError.networkIsNotHealthyYet`
-    ///            when trying to fetch registration status and
-    ///            network is not healthy yet
-    ///
-    public func nodeRegistrationStatus() throws {
-        log(type: .crumbs)
-
-        enum NodeRegistrationError: Error {
-            case amountIsTooLow
-        }
-
-        var shortRatio: String?
-
-        do {
-            let status = try getNodeRegistrationStatus()
-            let registered = Float(status.getRegistered())
-            let total = Float(status.getTotal())
-            let ratio = Float(registered/total)
-
-            let nf = NumberFormatter()
-            nf.roundingMode = .down
-            nf.maximumFractionDigits = 2
-            nf.numberStyle = .percent
-            shortRatio = nf.string(from: NSNumber(value: ratio))
-
-            guard ratio >= 0.85 else { throw NodeRegistrationError.amountIsTooLow }
-            log(string: "Node registration rate: \(shortRatio ?? "")", type: .info)
-        } catch NodeRegistrationError.amountIsTooLow {
-
-            let string = "Node registration rate is still below 85% (\(shortRatio ?? ""))"
-            log(string: string, type: .error)
-
-            let userError = "We are still establishing a secure registration with the decentralized network. Please try again in a few seconds."
-
-            throw NSError.create(userError)
-        } catch {
-            log(string: error.localizedDescription, type: .error)
-            throw error
-        }
-    }
-
-    /// Instantiates a transfer manager
-    ///
-    /// - Returns: An instance of *BindingsFileTransfer (TransferManager)*
-    ///
-    /// - Throws: `FTError.noInstance` if no error was thrown
-    ///            but also no instance was created
-    ///
-    public func generateTransferManager(
-        _ callback: @escaping (Data, String?, String?, Data?) -> Void
-    ) throws -> TransferManagerInterface {
-        log(type: .crumbs)
-
-        let incomingTransferCallback = IncomingTransferCallback { tid, name, type, sender, size, preview in
-            guard let tid = tid else { fatalError("An incoming transfer has no TID?") }
-
-            callback(tid, name, type, sender)
-        }
-
-        var error: NSError?
-        let manager = BindingsNewFileTransferManager(self, incomingTransferCallback, "", &error)
-
-        guard let error = error else { return manager! }
-        throw error.friendly()
-    }
-
-    public func generateDummyTraficManager() throws -> DummyTrafficManaging {
-        var error: NSError?
-        let manager = BindingsNewDummyTrafficManager(self, 5, 30000, 25000, &error)
-
-        guard let error = error else { return manager! }
-        throw error.friendly()
-    }
-
-    public func generateUDFromBackup(email: String?, phone: String?) throws -> UserDiscoveryInterface {
-        var error: NSError?
-
-        let paramEmail = email != nil ? "E\(email!)" : nil
-        let paramPhone = phone != nil ? "P\(phone!)" : nil
-
-        let udb = BindingsNewUserDiscoveryFromBackup(self, paramEmail, paramPhone, &error)
-
-        /// Alternate udb
-
-        guard let certPath = Bundle.module.path(forResource: "ud.elixxir.io", ofType: "crt") else {
-            fatalError("Couldn't retrieve cert.")
-        }
-
-        guard let contactFilePath = Bundle.module.path(forResource: "udContact-test", ofType: "bin") else {
-            fatalError("Couldn't retrieve cert.")
-        }
-
-//        try! udb!.setAlternative(
-//            "18.198.117.203:11420".data(using: .utf8),
-//            cert: try! Data(contentsOf: URL(fileURLWithPath: certPath)),
-//            contactFile: try! Data(contentsOf: URL(fileURLWithPath: contactFilePath))
-//        )
-
-        guard let error = error else { return udb! }
-        throw error.friendly()
-    }
-
-    public func generateUD() throws -> UserDiscoveryInterface {
-        log(type: .crumbs)
-
-        var error: NSError?
-        let udb = BindingsNewUserDiscovery(self, &error)
-
-        /// Alternate udb
-
-        guard let certPath = Bundle.module.path(forResource: "ud.elixxir.io", ofType: "crt") else {
-            fatalError("Couldn't retrieve cert.")
-        }
-
-        guard let contactFilePath = Bundle.module.path(forResource: "udContact-test", ofType: "bin") else {
-            fatalError("Couldn't retrieve cert.")
-        }
-
-//        try! udb!.setAlternative(
-//            "18.198.117.203:11420".data(using: .utf8),
-//            cert: try! Data(contentsOf: URL(fileURLWithPath: certPath)),
-//            contactFile: try! Data(contentsOf: URL(fileURLWithPath: contactFilePath))
-//        )
-
-        guard let error = error else { return udb! }
-        throw error.friendly()
-    }
-
-    public func restore(
-        ids: Data,
-        using ud: UserDiscoveryInterface,
-        lookupCallback: @escaping (Result<Contact, Error>) -> Void,
-        restoreCallback: @escaping (Int, Int, Int, String?) -> Void
-    ) -> RestoreReportType {
-        let restoreCb = RestoreContactsCallback(restoreCallback)
-
-        let lookupCb = LookupCallback {
-            switch $0 {
-            case .success(let contact):
-                lookupCallback(.success(.init(with: contact, status: .stranger)))
-            case .failure(let error):
-                lookupCallback(.failure(error))
-            }
-        }
-
-        return BindingsRestoreContactsFromBackup(ids, self, ud as? BindingsUserDiscovery, lookupCb, restoreCb)!
-    }
-}
-
-extension BindingsContact {
-
-    /// Scans the contact instance for a specified fact
-    ///
-    /// - Parameters:
-    ///   - fact: enum defined in ```FactType```
-    ///           that specifies the type we're
-    ///           searching
-    ///
-    /// - Note: Since GoLang does not support collections
-    ///         We need to do this workaround *length* and
-    ///         *get* instead of subscripting as in Swift.
-    ///
-    /// - Returns: Optional string in case we find the the fact
-    ///
-    /// - ToDo: Return a struct that contains all possible facts (?)
-    ///
-    func retrieve(fact: FactType) -> String? {
-        log(type: .crumbs)
-
-        guard let factList = getFactList() else { return nil }
-        for index in 0..<factList.num() {
-            if let actualFact = factList.get(index) {
-                if actualFact.type() == fact.rawValue {
-                    return String(actualFact.stringify().dropFirst())
-                }
-            }
-        }
-        return nil
-    }
-}
-
-extension BindingsSendReport: E2ESendReportType {
-    public var marshalled: Data { try! marshal() }
-    public var timestamp: Int64 { getTimestampNano() }
-    public var uniqueId: Data? { getMessageID() }
-    public var roundURL: String { getRoundURL() }
-}
-
-public protocol DummyTrafficManaging {
-    var status: Bool { get }
-    func setStatus(status: Bool)
-}
-
-extension BindingsDummyTraffic: DummyTrafficManaging {
-    public var status: Bool {
-        getStatus()
-    }
-
-    public func setStatus(status: Bool) {
-        try? setStatus(status)
-    }
-}
-
-extension BindingsBackup: BackupInterface {}
-
-extension BindingsRestoreContactsReport: RestoreReportType {}
diff --git a/Sources/Integration/Implementations/GroupManager.swift b/Sources/Integration/Implementations/GroupManager.swift
deleted file mode 100644
index 4b3dc4ef2e03abff18cea433fc30a1f2ed424b33..0000000000000000000000000000000000000000
--- a/Sources/Integration/Implementations/GroupManager.swift
+++ /dev/null
@@ -1,94 +0,0 @@
-import Models
-import XXModels
-import Bindings
-
-extension BindingsGroupChat: GroupManagerInterface {
-    public func send(_ payload: Data, to group: Data) -> Result<(Int64, Data?, String), Error> {
-        log(type: .crumbs)
-
-        do {
-            let report = try send(group, message: payload)
-            return .success((
-                report.getRoundID(),
-                report.getMessageID(),
-                report.getRoundURL()
-            ))
-        } catch {
-            return .failure(error)
-        }
-    }
-
-    public func create(
-        me: Data,
-        name: String,
-        welcome: String?,
-        with ids: [Data],
-        _ completion: @escaping (Result<Group, Error>) -> Void
-    ) {
-        log(type: .crumbs)
-
-        let list = BindingsIdList()
-        ids.forEach { try? list.add($0) }
-
-        var welcomeData: Data?
-
-        DispatchQueue.global().async { [weak self] in
-            guard let self = self else { return }
-
-            if let welcome = welcome {
-                welcomeData = welcome.data(using: .utf8)
-            }
-
-            let report = self.makeGroup(list, name: name.data(using: .utf8), message: welcomeData)
-
-            if let status = report?.getStatus() {
-                switch status {
-                case 0:
-                    completion(.failure(NSError.create("An error occurred before any requests could be sent")))
-                    return
-                case 1, 2:
-                    // 1. All requests failed to send
-                    // 2. Some requests failed and some succeeded
-
-                    if let id = report?.getGroup()?.getID() {
-                        do {
-                            try self.resendRequest(id)
-                            fallthrough
-                        } catch {
-                            completion(.failure(error))
-                            return
-                        }
-                    }
-                case 3:
-                    // All good
-                    guard let group = report?.getGroup() else {
-                        let errorContent = "Couldn't get report from group, although status was 3."
-                        completion(.failure(NSError.create(errorContent)))
-                        log(string: errorContent, type: .error)
-                        return
-                    }
-
-                    completion(.success(.init(
-                        id: group.getID()!,
-                        name: name,
-                        leaderId: me,
-                        createdAt: Date(),
-                        authStatus: .participating,
-                        serialized: group.serialize()!
-                    )))
-                    return
-                default:
-                    break
-                }
-            }
-        }
-    }
-
-    public func join(_ serializedGroup: Data) throws {
-        try joinGroup(serializedGroup)
-    }
-
-    public func leave(_ groupId: Data) throws {
-        try leaveGroup(groupId)
-    }
-}
diff --git a/Sources/Integration/Implementations/TransferManager.swift b/Sources/Integration/Implementations/TransferManager.swift
deleted file mode 100644
index 8c38a91fcc20eb289d83d0a62d9efb71592f2554..0000000000000000000000000000000000000000
--- a/Sources/Integration/Implementations/TransferManager.swift
+++ /dev/null
@@ -1,63 +0,0 @@
-import Models
-import Bindings
-import Foundation
-
-extension BindingsFileTransfer: TransferManagerInterface {
-
-    public func endTransferUpload(
-        with TID: Data
-    ) throws {
-        try closeSend(TID)
-    }
-
-    public func listenUploadFromTransfer(
-        with id: Data,
-        _ callback: @escaping (Bool, Int, Int, Int, Error?) -> Void
-    ) throws {
-        let cb = OutgoingTransferProgressCallback { completed, sent, arrived, total, error in
-            callback(completed, sent, arrived, total, error)
-        }
-
-        try registerSendProgressCallback(id, progressFunc: cb, periodMS: 1000)
-    }
-
-    public func listenDownloadFromTransfer(
-        with id: Data,
-        _ callback: @escaping (Bool, Int, Int, Error?) -> Void
-    ) throws {
-        let cb = IncomingTransferProgressCallback { completed, received, total, error in
-            callback(completed, received, total, error)
-        }
-
-        try registerReceiveProgressCallback(id, progressFunc: cb, periodMS: 1000)
-    }
-
-    public func downloadFileFromTransfer(
-        with id: Data
-    ) throws -> Data {
-        try receive(id)
-    }
-
-    public func uploadFile(
-        url: URL,
-        to recipient: Data,
-        _ callback: @escaping (Bool, Int, Int, Int, Error?) -> Void
-    ) throws -> Data {
-        let cb = OutgoingTransferProgressCallback { completed, sent, arrived, total, error in
-            callback(completed, sent, arrived, total, error)
-        }
-
-        guard let file = try? Data(contentsOf: url) else { fatalError() }
-
-        return try send(
-            url.lastPathComponent,
-            fileType: url.pathExtension,
-            fileData: file,
-            recipientID: recipient,
-            retry: 1,
-            preview: nil,
-            progressFunc: cb,
-            periodMS: 1000
-        )
-    }
-}
diff --git a/Sources/Integration/Implementations/UserDiscovery.swift b/Sources/Integration/Implementations/UserDiscovery.swift
deleted file mode 100644
index 56c9b4de349c6cc807e5a1e7e78a2755ffe7715e..0000000000000000000000000000000000000000
--- a/Sources/Integration/Implementations/UserDiscovery.swift
+++ /dev/null
@@ -1,166 +0,0 @@
-import Retry
-import Models
-import XXModels
-import Bindings
-import Foundation
-
-extension BindingsUserDiscovery: UserDiscoveryInterface {
-    public func lookup(forUserId: Data, _ completion: @escaping (Result<Contact, Error>) -> Void) {
-        let callback = LookupCallback {
-            switch $0 {
-            case .success(let contact):
-                completion(.success(.init(with: contact, status: .stranger)))
-            case .failure(let error):
-                completion(.failure(error))
-            }
-        }
-
-        retry(max: 10, retryStrategy: .delay(seconds: 1)) { [weak self] in
-            guard let self = self else { return }
-            try self.lookup(forUserId, callback: callback, timeoutMS: 20000)
-        }.finalCatch { error in
-            log(string: "UD.lookup 4E2E failed:\n\(error.localizedDescription)", type: .error)
-            completion(.failure(error.friendly()))
-        }
-    }
-
-    public func lookup(idList: [Data], _ completion: @escaping (Result<[Contact], Error>) -> Void) {
-        let list = BindingsIdList()
-        idList.forEach { try? list.add($0) }
-
-        let callback = MultiLookupCallback { [weak self] contactList, idList, error in
-            guard let self = self else { return }
-
-            if let error = error, error.count > 2 {
-                log(string: "UD.lookup group failed: \(error)", type: .error)
-                completion(.failure(NSError.create(error).friendly()))
-                return
-            }
-
-            guard let contacts = contactList else { return }
-            let count = contacts.len()
-            var results = [Contact]()
-
-            for index in 0..<count {
-                guard let contact = try? contacts.get(index),
-                      let marshal = try? contact.marshal(),
-                      ((try? self.retrieve(from: marshal, fact: .username) != nil) != nil) else {
-                    log(string: "Skipping", type: .error); continue
-                }
-
-                results.append(Contact(with: contact, status: .stranger))
-            }
-
-            completion(.success(results))
-        }
-
-        DispatchQueue.global().async { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.multiLookup(list, callback: callback, timeoutMS: 30000)
-            } catch {
-                log(string: "UD.lookup group failed: \(error.localizedDescription)", type: .error)
-                completion(.failure(error.friendly()))
-            }
-        }
-    }
-
-    public func deleteMyself(_ username: String) throws {
-        log(type: .crumbs)
-
-        do {
-            try removeUser("U\(username)")
-        } catch {
-            throw error.friendly()
-        }
-    }
-
-    public func register(_ fact: FactType, value: String, _ completion: @escaping (Result<String?, Error>) -> Void) {
-        log(type: .crumbs)
-
-        if fact == .username {
-            do {
-                try register(value)
-                completion(.success(value))
-                return
-            } catch {
-                completion(.failure(error.friendly()))
-                return
-            }
-        }
-
-        var error: NSError?
-        let bindingsFact = BindingsNewFact(fact.rawValue, value, &error)
-
-        if let error = error {
-            completion(.failure(error.friendly()))
-            return
-        }
-
-        var otherError: NSError?
-        let confirmationId = addFact(bindingsFact?.stringify(), error: &otherError)
-
-        if let otherError = otherError {
-            completion(.failure(otherError))
-            return
-        }
-
-        completion(.success(confirmationId))
-    }
-
-    public func confirm(code: String, id: String) throws {
-        log(type: .crumbs)
-
-        do {
-            try confirmFact(id, code: code)
-        } catch {
-            throw error.friendly()
-        }
-    }
-
-    public func retrieve(
-        from marshaled: Data,
-        fact: FactType
-    ) throws -> String? {
-
-        log(type: .crumbs)
-
-        var error: NSError?
-        let contact = BindingsUnmarshalContact(marshaled, &error)
-        if let err = error {
-            throw err.friendly()
-        }
-
-        return contact?.retrieve(fact: fact)
-    }
-
-    public func remove(_ fact: String) throws {
-        log(type: .crumbs)
-
-        do {
-            try removeFact(fact)
-        } catch {
-            throw error.friendly()
-        }
-    }
-
-    public func search(fact: String, _ completion: @escaping (Result<Contact, Error>) -> Void) throws {
-        log(type: .crumbs)
-
-        let callback = SearchCallback {
-            switch $0 {
-            case .success(let contact):
-                completion(.success(Contact(with: contact, status: .stranger)))
-            case .failure(let error):
-                completion(.failure(error))
-            }
-        }
-
-        do {
-            try searchSingle(fact, callback: callback, timeoutMS: 50000)
-        } catch {
-            throw error.friendly()
-        }
-    }
-}
diff --git a/Sources/Integration/Interfaces/BindingsInterface.swift b/Sources/Integration/Interfaces/BindingsInterface.swift
deleted file mode 100644
index b2eef8f94ba3283557530f35ca68e83a2b8dba62..0000000000000000000000000000000000000000
--- a/Sources/Integration/Interfaces/BindingsInterface.swift
+++ /dev/null
@@ -1,174 +0,0 @@
-import Models
-import Combine
-import XXModels
-import Foundation
-
-public enum MessageDeliveryStatus {
-    case sent
-    case failed
-    case timedout
-}
-
-public typealias DeliveryResult = (Data?, Bool, Bool, Data?)
-
-public typealias BackendEvent = (Int, String?, String?, String?)
-
-public typealias ClientNew = (String?, String?, Data?, String?, NSErrorPointer) -> Bool
-
-public typealias ClientFromBackup = (String?, String?, Data?, Data?, Data?, NSErrorPointer) -> Data?
-
-public typealias NotificationEvaluation = (String?, String?, NSErrorPointer) -> NotificationManyReportProtocol?
-
-public protocol E2ESendReportType {
-    var timestamp: Int64 { get }
-    var uniqueId: Data? { get }
-    var marshalled: Data { get }
-    var roundURL: String { get }
-}
-
-public protocol BackupInterface {
-    func stop() throws
-    func addJson(_: String?)
-    func isBackupRunning() -> Bool
-}
-
-public protocol RestoreReportType {
-    func lenFailed() -> Int
-    func lenRestored() -> Int
-    func getErrorAt(_: Int) -> String
-    func getFailedAt(_: Int) -> Data?
-    func getRestoreContactsError() -> String
-    func getRestoredAt(_: Int) -> Data?
-}
-
-public protocol BindingsInterface {
-
-    // MARK: Properties
-
-    var myId: Data { get }
-
-    var hasRunningTasks: Bool { get }
-
-    var receptionId: Data { get }
-
-    var meMarshalled: Data { get }
-
-    func meMarshalled(_: String, email: String?, phone: String?) -> Data
-
-    func verify(marshaled: Data, verifiedMarshaled: Data) throws -> Bool
-
-    func nodeRegistrationStatus() throws
-
-    // MARK: Static
-
-    static func updateErrors()
-
-    static var version: String { get }
-
-    static var secret: (Int) -> Data? { get }
-
-    static var login: (String?, Data?, String?, NSErrorPointer) -> BindingsInterface? { get }
-
-    static var new: ClientNew { get }
-
-    static var fromBackup: ClientFromBackup { get }
-
-    static func updateNDF(for: NetworkEnvironment, _: @escaping (Result<Data?, Error>) -> Void)
-
-    // MARK: Network
-
-    func startNetwork()
-    
-    func stopNetwork()
-
-    func replayRequests()
-
-    // MARK: Contacts
-    
-    func getId(from: Data) -> Data?
-
-    func confirm(_: Data, _: @escaping (Result<Bool, Error>) -> Void)
-
-    func add(_: Data, from: Data, _: @escaping (Result<Bool, Error>) -> Void)
-
-    // MARK: Messages
-
-    func send(_ payload: Data, to recipient: Data) -> Result<E2ESendReportType, Error>
-
-    func compress(image: Data, _: @escaping(Result<Data, Error>) -> Void)
-
-    func resetSessionWith(_: Data)
-
-    func listen(
-        report: Data,
-        _: @escaping (Result<MessageDeliveryStatus, Error>) -> Void
-    )
-    
-    func listenRound(
-        id: Int,
-        _: @escaping (Result<Bool, Error>) -> Void
-    )
-
-    // MARK: Notifications
-
-    func getPreImages() -> String
-
-    func registerNotifications(_ token: Data) throws
-
-    func unregisterNotifications() throws
-
-    func generateDummyTraficManager() throws -> DummyTrafficManaging
-
-    // MARK: UD
-    
-    func generateUD() throws -> UserDiscoveryInterface
-
-    func generateUDFromBackup(email: String?, phone: String?) throws -> UserDiscoveryInterface
-
-    // MARK: FileTransfer
-
-    func generateTransferManager(
-        _: @escaping (Data, String?, String?, Data?) -> Void
-    ) throws -> TransferManagerInterface
-
-    // MARK: Listeners
-
-    static func listenLogs()
-
-    func listenEvents(_: @escaping (BackendEvent) -> Void)
-
-    func listenMessages(_: @escaping (Message) -> Void) throws
-
-    func initializeBackup(
-        passphrase: String,
-        callback: @escaping (Data) -> Void
-    ) -> BackupInterface
-
-    func resumeBackup(
-        callback: @escaping (Data) -> Void
-    ) -> BackupInterface
-
-    func listenRequests(
-        _ requests: @escaping (Contact) -> Void,
-        _ confirmations: @escaping (Contact) -> Void,
-        _ resets: @escaping (Contact) -> Void
-    )
-
-    func listenPreImageUpdates()
-
-    func listenGroupRequests(
-        _: @escaping (Group, [Data], String?) -> Void,
-        groupMessages: @escaping (Message) -> Void
-    ) throws -> GroupManagerInterface?
-
-    func listenNetworkUpdates(_: @escaping (Bool) -> Void)
-
-    func removeContact(_ data: Data) throws
-
-    func restore(
-        ids: Data,
-        using: UserDiscoveryInterface,
-        lookupCallback: @escaping (Result<Contact, Error>) -> Void,
-        restoreCallback: @escaping (Int, Int, Int, String?) -> Void
-    ) -> RestoreReportType
-}
diff --git a/Sources/Integration/Interfaces/GroupManagerInterface.swift b/Sources/Integration/Interfaces/GroupManagerInterface.swift
deleted file mode 100644
index dcddfa9e39efe1f67d33a172e28c5cf84b595267..0000000000000000000000000000000000000000
--- a/Sources/Integration/Interfaces/GroupManagerInterface.swift
+++ /dev/null
@@ -1,14 +0,0 @@
-import Models
-import XXModels
-import Foundation
-
-public protocol GroupManagerInterface {
-
-    func join(_: Data) throws
-
-    func leave(_: Data) throws
-
-    func send(_: Data, to: Data) -> Result<(Int64, Data?, String), Error>
-
-    func create(me: Data, name: String, welcome: String?, with: [Data], _: @escaping (Result<Group, Error>) -> Void)
-}
diff --git a/Sources/Integration/Interfaces/TransferManagerInterface.swift b/Sources/Integration/Interfaces/TransferManagerInterface.swift
deleted file mode 100644
index b95d1ee34d2cd154192f74b336a44d6deed9dc69..0000000000000000000000000000000000000000
--- a/Sources/Integration/Interfaces/TransferManagerInterface.swift
+++ /dev/null
@@ -1,27 +0,0 @@
-import Foundation
-
-public protocol TransferManagerInterface {
-    func endTransferUpload(
-        with TID: Data
-    ) throws
-
-    func listenUploadFromTransfer(
-        with: Data,
-        _: @escaping (Bool, Int, Int, Int, Error?) -> Void
-    ) throws
-
-    func listenDownloadFromTransfer(
-        with: Data,
-        _: @escaping (Bool, Int, Int, Error?) -> Void
-    ) throws
-
-    func downloadFileFromTransfer(
-        with: Data
-    ) throws -> Data
-
-    func uploadFile(
-        url: URL,
-        to: Data,
-        _: @escaping (Bool, Int, Int, Int, Error?) -> Void
-    ) throws -> Data
-}
diff --git a/Sources/Integration/Interfaces/UserDiscoveryInterface.swift b/Sources/Integration/Interfaces/UserDiscoveryInterface.swift
deleted file mode 100644
index ded311ecdf19e7b179b2f91d325588f57a33ff2d..0000000000000000000000000000000000000000
--- a/Sources/Integration/Interfaces/UserDiscoveryInterface.swift
+++ /dev/null
@@ -1,27 +0,0 @@
-import Models
-import XXModels
-import Foundation
-
-public struct LookupResult {
-    public let id: Data
-    public let username: String
-}
-
-public protocol UserDiscoveryInterface {
-
-    func remove(_: String) throws
-
-    func deleteMyself(_: String) throws
-
-    func confirm(code: String, id: String) throws
-
-    func retrieve(from: Data, fact: FactType) throws -> String?
-
-    func lookup(forUserId: Data, _: @escaping (Result<Contact, Error>) -> Void)
-
-    func search(fact: String, _: @escaping (Result<Contact, Error>) -> Void) throws
-
-    func lookup(idList: [Data], _: @escaping (Result<[Contact], Error>) -> Void)
-
-    func register(_: FactType, value: String, _: @escaping (Result<String?, Error>) -> Void)
-}
diff --git a/Sources/Integration/Listeners.swift b/Sources/Integration/Listeners.swift
deleted file mode 100644
index 7ab877603cb879c6d660384a0a596e259a8856e3..0000000000000000000000000000000000000000
--- a/Sources/Integration/Listeners.swift
+++ /dev/null
@@ -1,151 +0,0 @@
-import Models
-import Shared
-import os.log
-import Combine
-import XXModels
-import Bindings
-import Foundation
-
-public extension BindingsClient {
-    static func listenLogs() {
-        let callback = LogCallback { log(string: $0 ?? "", type: .bindings) }
-        BindingsRegisterLogWriter(callback)
-    }
-
-    func listenPreImageUpdates() {
-        let callback = PreImageCallback { [weak self] _, _ in
-            if let defaults = UserDefaults(suiteName: "group.elixxir.messenger") {
-                let preImage = self?.getPreImages()
-                defaults.set(preImage, forKey: "preImage")
-            }
-        }
-
-        registerPreimageCallback(receptionId, pin: callback)
-    }
-
-    func initializeBackup(passphrase: String, callback: @escaping (Data) -> Void) -> BackupInterface {
-        var error: NSError?
-        os_signpost(.begin, log: logHandler, name: "Encrypting", "Calling BindingsInitializeBackup")
-        let backup = BindingsInitializeBackup(passphrase, UpdateBackupCallback(callback), self, &error)
-        os_signpost(.end, log: logHandler, name: "Encrypting", "Finished BindingsInitializeBackup")
-        return backup!
-    }
-
-    func resumeBackup(callback: @escaping (Data) -> Void) -> BackupInterface {
-        var error: NSError?
-        let backup = BindingsResumeBackup(UpdateBackupCallback(callback), self, &error)
-        return backup!
-    }
-
-    func listenMessages(_ callback: @escaping (Message) -> Void) throws {
-        let zeroBytes = [UInt8](repeating: 0, count: 33)
-
-        let listener = TextListener { bindingsMessage in
-            guard let message = bindingsMessage else { return }
-            let domainModel = Message(with: message, myId: self.myId)
-            callback(domainModel)
-        }
-
-        _ = try! registerListener(Data(zeroBytes), msgType: 2, listener: listener)
-    }
-
-    func listenRequests(
-        _ requests: @escaping (Contact) -> Void,
-        _ confirmations: @escaping (Contact) -> Void,
-        _ resets: @escaping (Contact) -> Void
-    ) {
-        let resetCallback = ResetCallback { resets(Contact(with: $0, status: .friend)) }
-        let confirmCallback = ConfirmationCallback { confirmations(Contact(with: $0, status: .friend)) }
-        let requestCallback = RequestCallback { requests(Contact(with: $0, status: .verificationInProgress)) }
-        registerAuthCallbacks(requestCallback, confirm: confirmCallback, reset: resetCallback)
-    }
-
-    func listenNetworkUpdates(_ callback: @escaping (Bool) -> Void) {
-        registerNetworkHealthCB(HealthCallback(callback))
-    }
-
-    func listenEvents(_ completion: @escaping (BackendEvent) -> Void) {
-        do {
-            try registerEventCallback("EventListener", myObj: EventCallback(completion))
-        } catch {
-            log(string: ">>> Event listener failed: \(error.localizedDescription)", type: .error)
-        }
-    }
-
-    func listenRound(id: Int, _ completion: @escaping (Result<Bool, Error>) -> Void) {
-        let callback = RoundCallback { completion(.success($0)) }
-        
-        do {
-            try wait(forRoundCompletion: id, rec: callback, timeoutMS: 15000)
-        } catch {
-            completion(.failure(error))
-        }
-    }
-
-    func listenDelivery(of report: Data, _ completion: @escaping (DeliveryResult) -> Void) throws {
-        let callback = DeliveryCallback { completion($0) }
-
-        var roundIds = [Int]()
-
-        var unmarshalError: NSError?
-
-        if let unmarshaled = BindingsUnmarshalSendReport(report, &unmarshalError),
-            let roundList = unmarshaled.getRoundList() {
-            let length = roundList.len()
-            for index in 0..<length {
-                var integer: Int = 0
-                do {
-                    try roundList.get(index, ret0_: &integer)
-                    roundIds.append(integer)
-                } catch {
-                    log(string: ">>> Error inspecting round list:\n\(error.localizedDescription)", type: .error)
-                }
-            }
-        }
-
-        try! wait(forMessageDelivery: report, mdc: callback, timeoutMS: 30000)
-    }
-
-    func listenGroupRequests(
-        _ groupRequests: @escaping (Group, [Data], String?) -> Void,
-        groupMessages: @escaping (Message) -> Void
-    ) throws -> GroupManagerInterface? {
-        var error: NSError?
-
-        let requestCallback = GroupRequestCallback {
-            guard let id = $0.getID(),
-                  let name = $0.getName(),
-                  let serialize = $0.serialize(),
-                  let memberList = $0.getMembership() else { return }
-
-            var members = [Data]()
-
-            var welcomeMessage: String?
-
-            if let welcomeData = $0.getInitMessage() {
-                welcomeMessage = String(data: welcomeData, encoding: .utf8)
-            }
-
-            for index in 0..<memberList.len() {
-                guard let member = try? memberList.get(index),
-                      let memberId = member.getID() else { continue }
-                members.append(memberId)
-            }
-
-            groupRequests(.init(
-                id: id,
-                name: String(data: name, encoding: .utf8)!,
-                leaderId: members.first!,
-                createdAt: Date(),
-                authStatus: .pending,
-                serialized: serialize
-            ), members, welcomeMessage)
-        }
-
-        let messageCallback = GroupMessageCallback { groupMessages(Message(with: $0)) }
-        let groupManager = BindingsNewGroupManager(self, requestCallback, messageCallback, &error)
-
-        guard let error = error else { return groupManager }
-        fatalError(error.localizedDescription)
-    }
-}
diff --git a/Sources/Integration/Logging.swift b/Sources/Integration/Logging.swift
deleted file mode 100644
index c37e60b32c1261112930efe2a84248a74290fe3b..0000000000000000000000000000000000000000
--- a/Sources/Integration/Logging.swift
+++ /dev/null
@@ -1,85 +0,0 @@
-import Bindings
-import XXLogger
-import CrashReporting
-import DependencyInjection
-import Foundation
-import os
-
-let oslogger = Logger(subsystem: "logs_xxmessenger", category: "Logging.swift")
-
-final class BindingsError: NSObject, BindingsClientErrorProtocol {
-    func report(_ source: String?, message: String?, trace: String?) {
-        var content = ""
-
-        content += String(describing: source) + "\n"
-        content += String(describing: message) + "\n"
-        content += String(describing: trace)
-
-        log(string: content, type: .error)
-    }
-}
-
-extension Error {
-    func friendly() -> NSError {
-        log(string: ">>> Switching to friendly error from: \(localizedDescription)", type: .error)
-        
-        let error = BindingsErrorStringToUserFriendlyMessage(localizedDescription)
-        if error.hasPrefix("UR") {
-            let crashReporter = try! DependencyInjection.Container.shared.resolve() as CrashReporter
-            crashReporter.sendError(self as NSError)
-            return NSError.create("Unexpected error. Please try again")
-        } else {
-            return NSError.create(error)
-        }
-    }
-}
-
-enum LogType {
-    case info
-    case error
-    case crumbs
-    case bindings
-
-    var icon: String {
-        switch self {
-        case .error:
-            return "🟥"
-        case .crumbs:
-            return "🍞"
-        case .bindings:
-            return "⚙️"
-        case .info:
-            return "✅"
-        }
-    }
-}
-
-func log(
-    string: String? = nil,
-    type: LogType,
-    function: String = #function,
-    file: String = #file,
-    line: Int = #line
-) {
-    var trimmedFile = ""
-    if let index = file.lastIndex(of: "/") {
-        let afterEqualsTo = String(file.suffix(from: index).dropFirst())
-        trimmedFile = afterEqualsTo
-    }
-
-    let content = "\(type.icon) \(function) @\(trimmedFile):\(line) \(string ?? "")"
-    let logger = try! DependencyInjection.Container.shared.resolve() as XXLogger
-
-    switch type {
-    case .info:
-        logger.info(content)
-        oslogger.info("\(content)")
-    case .error:
-        logger.error(content)
-        oslogger.error("\(content)")
-    case .crumbs:
-        logger.debug(content)
-    case .bindings:
-        logger.warning(content)
-    }
-}
diff --git a/Sources/Integration/Mocks/BindingsMock.swift b/Sources/Integration/Mocks/BindingsMock.swift
deleted file mode 100644
index b4eb5c0bdb65e64130ef0f1c6e85e01993fb8bf4..0000000000000000000000000000000000000000
--- a/Sources/Integration/Mocks/BindingsMock.swift
+++ /dev/null
@@ -1,302 +0,0 @@
-import Models
-import Combine
-import XXModels
-import Foundation
-
-public final class BindingsMock: BindingsInterface {
-    private var cancellables = Set<AnyCancellable>()
-    private let requestsSubject = PassthroughSubject<Contact, Never>()
-    private let groupRequestsSubject = PassthroughSubject<Group, Never>()
-    private let confirmationsSubject = PassthroughSubject<Contact, Never>()
-
-    public var hasRunningTasks: Bool {
-        false
-    }
-
-    public func replayRequests() {}
-
-    public var myId: Data {
-        "MOCK_USER".data(using: .utf8)!
-    }
-
-    public var receptionId: Data {
-        "RECEPTION_ID".data(using: .utf8)!
-    }
-
-    public var meMarshalled: Data {
-        "MOCK_USER_MARSHALLED".data(using: .utf8)!
-    }
-
-    public static var secret: (Int) -> Data? = {
-        "\($0)".data(using: .utf8)!
-    }
-
-    public func verify(marshaled: Data, verifiedMarshaled: Data) throws -> Bool {
-        true
-    }
-
-    public static let version: String = "MOCK"
-
-    public static var new: ClientNew = { _,_,_,_,_ in true }
-
-    public static var fromBackup: ClientFromBackup = { _,_,_,_,_,_ in Data() }
-
-    public static var login: (String?, Data?, String?, NSErrorPointer) -> BindingsInterface? = { _,_,_,_ in BindingsMock() }
-
-    public func meMarshalled(_: String, email: String?, phone: String?) -> Data {
-        meMarshalled
-    }
-
-    public func startNetwork() {}
-
-    public func stopNetwork() {}
-
-    public static func listenLogs() {}
-
-    public static func updateErrors() {}
-
-    public func listenPreImageUpdates() {}
-
-    public func getPreImages() -> String { "" }
-
-    public func nodeRegistrationStatus() throws {}
-
-    public func getId(from: Data) -> Data? { from }
-
-    public func unregisterNotifications() throws {}
-
-    public func registerNotifications(_: Data) throws {}
-
-    public func compress(image: Data, _: @escaping(Result<Data, Error>) -> Void) {}
-
-    public func generateUD() throws -> UserDiscoveryInterface { UserDiscoveryMock() }
-
-    public func generateUDFromBackup(
-        email: String?,
-        phone: String?
-    ) throws -> UserDiscoveryInterface { UserDiscoveryMock() }
-
-    public func generateTransferManager(
-        _: @escaping (Data, String?, String?, Data?) -> Void
-    ) throws -> TransferManagerInterface {
-        TransferManagerMock()
-    }
-
-    public func listenEvents(_: @escaping (BackendEvent) -> Void) {}
-
-    public func listenMessages(_: @escaping (Message) -> Void) throws {}
-
-
-    public func initializeBackup(
-        passphrase: String,
-        callback: @escaping (Data) -> Void
-    ) -> BackupInterface { BindingsBackupMock() }
-
-    public func resumeBackup(
-        callback: @escaping (Data) -> Void
-    ) -> BackupInterface { BindingsBackupMock() }
-
-    public func listenBackups(_: @escaping (Data) -> Void) -> BackupInterface { fatalError() }
-
-    public func listenNetworkUpdates(_: @escaping (Bool) -> Void) {}
-
-    public func confirm(_: Data, _ completion: @escaping (Result<Bool, Error>) -> Void) {
-        DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
-            completion(.success(true))
-        }
-    }
-    
-    public func listenRound(id: Int, _: @escaping (Result<Bool, Error>) -> Void) {}
-
-    public func add(_ contact: Data, from: Data, _ completion: @escaping (Result<Bool, Error>) -> Void) {
-        DispatchQueue.global().asyncAfter(deadline: .now() + 2) { [weak self] in
-            if contact == Contact.georgeDiscovered.marshaled {
-                completion(.success(true))
-            } else {
-                completion(.success(false))
-                return
-            }
-
-            self?.requestsSubject.send(.carlRequested)
-            self?.requestsSubject.send(.angelinaRequested)
-            self?.requestsSubject.send(.elonRequested)
-
-            DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [weak self] in
-                self?.confirmationsSubject.send(.georgeDiscovered)
-            }
-        }
-    }
-
-    public func send(
-        _ payload: Data,
-        to recipient: Data
-    ) -> Result<E2ESendReportType, Error> {
-        .success(MockE2ESendReport())
-    }
-
-    public func listen(
-        report: Data,
-        _ completion: @escaping (Result<MessageDeliveryStatus, Error>) -> Void
-    ) {
-        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
-            completion(.success(.sent))
-        }
-    }
-
-    public func generateDummyTraficManager() throws -> DummyTrafficManaging {
-        MockDummyManager()
-    }
-
-    public func removeContact(_ data: Data) throws {}
-
-    public func resetSessionWith(_: Data) {}
-
-    public func listenRequests(
-        _ requests: @escaping (Contact) -> Void,
-        _ confirmations: @escaping (Contact) -> Void,
-        _ resets: @escaping (Contact) -> Void
-    ) {
-        requestsSubject.sink(receiveValue: requests).store(in: &cancellables)
-        confirmationsSubject.sink(receiveValue: confirmations).store(in: &cancellables)
-    }
-
-    public func listenGroupRequests(
-        _ groupRequests: @escaping (Group, [Data], String?) -> Void,
-        groupMessages: @escaping (Message) -> Void
-    ) throws -> GroupManagerInterface? {
-        groupRequestsSubject
-            .sink { groupRequests($0, [], nil) }
-            .store(in: &cancellables)
-
-        return GroupManagerMock()
-    }
-
-    public func restore(
-        ids: Data,
-        using ud: UserDiscoveryInterface,
-        lookupCallback: @escaping (Result<Contact, Error>) -> Void,
-        restoreCallback: @escaping (Int, Int, Int, String?) -> Void
-    ) -> RestoreReportType {
-        fatalError()
-    }
-
-    public static func updateNDF(for: NetworkEnvironment, _ completion: @escaping (Result<Data?, Error>) -> Void) {
-        completion(.success(Data()))
-    }
-}
-
-extension Group {
-    static let mockGroup = Group(
-        id: "mockGroup".data(using: .utf8)!,
-        name: "Bruno's birthday 6/1",
-        leaderId: "mockGroupLeader".data(using: .utf8)!,
-        createdAt: Date.distantPast,
-        authStatus: .pending,
-        serialized: "mockGroup".data(using: .utf8)!
-    )
-}
-
-extension Contact {
-    static func mock(_ count: Int = 1) -> [Contact] {
-        var mocks = [Contact]()
-
-        for n in 0..<count {
-            mocks.append(
-                .init(
-                    id: "brad\(n)".data(using: .utf8)!,
-                    marshaled: "brad\(n)".data(using: .utf8)!,
-                    username: "brad\(n)",
-                    email: "brad\(n)@xx.io",
-                    phone: "819820212\(n)5BR",
-                    nickname: nil,
-                    photo: nil,
-                    authStatus: .verified,
-                    isRecent: false,
-                    createdAt: Date()
-            ))
-        }
-
-        return mocks
-    }
-
-    static let angelinaRequested = Contact(
-        id: "angelinajolie".data(using: .utf8)!,
-        marshaled: "angelinajolie".data(using: .utf8)!,
-        username: "angelinajolie",
-        email: nil,
-        phone: nil,
-        nickname: "Angelica Jolie",
-        photo: nil,
-        authStatus: .verificationInProgress,
-        isRecent: false,
-        createdAt: Date()
-    )
-
-    static let carlRequested = Contact(
-        id: "carlsagan".data(using: .utf8)!,
-        marshaled: "carlsagan".data(using: .utf8)!,
-        username: "carlsagan",
-        email: "carl@jpl.nasa",
-        phone: "81982022244BR",
-        nickname: "Carl Sagan",
-        photo: nil,
-        authStatus: .verified,
-        isRecent: false,
-        createdAt: Date.distantPast
-    )
-
-    static let elonRequested = Contact(
-        id: "elonmusk".data(using: .utf8)!,
-        marshaled: "elonmusk".data(using: .utf8)!,
-        username: "elonmusk",
-        email: "elon@tesla.com",
-        phone: nil,
-        nickname: "Elon Musk",
-        photo: nil,
-        authStatus: .verified,
-        isRecent: false,
-        createdAt: Date.distantPast
-    )
-
-    static let georgeDiscovered = Contact(
-        id: "georgebenson74".data(using: .utf8)!,
-        marshaled: "georgebenson74".data(using: .utf8)!,
-        username: "bruno_muniz74",
-        email: "george@xx.io",
-        phone: "81987022255BR",
-        nickname: "Bruno Muniz",
-        photo: nil,
-        authStatus: .stranger,
-        isRecent: false,
-        createdAt: Date()
-    )
-}
-
-public struct MockE2ESendReport: E2ESendReportType {
-    public var timestamp: Int64 { 1 }
-    public var marshalled: Data { Data() }
-    public var uniqueId: Data? { Data() }
-    public var roundURL: String { "https://www.google.com.br" }
-}
-
-public struct MockDummyManager: DummyTrafficManaging {
-    public var status: Bool { true }
-
-    public func setStatus(status: Bool) {
-        print("Dummy manager status set to \(status)")
-    }
-}
-
-public struct BindingsBackupMock: BackupInterface {
-    public func stop() throws {
-        // TODO
-    }
-
-    public func addJson(_: String?) {
-        // TODO
-    }
-
-    public func isBackupRunning() -> Bool {
-        return true
-    }
-}
diff --git a/Sources/Integration/Mocks/GroupManagerMock.swift b/Sources/Integration/Mocks/GroupManagerMock.swift
deleted file mode 100644
index 137cfca69c25a188f20c60af867befc3d79a04cf..0000000000000000000000000000000000000000
--- a/Sources/Integration/Mocks/GroupManagerMock.swift
+++ /dev/null
@@ -1,21 +0,0 @@
-import Models
-import XXModels
-import Foundation
-
-final class GroupManagerMock: GroupManagerInterface {
-    func join(_: Data) throws {}
-
-    func leave(_: Data) throws {}
-
-    func send(_: Data, to: Data) -> Result<(Int64, Data?, String), Error> {
-        .success((1, nil, "https://www.google.com.br"))
-    }
-
-    func create(
-        me: Data,
-        name: String,
-        welcome: String?,
-        with: [Data],
-        _: @escaping (Result<Group, Error>) -> Void
-    ) {}
-}
diff --git a/Sources/Integration/Mocks/TransferManagerMock.swift b/Sources/Integration/Mocks/TransferManagerMock.swift
deleted file mode 100644
index 9b87da5d214d5cf0dcc5d39cdd88a979ff1a6e76..0000000000000000000000000000000000000000
--- a/Sources/Integration/Mocks/TransferManagerMock.swift
+++ /dev/null
@@ -1,33 +0,0 @@
-import Foundation
-
-final class TransferManagerMock: TransferManagerInterface {
-    func endTransferUpload(
-        with TID: Data
-    ) throws {}
-
-    func listenDownloadFromTransfer(
-        with: Data,
-        _: @escaping (Bool, Int, Int, Error?) -> Void
-    ) throws {
-        fatalError()
-    }
-
-    func listenUploadFromTransfer(
-        with: Data,
-        _: @escaping (Bool, Int, Int, Int, Error?) -> Void
-    ) throws {}
-
-    func downloadFileFromTransfer(
-        with: Data
-    ) throws -> Data {
-        fatalError()
-    }
-
-    func uploadFile(
-        url: URL,
-        to: Data,
-        _: @escaping (Bool, Int, Int, Int, Error?) -> Void
-    ) throws -> Data {
-        Data()
-    }
-}
diff --git a/Sources/Integration/Mocks/UserDiscoveryMock.swift b/Sources/Integration/Mocks/UserDiscoveryMock.swift
deleted file mode 100644
index bb1a8f2440ce0baf65bd8eb5dff4ef305babb842..0000000000000000000000000000000000000000
--- a/Sources/Integration/Mocks/UserDiscoveryMock.swift
+++ /dev/null
@@ -1,45 +0,0 @@
-import Models
-import XXModels
-import Foundation
-
-final class UserDiscoveryMock: UserDiscoveryInterface {
-
-    func remove(_ fact: String) throws {}
-
-    func deleteMyself(_ username: String) throws {}
-
-    func confirm(code: String, id: String) throws {}
-
-    func lookup(idList: [Data], _: @escaping (Result<[Contact], Error>) -> Void) {}
-
-    func retrieve(from: Data, fact: FactType) throws -> String? { fact.description }
-
-    func search(fact: String, _ completion: @escaping (Result<Contact, Error>) -> Void) throws {
-        completion(.success(.georgeDiscovered))
-    }
-
-    func register(_: FactType, value: String, _ completion: @escaping (Result<String?, Error>) -> Void) {
-        completion(.success("#CONFIRMATION_CODE_FOR \(value)"))
-    }
-
-    func lookup(
-        forUserId: Data,
-        _ completion: @escaping (Result<Contact, Error>) -> Void
-    ) {
-        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
-            completion(.success(
-                .init(
-                    id: "mock_username".data(using: .utf8)!,
-                    marshaled: "mock_username".data(using: .utf8)!,
-                    username: "mock_username",
-                    email: nil,
-                    phone: nil,
-                    nickname: "mock_nickname",
-                    photo: nil,
-                    authStatus: .stranger,
-                    isRecent: false,
-                    createdAt: Date()
-            )))
-        }
-    }
-}
diff --git a/Sources/Integration/Resources/cert_mainnet.txt b/Sources/Integration/Resources/cert_mainnet.txt
deleted file mode 100644
index 40045d63666dded7450e22eb82c15b62a17d4d68..0000000000000000000000000000000000000000
--- a/Sources/Integration/Resources/cert_mainnet.txt
+++ /dev/null
@@ -1 +0,0 @@
-[PLACE THE CERTIFICATE CONTENT HERE]
diff --git a/Sources/Integration/Session/Session+Chat.swift b/Sources/Integration/Session/Session+Chat.swift
deleted file mode 100644
index b8d6e02d45f0cb6681fe206a31c117d7807a70c5..0000000000000000000000000000000000000000
--- a/Sources/Integration/Session/Session+Chat.swift
+++ /dev/null
@@ -1,270 +0,0 @@
-import UIKit
-import Models
-import Shared
-import XXModels
-import Foundation
-
-extension Session {
-    public func send(imageData: Data, to contact: Contact, completion: @escaping (Result<Void, Error>) -> Void) {
-        client.bindings.compress(image: imageData) { [weak self] result in
-            guard let self = self else {
-                completion(.success(()))
-                return
-            }
-
-            switch result {
-            case .success(let compressedImage):
-                do {
-                    let url = try FileManager.store(
-                        data: compressedImage,
-                        name: "image_\(Date.asTimestamp)",
-                        type: "jpeg"
-                    )
-
-                    self.sendFile(url: url, to: contact)
-                    completion(.success(()))
-                } catch {
-                    completion(.failure(error))
-                }
-
-            case .failure(let error):
-                completion(.failure(error))
-                log(string: "Error when compressing image: \(error.localizedDescription)", type: .error)
-            }
-        }
-    }
-
-    public func sendFile(url: URL, to contact: Contact) {
-        guard let manager = client.transferManager else { fatalError("A transfer manager was not created") }
-
-        DispatchQueue.global().async { [weak self] in
-            guard let self = self else { return }
-
-            var tid: Data?
-
-            do {
-                tid = try manager.uploadFile(url: url, to: contact.id) { completed, send, arrived, total, error in
-                    guard let tid = tid else { return }
-
-                    if completed {
-                        self.endTransferWith(tid: tid)
-                    } else {
-                        if error != nil {
-                            self.failTransferWith(tid: tid)
-                        } else {
-                            self.progressTransferWith(tid: tid, arrived: Float(arrived), total: Float(total))
-                        }
-                    }
-                }
-
-                guard let tid = tid else { return }
-
-                let content = url.pathExtension == "m4a" ? "a voice message" : "an image"
-
-                let transfer = FileTransfer(
-                    id: tid,
-                    contactId: contact.id,
-                    name: url.deletingPathExtension().lastPathComponent,
-                    type: url.pathExtension,
-                    data: try? Data(contentsOf: url),
-                    progress: 0.0,
-                    isIncoming: false,
-                    createdAt: Date()
-                )
-
-                _ = try? self.dbManager.saveFileTransfer(transfer)
-
-                let message = Message(
-                    networkId: nil,
-                    senderId: self.client.bindings.myId,
-                    recipientId: contact.id,
-                    groupId: nil,
-                    date: Date(),
-                    status: .sending,
-                    isUnread: false,
-                    text: "You sent \(content)",
-                    replyMessageId: nil,
-                    roundURL: nil,
-                    fileTransferId: tid
-                )
-
-                _ = try? self.dbManager.saveMessage(message)
-            } catch {
-                print(error.localizedDescription)
-            }
-        }
-    }
-
-    public func send(_ payload: Payload, toContact contact: Contact) {
-        var message = Message(
-            networkId: nil,
-            senderId: client.bindings.myId,
-            recipientId: contact.id,
-            groupId: nil,
-            date: Date(),
-            status: .sending,
-            isUnread: false,
-            text: payload.text,
-            replyMessageId: payload.reply?.messageId,
-            roundURL: nil,
-            fileTransferId: nil
-        )
-
-        do {
-            message = try dbManager.saveMessage(message)
-            send(message: message)
-        } catch {
-            log(string: error.localizedDescription, type: .error)
-        }
-    }
-
-    public func retryMessage(_ id: Int64) {
-        if var message = try? dbManager.fetchMessages(.init(id: [id])).first {
-            message.status = .sending
-            message.date = Date()
-
-            if let message = try? dbManager.saveMessage(message) {
-                if let _ = message.recipientId {
-                    send(message: message)
-                } else {
-                    send(groupMessage: message)
-                }
-            }
-        }
-    }
-
-    func send(message: Message) {
-        var message = message
-
-        var reply: Reply?
-        if let replyId = message.replyMessageId,
-           let replyMessage = try? dbManager.fetchMessages(Message.Query(networkId: replyId)).first {
-            reply = Reply(messageId: replyId, senderId: replyMessage.senderId)
-        }
-
-        let payloadData = Payload(text: message.text, reply: reply).asData()
-
-        DispatchQueue.global().async { [weak self] in
-            guard let self = self else { return }
-            switch self.client.bindings.send(payloadData, to: message.recipientId!) {
-            case .success(let report):
-                message.roundURL = report.roundURL
-
-                self.client.bindings.listen(report: report.marshalled) { result in
-                    switch result {
-                    case .success(let status):
-                        switch status {
-                        case .failed:
-                            message.status = .sendingFailed
-                        case .sent:
-                            message.status = .sent
-                        case .timedout:
-                            message.status = .sendingTimedOut
-                        }
-                    case .failure:
-                        message.status = .sendingFailed
-                    }
-
-                    message.networkId = report.uniqueId
-                    message.date = Date.fromTimestamp(Int(report.timestamp))
-                    DispatchQueue.main.async {
-                        do {
-                            _ = try self.dbManager.saveMessage(message)
-                        } catch {
-                            log(string: error.localizedDescription, type: .error)
-                        }
-                    }
-                }
-            case .failure(let error):
-                message.status = .sendingFailed
-                log(string: error.localizedDescription, type: .error)
-            }
-
-            DispatchQueue.main.async {
-                do {
-                    _ = try self.dbManager.saveMessage(message)
-                } catch {
-                    log(string: error.localizedDescription, type: .error)
-                }
-            }
-        }
-    }
-
-    private func endTransferWith(tid: Data) {
-        guard let manager = client.transferManager else {
-            fatalError("A transfer manager was not created")
-        }
-
-        try? manager.endTransferUpload(with: tid)
-
-        if var message = try? dbManager.fetchMessages(.init(fileTransferId: tid)).first {
-            message.status = .sent
-            _ = try? dbManager.saveMessage(message)
-        }
-
-        if var transfer = try? dbManager.fetchFileTransfers(.init(id: [tid])).first {
-            transfer.progress = 1.0
-            _ = try? dbManager.saveFileTransfer(transfer)
-        }
-    }
-
-    private func failTransferWith(tid: Data) {
-        if var message = try? dbManager.fetchMessages(.init(fileTransferId: tid)).first {
-            message.status = .sendingFailed
-            _ = try? dbManager.saveMessage(message)
-        }
-    }
-
-    private func progressTransferWith(tid: Data, arrived: Float, total: Float) {
-        if var transfer = try? dbManager.fetchFileTransfers(.init(id: [tid])).first {
-            transfer.progress = arrived/total
-            _ = try? dbManager.saveFileTransfer(transfer)
-        }
-    }
-
-    func handle(incomingTransfer transfer: FileTransfer) {
-        guard let manager = client.transferManager else {
-            fatalError("A transfer manager was not created")
-        }
-
-        let content = transfer.type == "m4a" ? "a voice message" : "an image"
-
-        var message = try! dbManager.saveMessage(
-            Message(
-                networkId: nil,
-                senderId: transfer.contactId,
-                recipientId: myId,
-                groupId: nil,
-                date: transfer.createdAt,
-                status: .receiving,
-                isUnread: true,
-                text: "Sent you \(content)",
-                replyMessageId: nil,
-                roundURL: nil,
-                fileTransferId: transfer.id
-            )
-        )
-
-        try! manager.listenDownloadFromTransfer(with: transfer.id) { completed, arrived, total, error in
-            if let error = error {
-                print(error.localizedDescription)
-                return
-            }
-
-            if completed {
-                if let data = try? manager.downloadFileFromTransfer(with: transfer.id),
-                   let _ = try? FileManager.store(data: data, name: transfer.name, type: transfer.type) {
-                    var transfer = transfer
-                    transfer.data = data
-                    transfer.progress = 1.0
-                    message.status = .received
-
-                    _ = try? self.dbManager.saveFileTransfer(transfer)
-                    _ = try? self.dbManager.saveMessage(message)
-                }
-            } else {
-                self.progressTransferWith(tid: transfer.id, arrived: Float(arrived), total: Float(total))
-            }
-        }
-    }
-}
diff --git a/Sources/Integration/Session/Session+Contacts.swift b/Sources/Integration/Session/Session+Contacts.swift
deleted file mode 100644
index d30f78c24e014cb411b0608638904d5664f626aa..0000000000000000000000000000000000000000
--- a/Sources/Integration/Session/Session+Contacts.swift
+++ /dev/null
@@ -1,258 +0,0 @@
-import Retry
-import Models
-import Shared
-import XXModels
-import Foundation
-
-extension Session {
-    public func getId(from data: Data) -> Data? {
-        client.bindings.getId(from: data)
-    }
-
-    public func verify(contact: Contact) {
-        var contact = contact
-        contact.authStatus = .verificationInProgress
-
-        do {
-            contact = try dbManager.saveContact(contact)
-        } catch {
-            log(string: "Failed to store contact request upon verification. Returning, request will be abandoned to not crash", type: .error)
-        }
-
-        retry(max: 4, retryStrategy: .delay(seconds: 1)) { [weak self] in
-            if self?.networkMonitor.xxStatus != .available {
-                log(string: "Network is not available yet for ownership. Retrying in 1 second...", type: .error)
-                throw NSError.create("")
-            }
-        }.finalCatch { error in
-            log(string: "Failed to verify contact cause network wasn't available at all", type: .crumbs)
-            return
-        }
-
-        let resultClosure: (Result<Contact, Error>) -> Void = { result in
-            switch result {
-            case .success(let mightBe):
-                guard try! self.client.bindings.verify(marshaled: contact.marshaled!, verifiedMarshaled: mightBe.marshaled!) else {
-                    do {
-                        try self.dbManager.deleteContact(contact)
-                    } catch {
-                        log(string: error.localizedDescription, type: .error)
-                    }
-
-                    return
-                }
-
-                contact.authStatus = .verified
-
-                do {
-                    try self.dbManager.saveContact(contact)
-                } catch {
-                    log(string: error.localizedDescription, type: .error)
-                }
-
-            case .failure:
-                contact.authStatus = .verificationFailed
-
-                do {
-                    try self.dbManager.saveContact(contact)
-                } catch {
-                    log(string: error.localizedDescription, type: .error)
-                }
-            }
-        }
-
-        let ud = client.userDiscovery!
-
-        let hasEmail = contact.email != nil
-        let hasPhone = contact.phone != nil
-
-        guard hasEmail || hasPhone else {
-            ud.lookup(forUserId: contact.id, resultClosure)
-            return
-        }
-
-        var fact: String
-
-        if hasEmail {
-            fact = "\(FactType.email.prefix)\(contact.email!)"
-        } else {
-            fact = "\(FactType.phone.prefix)\(contact.phone!)"
-        }
-
-        do {
-            try ud.search(fact: fact, resultClosure)
-        } catch {
-            log(string: error.localizedDescription, type: .error)
-            contact.authStatus = .verificationFailed
-
-            do {
-                try self.dbManager.saveContact(contact)
-            } catch {
-                log(string: error.localizedDescription, type: .error)
-            }
-        }
-    }
-
-    public func retryRequest(_ contact: Contact) throws {
-        let name = (contact.nickname ?? contact.username) ?? ""
-
-        client.bindings.add(contact.marshaled!, from: myQR) { [weak self, contact] in
-            var contact = contact
-            guard let self = self else { return }
-
-            do {
-                switch $0 {
-                case .success:
-                    contact.authStatus = .requested
-
-                    self.toastController.enqueueToast(model: .init(
-                        title: Localized.Requests.Sent.Toast.resent(name),
-                        leftImage: Asset.sharedSuccess.image
-                    ))
-
-                case .failure:
-                    contact.createdAt = Date()
-
-                    self.toastController.enqueueToast(model: .init(
-                        title: Localized.Requests.Sent.Toast.resentFailed(name),
-                        color: Asset.accentDanger.color,
-                        leftImage: Asset.requestFailedToaster.image
-                    ))
-                }
-
-                _ = try self.dbManager.saveContact(contact)
-            } catch {
-                log(string: error.localizedDescription, type: .error)
-            }
-        }
-    }
-
-    public func add(_ contact: Contact) throws {
-        /// Make sure we are not adding ourselves
-        ///
-        guard contact.username != username else {
-            throw NSError.create("You can't add yourself")
-        }
-
-        var contact = contact
-
-        /// Check if this contact is actually
-        /// being requested/confirmed after failing
-        ///
-        if [.requestFailed, .confirmationFailed].contains(contact.authStatus) {
-            /// If it is being re-requested or
-            /// re-confirmed, no need to save again
-            ///
-            contact.createdAt = Date()
-
-            if contact.authStatus == .confirmationFailed {
-                try confirm(contact)
-                return
-            }
-        } else {
-            /// If its not failed, lets make sure that
-            /// this is an actual new contact
-            ///
-            if let _ = try? dbManager.fetchContacts(.init(id: [contact.id])).first {
-                /// Found a user w/ that id already stored
-                ///
-                throw NSError.create("This user has already been requested")
-            }
-
-            contact.authStatus = .requesting
-        }
-
-        contact = try dbManager.saveContact(contact)
-
-        let myself = client.bindings.meMarshalled(
-            username!,
-            email: isSharingEmail ? email : nil,
-            phone: isSharingPhone ? phone : nil
-        )
-
-        client.bindings.add(contact.marshaled!, from: myself) { [weak self, contact] in
-            guard let self = self else { return }
-            var contact = contact
-
-            do {
-                switch $0 {
-                case .success(let success):
-                    contact.authStatus = success ? .requested : .requestFailed
-                    contact = try self.dbManager.saveContact(contact)
-
-                    let name = contact.nickname ?? contact.username
-
-                    self.toastController.enqueueToast(model: .init(
-                        title: Localized.Requests.Sent.Toast.sent(name ?? ""),
-                        leftImage: Asset.sharedSuccess.image
-                    ))
-
-                case .failure:
-                    contact.createdAt = Date()
-                    contact.authStatus = .requestFailed
-                    contact = try self.dbManager.saveContact(contact)
-
-                    self.toastController.enqueueToast(model: .init(
-                        title: Localized.Requests.Failed.toast(contact.nickname ?? contact.username!),
-                        color: Asset.accentDanger.color,
-                        leftImage: Asset.requestFailedToaster.image
-                    ))
-                }
-            } catch {
-                print(error.localizedDescription)
-            }
-        }
-    }
-
-    public func confirm(_ contact: Contact) throws {
-        var contact = contact
-        contact.authStatus = .confirming
-        contact = try dbManager.saveContact(contact)
-
-        client.bindings.confirm(contact.marshaled!) { [weak self] in
-            switch $0 {
-            case .success(let confirmed):
-                contact.isRecent = true
-                contact.createdAt = Date()
-                contact.authStatus = confirmed ? .friend : .confirmationFailed
-
-            case .failure:
-                contact.authStatus = .confirmationFailed
-            }
-
-            _ = try? self?.dbManager.saveContact(contact)
-        }
-    }
-
-    public func deleteContact(_ contact: Contact) throws {
-        if !(try dbManager.fetchFileTransfers(.init(contactId: contact.id))).isEmpty {
-            throw NSError.create("There is an ongoing file transfer with this contact as you are receiving or sending a file, please try again later once it’s done")
-        }
-
-        try client.bindings.removeContact(contact.marshaled!)
-
-        /// Currently this cascades into deleting
-        /// all messages w/ contact.id == senderId
-        /// But this shouldn't be the always the case
-        /// because if we have a group / this contact
-        /// the messages will be gone as well.
-        ///
-        /// Suggestion: If there's a group where this user belongs to
-        /// we will just cleanup the contact model stored on the db
-        /// leaving only username and id which are the equivalent to
-        /// .stranger contacts.
-        ///
-        //try dbManager.deleteContact(contact)
-
-        var contact = contact
-        contact.email = nil
-        contact.phone = nil
-        contact.photo = nil
-        contact.isRecent = false
-        contact.marshaled = nil
-        contact.isBlocked = true
-        contact.authStatus = .stranger
-        contact.nickname = contact.username
-        _ = try! dbManager.saveContact(contact)
-    }
-}
diff --git a/Sources/Integration/Session/Session+Group.swift b/Sources/Integration/Session/Session+Group.swift
deleted file mode 100644
index 47652ee94cc4ec7d870005ff69d2d21ea3839ce3..0000000000000000000000000000000000000000
--- a/Sources/Integration/Session/Session+Group.swift
+++ /dev/null
@@ -1,243 +0,0 @@
-import Models
-import XXModels
-import Foundation
-
-extension Session {
-    public func join(group: Group) throws {
-        guard let manager = client.groupManager else { fatalError("A group manager was not created") }
-
-        try manager.join(group.serialized)
-        var group = group
-        group.authStatus = .participating
-        scanStrangers {}
-        try dbManager.saveGroup(group)
-    }
-
-    public func leave(group: Group) throws {
-        guard let manager = client.groupManager else { fatalError("A group manager was not created") }
-        try manager.leave(group.id)
-        try dbManager.deleteGroup(group)
-    }
-
-    public func createGroup(
-        name: String,
-        welcome: String?,
-        members: [Contact],
-        _ completion: @escaping (Result<GroupInfo, Error>) -> Void
-    ) {
-        guard let manager = client.groupManager else {
-            fatalError("A group manager was not created")
-        }
-
-        manager.create(
-            me: myId,
-            name: name,
-            welcome: welcome,
-            with: members.map { $0.id }) { [weak self] result in
-            guard let self = self else { return }
-
-            switch result {
-            case .success(let group):
-                try! self.dbManager.saveGroup(group)
-
-                members
-                    .map { GroupMember(groupId: group.id, contactId: $0.id) }
-                    .forEach { try! self.dbManager.saveGroupMember($0) }
-
-                // TODO: Add saveBulkGroupMembers to the database
-
-                if let welcome = welcome {
-                    let message = Message(
-                        networkId: nil,
-                        senderId: self.myId,
-                        recipientId: nil,
-                        groupId: group.id,
-                        date: group.createdAt,
-                        status: .sent,
-                        isUnread: false,
-                        text: welcome,
-                        replyMessageId: nil,
-                        roundURL: nil,
-                        fileTransferId: nil
-                    )
-
-                    try! self.dbManager.saveMessage(message)
-                }
-
-                let query = GroupInfo.Query(groupId: group.id)
-                let info = try! self.dbManager.fetchGroupInfos(query).first
-                completion(.success(info!))
-
-            case .failure(let error):
-                completion(.failure(error))
-            }
-        }
-    }
-
-    @discardableResult
-    func processGroupCreation(_ group: Group, memberIds: [Data], welcome: String?) -> GroupInfo {
-        /// Save the group
-        ///
-        _ = try! dbManager.saveGroup(group)
-
-        /// Which of those members are not my friends?
-        ///
-        let friendsParticipating = try! dbManager.fetchContacts(Contact.Query(id: Set(memberIds)))
-
-        /// Save the strangers as contacts
-        ///
-        let friendIds = friendsParticipating.map(\.id)
-        memberIds.forEach {
-            if !friendIds.contains($0) {
-                try! dbManager.saveContact(.init(
-                    id: $0,
-                    marshaled: nil,
-                    username: nil,
-                    email: nil,
-                    phone: nil,
-                    nickname: nil,
-                    photo: nil,
-                    authStatus: .stranger,
-                    isRecent: false,
-                    createdAt: Date()
-                ))
-            }
-        }
-
-        /// Save group members relation
-        ///
-        memberIds.forEach {
-            try! dbManager.saveGroupMember(.init(groupId: group.id, contactId: $0))
-        }
-
-        /// Save the welcome message (if any)
-        ///
-        if let welcome = welcome {
-            _ = try! dbManager.saveMessage(.init(
-                networkId: nil,
-                senderId: group.leaderId,
-                recipientId: nil,
-                groupId: group.id,
-                date: group.createdAt,
-                status: .received,
-                isUnread: true,
-                text: welcome,
-                replyMessageId: nil,
-                roundURL: nil,
-                fileTransferId: nil
-            ))
-        }
-
-
-        if inappnotifications {
-            DeviceFeedback.sound(.contactAdded)
-            DeviceFeedback.shake(.notification)
-        }
-
-        scanStrangers {}
-
-        let info = try! dbManager.fetchGroupInfos(.init(groupId: group.id)).first
-        return info!
-    }
-}
-
-// MARK: - GroupMessages
-
-extension Session {
-    public func send(_ payload: Payload, toGroup group: Group) {
-        var message = Message(
-            senderId: client.bindings.myId,
-            recipientId: nil,
-            groupId: group.id,
-            date: Date(),
-            status: .sending,
-            isUnread: false,
-            text: payload.text,
-            replyMessageId: payload.reply?.messageId,
-            roundURL: nil,
-            fileTransferId: nil
-        )
-
-        do {
-            message = try dbManager.saveMessage(message)
-            send(groupMessage: message)
-        } catch {
-            log(string: error.localizedDescription, type: .error)
-        }
-    }
-
-    func send(groupMessage: Message) {
-        guard let manager = client.groupManager else { fatalError("A group manager was not created") }
-        var message = groupMessage
-
-        var reply: Reply?
-        if let replyId = message.replyMessageId,
-           let replyMessage = try? dbManager.fetchMessages(Message.Query(networkId: replyId)).first {
-            reply = Reply(messageId: replyId, senderId: replyMessage.senderId)
-        }
-
-        let payloadData = Payload(text: message.text, reply: reply).asData()
-
-        DispatchQueue.global().async { [weak self] in
-            guard let self = self else { return }
-
-            switch manager.send(payloadData, to: message.groupId!) {
-            case .success((let roundId, let uniqueId, let roundURL)):
-                message.roundURL = roundURL
-
-                self.client.bindings.listenRound(id: Int(roundId)) { result in
-                    switch result {
-                    case .success(let succeeded):
-                        message.networkId = uniqueId
-                        message.status = succeeded ? .sent : .sendingFailed
-                    case .failure:
-                        message.status = .sendingFailed
-                    }
-
-                    do {
-                        try self.dbManager.saveMessage(message)
-                    } catch {
-                        log(string: error.localizedDescription, type: .error)
-                    }
-                }
-            case .failure:
-                message.status = .sendingFailed
-            }
-
-            do {
-                try self.dbManager.saveMessage(message)
-            } catch {
-                log(string: error.localizedDescription, type: .error)
-            }
-        }
-    }
-
-    public func scanStrangers(_ completion: @escaping () -> Void) {
-        DispatchQueue.global().async { [weak self] in
-            guard let self = self,
-                  let ud = self.client.userDiscovery,
-                  let strangers = try? self.dbManager.fetchContacts(.init(username: .some(nil))),
-                  !strangers.isEmpty else { return }
-
-            ud.lookup(idList: strangers.map(\.id)) { result in
-                switch result {
-                case .success(let strangersWithUsernames):
-                    let acquaintances = strangers.map { stranger -> Contact in
-                        var exStranger = stranger
-                        exStranger.username = strangersWithUsernames.first(where: { $0.id == stranger.id })?.username
-                        return exStranger
-                    }
-
-                    DispatchQueue.main.async {
-                        acquaintances.forEach { _ = try? self.dbManager.saveContact($0) }
-                    }
-
-                    completion()
-                case .failure(let error):
-                    print(error.localizedDescription)
-                    DispatchQueue.main.async { completion() }
-                }
-            }
-        }
-    }
-}
diff --git a/Sources/Integration/Session/Session+Network.swift b/Sources/Integration/Session/Session+Network.swift
deleted file mode 100644
index 070ea2cddeb608418f2d80ec400a8b7f0d63e24d..0000000000000000000000000000000000000000
--- a/Sources/Integration/Session/Session+Network.swift
+++ /dev/null
@@ -1,15 +0,0 @@
-import Foundation
-
-extension Session {
-    public func start() {
-        DispatchQueue.global().async { [weak client] in
-            client?.bindings.startNetwork()
-        }
-    }
-
-    public func stop() {
-        DispatchQueue.global().async { [weak client] in
-            client?.bindings.stopNetwork()
-        }
-    }
-}
diff --git a/Sources/Integration/Session/Session+Notifications.swift b/Sources/Integration/Session/Session+Notifications.swift
deleted file mode 100644
index b4e98e9647d8292051d8df61679d629740c84407..0000000000000000000000000000000000000000
--- a/Sources/Integration/Session/Session+Notifications.swift
+++ /dev/null
@@ -1,11 +0,0 @@
-import Foundation
-
-extension Session {
-    public func registerNotifications(_ token: Data) throws {
-        try client.bindings.registerNotifications(token)
-    }
-
-    public func unregisterNotifications() throws {
-        try client.bindings.unregisterNotifications()
-    }
-}
diff --git a/Sources/Integration/Session/Session+UD.swift b/Sources/Integration/Session/Session+UD.swift
deleted file mode 100644
index 27add4b13b1839482aefe29badc0f0fbe4e61e8f..0000000000000000000000000000000000000000
--- a/Sources/Integration/Session/Session+UD.swift
+++ /dev/null
@@ -1,118 +0,0 @@
-import Retry
-import Models
-import Combine
-import XXModels
-import Foundation
-
-extension Session {
-    public 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.client.bindings.nodeRegistrationStatus()
-                    promise(.success(()))
-                }.finalCatch {
-                    promise(.failure($0))
-                }
-            }
-        }.eraseToAnyPublisher()
-    }
-
-    public func search(fact: String) -> AnyPublisher<Contact, Error> {
-        Deferred {
-            Future { promise in
-                guard let ud = self.client.userDiscovery else {
-                    let error = NSError(domain: "", code: 0)
-                    promise(.failure(error))
-                    return
-                }
-
-                do {
-                    try self.client.bindings.nodeRegistrationStatus()
-                    try ud.search(fact: fact) {
-                        switch $0 {
-                        case .success(let contact):
-                            promise(.success(contact))
-                        case .failure(let error):
-                            promise(.failure(error))
-                        }
-                    }
-                } catch {
-                    promise(.failure(error))
-                }
-            }
-        }.eraseToAnyPublisher()
-    }
-
-    public func search(fact: String, _ completion: @escaping (Result<Contact, Error>) -> Void) throws {
-        guard let ud = client.userDiscovery else { return }
-        try client.bindings.nodeRegistrationStatus()
-        try ud.search(fact: fact, completion)
-    }
-
-    public func extract(fact: FactType, from marshalled: Data) throws -> String? {
-        guard let ud = client.userDiscovery else { return nil }
-        return try ud.retrieve(from: marshalled, fact: fact)
-    }
-
-    public func unregister(fact: FactType) throws {
-        guard let ud = client.userDiscovery else { return }
-
-        switch fact {
-        case .phone:
-            try ud.remove("P" + phone!)
-            isSharingPhone = false
-            phone = nil
-        case .email:
-            try ud.remove("E" + email!)
-            isSharingEmail = false
-            email = nil
-        default:
-            break
-        }
-    }
-
-    public func register(_ fact: FactType, value: String, _ completion: @escaping (Result<String?, Error>) -> Void) {
-        guard let ud = client.userDiscovery else { return }
-
-        switch fact {
-        case .username:
-            ud.register(.username, value: value) { [weak self] in
-                guard let self = self else { return }
-
-                switch $0 {
-                case .success(_):
-                    self.username = value
-
-                    if var me = try? self.myContact() {
-                        me.username = value
-                        _ = try? self.dbManager.saveContact(me)
-                    }
-
-                    completion(.success(nil))
-                case .failure(let error):
-                    completion(.failure(error))
-                }
-            }
-        default:
-            ud.register(fact, value: value, completion)
-        }
-    }
-
-    public func confirm(code: String, confirmation: AttributeConfirmation) throws {
-        guard let ud = client.userDiscovery else { return }
-
-        try ud.confirm(code: code, id: confirmation.confirmationId!)
-
-        if confirmation.isEmail {
-            email = confirmation.content
-        } else {
-            phone = confirmation.content
-        }
-
-        if let _ = client.backupManager {
-            updateFactsOnBackup()
-        }
-    }
-}
diff --git a/Sources/Integration/Session/Session.swift b/Sources/Integration/Session/Session.swift
deleted file mode 100644
index 49e1c60e20b3be9765529620893c493d900b9cfd..0000000000000000000000000000000000000000
--- a/Sources/Integration/Session/Session.swift
+++ /dev/null
@@ -1,507 +0,0 @@
-import Retry
-import os.log
-import Models
-import Shared
-import Combine
-import Defaults
-import XXModels
-import XXDatabase
-import Foundation
-import ToastFeature
-import BackupFeature
-import NetworkMonitor
-import ReportingFeature
-import DependencyInjection
-import XXLegacyDatabaseMigrator
-
-let logHandler = OSLog(subsystem: "xx.network", category: "Performance debugging")
-
-struct BackupParameters: Codable {
-    var email: String?
-    var phone: String?
-    var username: String
-}
-
-struct BackupReport: Codable {
-    var contactIds: [String]
-    var parameters: String
-
-    private enum CodingKeys: String, CodingKey {
-        case parameters = "Params"
-        case contactIds = "RestoredContacts"
-    }
-}
-
-public final class Session: SessionType {
-    @KeyObject(.theme, defaultValue: nil) var theme: String?
-    @KeyObject(.email, defaultValue: nil) var email: String?
-    @KeyObject(.phone, defaultValue: nil) var phone: String?
-    @KeyObject(.avatar, defaultValue: nil) var avatar: Data?
-    @KeyObject(.username, defaultValue: nil) var username: String?
-    @KeyObject(.biometrics, defaultValue: false) var biometrics: Bool
-    @KeyObject(.hideAppList, defaultValue: false) var hideAppList: Bool
-    @KeyObject(.requestCounter, defaultValue: 0) var requestCounter: Int
-    @KeyObject(.sharingEmail, defaultValue: false) var isSharingEmail: Bool
-    @KeyObject(.sharingPhone, defaultValue: false) var isSharingPhone: Bool
-    @KeyObject(.recordingLogs, defaultValue: true) var recordingLogs: Bool
-    @KeyObject(.crashReporting, defaultValue: true) var crashReporting: Bool
-    @KeyObject(.icognitoKeyboard, defaultValue: false) var icognitoKeyboard: Bool
-    @KeyObject(.pushNotifications, defaultValue: false) var pushNotifications: Bool
-    @KeyObject(.inappnotifications, defaultValue: true) var inappnotifications: Bool
-
-    @Dependency var backupService: BackupService
-    @Dependency var toastController: ToastController
-    @Dependency var reportingStatus: ReportingStatus
-    @Dependency var networkMonitor: NetworkMonitoring
-
-    public let client: Client
-    public let dbManager: Database
-    private var cancellables = Set<AnyCancellable>()
-
-    public var myId: Data { client.bindings.myId }
-    public var version: String { type(of: client.bindings).version }
-
-    public var myQR: Data {
-        client
-            .bindings
-            .meMarshalled(
-                username!,
-                email: isSharingEmail ? email : nil,
-                phone: isSharingPhone ? phone : nil
-            )
-    }
-
-    public var hasRunningTasks: Bool {
-        client.bindings.hasRunningTasks
-    }
-
-    public var isOnline: AnyPublisher<Bool, Never> {
-        networkMonitor.statusPublisher.map { $0 == .available }.eraseToAnyPublisher()
-    }
-
-    public init(
-        passphrase: String,
-        backupFile: Data,
-        ndf: String
-    ) throws {
-        let network = try! DependencyInjection.Container.shared.resolve() as XXNetworking
-
-        os_signpost(.begin, log: logHandler, name: "Decrypting", "Calling newClientFromBackup")
-        let (client, backupData) = try network.newClientFromBackup(passphrase: passphrase, data: backupFile, ndf: ndf)
-        os_signpost(.end, log: logHandler, name: "Decrypting", "Finished newClientFromBackup")
-
-        self.client = client
-
-        let legacyOldPath = NSSearchPathForDirectoriesInDomains(
-            .documentDirectory, .userDomainMask, true
-        )[0].appending("/xxmessenger.sqlite")
-
-        let legacyPath = FileManager.default
-            .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
-            .appendingPathComponent("database")
-            .appendingPathExtension("sqlite").path
-
-        let dbExistsInLegacyOldPath = FileManager.default.fileExists(atPath: legacyOldPath)
-        let dbExistsInLegacyPath = FileManager.default.fileExists(atPath: legacyPath)
-
-        if dbExistsInLegacyOldPath && !dbExistsInLegacyPath {
-            try? FileManager.default.moveItem(atPath: legacyOldPath, toPath: legacyPath)
-        }
-
-        let dbPath = FileManager.default
-            .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
-            .appendingPathComponent("xxm_database")
-            .appendingPathExtension("sqlite").path
-
-        dbManager = try Database.onDisk(path: dbPath)
-
-        if dbExistsInLegacyPath {
-            try Migrator.live()(
-                try .init(path: legacyPath),
-                to: dbManager,
-                myContactId: client.bindings.myId,
-                meMarshaled: client.bindings.meMarshalled
-            )
-
-            try FileManager.default.moveItem(atPath: legacyPath, toPath: legacyPath.appending("-backup"))
-        }
-
-        let report = try! JSONDecoder().decode(BackupReport.self, from: backupData!)
-
-        if !report.parameters.isEmpty {
-            let params = try! JSONDecoder().decode(BackupParameters.self, from: Data(report.parameters.utf8))
-
-            username = params.username
-
-            if let paramsPhone = params.phone, !paramsPhone.isEmpty {
-                phone = paramsPhone
-            }
-
-            if let paramsEmail = params.email, !paramsEmail.isEmpty {
-                email = paramsEmail
-            }
-        }
-
-        guard let username = username, username.isEmpty == false else {
-            fatalError("Trying to restore an account that has no username")
-        }
-
-        try continueInitialization()
-
-        if !report.contactIds.isEmpty {
-            client.restoreContacts(fromBackup: try! JSONSerialization.data(withJSONObject: report.contactIds))
-        }
-    }
-
-    public init(ndf: String) throws {
-        let network = try! DependencyInjection.Container.shared.resolve() as XXNetworking
-        self.client = try network.newClient(ndf: ndf)
-
-        let legacyOldPath = NSSearchPathForDirectoriesInDomains(
-            .documentDirectory, .userDomainMask, true
-        )[0].appending("/xxmessenger.sqlite")
-
-        let legacyPath = FileManager.default
-            .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
-            .appendingPathComponent("database")
-            .appendingPathExtension("sqlite").path
-
-        let dbExistsInLegacyOldPath = FileManager.default.fileExists(atPath: legacyOldPath)
-        let dbExistsInLegacyPath = FileManager.default.fileExists(atPath: legacyPath)
-
-        if dbExistsInLegacyOldPath && !dbExistsInLegacyPath {
-            try? FileManager.default.moveItem(atPath: legacyOldPath, toPath: legacyPath)
-        }
-
-        let dbPath = FileManager.default
-            .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
-            .appendingPathComponent("xxm_database")
-            .appendingPathExtension("sqlite").path
-
-        dbManager = try Database.onDisk(path: dbPath)
-
-        if dbExistsInLegacyPath {
-            try Migrator.live()(
-                try .init(path: legacyPath),
-                to: dbManager,
-                myContactId: client.bindings.myId,
-                meMarshaled: client.bindings.meMarshalled
-            )
-
-            try FileManager.default.moveItem(atPath: legacyPath, toPath: legacyPath.appending("-backup"))
-        }
-
-        try continueInitialization()
-    }
-
-    private func continueInitialization() throws {
-        var myContact = try self.myContact()
-        myContact.marshaled = client.bindings.meMarshalled
-        myContact.username = username
-        myContact.email = email
-        myContact.phone = phone
-        myContact.authStatus = .friend
-        myContact.isRecent = false
-        _ = try dbManager.saveContact(myContact)
-
-        setupBindings()
-        networkMonitor.start()
-
-        networkMonitor.statusPublisher
-            .filter { $0 == .available }.first()
-            .sink { [unowned self] _ in
-                client.bindings.replayRequests()
-                scanStrangers {}
-            }
-            .store(in: &cancellables)
-
-        registerUnfinishedUploadTransfers()
-        registerUnfinishedDownloadTransfers()
-
-        let query = Contact.Query(authStatus: [.verificationInProgress])
-        _ = try? dbManager.bulkUpdateContacts(query, .init(authStatus: .verificationFailed))
-    }
-
-    public func setDummyTraffic(status: Bool) {
-        client.dummyManager?.setStatus(status: status)
-    }
-
-    public func deleteMyself() throws {
-        guard let username = username, let ud = client.userDiscovery else { return }
-
-        try? unregisterNotifications()
-        try ud.deleteMyself(username)
-
-        stop()
-        cleanUp()
-    }
-
-    private func cleanUp() {
-        retry(max: 10, retryStrategy: .delay(seconds: 1)) { [unowned self] in
-            guard self.hasRunningTasks == false else { throw NSError.create("") }
-        }.finalCatch { _ in fatalError("Couldn't delete account because network is not stopping") }
-
-        try! dbManager.drop()
-        FileManager.xxCleanup()
-
-        email = nil
-        phone = nil
-        theme = nil
-        avatar = nil
-        self.username = nil
-        isSharingEmail = false
-        isSharingPhone = false
-        requestCounter = 0
-        biometrics = false
-        hideAppList = false
-        recordingLogs = true
-        crashReporting = true
-        icognitoKeyboard = false
-        pushNotifications = false
-        inappnotifications = true
-    }
-
-    private func registerUnfinishedDownloadTransfers() {
-        guard let unfinishedReceivingMessages = try? dbManager.fetchMessages(.init(status: [.receiving])),
-              let unfinishedReceivingTransfers = try? dbManager.fetchFileTransfers(.init(
-                id: Set(unfinishedReceivingMessages
-                    .filter { $0.fileTransferId != nil }
-                    .compactMap(\.fileTransferId))))
-        else { return }
-
-        let pairs = unfinishedReceivingMessages.compactMap { message -> (Message, FileTransfer)? in
-            guard let transfer = unfinishedReceivingTransfers.first(where: { ft in
-                ft.id == message.fileTransferId
-            }) else { return nil }
-
-            return (message, transfer)
-        }
-
-        pairs.forEach { message, transfer in
-            var message = message
-            var transfer = transfer
-
-            do {
-                try client.transferManager?.listenDownloadFromTransfer(with: transfer.id) { [weak self] completed, received, total, error in
-                    guard let self = self else { return }
-                    if completed {
-                        transfer.progress = 1.0
-                        message.status = .received
-
-                        if let data = try? self.client.transferManager?.downloadFileFromTransfer(with: transfer.id),
-                           let _ = try? FileManager.store(data: data, name: transfer.name, type: transfer.type) {
-                            transfer.data = data
-                        }
-                    } else {
-                        if error != nil {
-                            message.status = .receivingFailed
-                        } else {
-                            transfer.progress = Float(received)/Float(total)
-                        }
-                    }
-
-                    _ = try? self.dbManager.saveFileTransfer(transfer)
-                    _ = try? self.dbManager.saveMessage(message)
-                }
-            } catch {
-                message.status = .receivingFailed
-                _ = try? self.dbManager.saveMessage(message)
-            }
-        }
-    }
-
-    private func registerUnfinishedUploadTransfers() {
-        guard let unfinishedSendingMessages = try? dbManager.fetchMessages(.init(status: [.sending])),
-              let unfinishedSendingTransfers = try? dbManager.fetchFileTransfers(.init(
-                id: Set(unfinishedSendingMessages
-                    .filter { $0.fileTransferId != nil }
-                    .compactMap(\.fileTransferId))))
-        else { return }
-
-        let pairs = unfinishedSendingMessages.compactMap { message -> (Message, FileTransfer)? in
-            guard let transfer = unfinishedSendingTransfers.first(where: { ft in
-                ft.id == message.fileTransferId
-            }) else { return nil }
-
-            return (message, transfer)
-        }
-
-        pairs.forEach { message, transfer in
-            var message = message
-            var transfer = transfer
-
-            do {
-                try client.transferManager?.listenUploadFromTransfer(with: transfer.id) { [weak self] completed, sent, arrived, total, error in
-                    guard let self = self else { return }
-
-                    if completed {
-                        transfer.progress = 1.0
-                        message.status = .sent
-
-                        try? self.client.transferManager?.endTransferUpload(with: transfer.id)
-                    } else {
-                        if error != nil {
-                            message.status = .sendingFailed
-                        } else {
-                            transfer.progress = Float(arrived)/Float(total)
-                        }
-                    }
-
-                    _ = try? self.dbManager.saveFileTransfer(transfer)
-                    _ = try? self.dbManager.saveMessage(message)
-                }
-            } catch {
-                message.status = .sendingFailed
-                _ = try? self.dbManager.saveMessage(message)
-            }
-        }
-    }
-
-    func updateFactsOnBackup() {
-        struct BackupParameters: Codable {
-            var email: String?
-            var phone: String?
-            var username: String
-
-            var jsonFormat: String {
-                let data = try! JSONEncoder().encode(self)
-                let json = String(data: data, encoding: .utf8)
-                return json!
-            }
-        }
-
-        let params = BackupParameters(
-            email: email,
-            phone: phone,
-            username: username!
-        ).jsonFormat
-
-        client.addJson(params)
-
-        guard username!.isEmpty == false else {
-            fatalError("Tried to build a backup with my username but an empty string was set to it")
-        }
-
-        backupService.performBackupIfAutomaticIsEnabled()
-    }
-
-    private func setupBindings() {
-        client.requests
-            .sink { [unowned self] contact in
-                let query = Contact.Query(id: [contact.id])
-
-                if let prexistent = try? dbManager.fetchContacts(query).first {
-                    guard prexistent.authStatus == .stranger else { return }
-                }
-
-                if self.inappnotifications {
-                    DeviceFeedback.sound(.contactAdded)
-                    DeviceFeedback.shake(.notification)
-                }
-
-                verify(contact: contact)
-            }.store(in: &cancellables)
-
-        client.requestsSent
-            .sink { [unowned self] in _ = try? dbManager.saveContact($0) }
-            .store(in: &cancellables)
-
-        client.backup
-            .sink { [unowned self] in backupService.updateBackup(data: $0) }
-            .store(in: &cancellables)
-
-        client.resets
-            .sink { [unowned self] in
-                /// This will get called when my contact restore its contact.
-                /// TODO: Hold a record on the chat that this contact restored.
-                ///
-                if var contact = try? dbManager.fetchContacts(.init(id: [$0.id])).first {
-                    contact.authStatus = .friend
-                    _ = try? dbManager.saveContact(contact)
-                }
-            }.store(in: &cancellables)
-
-        backupService.settingsPublisher
-            .map { $0.enabledService != nil }
-            .removeDuplicates()
-            .sink { [unowned self] in
-                if $0 == true {
-                    guard let passphrase = backupService.passphrase else {
-                        client.resumeBackup()
-                        return
-                    }
-
-                    client.initializeBackup(passphrase: passphrase)
-                    backupService.passphrase = nil
-                    updateFactsOnBackup()
-                } else {
-                    backupService.passphrase = nil
-                    client.stopListeningBackup()
-                }
-            }
-            .store(in: &cancellables)
-
-        client.messages
-            .sink { [unowned self] in
-                if var contact = try? dbManager.fetchContacts(.init(id: [$0.senderId])).first {
-                    guard contact.isBanned == false else { return }
-                    contact.isRecent = false
-                    _ = try? dbManager.saveContact(contact)
-                }
-
-                _ = try? dbManager.saveMessage($0)
-            }.store(in: &cancellables)
-
-        client.network
-            .sink { [unowned self] in networkMonitor.update($0) }
-            .store(in: &cancellables)
-
-        client.groupRequests
-            .sink { [unowned self] request in
-                if let _ = try? dbManager.fetchGroups(.init(id: [request.0.id])).first {
-                    return
-                }
-
-                if let contact = try! dbManager.fetchContacts(.init(id: [request.0.leaderId])).first {
-                    if reportingStatus.isEnabled(), (contact.isBlocked || contact.isBanned) {
-                        return
-                    }
-                }
-
-                DispatchQueue.global().async { [weak self] in
-                    self?.processGroupCreation(request.0, memberIds: request.1, welcome: request.2)
-                }
-            }.store(in: &cancellables)
-
-        client.confirmations
-            .sink { [unowned self] in
-                if var contact = try? dbManager.fetchContacts(.init(id: [$0.id])).first {
-                    contact.authStatus = .friend
-                    contact.isRecent = true
-                    contact.createdAt = Date()
-                    _ = try? dbManager.saveContact(contact)
-
-                    toastController.enqueueToast(model: .init(
-                        title: contact.nickname ?? contact.username!,
-                        subtitle: Localized.Requests.Confirmations.toaster,
-                        leftImage: Asset.sharedSuccess.image
-                    ))
-                }
-            }.store(in: &cancellables)
-
-        client.transfers
-            .sink { [unowned self] in
-                guard let transfer = try? dbManager.saveFileTransfer($0) else { return }
-                handle(incomingTransfer: transfer)
-            }
-            .store(in: &cancellables)
-    }
-
-    func myContact() throws -> Contact {
-        if let contact = try dbManager.fetchContacts(.init(id: [client.bindings.myId])).first {
-            return contact
-        } else {
-            return try dbManager.saveContact(.init(id: client.bindings.myId))
-        }
-    }
-}
diff --git a/Sources/Integration/Session/SessionType.swift b/Sources/Integration/Session/SessionType.swift
deleted file mode 100644
index effd6c96c3239904722b74f2856f2a953f0b986e..0000000000000000000000000000000000000000
--- a/Sources/Integration/Session/SessionType.swift
+++ /dev/null
@@ -1,73 +0,0 @@
-import Models
-import Combine
-import XXModels
-import Foundation
-
-public protocol SessionType {
-    var myId: Data { get }
-    var myQR: Data { get }
-    var version: String { get }
-    var hasRunningTasks: Bool { get }
-    var isOnline: AnyPublisher<Bool, Never> { get }
-
-    var dbManager: Database { get }
-
-    func deleteMyself() throws
-    func getId(from: Data) -> Data?
-
-    func sendFile(url: URL, to: Contact)
-    func send(imageData: Data, to: Contact, completion: @escaping (Result<Void, Error>) -> Void)
-
-    func verify(contact: Contact)
-
-    func setDummyTraffic(status: Bool)
-
-    // UserDiscovery
-
-    func unregister(fact: FactType) throws
-    func extract(fact: FactType, from: Data) throws -> String?
-    func confirm(code: String, confirmation: AttributeConfirmation) throws
-    func search(fact: String, _: @escaping (Result<Contact, Error>) -> Void) throws
-    func register(_: FactType, value: String, _: @escaping (Result<String?, Error>) -> Void)
-
-    // Notifications
-
-    func unregisterNotifications() throws
-    func registerNotifications(_ token: Data) throws
-
-    // Network
-
-    func start()
-    func stop()
-
-    // Messages
-
-    func retryMessage(_: Int64)
-    func send(_: Payload, toContact: Contact)
-
-    // Contacts
-
-    func add(_: Contact) throws
-    func confirm(_: Contact) throws
-    func deleteContact(_: Contact) throws
-
-    func retryRequest(_: Contact) throws
-    func scanStrangers(_: @escaping () -> Void)
-
-    // Groups
-
-    func join(group: Group) throws
-    func send(_: Payload, toGroup: Group)
-    func leave(group: Group) throws
-
-    func createGroup(
-        name: String,
-        welcome: String?,
-        members: [Contact],
-        _ completion: @escaping (Result<GroupInfo, Error>) -> Void
-    )
-
-    func search(fact: String) -> AnyPublisher<Contact, Error>
-
-    func waitForNodes(timeout: Int) -> AnyPublisher<Void, Error>
-}
diff --git a/Sources/Integration/XXNetwork.swift b/Sources/Integration/XXNetwork.swift
deleted file mode 100644
index 1273a26a68e4487227c96fe953ecabe23994e450..0000000000000000000000000000000000000000
--- a/Sources/Integration/XXNetwork.swift
+++ /dev/null
@@ -1,173 +0,0 @@
-import Shared
-import XXLogger
-import Keychain
-import Foundation
-import DependencyInjection
-
-public enum NetworkEnvironment {
-    case mainnet
-}
-
-public protocol XXNetworking {
-    var hasClient: Bool { get }
-
-    func writeLogs()
-    func purgeFiles()
-    func updateErrors()
-    func newClient(ndf: String) throws -> Client
-
-    func updateNDF(
-        _: @escaping (Result<String, Error>) -> Void
-    )
-
-    func loadClient(
-        with: Data,
-        fromBackup: Bool,
-        email: String?,
-        phone: String?
-    ) throws -> Client
-
-    func newClientFromBackup(
-        passphrase: String,
-        data: Data,
-        ndf: String
-    ) throws -> (Client, Data?)
-}
-
-public struct XXNetwork<B: BindingsInterface> {
-    @Dependency private var logger: XXLogger
-    @Dependency private var keychain: KeychainHandling
-
-    public init() {}
-}
-
-extension XXNetwork: XXNetworking {
-    public var hasClient: Bool {
-        guard let files = FileManager.xxContents else { return false }
-        return files.count > 0
-    }
-
-    public func writeLogs() {
-        B.listenLogs()
-    }
-
-    public func updateErrors() {
-        B.updateErrors()
-    }
-
-    public func updateNDF(_ completion: @escaping (Result<String, Error>) -> Void) {
-        B.updateNDF(for: .mainnet) {
-            switch $0 {
-            case .success(let data):
-                guard let ndfData = data, let ndf = String(data: ndfData, encoding: .utf8) else {
-                    completion(.failure(NSError.create("NDF is empty (?)")))
-                    return
-                }
-
-                completion(.success(ndf))
-            case .failure(let error):
-                completion(.failure(error))
-            }
-        }
-    }
-
-    public func purgeFiles() {
-        FileManager.xxCleanup()
-    }
-
-    public func newClientFromBackup(
-        passphrase: String,
-        data: Data,
-        ndf: String
-    ) throws -> (Client, Data?) {
-        var error: NSError?
-
-        let password = B.secret(32)!
-        try keychain.store(password: password)
-
-        let backupData = B.fromBackup(
-            ndf,
-            FileManager.xxPath,
-            password,
-            "\(passphrase)".data(using: .utf8),
-            data,
-            &error
-        )
-
-        if let error = error { throw error }
-
-        var email: String?
-        var phone: String?
-
-        let report = try! JSONDecoder().decode(BackupReport.self, from: backupData!)
-
-        if !report.parameters.isEmpty {
-            let params = try! JSONDecoder().decode(BackupParameters.self, from: Data(report.parameters.utf8))
-            phone = params.phone
-            email = params.email
-        }
-
-        let client = try loadClient(with: password, fromBackup: true, email: email, phone: phone)
-        return (client, backupData)
-    }
-
-    public func newClient(ndf: String) throws -> Client {
-        var password: Data!
-
-        if hasClient == false {
-            var error: NSError?
-
-            password = B.secret(32)
-            try keychain.store(password: password)
-
-            _ = B.new(ndf, FileManager.xxPath, password, nil, &error)
-            if let error = error { throw error }
-        } else {
-            guard let secret = try keychain.getPassword() else {
-                throw NSError.create("Empty stored secret")
-            }
-
-            password = secret
-        }
-
-        return try loadClient(with: password, fromBackup: false, email: nil, phone: nil)
-    }
-
-    public func loadClient(
-        with secret: Data,
-        fromBackup: Bool,
-        email: String?,
-        phone: String?
-    ) throws -> Client {
-        var error: NSError?
-        let bindings = B.login(FileManager.xxPath, secret, "", &error)
-        if let error = error { throw error }
-
-        if let defaults = UserDefaults(suiteName: "group.elixxir.messenger") {
-            defaults.set(bindings!.receptionId.base64EncodedString(), forKey: "receptionId")
-        }
-
-        return Client(bindings!, fromBackup: fromBackup, email: email, phone: phone)
-    }
-}
-
-extension NetworkEnvironment {
-    var url: String {
-        switch self {
-        case .mainnet:
-            return "https://elixxir-bins.s3.us-west-1.amazonaws.com/ndf/mainnet.json"
-        }
-    }
-
-    var cert: String {
-        switch self {
-        case .mainnet:
-            guard let filepath = Bundle.module.path(forResource: "cert_mainnet", ofType: "txt"),
-                  let certString = try? String(contentsOfFile: filepath) else {
-                      fatalError("Couldn't retrieve network cert file.")
-                  }
-
-            return certString
-        }
-    }
-}
diff --git a/Sources/Keychain/Dependency.swift b/Sources/Keychain/Dependency.swift
new file mode 100644
index 0000000000000000000000000000000000000000..02ca45a9267826025a38dc77c4826eb034113bb1
--- /dev/null
+++ b/Sources/Keychain/Dependency.swift
@@ -0,0 +1,13 @@
+import Dependencies
+
+private enum KeychainDependencyKey: DependencyKey {
+  static let liveValue: KeychainManager = .live
+  static let testValue: KeychainManager = .unimplemented
+}
+
+extension DependencyValues {
+  public var keychain: KeychainManager {
+    get { self[KeychainDependencyKey.self] }
+    set { self[KeychainDependencyKey.self] = newValue }
+  }
+}
diff --git a/Sources/Keychain/DestroyKeychain.swift b/Sources/Keychain/DestroyKeychain.swift
new file mode 100644
index 0000000000000000000000000000000000000000..8bb24fe094745fa5b65e2c3e9e68b6cfc2fabc08
--- /dev/null
+++ b/Sources/Keychain/DestroyKeychain.swift
@@ -0,0 +1,22 @@
+import KeychainAccess
+import XCTestDynamicOverlay
+
+public struct DestroyKeychain {
+  public var run: () throws -> Void
+
+  public func callAsFunction() throws -> Void {
+    try run()
+  }
+}
+
+extension DestroyKeychain {
+  public static let live = DestroyKeychain {
+    try Keychain(service: "XXM").removeAll()
+  }
+}
+
+extension DestroyKeychain {
+  public static let unimplemented = DestroyKeychain(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/Keychain/GetValueForKey.swift b/Sources/Keychain/GetValueForKey.swift
new file mode 100644
index 0000000000000000000000000000000000000000..31fe3ca81fc53279bf5f1d0a99ca10704927839e
--- /dev/null
+++ b/Sources/Keychain/GetValueForKey.swift
@@ -0,0 +1,22 @@
+import KeychainAccess
+import XCTestDynamicOverlay
+
+public struct GetValueForKey {
+  public var run: (String) throws -> String?
+
+  public func callAsFunction(_ key: String) throws -> String? {
+    try run(key)
+  }
+}
+
+extension GetValueForKey {
+  public static let live = GetValueForKey {
+    try Keychain(service: "XXM").get($0)
+  }
+}
+
+extension GetValueForKey {
+  public static let unimplemented = GetValueForKey(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/Keychain/KeychainHandler.swift b/Sources/Keychain/KeychainHandler.swift
index 6ac0d645d6def33360ee5ed0d0ab1e973a0487fc..6b55051142ba5937692e491e1620460469cf0b6e 100644
--- a/Sources/Keychain/KeychainHandler.swift
+++ b/Sources/Keychain/KeychainHandler.swift
@@ -1,46 +1,24 @@
-import Foundation
-import KeychainAccess
-
-public enum KeychainSFTP: String {
-    case pwd
-    case host
-    case username
+public struct KeychainManager {
+  public var set: SetValueForKey
+  public var get: GetValueForKey
+  public var remove: RemoveValueForKey
+  public var destroy: DestroyKeychain
 }
 
-public protocol KeychainHandling {
-    func clear() throws
-    func getPassword() throws -> Data?
-    func store(password pwd: Data) throws
-
-    func get(key: KeychainSFTP) throws -> String?
-    func store(key: KeychainSFTP, value: String) throws
+extension KeychainManager {
+  public static let live = KeychainManager(
+    set: .live,
+    get: .live,
+    remove: .live,
+    destroy: .live
+  )
 }
 
-public struct KeychainHandler: KeychainHandling {
-    private let keychain: Keychain
-    private let password = "password"
-
-    public init() {
-        self.keychain = Keychain(service: "XXM")
-    }
-
-    public func clear() throws {
-        try keychain.removeAll()
-    }
-
-    public func store(password pwd: Data) throws {
-        try keychain.set(pwd, key: password)
-    }
-
-    public func getPassword() throws -> Data? {
-        try keychain.getData(password)
-    }
-
-    public func get(key: KeychainSFTP) throws -> String? {
-        try keychain.get(key.rawValue)
-    }
-
-    public func store(key: KeychainSFTP, value: String) throws {
-        try keychain.set(value, key: key.rawValue)
-    }
+extension KeychainManager {
+  public static let unimplemented = KeychainManager(
+    set: .unimplemented,
+    get: .unimplemented,
+    remove: .unimplemented,
+    destroy: .unimplemented
+  )
 }
diff --git a/Sources/Keychain/MockKeychainHandler.swift b/Sources/Keychain/MockKeychainHandler.swift
deleted file mode 100644
index 39d4a33ddb1e0e6d8a75d8b5a1ea980d8fb189bf..0000000000000000000000000000000000000000
--- a/Sources/Keychain/MockKeychainHandler.swift
+++ /dev/null
@@ -1,11 +0,0 @@
-import Foundation
-
-public struct MockKeychainHandler: KeychainHandling {
-    public init() {}
-
-    public func clear() throws {}
-    public func store(password pwd: Data) throws {}
-    public func getPassword() throws -> Data? { Data() }
-    public func get(key: KeychainSFTP) throws -> String? { nil }
-    public func store(key: KeychainSFTP, value: String) throws {}
-}
diff --git a/Sources/Keychain/RemoveValueForKey.swift b/Sources/Keychain/RemoveValueForKey.swift
new file mode 100644
index 0000000000000000000000000000000000000000..9868a6f6d6bed828d64881877a8d082a90d8afe1
--- /dev/null
+++ b/Sources/Keychain/RemoveValueForKey.swift
@@ -0,0 +1,22 @@
+import KeychainAccess
+import XCTestDynamicOverlay
+
+public struct RemoveValueForKey {
+  public var run: (String) throws -> Void
+
+  public func callAsFunction(_ key: String) throws -> Void {
+    try run(key)
+  }
+}
+
+extension RemoveValueForKey {
+  public static let live = RemoveValueForKey {
+    try Keychain(service: "XXM").remove($0)
+  }
+}
+
+extension RemoveValueForKey {
+  public static let unimplemented = RemoveValueForKey(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/Keychain/SetValueForKey.swift b/Sources/Keychain/SetValueForKey.swift
new file mode 100644
index 0000000000000000000000000000000000000000..c0cc66ba4816f3156798705ef2bfc34275698a58
--- /dev/null
+++ b/Sources/Keychain/SetValueForKey.swift
@@ -0,0 +1,22 @@
+import KeychainAccess
+import XCTestDynamicOverlay
+
+public struct SetValueForKey {
+  public var run: (String, String) throws -> Void
+
+  public func callAsFunction(_ value: String, for key: String) throws -> Void {
+    try run(value, key)
+  }
+}
+
+extension SetValueForKey {
+  public static let live = SetValueForKey { value, key in
+    try Keychain(service: "XXM").set(value, key: key)
+  }
+}
+
+extension SetValueForKey {
+  public static let unimplemented = SetValueForKey(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/LaunchFeature/LaunchController.swift b/Sources/LaunchFeature/LaunchController.swift
index cf8b093b2131d2becbb7d5e6bb55a3b02fb73c0e..d6d1780b9c64bae58cf3508c0e3be095144cea90 100644
--- a/Sources/LaunchFeature/LaunchController.swift
+++ b/Sources/LaunchFeature/LaunchController.swift
@@ -1,162 +1,137 @@
-import HUD
 import UIKit
 import Shared
 import Combine
-import Defaults
-import PushFeature
-import DependencyInjection
+import Dependencies
+import AppResources
+import DrawerFeature
+import AppNavigation
 
 public final class LaunchController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: LaunchCoordinating
-
-    @KeyObject(.acceptedTerms, defaultValue: false) var didAcceptTerms: Bool
-
-    lazy private var screenView = LaunchView()
-
-    private let blocker = UpdateBlocker()
-    private let viewModel = LaunchViewModel()
-    public var pendingPushRoute: PushRouter.Route?
-    private var cancellables = Set<AnyCancellable>()
-
-    public override func viewDidAppear(_ animated: Bool) {
-        super.viewDidAppear(animated)
-        viewModel.viewDidAppear()
-    }
-
-    public override func loadView() {
-        view = screenView
-    }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationController?
-            .navigationBar
-            .customize(translucent: true)
-    }
-
-    public override func viewDidLayoutSubviews() {
-        super.viewDidLayoutSubviews()
-        screenView.setupGradient()
+  @Dependency(\.navigator) var navigator: Navigator
+
+  private lazy var screenView = LaunchView()
+  private let viewModel = LaunchViewModel()
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
+
+  public var pendingPushNotificationRoute: PushNotificationRouter.Route?
+
+  public override func loadView() {
+    view = screenView
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+
+    viewModel
+      .statePublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        guard $0.shouldPushChats == false else {
+          guard $0.shouldShowTerms == false else {
+            navigator.perform(PresentTermsAndConditions(replacing: true, on: navigationController!))
+            return
+          }
+          if let route = pendingPushNotificationRoute {
+            hasPendingPushRoute(route)
+            return
+          }
+          navigator.perform(PresentChatList(on: navigationController!))
+          return
+        }
+        guard $0.shouldPushOnboarding == false else {
+          navigator.perform(PresentOnboardingStart(on: navigationController!))
+          return
+        }
+        if let update = $0.shouldOfferUpdate {
+          offerUpdate(model: update)
+        }
+      }.store(in: &cancellables)
+
+    viewModel.startLaunch()
+  }
+
+  private func hasPendingPushRoute(_ route: PushNotificationRouter.Route) {
+    switch route {
+    case .requests:
+      navigator.perform(PresentRequests(on: navigationController!))
+    case .search(username: let username):
+      navigator.perform(PresentSearch(
+        searching: username,
+        fromOnboarding: true,
+        on: navigationController!))
+    case .groupChat(id: let groupId):
+      if let info = viewModel.getGroupInfoWith(groupId: groupId) {
+        navigator.perform(PresentGroupChat(groupInfo: info, on: navigationController!))
+        return
+      }
+      navigator.perform(PresentChatList(on: navigationController!))
+    case .contactChat(id: let userId):
+      if let model = viewModel.getContactWith(userId: userId) {
+        navigator.perform(PresentChat(contact: model, on: navigationController!))
+        return
+      }
+      navigator.perform(PresentChatList(on: navigationController!))
     }
-
-    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
-                switch $0 {
-                case .chats:
-                    guard didAcceptTerms == true else {
-                        coordinator.toTerms(from: self)
-                        return
-                    }
-
-                    if let pushRoute = pendingPushRoute {
-                        switch pushRoute {
-                        case .requests:
-                            coordinator.toRequests(from: self)
-
-                        case .search(username: let username):
-                            coordinator.toSearch(searching: username, from: self)
-
-                        case .groupChat(id: let groupId):
-                            if let groupInfo = viewModel.getGroupInfoWith(groupId: groupId) {
-                                coordinator.toGroupChat(with: groupInfo, from: self)
-                                return
-                            }
-                            coordinator.toChats(from: self)
-
-                        case .contactChat(id: let userId):
-                            if let contact = viewModel.getContactWith(userId: userId) {
-                                coordinator.toSingleChat(with: contact, from: self)
-                                return
-                            }
-                            coordinator.toChats(from: self)
-                        }
-
-                        return
-                    }
-
-                    coordinator.toChats(from: self)
-
-                case .onboarding(let ndf):
-                    coordinator.toOnboarding(with: ndf, from: self)
-
-                case .update(let model):
-                    offerUpdate(model: model)
-                }
-            }.store(in: &cancellables)
+  }
+
+  private func offerUpdate(model: LaunchViewModel.UpdateModel) {
+    let updateButton = CapsuleButton()
+    updateButton.set(
+      style: .brandColored,
+      title: model.positiveActionTitle
+    )
+    let notNowButton = CapsuleButton()
+    if let negativeTitle = model.negativeActionTitle {
+      notNowButton.set(
+        style: .red,
+        title: negativeTitle
+      )
     }
-
-    private func offerUpdate(model: Update) {
-        let drawerView = UIView()
-        drawerView.backgroundColor = Asset.neutralSecondary.color
-        drawerView.layer.cornerRadius = 5
-
-        let vStack = UIStackView()
-        vStack.axis = .vertical
-        vStack.spacing = 10
-        drawerView.addSubview(vStack)
-
-        vStack.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(18)
-            $0.left.equalToSuperview().offset(18)
-            $0.right.equalToSuperview().offset(-18)
-            $0.bottom.equalToSuperview().offset(-18)
+    updateButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) {
+          self.drawerCancellables.removeAll()
+          UIApplication.shared.open(.init(string: model.urlString)!)
         }
-
-        let title = UILabel()
-        title.text = "App Update"
-        title.textAlignment = .center
-        title.textColor = Asset.neutralDark.color
-
-        let body = UILabel()
-        body.numberOfLines = 0
-        body.textAlignment = .center
-        body.textColor = Asset.neutralDark.color
-
-        let update = CapsuleButton()
-        update.publisher(for: .touchUpInside)
-            .sink { UIApplication.shared.open(.init(string: model.urlString)!, options: [:]) }
-            .store(in: &cancellables)
-
-        vStack.addArrangedSubview(title)
-        vStack.addArrangedSubview(body)
-        vStack.addArrangedSubview(update)
-
-        body.text = model.content
-        update.set(
-            style: model.actionStyle,
-            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.versionApproved()
-                }.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)
+      }.store(in: &drawerCancellables)
+
+    notNowButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) {
+          self.drawerCancellables.removeAll()
+          self.viewModel.didRefuseUpdating()
         }
+      }.store(in: &drawerCancellables)
 
-        blocker.showWindow()
+    var actions: [UIView] = [updateButton]
+    if model.negativeActionTitle != nil {
+      actions.append(notNowButton)
     }
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: "App Update",
+        color: Asset.neutralActive.color,
+        alignment: .center,
+        spacingAfter: 19
+      ),
+      DrawerText(
+        font: Fonts.Mulish.regular.font(size: 16.0),
+        text: model.content,
+        color: Asset.neutralBody.color,
+        alignment: .center,
+        spacingAfter: 19
+      ),
+      DrawerStack(
+        axis: .vertical,
+        views: actions
+      )
+    ], isDismissable: false, from: self))
+  }
 }
diff --git a/Sources/LaunchFeature/LaunchCoordinator.swift b/Sources/LaunchFeature/LaunchCoordinator.swift
deleted file mode 100644
index 37035773d6cfd875d5acf6de309b4ac84d72ae6f..0000000000000000000000000000000000000000
--- a/Sources/LaunchFeature/LaunchCoordinator.swift
+++ /dev/null
@@ -1,84 +0,0 @@
-import UIKit
-import Models
-import XXModels
-import Presentation
-
-public protocol LaunchCoordinating {
-    func toChats(from: UIViewController)
-    func toTerms(from: UIViewController)
-    func toRequests(from: UIViewController)
-    func toSearch(searching: String, from: UIViewController)
-    func toOnboarding(with: String, from: UIViewController)
-    func toSingleChat(with: Contact, from: UIViewController)
-    func toGroupChat(with: GroupInfo, from: UIViewController)
-}
-
-public struct LaunchCoordinator: LaunchCoordinating {
-    var replacePresenter: Presenting = ReplacePresenter()
-
-    var termsFactory: (String?) -> UIViewController
-    var searchFactory: (String) -> UIViewController
-    var requestsFactory: () -> UIViewController
-    var chatListFactory: () -> UIViewController
-    var onboardingFactory: (String) -> UIViewController
-    var singleChatFactory: (Contact) -> UIViewController
-    var groupChatFactory: (GroupInfo) -> UIViewController
-
-    public init(
-        termsFactory: @escaping (String?) -> UIViewController,
-        searchFactory: @escaping (String) -> UIViewController,
-        requestsFactory: @escaping () -> UIViewController,
-        chatListFactory: @escaping () -> UIViewController,
-        onboardingFactory: @escaping (String) -> UIViewController,
-        singleChatFactory: @escaping (Contact) -> UIViewController,
-        groupChatFactory: @escaping (GroupInfo) -> UIViewController
-    ) {
-        self.termsFactory = termsFactory
-        self.searchFactory = searchFactory
-        self.requestsFactory = requestsFactory
-        self.chatListFactory = chatListFactory
-        self.groupChatFactory = groupChatFactory
-        self.onboardingFactory = onboardingFactory
-        self.singleChatFactory = singleChatFactory
-    }
-}
-
-public extension LaunchCoordinator {
-    func toSearch(searching: String, from parent: UIViewController) {
-        let screen = searchFactory(searching)
-        let chatListScreen = chatListFactory()
-        replacePresenter.present(chatListScreen, screen, from: parent)
-    }
-
-    func toTerms(from parent: UIViewController) {
-        let screen = termsFactory(nil)
-        replacePresenter.present(screen, from: parent)
-    }
-
-    func toChats(from parent: UIViewController) {
-        let screen = chatListFactory()
-        replacePresenter.present(screen, from: parent)
-    }
-
-    func toRequests(from parent: UIViewController) {
-        let screen = requestsFactory()
-        replacePresenter.present(screen, from: parent)
-    }
-
-    func toOnboarding(with ndf: String, from parent: UIViewController) {
-        let screen = onboardingFactory(ndf)
-        replacePresenter.present(screen, from: parent)
-    }
-
-    func toSingleChat(with contact: Contact, from parent: UIViewController) {
-        let chatListScreen = chatListFactory()
-        let singleChatScreen = singleChatFactory(contact)
-        replacePresenter.present(chatListScreen, singleChatScreen, from: parent)
-    }
-
-    func toGroupChat(with group: GroupInfo, from parent: UIViewController) {
-        let chatListScreen = chatListFactory()
-        let groupChatScreen = groupChatFactory(group)
-        replacePresenter.present(chatListScreen, groupChatScreen, from: parent)
-    }
-}
diff --git a/Sources/LaunchFeature/LaunchView.swift b/Sources/LaunchFeature/LaunchView.swift
index 995c9f8c6b66501798069a736594c3e4a87a5537..40f6fb44b8b23520c062774adca2df350b98a409 100644
--- a/Sources/LaunchFeature/LaunchView.swift
+++ b/Sources/LaunchFeature/LaunchView.swift
@@ -1,37 +1,37 @@
 import UIKit
-import Shared
+import AppResources
 
 final class LaunchView: UIView {
-    private var imageView = UIImageView()
+  let imageView = UIImageView()
+  let gradientLayer = CAGradientLayer()
 
-    init() {
-        super.init(frame: .zero)
-        imageView.image = Asset.splash.image
-        imageView.contentMode = .scaleAspectFit
-        backgroundColor = Asset.neutralWhite.color
+  init() {
+    super.init(frame: .zero)
 
-        addSubview(imageView)
+    gradientLayer.colors = [
+      UIColor(red: 122/255, green: 235/255, blue: 239/255, alpha: 1).cgColor,
+      UIColor(red: 56/255, green: 204/255, blue: 232/255, alpha: 1).cgColor,
+      UIColor(red: 63/255, green: 186/255, blue: 253/255, alpha: 1).cgColor,
+      UIColor(red: 98/255, green: 163/255, blue: 255/255, alpha: 1).cgColor
+    ]
+    gradientLayer.startPoint = CGPoint(x: 1, y: 0)
+    gradientLayer.endPoint = CGPoint(x: 0, y: 1)
+    layer.insertSublayer(gradientLayer, at: 0)
+    imageView.image = Asset.splash.image
+    imageView.contentMode = .scaleAspectFit
+    backgroundColor = Asset.neutralWhite.color
 
-        imageView.snp.makeConstraints {
-            $0.center.equalToSuperview()
-            $0.left.equalToSuperview().offset(100)
-        }
-    }
+    addSubview(imageView)
 
-    required init?(coder: NSCoder) { nil }
+    imageView.snp.makeConstraints {
+      $0.center.equalToSuperview()
+      $0.left.equalToSuperview().offset(100)
+    }
+  }
 
-    func setupGradient() {
-        let gradient = CAGradientLayer()
-        gradient.colors = [
-            UIColor(red: 122/255, green: 235/255, blue: 239/255, alpha: 1).cgColor,
-            UIColor(red: 56/255, green: 204/255, blue: 232/255, alpha: 1).cgColor,
-            UIColor(red: 63/255, green: 186/255, blue: 253/255, alpha: 1).cgColor,
-            UIColor(red: 98/255, green: 163/255, blue: 255/255, alpha: 1).cgColor
-        ]
+  required init?(coder: NSCoder) { nil }
 
-        gradient.frame = bounds
-        gradient.startPoint = CGPoint(x: 1, y: 0)
-        gradient.endPoint = CGPoint(x: 0, y: 1)
-        layer.insertSublayer(gradient, at: 0)
-    }
+  override func layoutSubviews() {
+    gradientLayer.frame = bounds
+  }
 }
diff --git a/Sources/LaunchFeature/LaunchViewModel.swift b/Sources/LaunchFeature/LaunchViewModel.swift
index 73fa6722f37c0fec119a11866046db37ddb0ae9e..bbb68e5dc47560b8542113565e198eb12c96e1bf 100644
--- a/Sources/LaunchFeature/LaunchViewModel.swift
+++ b/Sources/LaunchFeature/LaunchViewModel.swift
@@ -1,266 +1,325 @@
-import HUD
 import Shared
-import Models
 import Combine
 import Defaults
 import XXModels
 import Keychain
-import Foundation
-import Integration
-import Permissions
-import ToastFeature
-import DropboxFeature
-import VersionChecking
+import XXClient
+import CloudFiles
+import CheckVersion
+import AppResources
+import BackupFeature
 import ReportingFeature
-import CombineSchedulers
-import DependencyInjection
+import CloudFilesDropbox
+import XXMessengerClient
+
+import UpdateErrors
+import FetchBannedList
+import ProcessBannedList
+
+import AppCore
+import Foundation
+import PermissionsFeature
+import ComposableArchitecture
+
+import XXDatabase
+import XXLegacyDatabaseMigrator
+
+import class XXClient.Cancellable
+
+import PulseLogHandler
 
-struct Update {
+final class LaunchViewModel {
+  struct UpdateModel {
     let content: String
     let urlString: String
     let positiveActionTitle: String
     let negativeActionTitle: String?
     let actionStyle: CapsuleButtonStyle
-}
-
-enum LaunchRoute {
-    case chats
-    case update(Update)
-    case onboarding(String)
-}
-
-final class LaunchViewModel {
-    @Dependency private var network: XXNetworking
-    @Dependency private var versionChecker: VersionChecker
-    @Dependency private var dropboxService: DropboxInterface
-    @Dependency private var keychainHandler: KeychainHandling
-    @Dependency private var permissionHandler: PermissionHandling
-    @Dependency private var fetchBannedList: FetchBannedList
-    @Dependency private var reportingStatus: ReportingStatus
-    @Dependency private var processBannedList: ProcessBannedList
-    @Dependency private var toastController: ToastController
-    @Dependency private var session: SessionType
-
-    @KeyObject(.username, defaultValue: nil) var username: String?
-    @KeyObject(.biometrics, defaultValue: false) var isBiometricsOn: Bool
-
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
-
-    var routePublisher: AnyPublisher<LaunchRoute, Never> {
-        routeSubject.eraseToAnyPublisher()
+  }
+
+  struct ViewState {
+    var shouldShowTerms = false
+    var shouldPushChats = false
+    var shouldOfferUpdate: UpdateModel?
+    var shouldPushOnboarding = false
+  }
+
+  @Dependency(\.app.log) var log
+  @Dependency(\.app.bgQueue) var bgQueue
+  @Dependency(\.permissions) var permissions
+  @Dependency(\.app.messenger) var messenger
+  @Dependency(\.app.dbManager) var dbManager
+  @Dependency(\.updateErrors) var updateErrors
+  @Dependency(\.app.hudManager) var hudManager
+  @Dependency(\.checkVersion) var checkVersion
+  @Dependency(\.dummyTraffic) var dummyTraffic
+  @Dependency(\.app.toastManager) var toastManager
+  @Dependency(\.fetchBannedList) var fetchBannedList
+  @Dependency(\.reportingStatus) var reportingStatus
+  @Dependency(\.app.networkMonitor) var networkMonitor
+  @Dependency(\.processBannedList) var processBannedList
+
+  @Dependency(\.app.authHandler) var authHandler
+  @Dependency(\.app.groupRequest) var groupRequest
+  @Dependency(\.app.backupHandler) var backupHandler
+  @Dependency(\.app.messageListener) var messageListener
+  @Dependency(\.app.receiveFileHandler) var receiveFileHandler
+  @Dependency(\.app.groupMessageHandler) var groupMessageHandler
+
+  var authHandlerCancellable: Cancellable?
+  var groupRequestCancellable: Cancellable?
+  var backupHandlerCancellable: Cancellable?
+  var networkHandlerCancellable: Cancellable?
+  var receiveFileHandlerCancellable: Cancellable?
+  var groupMessageHandlerCancellable: Cancellable?
+  var messageListenerHandlerCancellable: Cancellable?
+
+  @KeyObject(.username, defaultValue: nil) var username: String?
+  @KeyObject(.biometrics, defaultValue: false) var isBiometricsOn: Bool
+  @KeyObject(.acceptedTerms, defaultValue: false) var didAcceptTerms: Bool
+  @KeyObject(.dummyTrafficOn, defaultValue: false) var dummyTrafficOn: Bool
+
+  var statePublisher: AnyPublisher<ViewState, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
+
+  let dropboxManager = CloudFilesManager.dropbox(
+    appKey: "ppx0de5f16p9aq2",
+    path: "/backup/backup.xxm"
+  )
+
+  let sftpManager = CloudFilesManager.sftp(
+    host: "",
+    username: "",
+    password: "",
+    fileName: ""
+  )
+
+  let stateSubject = CurrentValueSubject <ViewState, Never>(.init())
+
+  func startLaunch() {
+    if !didAcceptTerms {
+      stateSubject.value.shouldShowTerms = true
     }
+    hudManager.show()
+    checkVersion {
+      switch $0 {
+      case .success(let result):
+        switch result {
+        case .updated:
+          self.didVerifyVersion()
+        case .outdated(let appUrl):
+          self.hudManager.hide()
+
+          self.stateSubject.value.shouldOfferUpdate = .init(
+            content: Localized.Launch.Version.Recommended.title,
+            urlString: appUrl,
+            positiveActionTitle: Localized.Launch.Version.Recommended.positive,
+            negativeActionTitle: Localized.Launch.Version.Recommended.negative,
+            actionStyle: .simplestColoredRed
+          )
+        case .wayTooOld(let appUrl, let minimumVersionMessage):
+          self.hudManager.hide()
 
-    var mainScheduler: AnySchedulerOf<DispatchQueue> = {
-        DispatchQueue.main.eraseToAnyScheduler()
-    }()
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = {
-        DispatchQueue.global().eraseToAnyScheduler()
-    }()
-
-    private var cancellables = Set<AnyCancellable>()
-    private let routeSubject = PassthroughSubject<LaunchRoute, Never>()
-    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
-
-    func viewDidAppear() {
-        mainScheduler.schedule(after: .init(.now() + 1)) { [weak self] in
-            guard let self = self else { return }
-
-            self.hudSubject.send(.on)
-
-            self.versionChecker().sink { [unowned self] in
-                switch $0 {
-                case .upToDate:
-                    self.versionApproved()
-                case .failure(let error):
-                    self.versionFailed(error: error)
-                case .updateRequired(let info):
-                    self.versionUpdateRequired(info)
-                case .updateRecommended(let info):
-                    self.versionUpdateRecommended(info)
-                }
-            }.store(in: &self.cancellables)
+          self.stateSubject.value.shouldOfferUpdate = .init(
+            content: minimumVersionMessage,
+            urlString: appUrl,
+            positiveActionTitle: Localized.Launch.Version.Required.positive,
+            negativeActionTitle: nil,
+            actionStyle: .brandColored
+          )
         }
+      case .failure(let error):
+        self.hudManager.show(.init(
+          title: Localized.Launch.Version.failed,
+          content: error.localizedDescription
+        ))
+      }
     }
-
-    func versionApproved() {
-        network.writeLogs()
-
-        network.updateNDF { [weak self] in
-            guard let self = self else { return }
-
-            switch $0 {
-            case .success(let ndf):
-                self.network.updateErrors()
-
-                guard self.network.hasClient else {
-                    self.hudSubject.send(.none)
-                    self.routeSubject.send(.onboarding(ndf))
-                    self.dropboxService.unlink()
-                    try? self.keychainHandler.clear()
-                    return
-                }
-
-                guard self.username != nil else {
-                    self.network.purgeFiles()
-                    self.hudSubject.send(.none)
-                    self.routeSubject.send(.onboarding(ndf))
-                    self.dropboxService.unlink()
-                    try? self.keychainHandler.clear()
-                    return
-                }
-
-                self.backgroundScheduler.schedule { [weak self] in
-                    guard let self = self else { return }
-
-                    do {
-                        let session = try Session(ndf: ndf)
-                        DependencyInjection.Container.shared.register(session as SessionType)
-
-                        self.updateBannedList {
-                            DispatchQueue.main.async {
-                                self.hudSubject.send(.none)
-                                self.checkBiometrics()
-                            }
-                        }
-                    } catch {
-                        DispatchQueue.main.async {
-                            self.hudSubject.send(.error(HUDError(with: error)))
-                        }
-                    }
-                }
-
-            case .failure(let error):
-                self.hudSubject.send(.error(HUDError(with: error)))
+  }
+
+  func didRefuseUpdating() {
+    hudManager.show()
+    didVerifyVersion()
+  }
+
+  private func didVerifyVersion() {
+    updateBannedList {
+      self.updateErrors {
+        switch $0 {
+        case .success:
+          do {
+            if !self.dbManager.hasDB() {
+              try self.dbManager.makeDB()
             }
+            try self.setupMessenger()
+          } catch {
+            let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+            self.hudManager.show(.init(content: xxError))
+          }
+        case .failure(let error):
+          self.hudManager.show(.init(error: error))
         }
+      }
     }
+  }
+}
 
-    func getContactWith(userId: Data) -> Contact? {
-        let query = Contact.Query(
-            id: [userId],
-            isBlocked: reportingStatus.isEnabled() ? false : nil,
-            isBanned: reportingStatus.isEnabled() ? false : nil
-        )
+extension LaunchViewModel {
+  func setupMessenger() throws {
+    _ = try messenger.setLogLevel(.trace)
+    messenger.startLogging()
 
-        return try! session.dbManager.fetchContacts(query).first
+    authHandlerCancellable = authHandler { [weak self] in
+      self?.log(.error($0 as NSError))
     }
-
-    func getGroupInfoWith(groupId: Data) -> GroupInfo? {
-        let query = GroupInfo.Query(groupId: groupId)
-        return try! session.dbManager.fetchGroupInfos(query).first
+    backupHandlerCancellable = backupHandler { [weak self] in
+      self?.log(.error($0 as NSError))
     }
-
-    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))
+    receiveFileHandlerCancellable = receiveFileHandler { [weak self] in
+      self?.log(.error($0 as NSError))
+    }
+    messageListenerHandlerCancellable = messageListener { [weak self] in
+      self?.log(.error($0 as NSError))
     }
 
-    private func versionUpdateRequired(_ info: DappVersionInformation) {
-        hudSubject.send(.none)
+    if messenger.isLoaded() == false {
+      if messenger.isCreated() == false {
+        try messenger.create()
+      }
+      try messenger.load()
+    }
+    try messenger.start()
+    if messenger.isConnected() == false {
+      try messenger.connect()
+      try messenger.listenForMessages()
+    }
 
-        let model = Update(
-            content: info.minimumMessage,
-            urlString: info.appUrl,
-            positiveActionTitle: Localized.Launch.Version.Required.positive,
-            negativeActionTitle: nil,
-            actionStyle: .brandColored
-        )
+    let dummyTrafficManager = try NewDummyTrafficManager.live(
+      cMixId: messenger.e2e()!.getId()
+    )
+    dummyTraffic.set(dummyTrafficManager)
+
+    try dummyTrafficManager.setStatus(dummyTrafficOn)
+
+    if messenger.isLoggedIn() == false {
+      if try messenger.isRegistered() {
+        try messenger.logIn()
+        hudManager.hide()
+        stateSubject.value.shouldPushChats = true
+      } else {
+        try? sftpManager.unlink()
+        try? dropboxManager.unlink()
+        hudManager.hide()
+        stateSubject.value.shouldPushOnboarding = true
+      }
+    } else {
+      hudManager.hide()
+      stateSubject.value.shouldPushChats = true
+    }
+    if !messenger.isBackupRunning() {
+      try? messenger.resumeBackup()
+    }
 
-        routeSubject.send(.update(model))
+    groupRequestCancellable = groupRequest { [weak self] in
+      self?.log(.error($0 as NSError))
     }
 
-    private func versionUpdateRecommended(_ info: DappVersionInformation) {
-        hudSubject.send(.none)
+    groupMessageHandlerCancellable = groupMessageHandler { [weak self] in
+      self?.log(.error($0 as NSError))
+    }
 
-        let model = Update(
-            content: Localized.Launch.Version.Recommended.title,
-            urlString: info.appUrl,
-            positiveActionTitle: Localized.Launch.Version.Recommended.positive,
-            negativeActionTitle: Localized.Launch.Version.Recommended.negative,
-            actionStyle: .simplestColoredRed
-        )
+    try messenger.startGroupChat()
 
-        routeSubject.send(.update(model))
+    try messenger.trackServices { [weak self] in
+      self?.log(.error($0 as NSError))
     }
 
-    private func checkBiometrics() {
-        if permissionHandler.isBiometricsAvailable && isBiometricsOn {
-            permissionHandler.requestBiometrics { [weak self] in
-                guard let self = self else { return }
+    try messenger.startFileTransfer()
 
-                switch $0 {
-                case .success(let granted):
-                    guard granted else { return }
-                    self.routeSubject.send(.chats)
+    networkMonitor.start()
+    networkHandlerCancellable = messenger.cMix.get()!.addHealthCallback(
+      HealthCallback {
+        self.networkMonitor.update($0)
+      }
+    )
 
-                case .failure(let error):
-                    self.hudSubject.send(.error(HUDError(with: error)))
-                }
-            }
-        } else {
-            self.routeSubject.send(.chats)
-        }
-    }
+    try failPendingProcessesFromLastSession()
+  }
+}
 
-    private func updateBannedList(completion: @escaping () -> Void) {
-        fetchBannedList { result in
-            switch result {
-            case .failure(_):
-                DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
-                    self.updateBannedList(completion: completion)
-                }
-            case .success(let data):
-                self.processBannedList(data, completion: completion)
-            }
+extension LaunchViewModel {
+  func failPendingProcessesFromLastSession() throws {
+    try dbManager.getDB().bulkUpdateMessages(
+      .init(status: [.sending]),
+      .init(status: .sendingFailed)
+    )
+  }
+
+  func updateBannedList(completion: @escaping () -> Void) {
+    fetchBannedList { result in
+      switch result {
+      case .failure(_):
+        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
+          self.updateBannedList(completion: completion)
         }
+      case .success(let data):
+        self.processBannedList(data, completion: completion)
+      }
     }
-
-    private func processBannedList(_ data: Data, completion: @escaping () -> Void) {
-        processBannedList(
-            data: data,
-            forEach: { result in
-                switch result {
-                case .success(let userId):
-                    let query = Contact.Query(id: [userId])
-                    if var contact = try! self.session.dbManager.fetchContacts(query).first {
-                        if contact.isBanned == false {
-                            contact.isBanned = true
-                            try! self.session.dbManager.saveContact(contact)
-                            self.enqueueBanWarning(contact: contact)
-                        }
-                    } else {
-                        try! self.session.dbManager.saveContact(.init(id: userId, isBanned: true))
-                    }
-
-                case .failure(_):
-                    break
-                }
-            },
-            completion: { result in
-                switch result {
-                case .failure(_):
-                    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
-                        self.updateBannedList(completion: completion)
-                    }
-
-                case .success(_):
-                    completion()
-                }
+  }
+
+  func processBannedList(_ data: Data, completion: @escaping () -> Void) {
+    processBannedList(
+      data: data,
+      forEach: { result in
+        switch result {
+        case .success(let userId):
+          let query = Contact.Query(id: [userId])
+          if var contact = try! dbManager.getDB().fetchContacts(query).first {
+            if contact.isBanned == false {
+              contact.isBanned = true
+              try! dbManager.getDB().saveContact(contact)
+              enqueueBanWarning(contact: contact)
             }
-        )
-    }
+          } else {
+            try! dbManager.getDB().saveContact(.init(id: userId, isBanned: true))
+          }
 
-    private func enqueueBanWarning(contact: Contact) {
-        let name = (contact.nickname ?? contact.username) ?? "One of your contacts"
-        toastController.enqueueToast(model: .init(
-            title: "\(name) has been banned for offensive content.",
-            leftImage: Asset.requestSentToaster.image
-        ))
-    }
+        case .failure(_):
+          break
+        }
+      },
+      completion: { result in
+        switch result {
+        case .failure(_):
+          DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
+            self.updateBannedList(completion: completion)
+          }
+        case .success(_):
+          completion()
+        }
+      }
+    )
+  }
+
+  func enqueueBanWarning(contact: XXModels.Contact) {
+    let name = (contact.nickname ?? contact.username) ?? "One of your contacts"
+    toastManager.enqueue(.init(
+      title: "\(name) has been banned for offensive content.",
+      leftImage: Asset.requestSentToaster.image
+    ))
+  }
+
+  func getContactWith(userId: Data) -> XXModels.Contact? {
+    try? dbManager.getDB().fetchContacts(.init(
+      id: [userId],
+      isBlocked: reportingStatus.isEnabled() ? false : nil,
+      isBanned: reportingStatus.isEnabled() ? false : nil
+    )).first
+  }
+
+  func getGroupInfoWith(groupId: Data) -> GroupInfo? {
+    try? dbManager.getDB().fetchGroupInfos(.init(groupId: groupId)).first
+  }
 }
diff --git a/Sources/LaunchFeature/PushNotificationRouter.swift b/Sources/LaunchFeature/PushNotificationRouter.swift
new file mode 100644
index 0000000000000000000000000000000000000000..45b7850af57987213fd45b3c6376d1792165c0e5
--- /dev/null
+++ b/Sources/LaunchFeature/PushNotificationRouter.swift
@@ -0,0 +1,25 @@
+import Foundation
+import XCTestDynamicOverlay
+
+public struct PushNotificationRouter {
+  public typealias NavigateTo = (Route, @escaping () -> Void) -> Void
+
+  public enum Route {
+    case requests
+    case groupChat(id: Data)
+    case contactChat(id: Data)
+    case search(username: String)
+  }
+
+  public var navigateTo: NavigateTo
+
+  public init(navigateTo: @escaping NavigateTo) {
+    self.navigateTo = navigateTo
+  }
+}
+
+public extension PushNotificationRouter {
+  static let unimplemented = PushNotificationRouter(
+    navigateTo: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/LaunchFeature/UpdateBlocker.swift b/Sources/LaunchFeature/UpdateBlocker.swift
deleted file mode 100644
index 571c2a527a33e8411c320cbc7b25fdec6145d399..0000000000000000000000000000000000000000
--- a/Sources/LaunchFeature/UpdateBlocker.swift
+++ /dev/null
@@ -1,24 +0,0 @@
-import UIKit
-import Theme
-import Shared
-
-final class UpdateBlocker {
-    private(set) var window: Window? = Window()
-
-    func showWindow() {
-        window?.backgroundColor = UIColor.black.withAlphaComponent(0.5)
-        window?.rootViewController = StatusBarViewController(nil)
-        window?.alpha = 0.0
-        window?.makeKeyAndVisible()
-
-        UIView.animate(withDuration: 0.3) { self.window?.alpha = 1.0 }
-    }
-
-    func hideWindow() {
-        UIView.animate(withDuration: 0.3) {
-            self.window?.alpha = 0.0
-        } completion: { _ in
-            self.window = nil
-        }
-    }
-}
diff --git a/Sources/MenuFeature/Controllers/MenuController.swift b/Sources/MenuFeature/Controllers/MenuController.swift
index 93f1fcf4a328da1f73f077b8f0f8806ceee0c7f5..17a9a73505fc73230b5fad387c0dd9c247621ea1 100644
--- a/Sources/MenuFeature/Controllers/MenuController.swift
+++ b/Sources/MenuFeature/Controllers/MenuController.swift
@@ -1,235 +1,252 @@
-import Theme
 import UIKit
 import Shared
 import Combine
+import AppCore
+import Dependencies
+import AppResources
+import AppNavigation
 import DrawerFeature
-import DependencyInjection
-
-public enum MenuItem {
-    case join
-    case scan
-    case chats
-    case share
-    case profile
-    case contacts
-    case requests
-    case settings
-    case dashboard
-}
 
 public final class MenuController: UIViewController {
-    @Dependency private var coordinator: MenuCoordinating
-    @Dependency private var statusBarController: StatusBarStyleControlling
-
-    lazy private var screenView = MenuView()
-
-    private let previousItem: MenuItem
-    private let viewModel = MenuViewModel()
-    private let previousController: UIViewController
-    private var cancellables = Set<AnyCancellable>()
-    private var drawerCancellables = Set<AnyCancellable>()
-
-    public init(
-        _ previousItem: MenuItem,
-        _ previousController: UIViewController
-    ) {
-        self.previousItem = previousItem
-        self.previousController = previousController
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func loadView() {
-        view = screenView
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-
-        screenView.headerView.set(
-            username: viewModel.username,
-            image: viewModel.avatar
-        )
-
-        screenView.select(item: previousItem)
-        screenView.xxdkVersionLabel.text = "XXDK \(viewModel.xxdk)"
-        screenView.buildLabel.text = Localized.Menu.build(viewModel.build)
-        screenView.versionLabel.text = Localized.Menu.version(viewModel.version)
-        setupBindings()
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+
+  private lazy var screenView = MenuView()
+
+  private let currentItem: MenuItem
+  private let viewModel = MenuViewModel()
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
+
+  private var navController: UINavigationController?
+
+  public init(
+    _ currentItem: MenuItem,
+    _ navController: UINavigationController? = nil
+  ) {
+    self.currentItem = currentItem
+    self.navController = navController
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func loadView() {
+    view = screenView
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    screenView.headerView.set(
+      username: viewModel.username,
+      image: viewModel.avatar
+    )
+
+    switch currentItem {
+    case .scan:
+      screenView.scanButton.set(color: Asset.brandPrimary.color)
+    case .chats:
+      screenView.chatsButton.set(color: Asset.brandPrimary.color)
+    case .contacts:
+      screenView.contactsButton.set(color: Asset.brandPrimary.color)
+    case .requests:
+      screenView.requestsButton.set(color: Asset.brandPrimary.color)
+    case .settings:
+      screenView.settingsButton.set(color: Asset.brandPrimary.color)
+    default:
+      break
     }
 
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        statusBarController.style.send(.lightContent)
-    }
-
-    public override func viewWillDisappear(_ animated: Bool) {
-        super.viewWillDisappear(animated)
-        statusBarController.style.send(.darkContent)
-    }
-
-    private func setupBindings() {
-        screenView.headerView.scanButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                dismiss(animated: true) { [weak self] in
-                    guard let self = self, self.previousItem != .scan else { return }
-                    self.coordinator.toFlow(.scan, from: self.previousController)
-                }
-            }.store(in: &cancellables)
-
-        screenView.headerView.nameButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                dismiss(animated: true) { [weak self] in
-                    guard let self = self, self.previousItem != .profile else { return }
-                    self.coordinator.toFlow(.profile, from: self.previousController)
-                }
-            }.store(in: &cancellables)
-
-        screenView.scanButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                dismiss(animated: true) { [weak self] in
-                    guard let self = self, self.previousItem != .scan else { return }
-                    self.coordinator.toFlow(.scan, from: self.previousController)
-                }
-            }.store(in: &cancellables)
-
-        screenView.chatsButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                dismiss(animated: true) { [weak self] in
-                    guard let self = self, self.previousItem != .chats else { return }
-                    self.coordinator.toFlow(.chats, from: self.previousController)
-                }
-            }.store(in: &cancellables)
-
-        screenView.contactsButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                dismiss(animated: true) { [weak self] in
-                    guard let self = self, self.previousItem != .contacts else { return }
-                    self.coordinator.toFlow(.contacts, from: self.previousController)
-                }
-            }.store(in: &cancellables)
-
-        screenView.settingsButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                dismiss(animated: true) { [weak self] in
-                    guard let self = self, self.previousItem != .settings else { return }
-                    self.coordinator.toFlow(.settings, from: self.previousController)
-                }
-            }.store(in: &cancellables)
-
-        screenView.dashboardButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                dismiss(animated: true) { [weak self] in
-                    guard let self = self, self.previousItem != .dashboard else { return }
-                    self.presentDrawer(
-                        title: Localized.ChatList.Dashboard.title,
-                        subtitle: Localized.ChatList.Dashboard.subtitle,
-                        actionTitle: Localized.ChatList.Dashboard.open) {
-                            guard let url = URL(string: "https://dashboard.xx.network") else { return }
-                            UIApplication.shared.open(url, options: [:])
-                        }
-                }
-            }.store(in: &cancellables)
-
-        screenView.requestsButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                dismiss(animated: true) { [weak self] in
-                    guard let self = self, self.previousItem != .requests else { return }
-                    self.coordinator.toFlow(.requests, from: self.previousController)
-                }
-            }.store(in: &cancellables)
-
-        screenView.joinButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                dismiss(animated: true) { [weak self] in
-                    guard let self = self, self.previousItem != .join else { return }
-                    self.presentDrawer(
-                        title: Localized.ChatList.Join.title,
-                        subtitle: Localized.ChatList.Join.subtitle,
-                        actionTitle: Localized.ChatList.Dashboard.open) {
-                            guard let url = URL(string: "https://xx.network") else { return }
-                            UIApplication.shared.open(url, options: [:])
-                        }
-                }
-            }.store(in: &cancellables)
-
-        screenView.shareButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                dismiss(animated: true) { [weak self] in
-                    guard let self = self, self.previousItem != .share else { return }
-                    self.coordinator.toActivityController(
-                        with: [Localized.Menu.shareContent(self.viewModel.referralDeeplink)],
-                        from: self.previousController
-                    )
-                }
-            }.store(in: &cancellables)
-
-        viewModel.requestCount
-            .receive(on: DispatchQueue.main)
-            .sink { [weak screenView] in screenView?.requestsButton.updateNotification($0) }
-            .store(in: &cancellables)
-    }
-
-    private func presentDrawer(
-        title: String,
-        subtitle: String,
-        actionTitle: String,
-        action: @escaping () -> Void
-    ) {
-        let actionButton = DrawerCapsuleButton(model: .init(
-            title: actionTitle,
-            style: .red
-        ))
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
-            ),
-            DrawerText(
-                font: Fonts.Mulish.regular.font(size: 16.0),
-                text: subtitle,
-                color: Asset.neutralBody.color,
-                alignment: .left,
-                lineHeightMultiple: 1.1,
-                spacingAfter: 39
-            ),
-            actionButton
-        ])
-
-        actionButton.action.receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                    action()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: previousController)
-    }
+    screenView.xxdkVersionLabel.text = "XXDK \(viewModel.xxdk)"
+    screenView.buildLabel.text = Localized.Menu.build(viewModel.build)
+    screenView.versionLabel.text = Localized.Menu.version(viewModel.version)
+    setupBindings()
+  }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    statusBar.set(.lightContent)
+  }
+
+  public override func viewWillDisappear(_ animated: Bool) {
+    super.viewWillDisappear(animated)
+    statusBar.set(.darkContent)
+  }
+
+  private func setupBindings() {
+    screenView
+      .headerView
+      .scanButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self, self.currentItem != .scan else { return }
+          self.navigator.perform(PresentScan(on: self.navController!))
+        }
+      }.store(in: &cancellables)
+
+    screenView
+      .headerView
+      .nameButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self, self.currentItem != .profile else { return }
+          self.navigator.perform(PresentProfile(on: self.navController!))
+        }
+      }.store(in: &cancellables)
+
+    screenView
+      .scanButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self, self.currentItem != .scan else { return }
+          self.navigator.perform(PresentScan(on: self.navController!))
+        }
+      }.store(in: &cancellables)
+
+    screenView
+      .chatsButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self, self.currentItem != .chats else { return }
+          self.navigator.perform(PresentChatList(on: self.navController!))
+        }
+      }.store(in: &cancellables)
+
+    screenView
+      .contactsButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self, self.currentItem != .contacts else { return }
+          self.navigator.perform(PresentContactList(on: self.navController!))
+        }
+      }.store(in: &cancellables)
+
+    screenView
+      .settingsButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self, self.currentItem != .settings else { return }
+          self.navigator.perform(PresentSettings(on: self.navController!))
+        }
+      }.store(in: &cancellables)
+
+    screenView
+      .dashboardButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self, self.currentItem != .dashboard else { return }
+          self.presentDrawer(
+            title: Localized.ChatList.Dashboard.title,
+            subtitle: Localized.ChatList.Dashboard.subtitle,
+            actionTitle: Localized.ChatList.Dashboard.open) {
+              guard let url = URL(string: "https://dashboard.xx.network") else { return }
+              UIApplication.shared.open(url, options: [:])
+            }
+        }
+      }.store(in: &cancellables)
+
+    screenView
+      .requestsButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self, self.currentItem != .requests else { return }
+          self.navigator.perform(PresentRequests(on: self.navController!))
+        }
+      }.store(in: &cancellables)
+
+    screenView
+      .joinButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self, self.currentItem != .join else { return }
+          self.presentDrawer(
+            title: Localized.ChatList.Join.title,
+            subtitle: Localized.ChatList.Join.subtitle,
+            actionTitle: Localized.ChatList.Dashboard.open) {
+              guard let url = URL(string: "https://xx.network") else { return }
+              UIApplication.shared.open(url, options: [:])
+            }
+        }
+      }.store(in: &cancellables)
+
+    screenView
+      .shareButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self, self.currentItem != .share else { return }
+          self.navigator.perform(PresentActivitySheet(items: [
+            Localized.Menu.shareContent(self.viewModel.referralDeeplink)
+          ], from: self.navController!.topViewController!))
+        }
+      }.store(in: &cancellables)
+
+    viewModel
+      .requestCount
+      .receive(on: DispatchQueue.main)
+      .sink { [weak screenView] in
+        screenView?.requestsButton.updateNotification($0)
+      }.store(in: &cancellables)
+  }
+
+  private func presentDrawer(
+    title: String,
+    subtitle: String,
+    actionTitle: String,
+    action: @escaping () -> Void
+  ) {
+    let actionButton = DrawerCapsuleButton(model: .init(
+      title: actionTitle,
+      style: .red
+    ))
+
+    actionButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          action()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: title,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 19
+      ),
+      DrawerText(
+        font: Fonts.Mulish.regular.font(size: 16.0),
+        text: subtitle,
+        color: Asset.neutralBody.color,
+        alignment: .left,
+        lineHeightMultiple: 1.1,
+        spacingAfter: 39
+      ),
+      actionButton
+    ], isDismissable: true, from: navController!.topViewController!))
+  }
 }
diff --git a/Sources/MenuFeature/Coordinator/MenuCoordinator.swift b/Sources/MenuFeature/Coordinator/MenuCoordinator.swift
deleted file mode 100644
index 3e7d20cc85f6a4afa7e50e2f8133bdf041f5ace6..0000000000000000000000000000000000000000
--- a/Sources/MenuFeature/Coordinator/MenuCoordinator.swift
+++ /dev/null
@@ -1,73 +0,0 @@
-import UIKit
-import Presentation
-
-public protocol MenuCoordinating {
-    func toFlow(_ item: MenuItem, from: UIViewController)
-    func toDrawer(_: UIViewController, from: UIViewController)
-    func toActivityController(with: [Any], from: UIViewController)
-}
-
-public struct MenuCoordinator: MenuCoordinating {
-    var modalPresenter: Presenting = ModalPresenter()
-    var bottomPresenter: Presenting = BottomPresenter()
-    var replacePresenter: Presenting = ReplacePresenter()
-
-    var scanFactory: () -> UIViewController
-    var chatsFactory: () -> UIViewController
-    var profileFactory: () -> UIViewController
-    var settingsFactory: () -> UIViewController
-    var contactsFactory: () -> UIViewController
-    var requestsFactory: () -> UIViewController
-    var activityControllerFactory: ([Any]) -> UIViewController
-    = { UIActivityViewController(activityItems: $0, applicationActivities: nil) }
-
-    public init(
-        scanFactory: @escaping () -> UIViewController,
-        chatsFactory: @escaping () -> UIViewController,
-        profileFactory: @escaping () -> UIViewController,
-        settingsFactory: @escaping () -> UIViewController,
-        contactsFactory: @escaping () -> UIViewController,
-        requestsFactory: @escaping () -> UIViewController
-    ) {
-        self.scanFactory = scanFactory
-        self.chatsFactory = chatsFactory
-        self.profileFactory = profileFactory
-        self.settingsFactory = settingsFactory
-        self.contactsFactory = contactsFactory
-        self.requestsFactory = requestsFactory
-    }
-}
-
-public extension MenuCoordinator {
-    func toDrawer(_ drawer: UIViewController, from parent: UIViewController) {
-        bottomPresenter.present(drawer, from: parent)
-    }
-
-    func toFlow(_ item: MenuItem, from parent: UIViewController) {
-        let controller: UIViewController
-
-        switch item {
-        case .scan:
-            controller = scanFactory()
-        case .chats:
-            controller = chatsFactory()
-        case .profile:
-            controller = profileFactory()
-        case .contacts:
-            controller = contactsFactory()
-        case .requests:
-            controller = requestsFactory()
-        case .settings:
-            controller = settingsFactory()
-        default:
-            fatalError()
-        }
-
-        replacePresenter.present(controller, from: parent)
-    }
-
-    func toActivityController(with items: [Any], from parent: UIViewController) {
-        let screen = activityControllerFactory(items)
-        modalPresenter.present(screen, from: parent)
-    }
-}
diff --git a/Sources/MenuFeature/ViewModels/MenuViewModel.swift b/Sources/MenuFeature/ViewModels/MenuViewModel.swift
index 72fbe071d7b81db1f192b41d1ee5480a7a03d226..3005d8e2a7b22b375f44dfdac8635fd31f8c47d3 100644
--- a/Sources/MenuFeature/ViewModels/MenuViewModel.swift
+++ b/Sources/MenuFeature/ViewModels/MenuViewModel.swift
@@ -1,58 +1,61 @@
 import Combine
+import AppCore
 import XXModels
+import XXClient
 import Defaults
 import Foundation
-import Integration
 import ReportingFeature
-import DependencyInjection
+import ComposableArchitecture
 
 final class MenuViewModel {
-    @Dependency private var session: SessionType
-    @Dependency private var reportingStatus: ReportingStatus
-
-    @KeyObject(.avatar, defaultValue: nil) var avatar: Data?
-    @KeyObject(.username, defaultValue: "") var username: String
-
-    var requestCount: AnyPublisher<Int, Never> {
-        let groupQuery = Group.Query(
-            authStatus: [.pending],
-            isLeaderBlocked: reportingStatus.isEnabled() ? false : nil,
-            isLeaderBanned: reportingStatus.isEnabled() ? false : nil
-        )
-
-        let contactsQuery = Contact.Query(
-            authStatus: [
-                .verified,
-                .confirming,
-                .confirmationFailed,
-                .verificationFailed,
-                .verificationInProgress
-            ],
-            isBlocked: reportingStatus.isEnabled() ? false : nil,
-            isBanned: reportingStatus.isEnabled() ? false : nil
-        )
-
-        return Publishers.CombineLatest(
-            session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure(),
-            session.dbManager.fetchGroupsPublisher(groupQuery).assertNoFailure()
-        )
-        .map { $0.0.count + $0.1.count }
-        .eraseToAnyPublisher()
-    }
-
-    var xxdk: String {
-        session.version
-    }
-
-    var build: String {
-        Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? ""
-    }
-
-    var version: String {
-        Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
-    }
-
-    var referralDeeplink: String {
-        "https://elixxir.io/connect?username=\(username)"
-    }
+  @Dependency(\.app.dbManager) var dbManager: DBManager
+  @Dependency(\.reportingStatus) var reportingStatus: ReportingStatus
+
+  @KeyObject(.avatar, defaultValue: nil) var avatar: Data?
+  @KeyObject(.username, defaultValue: "") var username: String
+
+  var requestCount: AnyPublisher<Int, Never> {
+    let groupQuery = Group.Query(
+      authStatus: [.pending],
+      isLeaderBlocked: reportingStatus.isEnabled() ? false : nil,
+      isLeaderBanned: reportingStatus.isEnabled() ? false : nil
+    )
+
+    let contactsQuery = Contact.Query(
+      authStatus: [
+        .verified,
+        .confirming,
+        .confirmationFailed,
+        .verificationFailed,
+        .verificationInProgress
+      ],
+      isBlocked: reportingStatus.isEnabled() ? false : nil,
+      isBanned: reportingStatus.isEnabled() ? false : nil
+    )
+
+    return Publishers.CombineLatest(
+      try! dbManager.getDB().fetchContactsPublisher(contactsQuery)
+        .replaceError(with: []),
+      try! dbManager.getDB().fetchGroupsPublisher(groupQuery)
+        .replaceError(with: [])
+    )
+    .map { $0.0.count + $0.1.count }
+    .eraseToAnyPublisher()
+  }
+
+  var xxdk: String {
+    GetVersion.live()
+  }
+
+  var build: String {
+    Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? ""
+  }
+
+  var version: String {
+    Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
+  }
+
+  var referralDeeplink: String {
+    "https://elixxir.io/connect?username=\(username)"
+  }
 }
diff --git a/Sources/MenuFeature/Views/MenuHeaderView.swift b/Sources/MenuFeature/Views/MenuHeaderView.swift
index 2f925646b3b03c3aec498ccb67578a1b4f40fb89..eaea1321b5ea8f1eaaf7a7ebbda4c0190433979d 100644
--- a/Sources/MenuFeature/Views/MenuHeaderView.swift
+++ b/Sources/MenuFeature/Views/MenuHeaderView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 final class MenuHeaderView: UIView {
     let nameButton = UIButton()
diff --git a/Sources/MenuFeature/Views/MenuSectionButton.swift b/Sources/MenuFeature/Views/MenuSectionButton.swift
index c5f6ea371a10f0ff3fc087b31f6dc744e11fd4a9..2432cdf6b8cb86833e407c9f5e22deeed2def9f5 100644
--- a/Sources/MenuFeature/Views/MenuSectionButton.swift
+++ b/Sources/MenuFeature/Views/MenuSectionButton.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 final class MenuSectionButton: UIControl {
     let titleLabel = UILabel()
diff --git a/Sources/MenuFeature/Views/MenuView.swift b/Sources/MenuFeature/Views/MenuView.swift
index e256782eddaa5e686d2bfc7e3ef4af237c3eb51c..e62deae1b0ef07fdb6938845aec4048a824d2386 100644
--- a/Sources/MenuFeature/Views/MenuView.swift
+++ b/Sources/MenuFeature/Views/MenuView.swift
@@ -1,97 +1,81 @@
 import UIKit
 import Shared
+import AppResources
 
 final class MenuView: UIView {
-    let buildLabel = UILabel()
-    let versionLabel = UILabel()
-    let stackView = UIStackView()
-    let xxdkVersionLabel = UILabel()
-    let infoStackView = UIStackView()
-    let headerView = MenuHeaderView()
-    let joinButton = MenuSectionButton()
-    let scanButton = MenuSectionButton()
-    let shareButton = MenuSectionButton()
-    let chatsButton = MenuSectionButton()
-    let contactsButton = MenuSectionButton()
-    let requestsButton = MenuSectionButton()
-    let settingsButton = MenuSectionButton()
-    let dashboardButton = MenuSectionButton()
+  let buildLabel = UILabel()
+  let versionLabel = UILabel()
+  let stackView = UIStackView()
+  let xxdkVersionLabel = UILabel()
+  let infoStackView = UIStackView()
+  let headerView = MenuHeaderView()
+  let joinButton = MenuSectionButton()
+  let scanButton = MenuSectionButton()
+  let shareButton = MenuSectionButton()
+  let chatsButton = MenuSectionButton()
+  let contactsButton = MenuSectionButton()
+  let requestsButton = MenuSectionButton()
+  let settingsButton = MenuSectionButton()
+  let dashboardButton = MenuSectionButton()
 
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralDark.color
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralDark.color
 
-        scanButton.set(title: Localized.Menu.scan, image: Asset.menuScan.image)
-        shareButton.set(title: Localized.Menu.share, image: Asset.menuShare.image)
-        chatsButton.set(title: Localized.Menu.chats, image: Asset.menuChats.image)
-        joinButton.set(title: Localized.Menu.join, image: Asset.permissionLogo.image)
-        requestsButton.set(title: Localized.Menu.requests, image: Asset.menuRequests.image)
-        contactsButton.set(title: Localized.Menu.contacts, image: Asset.menuContacts.image)
-        settingsButton.set(title: Localized.Menu.settings, image: Asset.menuSettings.image)
-        dashboardButton.set(title: Localized.Menu.dashboard, image: Asset.menuDashboard.image)
+    scanButton.set(title: Localized.Menu.scan, image: Asset.menuScan.image)
+    shareButton.set(title: Localized.Menu.share, image: Asset.menuShare.image)
+    chatsButton.set(title: Localized.Menu.chats, image: Asset.menuChats.image)
+    joinButton.set(title: Localized.Menu.join, image: Asset.permissionLogo.image)
+    requestsButton.set(title: Localized.Menu.requests, image: Asset.menuRequests.image)
+    contactsButton.set(title: Localized.Menu.contacts, image: Asset.menuContacts.image)
+    settingsButton.set(title: Localized.Menu.settings, image: Asset.menuSettings.image)
+    dashboardButton.set(title: Localized.Menu.dashboard, image: Asset.menuDashboard.image)
 
-        stackView.addArrangedSubview(chatsButton)
-        stackView.addArrangedSubview(contactsButton)
-        stackView.addArrangedSubview(requestsButton)
-        stackView.addArrangedSubview(scanButton)
-        stackView.addArrangedSubview(settingsButton)
-        stackView.addArrangedSubview(dashboardButton)
-        stackView.addArrangedSubview(joinButton)
-        stackView.addArrangedSubview(shareButton)
+    stackView.addArrangedSubview(chatsButton)
+    stackView.addArrangedSubview(contactsButton)
+    stackView.addArrangedSubview(requestsButton)
+    stackView.addArrangedSubview(scanButton)
+    stackView.addArrangedSubview(settingsButton)
+    stackView.addArrangedSubview(dashboardButton)
+    stackView.addArrangedSubview(joinButton)
+    stackView.addArrangedSubview(shareButton)
 
-        infoStackView.spacing = 10
-        infoStackView.axis = .vertical
-        [buildLabel, versionLabel, xxdkVersionLabel].forEach {
-            $0.textColor = Asset.neutralWeak.color
-            $0.font = Fonts.Mulish.regular.font(size: 12.0)
-            infoStackView.addArrangedSubview($0)
-        }
+    infoStackView.spacing = 10
+    infoStackView.axis = .vertical
+    [buildLabel, versionLabel, xxdkVersionLabel].forEach {
+      $0.textColor = Asset.neutralWeak.color
+      $0.font = Fonts.Mulish.regular.font(size: 12.0)
+      infoStackView.addArrangedSubview($0)
+    }
 
-        stackView.spacing = 28
-        stackView.axis = .vertical
-        stackView.distribution = .equalSpacing
+    stackView.spacing = 28
+    stackView.axis = .vertical
+    stackView.distribution = .equalSpacing
 
-        addSubview(headerView)
-        addSubview(stackView)
-        addSubview(infoStackView)
+    addSubview(headerView)
+    addSubview(stackView)
+    addSubview(infoStackView)
 
-        setupConstraints()
-    }
+    setupConstraints()
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 
-    func select(item: MenuItem) {
-        switch item {
-        case .scan:
-            scanButton.set(color: Asset.brandPrimary.color)
-        case .chats:
-            chatsButton.set(color: Asset.brandPrimary.color)
-        case .contacts:
-            contactsButton.set(color: Asset.brandPrimary.color)
-        case .requests:
-            requestsButton.set(color: Asset.brandPrimary.color)
-        case .settings:
-            settingsButton.set(color: Asset.brandPrimary.color)
-        case .share, .join, .profile, .dashboard:
-            break
-        }
+  private func setupConstraints() {
+    headerView.snp.makeConstraints {
+      $0.top.equalTo(safeAreaLayoutGuide).offset(20)
+      $0.left.equalToSuperview().offset(30)
+      $0.right.equalToSuperview().offset(-24)
     }
 
-    private func setupConstraints() {
-        headerView.snp.makeConstraints {
-            $0.top.equalTo(safeAreaLayoutGuide).offset(20)
-            $0.left.equalToSuperview().offset(30)
-            $0.right.equalToSuperview().offset(-24)
-        }
-
-        stackView.snp.makeConstraints {
-            $0.left.equalToSuperview().offset(26)
-            $0.top.equalTo(headerView.snp.bottom).offset(75)
-        }
+    stackView.snp.makeConstraints {
+      $0.left.equalToSuperview().offset(26)
+      $0.top.equalTo(headerView.snp.bottom).offset(75)
+    }
 
-        infoStackView.snp.makeConstraints {
-            $0.bottom.equalTo(safeAreaLayoutGuide).offset(-20)
-            $0.left.equalToSuperview().offset(20)
-        }
+    infoStackView.snp.makeConstraints {
+      $0.bottom.equalTo(safeAreaLayoutGuide).offset(-20)
+      $0.left.equalToSuperview().offset(20)
     }
+  }
 }
diff --git a/Sources/Models/AttributeConfirmation.swift b/Sources/Models/AttributeConfirmation.swift
deleted file mode 100644
index 2f8b99ea4a332674224eeffb1559295d112f2099..0000000000000000000000000000000000000000
--- a/Sources/Models/AttributeConfirmation.swift
+++ /dev/null
@@ -1,15 +0,0 @@
-public struct AttributeConfirmation: Equatable {
-    public var content: String
-    public var isEmail: Bool = false
-    public var confirmationId: String?
-
-    public init(
-        content: String,
-        isEmail: Bool = false,
-        confirmationId: String? = nil
-    ) {
-        self.content = content
-        self.isEmail = isEmail
-        self.confirmationId = confirmationId
-    }
-}
diff --git a/Sources/Models/Backup.swift b/Sources/Models/Backup.swift
deleted file mode 100644
index a3518af44998b729a0c66f0c347d26d295634898..0000000000000000000000000000000000000000
--- a/Sources/Models/Backup.swift
+++ /dev/null
@@ -1,17 +0,0 @@
-import Foundation
-
-public struct Backup: Equatable, Codable {
-    public var id: String
-    public var date: Date
-    public var size: Float
-
-    public init(
-        id: String,
-        date: Date,
-        size: Float
-    ) {
-        self.id = id
-        self.date = date
-        self.size = size
-    }
-}
diff --git a/Sources/Models/BackupSettings.swift b/Sources/Models/BackupSettings.swift
deleted file mode 100644
index 1ec2883fc6119882f5008d26b86bb65ed54962c4..0000000000000000000000000000000000000000
--- a/Sources/Models/BackupSettings.swift
+++ /dev/null
@@ -1,51 +0,0 @@
-import Foundation
-
-public struct BackupSettings: Equatable, Codable {
-    public var wifiOnlyBackup: Bool
-    public var automaticBackups: Bool
-    public var enabledService: CloudService?
-    public var connectedServices: Set<CloudService>
-    public var backups: [CloudService: Backup]
-
-    public init(
-        wifiOnlyBackup: Bool = false,
-        automaticBackups: Bool = false,
-        enabledService: CloudService? = nil,
-        connectedServices: Set<CloudService> = [],
-        backups: [CloudService: Backup] = [:]
-    ) {
-        self.wifiOnlyBackup = wifiOnlyBackup
-        self.automaticBackups = automaticBackups
-        self.enabledService = enabledService
-        self.connectedServices = connectedServices
-        self.backups = backups
-    }
-
-    public func toData() -> Data {
-        (try? PropertyListEncoder().encode(self)) ?? Data()
-    }
-
-    public init(fromData data: Data) {
-        let settings = try? PropertyListDecoder().decode(BackupSettings.self, from: data)
-        self.init(
-            wifiOnlyBackup: settings?.wifiOnlyBackup ?? false,
-            automaticBackups: settings?.automaticBackups ?? false,
-            enabledService: settings?.enabledService,
-            connectedServices: settings?.connectedServices ?? [],
-            backups: settings?.backups ?? [:]
-        )
-    }
-}
-
-public struct RestoreSettings {
-    public var backup: Backup?
-    public var cloudService: CloudService
-
-    public init(
-        backup: Backup? = nil,
-        cloudService: CloudService
-    ) {
-        self.backup = backup
-        self.cloudService = cloudService
-    }
-}
diff --git a/Sources/Models/CloudService.swift b/Sources/Models/CloudService.swift
deleted file mode 100644
index d217dca853e6ecf22f119520d9805567f7ead3a5..0000000000000000000000000000000000000000
--- a/Sources/Models/CloudService.swift
+++ /dev/null
@@ -1,6 +0,0 @@
-public enum CloudService: Equatable, Codable {
-    case drive
-    case icloud
-    case dropbox
-    case sftp
-}
diff --git a/Sources/Models/FactType.swift b/Sources/Models/FactType.swift
deleted file mode 100644
index 5dce1f05c2a327db9a09852dfc62384e3636546a..0000000000000000000000000000000000000000
--- a/Sources/Models/FactType.swift
+++ /dev/null
@@ -1,34 +0,0 @@
-import Foundation
-
-public enum FactType: Int {
-    case username = 0
-    case email
-    case phone
-    case nickname
-
-    public var description: String {
-        switch self {
-        case .email:
-            return "Email"
-        case .nickname:
-            return "Nickname"
-        case .phone:
-            return "Phone"
-        case .username:
-            return "Username"
-        }
-    }
-
-    public var prefix: String {
-        switch self {
-        case .email:
-            return "E"
-        case .nickname:
-            return "N"
-        case .phone:
-            return "P"
-        case .username:
-            return "U"
-        }
-    }
-}
diff --git a/Sources/Models/Payload.swift b/Sources/Models/Payload.swift
deleted file mode 100644
index 8e6f519b5ca09531b97961d1616a63477325d002..0000000000000000000000000000000000000000
--- a/Sources/Models/Payload.swift
+++ /dev/null
@@ -1,37 +0,0 @@
-import Foundation
-
-public struct Payload: Codable, Equatable, Hashable {
-    public var text: String
-    public var reply: Reply?
-
-    public init(text: String, reply: Reply?) {
-        self.text = text
-        self.reply = reply
-    }
-
-    public init(with marshaled: Data) throws {
-        let proto = try CMIXText(serializedData: marshaled)
-
-        var reply: Reply?
-
-        if proto.hasReply {
-            reply = Reply(
-                messageId: proto.reply.messageID,
-                senderId: proto.reply.senderID
-            )
-        }
-
-        self.init(text: proto.text, reply: reply)
-    }
-
-    public func asData() -> Data {
-        var protoModel = CMIXText()
-        protoModel.text = text
-
-        if let reply = reply {
-            protoModel.reply = reply.asTextReply()
-        }
-
-        return try! protoModel.serializedData()
-    }
-}
diff --git a/Sources/Models/Reply.swift b/Sources/Models/Reply.swift
deleted file mode 100644
index 22fcf55aaa3c0b7f6c93618efe21531b1e4e36e6..0000000000000000000000000000000000000000
--- a/Sources/Models/Reply.swift
+++ /dev/null
@@ -1,19 +0,0 @@
-import Foundation
-
-public struct Reply: Codable, Equatable, Hashable {
-    public let messageId: Data
-    public let senderId: Data
-
-    public init(messageId: Data, senderId: Data) {
-        self.messageId = messageId
-        self.senderId = senderId
-    }
-
-    func asTextReply() -> TextReply {
-        var reply = TextReply()
-        reply.messageID = messageId
-        reply.senderID = senderId
-
-        return reply
-    }
-}
diff --git a/Sources/NetworkMonitor/MockNetworkMonitor.swift b/Sources/NetworkMonitor/MockNetworkMonitor.swift
deleted file mode 100644
index 30bf846df9e36359ed80a7572a966e56091dce4d..0000000000000000000000000000000000000000
--- a/Sources/NetworkMonitor/MockNetworkMonitor.swift
+++ /dev/null
@@ -1,44 +0,0 @@
-import Combine
-import Foundation
-
-public struct MockNetworkMonitor: NetworkMonitoring {
-    private let statusRelay = PassthroughSubject<NetworkStatus, Never>()
-
-    public var connType: AnyPublisher<ConnectionType, Never> {
-        Just(.wifi).eraseToAnyPublisher()
-    }
-
-    public var statusPublisher: AnyPublisher<NetworkStatus, Never> {
-        statusRelay.eraseToAnyPublisher()
-    }
-
-    public var xxStatus: NetworkStatus {
-        .available
-    }
-
-    public init() {
-        // TODO
-    }
-
-    public func start() {
-        simulateOscilation(.available)
-    }
-
-    public func update(_ status: Bool) {
-        // TODO
-    }
-
-    private func simulateOscilation(_ status: NetworkStatus) {
-        statusRelay.send(status)
-
-        if status == .available {
-            DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
-                simulateOscilation(.internetNotAvailable)
-            }
-        } else if status == .internetNotAvailable {
-            DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
-                simulateOscilation(.available)
-            }
-        }
-    }
-}
diff --git a/Sources/NetworkMonitor/NetworkMonitor.swift b/Sources/NetworkMonitor/NetworkMonitor.swift
deleted file mode 100644
index ab805ca524e337bc8961c9dac719ac79d74f72f2..0000000000000000000000000000000000000000
--- a/Sources/NetworkMonitor/NetworkMonitor.swift
+++ /dev/null
@@ -1,90 +0,0 @@
-// https://www.reddit.com/r/swift/comments/ir8wn5/network_connectivity_is_always_unsatisfied_when/
-
-import Network
-import Combine
-import Foundation
-
-public enum NetworkStatus: Equatable {
-    case unknown
-    case available
-    case xxNotAvailable
-    case internetNotAvailable
-}
-
-public enum ConnectionType {
-    case wifi
-    case ethernet
-    case cellular
-    case unknown
-}
-
-public protocol NetworkMonitoring {
-    func start()
-    func update(_ status: Bool)
-
-    var xxStatus: NetworkStatus { get }
-    var connType: AnyPublisher<ConnectionType, Never> { get }
-    var statusPublisher: AnyPublisher<NetworkStatus, Never> { get }
-}
-
-public struct NetworkMonitor: NetworkMonitoring {
-    public init() {}
-
-    private var monitor = NWPathMonitor()
-    private let isXXAvailableRelay = CurrentValueSubject<Bool?, Never>(nil)
-    private let isInternetAvailableRelay = CurrentValueSubject<Bool?, Never>(nil)
-    private let connTypeSubject = PassthroughSubject<ConnectionType, Never>()
-
-    public var xxStatus: NetworkStatus {
-        isXXAvailableRelay.value == true ? .available : .xxNotAvailable
-    }
-
-    public var connType: AnyPublisher<ConnectionType, Never> {
-        connTypeSubject.eraseToAnyPublisher()
-    }
-
-    public var statusPublisher: AnyPublisher<NetworkStatus, Never> {
-        isInternetAvailableRelay.combineLatest(isXXAvailableRelay)
-            .map { (isInternetAvailable, isXXAvailable) -> NetworkStatus in
-
-                guard let isInternetAvailable = isInternetAvailable,
-                      let isXXAvailable = isXXAvailable else { return .unknown }
-
-                switch (isInternetAvailable, isXXAvailable) {
-                case (true, true):
-                    return .available
-                case (true, false):
-                    return .xxNotAvailable
-                case (false, _):
-                    return .internetNotAvailable
-                }
-            }
-            .removeDuplicates()
-            .eraseToAnyPublisher()
-    }
-
-    public func start() {
-        monitor.pathUpdateHandler = { [weak isInternetAvailableRelay, weak connTypeSubject] in
-            connTypeSubject?.send(checkConnectionTypeForPath($0))
-            isInternetAvailableRelay?.send($0.status == .satisfied)
-        }
-
-        monitor.start(queue: .global())
-    }
-
-    public func update(_ status: Bool) {
-        isXXAvailableRelay.send(status)
-    }
-
-    private func checkConnectionTypeForPath(_ path: NWPath) -> ConnectionType {
-        if path.usesInterfaceType(.wifi) {
-            return .wifi
-        } else if path.usesInterfaceType(.wiredEthernet) {
-            return .ethernet
-        } else if path.usesInterfaceType(.cellular) {
-            return .cellular
-        }
-
-        return .unknown
-    }
-}
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingCodeController.swift b/Sources/OnboardingFeature/Controllers/OnboardingCodeController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..92cdda4e7d229e51328343edce602841a14bb529
--- /dev/null
+++ b/Sources/OnboardingFeature/Controllers/OnboardingCodeController.swift
@@ -0,0 +1,184 @@
+import UIKit
+import Shared
+import Combine
+import AppCore
+import AppResources
+import Dependencies
+import AppNavigation
+import DrawerFeature
+import ScrollViewController
+
+public final class OnboardingCodeController: UIViewController {
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+
+  private lazy var screenView = OnboardingCodeView()
+  private lazy var scrollViewController = ScrollViewController()
+
+  private let isEmail: Bool
+  private let content: String
+  private let viewModel: OnboardingCodeViewModel
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
+
+  public init(
+    _ isEmail: Bool,
+    _ content: String,
+    _ confirmationId: String
+  ) {
+    self.viewModel = .init(
+      isEmail: isEmail,
+      content: content,
+      confirmationId: confirmationId
+    )
+    self.isEmail = isEmail
+    self.content = content
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    statusBar.set(.darkContent)
+    navigationController?.navigationBar.customize(translucent: true)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupScrollView()
+    setupBindings()
+
+    screenView.setupSubtitle(
+      isEmail ?
+      Localized.Onboarding.EmailConfirmation.subtitle(content) :
+      Localized.Onboarding.PhoneConfirmation.subtitle(content)
+    )
+
+    screenView.didTapInfo = { [weak self] in
+      guard let self else { return }
+      if self.isEmail {
+        self.presentInfo(
+          title: Localized.Onboarding.EmailConfirmation.Info.title,
+          subtitle: Localized.Onboarding.EmailConfirmation.Info.subtitle
+        )
+      } else {
+        self.presentInfo(
+          title: Localized.Onboarding.PhoneConfirmation.Info.title,
+          subtitle: Localized.Onboarding.PhoneConfirmation.Info.subtitle
+        )
+      }
+    }
+  }
+
+  private func setupScrollView() {
+    addChild(scrollViewController)
+    view.addSubview(scrollViewController.view)
+    scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
+    scrollViewController.didMove(toParent: self)
+    scrollViewController.contentView = screenView
+    scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color
+  }
+
+  private func setupBindings() {
+    screenView
+      .inputField
+      .textPublisher
+      .sink { [unowned self] in
+        viewModel.didInput($0)
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.status)
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.update(status: $0)
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.didConfirm)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        guard $0 == true else { return }
+        if isEmail {
+          navigator.perform(PresentOnboardingPhone(on: navigationController!))
+        } else {
+          navigator.perform(PresentSearch(
+            fromOnboarding: true,
+            on: navigationController!
+          ))
+        }
+      }.store(in: &cancellables)
+
+    screenView
+      .nextButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        viewModel.didTapNext()
+      }.store(in: &cancellables)
+
+    screenView
+      .resendButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        viewModel.didTapResend()
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.resendDebouncer)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.resendButton.isEnabled = $0 == 0
+        if $0 == 0 {
+          screenView.resendButton.setTitle(Localized.Profile.Code.resend(""), for: .normal)
+        } else {
+          screenView.resendButton.setTitle(Localized.Profile.Code.resend("(\($0))"), for: .disabled)
+        }
+      }.store(in: &cancellables)
+  }
+
+  private func presentInfo(title: String, subtitle: String) {
+    let actionButton = CapsuleButton()
+    actionButton.set(
+      style: .seeThrough,
+      title: Localized.Settings.InfoDrawer.action
+    )
+    actionButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) {
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: title,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 19
+      ),
+      DrawerText(
+        font: Fonts.Mulish.regular.font(size: 16.0),
+        text: subtitle,
+        color: Asset.neutralBody.color,
+        alignment: .left,
+        lineHeightMultiple: 1.1,
+        spacingAfter: 37
+      ),
+      DrawerStack(views: [
+        actionButton,
+        FlexibleSpace()
+      ])
+    ], isDismissable: true, from: self))
+  }
+}
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingEmailConfirmationController.swift b/Sources/OnboardingFeature/Controllers/OnboardingEmailConfirmationController.swift
deleted file mode 100644
index 578a27dc5bdedfcd58ab9dbecfd1338c07bad324..0000000000000000000000000000000000000000
--- a/Sources/OnboardingFeature/Controllers/OnboardingEmailConfirmationController.swift
+++ /dev/null
@@ -1,152 +0,0 @@
-import HUD
-import DrawerFeature
-import Theme
-import UIKit
-import Shared
-import Combine
-import DependencyInjection
-import ScrollViewController
-import Models
-
-public final class OnboardingEmailConfirmationController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: OnboardingCoordinating
-    @Dependency private var statusBarController: StatusBarStyleControlling
-
-    lazy private var screenView = OnboardingEmailConfirmationView()
-    lazy private var scrollViewController = ScrollViewController()
-
-    private var cancellables = Set<AnyCancellable>()
-    private let completion: (UIViewController) -> Void
-    private var drawerCancellables = Set<AnyCancellable>()
-    private let viewModel: OnboardingEmailConfirmationViewModel
-
-    public init(
-        _ confirmation: AttributeConfirmation,
-        _ completion: @escaping (UIViewController) -> Void
-    ) {
-        self.completion = completion
-        self.viewModel = OnboardingEmailConfirmationViewModel(confirmation)
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        statusBarController.style.send(.darkContent)
-        navigationController?.navigationBar.customize(translucent: true)
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupScrollView()
-        setupBindings()
-
-        screenView.setupSubtitle(
-            Localized.Onboarding.EmailConfirmation.subtitle(viewModel.confirmation.content)
-        )
-
-        screenView.didTapInfo = { [weak self] in
-            self?.presentInfo(
-                title: Localized.Onboarding.EmailConfirmation.Info.title,
-                subtitle: Localized.Onboarding.EmailConfirmation.Info.subtitle
-            )
-        }
-    }
-
-    private func setupScrollView() {
-        addChild(scrollViewController)
-        view.addSubview(scrollViewController.view)
-        scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
-        scrollViewController.didMove(toParent: self)
-        scrollViewController.contentView = screenView
-        scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color
-    }
-
-    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)
-
-        viewModel.state
-            .map(\.status)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.update(status: $0) }
-            .store(in: &cancellables)
-
-        screenView.nextButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in viewModel.didTapNext() }
-            .store(in: &cancellables)
-
-        viewModel.completionPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] _ in completion(self) }
-            .store(in: &cancellables)
-
-        screenView.resendButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in viewModel.didTapResend() }
-            .store(in: &cancellables)
-
-        viewModel.state
-            .map(\.resendDebouncer)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                screenView.resendButton.isEnabled = $0 == 0
-
-                if $0 == 0 {
-                    screenView.resendButton.setTitle(Localized.Profile.Code.resend(""), for: .normal)
-                } else {
-                    screenView.resendButton.setTitle(Localized.Profile.Code.resend("(\($0))"), for: .disabled)
-                }
-            }.store(in: &cancellables)
-    }
-
-    private func presentInfo(title: String, subtitle: String) {
-        let actionButton = CapsuleButton()
-        actionButton.set(style: .seeThrough, title: Localized.Settings.InfoDrawer.action)
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
-            ),
-            DrawerText(
-                font: Fonts.Mulish.regular.font(size: 16.0),
-                text: subtitle,
-                color: Asset.neutralBody.color,
-                alignment: .left,
-                lineHeightMultiple: 1.1,
-                spacingAfter: 37
-            ),
-            DrawerStack(views: [
-                actionButton,
-                FlexibleSpace()
-            ])
-        ])
-
-        actionButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
-}
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingEmailController.swift b/Sources/OnboardingFeature/Controllers/OnboardingEmailController.swift
index 54fb8cf4efcf4cd29489729598ed22144b918f65..d15c6594393c5cb7942d78abd3173378dc4756b4 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingEmailController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingEmailController.swift
@@ -1,140 +1,147 @@
-import HUD
-import DrawerFeature
-import Theme
 import UIKit
 import Shared
 import Combine
-import DependencyInjection
+import AppCore
+import AppResources
+import Dependencies
+import AppNavigation
+import DrawerFeature
 import ScrollViewController
 
 public final class OnboardingEmailController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: OnboardingCoordinating
-    @Dependency private var statusBarController: StatusBarStyleControlling
-
-    lazy private var screenView = OnboardingEmailView()
-    lazy private var scrollViewController = ScrollViewController()
-
-    private var cancellables = Set<AnyCancellable>()
-    private let viewModel = OnboardingEmailViewModel()
-    private var drawerCancellables = Set<AnyCancellable>()
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        statusBarController.style.send(.darkContent)
-        navigationController?.navigationBar.customize(translucent: true)
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+
+  private lazy var screenView = OnboardingEmailView()
+  private lazy var scrollViewController = ScrollViewController()
+
+  private var cancellables = Set<AnyCancellable>()
+  private let viewModel = OnboardingEmailViewModel()
+  private var drawerCancellables = Set<AnyCancellable>()
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = " "
+    statusBar.set(.darkContent)
+    navigationController?.navigationBar.customize(translucent: true)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupScrollView()
+    setupBindings()
+    screenView.didTapInfo = { [weak self] in
+      guard let self else { return }
+      self.presentInfo(
+        title: Localized.Onboarding.Email.Info.title,
+        subtitle: Localized.Onboarding.Email.Info.subtitle,
+        urlString: "https://links.xx.network/ud"
+      )
     }
+  }
 
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        navigationItem.backButtonTitle = " "
-
-        setupScrollView()
-        setupBindings()
-
-        screenView.didTapInfo = { [weak self] in
-            self?.presentInfo(
-                title: Localized.Onboarding.Email.Info.title,
-                subtitle: Localized.Onboarding.Email.Info.subtitle,
-                urlString: "https://links.xx.network/ud"
-            )
-        }
-    }
-
-    private func setupScrollView() {
-        addChild(scrollViewController)
-        view.addSubview(scrollViewController.view)
-        scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
-        scrollViewController.didMove(toParent: self)
-        scrollViewController.contentView = screenView
-        scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color
+  private func setupScrollView() {
+    addChild(scrollViewController)
+    view.addSubview(scrollViewController.view)
+    scrollViewController.view.snp.makeConstraints {
+      $0.edges.equalToSuperview()
     }
-
-    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)
-
-        screenView.inputField.returnPublisher
-            .sink { [unowned self] in screenView.inputField.endEditing(true) }
-            .store(in: &cancellables)
-
-        viewModel.state
-            .map(\.confirmation)
-            .receive(on: DispatchQueue.main)
-            .compactMap { $0 }
-            .sink { [unowned self] in
-                viewModel.clearUp()
-                coordinator.toEmailConfirmation(with: $0, from: self) { controller in
-                    let successModel = OnboardingSuccessModel(
-                        title: Localized.Onboarding.Success.Email.title,
-                        subtitle: nil,
-                        nextController: coordinator.toPhone(from:)
-                    )
-
-                    coordinator.toSuccess(with: successModel, from: controller)
-                }
-            }.store(in: &cancellables)
-
-        viewModel.state
-            .map(\.status)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.update(status: $0) }
-            .store(in: &cancellables)
-
-        screenView.nextButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewModel.didTapNext() }
-            .store(in: &cancellables)
-
-        screenView.skipButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in coordinator.toPhone(from: self) }
-            .store(in: &cancellables)
-    }
-
-    private func presentInfo(
-        title: String,
-        subtitle: String,
-        urlString: String = ""
-    ) {
-        let actionButton = CapsuleButton()
-        actionButton.set(
-            style: .seeThrough,
-            title: Localized.Settings.InfoDrawer.action
+    scrollViewController.didMove(toParent: self)
+    scrollViewController.contentView = screenView
+    scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color
+  }
+
+  private func setupBindings() {
+    screenView
+      .inputField
+      .textPublisher
+      .sink { [unowned self] in
+        viewModel.didInput($0)
+      }.store(in: &cancellables)
+
+    screenView
+      .inputField
+      .returnPublisher
+      .sink { [unowned self] in
+        screenView.inputField.endEditing(true)
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        guard let id = $0.confirmationId else { return }
+        viewModel.clearUp()
+        navigator.perform(
+          PresentOnboardingCode(
+            isEmail: true,
+            content: $0.input,
+            confirmationId: id,
+            on: navigationController!
+          )
         )
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
-            ),
-            DrawerLinkText(
-                text: subtitle,
-                urlString: urlString,
-                spacingAfter: 37
-            ),
-            DrawerStack(views: [
-                actionButton,
-                FlexibleSpace()
-            ])
-        ])
-
-        actionButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.status)
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.update(status: $0)
+      }.store(in: &cancellables)
+
+    screenView
+      .nextButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        viewModel.didTapNext()
+      }.store(in: &cancellables)
+
+    screenView
+      .skipButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        navigator.perform(PresentOnboardingPhone(on: navigationController!))
+      }.store(in: &cancellables)
+  }
+
+  private func presentInfo(
+    title: String,
+    subtitle: String,
+    urlString: String = ""
+  ) {
+    let actionButton = CapsuleButton()
+    actionButton.set(
+      style: .seeThrough,
+      title: Localized.Settings.InfoDrawer.action
+    )
+    actionButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) {
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: title,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 19
+      ),
+      DrawerLinkText(
+        text: subtitle,
+        urlString: urlString,
+        spacingAfter: 37
+      ),
+      DrawerStack(views: [
+        actionButton,
+        FlexibleSpace()
+      ])
+    ], isDismissable: true, from: self))
+  }
 }
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingPhoneConfirmationController.swift b/Sources/OnboardingFeature/Controllers/OnboardingPhoneConfirmationController.swift
deleted file mode 100644
index 6207a5a7c9b19b1a5184557eb45cd07bd8aeb21b..0000000000000000000000000000000000000000
--- a/Sources/OnboardingFeature/Controllers/OnboardingPhoneConfirmationController.swift
+++ /dev/null
@@ -1,152 +0,0 @@
-import HUD
-import DrawerFeature
-import Theme
-import UIKit
-import Shared
-import Combine
-import DependencyInjection
-import ScrollViewController
-import Models
-
-public final class OnboardingPhoneConfirmationController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: OnboardingCoordinating
-    @Dependency private var statusBarController: StatusBarStyleControlling
-
-    lazy private var screenView = OnboardingPhoneConfirmationView()
-    lazy private var scrollViewController = ScrollViewController()
-
-    private var cancellables = Set<AnyCancellable>()
-    private let completion: (UIViewController) -> Void
-    private var drawerCancellables = Set<AnyCancellable>()
-    private let viewModel: OnboardingPhoneConfirmationViewModel
-
-    public init(
-        _ confirmation: AttributeConfirmation,
-        _ completion: @escaping (UIViewController) -> Void
-    ) {
-        self.completion = completion
-        self.viewModel = OnboardingPhoneConfirmationViewModel(confirmation)
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        statusBarController.style.send(.darkContent)
-        navigationController?.navigationBar.customize(translucent: true)
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupScrollView()
-        setupBindings()
-
-        screenView.setupSubtitle(
-            Localized.Onboarding.PhoneConfirmation.subtitle(viewModel.confirmation.content)
-        )
-
-        screenView.didTapInfo = { [weak self] in
-            self?.presentInfo(
-                title: Localized.Onboarding.PhoneConfirmation.Info.title,
-                subtitle: Localized.Onboarding.PhoneConfirmation.Info.subtitle
-            )
-        }
-    }
-
-    private func setupScrollView() {
-        addChild(scrollViewController)
-        view.addSubview(scrollViewController.view)
-        scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
-        scrollViewController.didMove(toParent: self)
-        scrollViewController.contentView = screenView
-        scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color
-    }
-
-    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)
-
-        viewModel.state
-            .map(\.status)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.update(status: $0) }
-            .store(in: &cancellables)
-
-        screenView.nextButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in viewModel.didTapNext() }
-            .store(in: &cancellables)
-
-        viewModel.completionPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] _ in completion(self) }
-            .store(in: &cancellables)
-
-        screenView.resendButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in viewModel.didTapResend() }
-            .store(in: &cancellables)
-
-        viewModel.state
-            .map(\.resendDebouncer)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                screenView.resendButton.isEnabled = $0 == 0
-
-                if $0 == 0 {
-                    screenView.resendButton.setTitle(Localized.Profile.Code.resend(""), for: .normal)
-                } else {
-                    screenView.resendButton.setTitle(Localized.Profile.Code.resend("(\($0))"), for: .disabled)
-                }
-            }.store(in: &cancellables)
-    }
-
-    private func presentInfo(title: String, subtitle: String) {
-        let actionButton = CapsuleButton()
-        actionButton.set(style: .seeThrough, title: Localized.Settings.InfoDrawer.action)
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
-            ),
-            DrawerText(
-                font: Fonts.Mulish.regular.font(size: 16.0),
-                text: subtitle,
-                color: Asset.neutralBody.color,
-                alignment: .left,
-                lineHeightMultiple: 1.1,
-                spacingAfter: 37
-            ),
-            DrawerStack(views: [
-                actionButton,
-                FlexibleSpace()
-            ])
-        ])
-
-        actionButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
-}
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift b/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift
index 8793508fe6ec8d94e3ee00ff5f3052031c70ee53..4eadd53e32ffa9fa633f2fd61824dab9e871c70b 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingPhoneController.swift
@@ -1,153 +1,172 @@
-import HUD
-import DrawerFeature
-import Theme
 import UIKit
 import Shared
 import Combine
-import DependencyInjection
+import AppCore
+import AppResources
+import Dependencies
+import AppNavigation
+import DrawerFeature
 import ScrollViewController
 
 public final class OnboardingPhoneController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: OnboardingCoordinating
-    @Dependency private var statusBarController: StatusBarStyleControlling
-
-    lazy private var screenView = OnboardingPhoneView()
-    lazy private var scrollViewController = ScrollViewController()
-
-    private var cancellables = Set<AnyCancellable>()
-    private let viewModel = OnboardingPhoneViewModel()
-    private var drawerCancellables = Set<AnyCancellable>()
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        statusBarController.style.send(.darkContent)
-        navigationController?.navigationBar.customize(translucent: true)
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        navigationItem.backButtonTitle = " "
-
-        setupScrollView()
-        setupBindings()
-
-        screenView.didTapInfo = { [weak self] in
-            self?.presentInfo(
-                title: Localized.Onboarding.Phone.Info.title,
-                subtitle: Localized.Onboarding.Phone.Info.subtitle,
-                urlString: "https://links.xx.network/ud"
-            )
-        }
-    }
-
-    private func setupScrollView() {
-        addChild(scrollViewController)
-        view.addSubview(scrollViewController.view)
-        scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
-        scrollViewController.didMove(toParent: self)
-        scrollViewController.contentView = screenView
-        scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+
+  private lazy var screenView = OnboardingPhoneView()
+  private lazy var scrollViewController = ScrollViewController()
+
+  private var cancellables = Set<AnyCancellable>()
+  private let viewModel = OnboardingPhoneViewModel()
+  private var drawerCancellables = Set<AnyCancellable>()
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    statusBar.set(.darkContent)
+    navigationController?.navigationBar.customize(translucent: true)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupScrollView()
+    setupBindings()
+    screenView.didTapInfo = { [weak self] in
+      guard let self else { return }
+      self.presentInfo(
+        title: Localized.Onboarding.Phone.Info.title,
+        subtitle: Localized.Onboarding.Phone.Info.subtitle,
+        urlString: "https://links.xx.network/ud"
+      )
     }
+  }
 
-    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)
-
-        screenView.inputField.returnPublisher
-            .sink { [unowned self] in screenView.inputField.endEditing(true) }
-            .store(in: &cancellables)
-
-        viewModel.state.map(\.status)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.update(status: $0) }
-            .store(in: &cancellables)
-
-        screenView.nextButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewModel.didTapNext() }
-            .store(in: &cancellables)
-
-        screenView.skipButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in coordinator.toChats(from: self) }
-            .store(in: &cancellables)
-
-        screenView.inputField.codePublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                coordinator.toCountries(from: self) { viewModel.didChooseCountry($0) }
-            }.store(in: &cancellables)
-
-        viewModel.state.map(\.confirmation)
-            .receive(on: DispatchQueue.main)
-            .compactMap { $0 }
-            .sink { [unowned self] in
-                viewModel.clearUp()
-                coordinator.toPhoneConfirmation(with: $0, from: self) { controller in
-                    let successModel = OnboardingSuccessModel(
-                        title: Localized.Onboarding.Success.Phone.title,
-                        subtitle: nil,
-                        nextController: coordinator.toChats(from:)
-                    )
-
-                    coordinator.toSuccess(with: successModel, from: controller)
-                }
-            }.store(in: &cancellables)
-
-        viewModel.state.map(\.country)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                screenView.inputField.set(prefix: $0.prefixWithFlag)
-                screenView.inputField.update(placeholder: $0.example)
-            }.store(in: &cancellables)
+  private func setupScrollView() {
+    addChild(scrollViewController)
+    view.addSubview(scrollViewController.view)
+    scrollViewController.view.snp.makeConstraints {
+      $0.edges.equalToSuperview()
     }
-
-    private func presentInfo(
-        title: String,
-        subtitle: String,
-        urlString: String = ""
-    ) {
-        let actionButton = CapsuleButton()
-        actionButton.set(
-            style: .seeThrough,
-            title: Localized.Settings.InfoDrawer.action
+    scrollViewController.didMove(toParent: self)
+    scrollViewController.contentView = screenView
+    scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color
+  }
+
+  private func setupBindings() {
+    screenView
+      .inputField
+      .textPublisher
+      .sink { [unowned self] in
+        viewModel.didInput($0)
+      }.store(in: &cancellables)
+
+    screenView
+      .inputField
+      .returnPublisher
+      .sink { [unowned self] in
+        screenView.inputField.endEditing(true)
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.status)
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.update(status: $0)
+      }.store(in: &cancellables)
+
+    screenView
+      .nextButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        viewModel.didTapNext()
+      }.store(in: &cancellables)
+
+    screenView
+      .skipButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        navigator.perform(PresentSearch(
+          fromOnboarding: true,
+          on: navigationController!
+        ))
+      }.store(in: &cancellables)
+
+    screenView
+      .inputField
+      .codePublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentCountryList(completion: { [weak self] in
+          guard let self else { return }
+          self.navigator.perform(DismissModal(from: self))
+          self.viewModel.didChooseCountry($0 as! Country)
+        }, from: self))
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        guard let id = $0.confirmationId, let content = $0.content else { return }
+        viewModel.clearUp()
+        navigator.perform(
+          PresentOnboardingCode(
+            isEmail: false,
+            content: content,
+            confirmationId: id,
+            on: navigationController!
+          )
         )
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
-            ),
-            DrawerLinkText(
-                text: subtitle,
-                urlString: urlString,
-                spacingAfter: 37
-            ),
-            DrawerStack(views: [
-                actionButton,
-                FlexibleSpace()
-            ])
-        ])
-
-        actionButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.country)
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.inputField.set(prefix: $0.prefixWithFlag)
+        screenView.inputField.update(placeholder: $0.example)
+      }.store(in: &cancellables)
+  }
+
+  private func presentInfo(
+    title: String,
+    subtitle: String,
+    urlString: String = ""
+  ) {
+    let actionButton = CapsuleButton()
+    actionButton.set(
+      style: .seeThrough,
+      title: Localized.Settings.InfoDrawer.action
+    )
+    actionButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) {
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: title,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 19
+      ),
+      DrawerLinkText(
+        text: subtitle,
+        urlString: urlString,
+        spacingAfter: 37
+      ),
+      DrawerStack(views: [
+        actionButton,
+        FlexibleSpace()
+      ])
+    ], isDismissable: true, from: self))
+  }
 }
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift b/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift
index d9b970329040862abe2222493c0ecaf0ee2b34c4..0fd4158db96cda5d8f7cea46388de3b6890a7864 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingStartController.swift
@@ -1,59 +1,51 @@
-import HUD
 import UIKit
-import Theme
-import Shared
 import Combine
-import DependencyInjection
+import AppNavigation
+import ComposableArchitecture
 
 public final class OnboardingStartController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: OnboardingCoordinating
-
-    lazy private var screenView = OnboardingStartView()
-
-    private let ndf: String
-    private var cancellables = Set<AnyCancellable>()
-
-    public override func loadView() {
-        view = screenView
-    }
-
-    public init(_ ndf: String) {
-        self.ndf = ndf
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        navigationController?.navigationBar.customize(translucent: true)
-    }
-
-    public override func viewDidLayoutSubviews() {
-        super.viewDidLayoutSubviews()
-
-        let gradient = CAGradientLayer()
-        gradient.colors = [
-            UIColor(red: 122/255, green: 235/255, blue: 239/255, alpha: 1).cgColor,
-            UIColor(red: 56/255, green: 204/255, blue: 232/255, alpha: 1).cgColor,
-            UIColor(red: 63/255, green: 186/255, blue: 253/255, alpha: 1).cgColor,
-            UIColor(red: 98/255, green: 163/255, blue: 255/255, alpha: 1).cgColor
-        ]
-
-        gradient.startPoint = CGPoint(x: 0, y: 0)
-        gradient.endPoint = CGPoint(x: 1, y: 1)
-
-        gradient.frame = screenView.bounds
-        screenView.layer.insertSublayer(gradient, at: 0)
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-
-        screenView.startButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in coordinator.toTerms(ndf: ndf, from: self) }
-            .store(in: &cancellables)
-    }
+  @Dependency(\.navigator) var navigator: Navigator
+
+  private lazy var screenView = OnboardingStartView()
+
+  private var cancellables = Set<AnyCancellable>()
+
+  public override func loadView() {
+    view = screenView
+  }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    navigationController?.navigationBar.customize(translucent: true)
+  }
+
+  public override func viewDidLayoutSubviews() {
+    super.viewDidLayoutSubviews()
+
+    let gradient = CAGradientLayer()
+    gradient.colors = [
+      UIColor(red: 122/255, green: 235/255, blue: 239/255, alpha: 1).cgColor,
+      UIColor(red: 56/255, green: 204/255, blue: 232/255, alpha: 1).cgColor,
+      UIColor(red: 63/255, green: 186/255, blue: 253/255, alpha: 1).cgColor,
+      UIColor(red: 98/255, green: 163/255, blue: 255/255, alpha: 1).cgColor
+    ]
+
+    gradient.startPoint = CGPoint(x: 0, y: 0)
+    gradient.endPoint = CGPoint(x: 1, y: 1)
+
+    gradient.frame = screenView.bounds
+    screenView.layer.insertSublayer(gradient, at: 0)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+
+    screenView
+      .startButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        navigator.perform(PresentTermsAndConditions(replacing: false, on: navigationController!))
+      }.store(in: &cancellables)
+  }
 }
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingSuccessController.swift b/Sources/OnboardingFeature/Controllers/OnboardingSuccessController.swift
deleted file mode 100644
index a260afa9a7c69369f0bf9a9510c851ebe2e20313..0000000000000000000000000000000000000000
--- a/Sources/OnboardingFeature/Controllers/OnboardingSuccessController.swift
+++ /dev/null
@@ -1,68 +0,0 @@
-import UIKit
-import Theme
-import Models
-import Shared
-import Combine
-import Countries
-import DependencyInjection
-
-public struct OnboardingSuccessModel {
-    var title: String
-    var subtitle: String?
-    var nextController: (UIViewController) -> Void
-}
-
-public final class OnboardingSuccessController: UIViewController {
-    @Dependency private var coordinator: OnboardingCoordinating
-
-    lazy private var screenView = OnboardingSuccessView()
-    private var cancellables = Set<AnyCancellable>()
-
-    private var model: OnboardingSuccessModel
-
-    public override func loadView() {
-        view = screenView
-    }
-
-    public init(_ model: OnboardingSuccessModel) {
-        self.model = model
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationController?.navigationBar.customize(translucent: true)
-    }
-
-    public override func viewDidLayoutSubviews() {
-        super.viewDidLayoutSubviews()
-
-        let gradient = CAGradientLayer()
-        gradient.colors = [
-            UIColor(red: 122/255, green: 235/255, blue: 239/255, alpha: 1).cgColor,
-            UIColor(red: 56/255, green: 204/255, blue: 232/255, alpha: 1).cgColor,
-            UIColor(red: 63/255, green: 186/255, blue: 253/255, alpha: 1).cgColor,
-            UIColor(red: 98/255, green: 163/255, blue: 255/255, alpha: 1).cgColor
-        ]
-
-        gradient.startPoint = CGPoint(x: 0, y: 0)
-        gradient.endPoint = CGPoint(x: 1, y: 1)
-
-        gradient.frame = screenView.bounds
-        screenView.layer.insertSublayer(gradient, at: 0)
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-
-        screenView.setTitle(model.title)
-        screenView.setSubtitle(model.subtitle)
-
-        screenView.nextButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in model.nextController(self) }
-            .store(in: &cancellables)
-    }
-}
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift b/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift
index d42e1f81f055d800cb2fe86afb5ac0870c6ca39a..619a983b7149929bf7ca1232aff937bcaf34baa8 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingUsernameController.swift
@@ -1,147 +1,147 @@
-import HUD
-import Theme
 import UIKit
 import Shared
 import Combine
+import AppCore
+import AppResources
+import Dependencies
+import AppNavigation
 import DrawerFeature
-import DependencyInjection
 import ScrollViewController
 
 public final class OnboardingUsernameController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: OnboardingCoordinating
-    @Dependency private var statusBarController: StatusBarStyleControlling
-
-    lazy private var screenView = OnboardingUsernameView()
-    lazy private var scrollViewController = ScrollViewController()
-
-    private let ndf: String
-    private var cancellables = Set<AnyCancellable>()
-    private let viewModel: OnboardingUsernameViewModel!
-    private var drawerCancellables = Set<AnyCancellable>()
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        statusBarController.style.send(.darkContent)
-        navigationController?.navigationBar.customize(translucent: true)
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+
+  private lazy var screenView = OnboardingUsernameView()
+  private lazy var scrollViewController = ScrollViewController()
+
+  private var cancellables = Set<AnyCancellable>()
+  private let viewModel = OnboardingUsernameViewModel()
+  private var drawerCancellables = Set<AnyCancellable>()
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    statusBar.set(.darkContent)
+    navigationController?.navigationBar.customize(translucent: true)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupScrollView()
+    setupBindings()
+    screenView.didTapInfo = { [weak self] in
+      guard let self else { return }
+      self.presentInfo(
+        title: Localized.Onboarding.Username.Info.title,
+        subtitle: Localized.Onboarding.Username.Info.subtitle,
+        urlString: "https://links.xx.network/ud"
+      )
     }
-
-    public init(_ ndf: String) {
-        self.ndf = ndf
-        self.viewModel = OnboardingUsernameViewModel(ndf: ndf)
-        super.init(nibName: nil, bundle: nil)
+  }
+
+  private func setupScrollView() {
+    scrollViewController.scrollView.backgroundColor = .white
+    addChild(scrollViewController)
+    view.addSubview(scrollViewController.view)
+    scrollViewController.view.snp.makeConstraints {
+      $0.edges.equalToSuperview()
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupScrollView()
-        setupBindings()
-
-        screenView.didTapInfo = { [weak self] in
-            self?.presentInfo(
-                title: Localized.Onboarding.Username.Info.title,
-                subtitle: Localized.Onboarding.Username.Info.subtitle,
-                urlString: "https://links.xx.network/ud"
-            )
+    scrollViewController.didMove(toParent: self)
+    scrollViewController.contentView = screenView
+  }
+
+  private func setupBindings() {
+    screenView
+      .inputField
+      .textPublisher
+      .removeDuplicates()
+      .compactMap { $0 }
+      .sink { [unowned self] in
+        viewModel.didInput($0)
+      }.store(in: &cancellables)
+
+    screenView
+      .restoreView
+      .restoreButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentRestoreList(on: navigationController!))
+      }.store(in: &cancellables)
+
+    screenView
+      .inputField
+      .returnPublisher
+      .sink { [unowned self] in
+        if screenView.nextButton.isEnabled {
+          viewModel.didTapRegister()
+        } else {
+          screenView.inputField.endEditing(true)
         }
-    }
-
-    private func setupScrollView() {
-        scrollViewController.scrollView.backgroundColor = .white
-
-        addChild(scrollViewController)
-        view.addSubview(scrollViewController.view)
-        scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
-        scrollViewController.didMove(toParent: self)
-        scrollViewController.contentView = screenView
-    }
-
-    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 }
-            .sink { [unowned self] in viewModel.didInput($0) }
-            .store(in: &cancellables)
-
-        screenView.restoreView.restoreButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toRestoreList(with: ndf, from: self) }
-            .store(in: &cancellables)
-
-        screenView.inputField.returnPublisher
-            .sink { [unowned self] in
-                if screenView.nextButton.isEnabled {
-                    viewModel.didTapRegister()
-                } else {
-                    screenView.inputField.endEditing(true)
-                }
-            }.store(in: &cancellables)
-
-        screenView.nextButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewModel.didTapRegister() }
-            .store(in: &cancellables)
-
-        viewModel.greenPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toWelcome(from: self) }
-            .store(in: &cancellables)
-
-        viewModel.state
-            .map(\.status)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.update(status: $0) }
-            .store(in: &cancellables)
-    }
-
-    private func presentInfo(
-        title: String,
-        subtitle: String,
-        urlString: String = ""
-    ) {
-        let actionButton = CapsuleButton()
-        actionButton.set(
-            style: .seeThrough,
-            title: Localized.Settings.InfoDrawer.action
-        )
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
-            ),
-            DrawerLinkText(
-                text: subtitle,
-                urlString: urlString,
-                spacingAfter: 37
-            ),
-            DrawerStack(views: [
-                actionButton,
-                FlexibleSpace()
-            ])
-        ])
-
-        actionButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
+      }.store(in: &cancellables)
+
+    screenView
+      .nextButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        viewModel.didTapRegister()
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        guard $0.didConfirm == true else { return }
+        navigator.perform(PresentOnboardingWelcome(on: navigationController!))
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.status)
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.update(status: $0)
+      }.store(in: &cancellables)
+  }
+
+  private func presentInfo(
+    title: String,
+    subtitle: String,
+    urlString: String = ""
+  ) {
+    let actionButton = CapsuleButton()
+    actionButton.set(
+      style: .seeThrough,
+      title: Localized.Settings.InfoDrawer.action
+    )
+    actionButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) {
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: title,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 19
+      ),
+      DrawerLinkText(
+        text: subtitle,
+        urlString: urlString,
+        spacingAfter: 37
+      ),
+      DrawerStack(views: [
+        actionButton,
+        FlexibleSpace()
+      ])
+    ], isDismissable: true, from: self))
+  }
 }
diff --git a/Sources/OnboardingFeature/Controllers/OnboardingWelcomeController.swift b/Sources/OnboardingFeature/Controllers/OnboardingWelcomeController.swift
index 33455d6627930f945bbadc2ddb19d53a047d5dc8..701e1359cea59cfd2912d099b7d6fe897aa2de64 100644
--- a/Sources/OnboardingFeature/Controllers/OnboardingWelcomeController.swift
+++ b/Sources/OnboardingFeature/Controllers/OnboardingWelcomeController.swift
@@ -1,95 +1,102 @@
-import DrawerFeature
-import Theme
 import UIKit
 import Shared
 import Combine
 import Defaults
-import DependencyInjection
+import AppCore
+import Dependencies
+import AppResources
+import AppNavigation
+import DrawerFeature
 
 public final class OnboardingWelcomeController: UIViewController {
-    @KeyObject(.username, defaultValue: "") var username: String
-    @Dependency private var coordinator: OnboardingCoordinating
-    @Dependency private var statusBarController: StatusBarStyleControlling
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
 
-    lazy private var screenView = OnboardingWelcomeView()
+  @KeyObject(.username, defaultValue: "") var username: String
 
-    private var cancellables = Set<AnyCancellable>()
-    private var drawerCancellables = Set<AnyCancellable>()
+  private lazy var screenView = OnboardingWelcomeView()
 
-    public override func loadView() {
-        view = screenView
-    }
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
 
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        statusBarController.style.send(.darkContent)
-        navigationController?.navigationBar.customize(translucent: true)
-    }
+  public override func loadView() {
+    view = screenView
+  }
 
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupBindings()
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    statusBar.set(.darkContent)
+    navigationController?.navigationBar.customize(translucent: true)
+  }
 
-        screenView.setupTitle(Localized.Onboarding.Welcome.title(username))
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    screenView.setupTitle(
+      Localized.Onboarding.Welcome.title(username)
+    )
+    screenView
+      .continueButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        navigator.perform(PresentOnboardingEmail(on: navigationController!))
+      }.store(in: &cancellables)
 
-        screenView.didTapInfo = { [weak self] in
-            self?.presentInfo(
-                title: Localized.Onboarding.Welcome.Info.title,
-                subtitle: Localized.Onboarding.Welcome.Info.subtitle,
-                urlString: "https://links.xx.network/ud"
-            )
-        }
-    }
+    screenView
+      .skipButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        navigator.perform(PresentSearch(
+          fromOnboarding: true,
+          on: navigationController!
+        ))
+      }.store(in: &cancellables)
 
-    private func setupBindings() {
-        screenView.continueButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in coordinator.toEmail(from: self) }
-            .store(in: &cancellables)
-
-        screenView.skipButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in coordinator.toChats(from: self) }
-            .store(in: &cancellables)
+    screenView.didTapInfo = { [weak self] in
+      guard let self else { return }
+      self.presentInfo(
+        title: Localized.Onboarding.Welcome.Info.title,
+        subtitle: Localized.Onboarding.Welcome.Info.subtitle,
+        urlString: "https://links.xx.network/ud"
+      )
     }
+  }
 
-    private func presentInfo(
-        title: String,
-        subtitle: String,
-        urlString: String = ""
-    ) {
-        let actionButton = CapsuleButton()
-        actionButton.set(
-            style: .seeThrough,
-            title: Localized.Settings.InfoDrawer.action
-        )
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
-            ),
-            DrawerLinkText(
-                text: subtitle,
-                urlString: urlString,
-                spacingAfter: 37
-            ),
-            DrawerStack(views: [
-                actionButton,
-                FlexibleSpace()
-            ])
-        ])
-
-        actionButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
+  private func presentInfo(
+    title: String,
+    subtitle: String,
+    urlString: String = ""
+  ) {
+    let actionButton = CapsuleButton()
+    actionButton.set(
+      style: .seeThrough,
+      title: Localized.Settings.InfoDrawer.action
+    )
+    actionButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) {
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
 
-        coordinator.toDrawer(drawer, from: self)
-    }
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: title,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 19
+      ),
+      DrawerLinkText(
+        text: subtitle,
+        urlString: urlString,
+        spacingAfter: 37
+      ),
+      DrawerStack(views: [
+        actionButton,
+        FlexibleSpace()
+      ])
+    ], isDismissable: true, from: self))
+  }
 }
diff --git a/Sources/OnboardingFeature/Coordinator/OnboardingCoordinator.swift b/Sources/OnboardingFeature/Coordinator/OnboardingCoordinator.swift
deleted file mode 100644
index 9ea57da2c51a02679f40c9132b88498fb139fa68..0000000000000000000000000000000000000000
--- a/Sources/OnboardingFeature/Coordinator/OnboardingCoordinator.swift
+++ /dev/null
@@ -1,156 +0,0 @@
-import UIKit
-import Shared
-import Models
-import Countries
-import Presentation
-
-public typealias AttributeControllerClosure = (UIViewController) -> Void
-
-public protocol OnboardingCoordinating {
-    func toChats(from: UIViewController)
-    func toEmail(from: UIViewController)
-    func toPhone(from: UIViewController)
-    func toWelcome(from: UIViewController)
-    func toTerms(ndf: String, from: UIViewController)
-    func toUsername(with: String, from: UIViewController)
-    func toRestoreList(with: String, from: UIViewController)
-    func toDrawer(_: UIViewController, from: UIViewController)
-    func toSuccess(with: OnboardingSuccessModel, from: UIViewController)
-
-    func toEmailConfirmation(
-        with: AttributeConfirmation,
-        from: UIViewController,
-        completion: @escaping (UIViewController) -> Void
-    )
-
-    func toPhoneConfirmation(
-        with: AttributeConfirmation,
-        from: UIViewController,
-        completion: @escaping (UIViewController) -> Void
-    )
-
-    func toCountries(
-        from: UIViewController,
-        _ onChoose: @escaping (Country) -> Void
-    )
-}
-
-public struct OnboardingCoordinator: OnboardingCoordinating {
-    var pushPresenter: Presenting = PushPresenter()
-    var bottomPresenter: Presenting = BottomPresenter()
-    var replacePresenter: Presenting = ReplacePresenter()
-
-    var emailFactory: () -> UIViewController
-    var phoneFactory: () -> UIViewController
-    var searchFactory: (String?) -> UIViewController
-    var welcomeFactory: () -> UIViewController
-    var chatListFactory: () -> UIViewController
-    var usernameFactory: (String) -> UIViewController
-    var restoreListFactory: (String) -> UIViewController
-    var termsFactory: (String?) -> UIViewController
-    var successFactory: (OnboardingSuccessModel) -> UIViewController
-    var countriesFactory: (@escaping (Country) -> Void) -> UIViewController
-    var phoneConfirmationFactory: (AttributeConfirmation, @escaping AttributeControllerClosure) -> UIViewController
-    var emailConfirmationFactory: (AttributeConfirmation, @escaping AttributeControllerClosure) -> UIViewController
-
-    public init(
-        emailFactory: @escaping () -> UIViewController,
-        phoneFactory: @escaping () -> UIViewController,
-        searchFactory: @escaping (String?) -> UIViewController,
-        welcomeFactory: @escaping () -> UIViewController,
-        chatListFactory: @escaping () -> UIViewController,
-        termsFactory: @escaping (String?) -> UIViewController,
-        usernameFactory: @escaping (String) -> UIViewController,
-        restoreListFactory: @escaping (String) -> UIViewController,
-        successFactory: @escaping (OnboardingSuccessModel) -> UIViewController,
-        countriesFactory: @escaping (@escaping (Country) -> Void) -> UIViewController,
-        phoneConfirmationFactory: @escaping (AttributeConfirmation, @escaping AttributeControllerClosure) -> UIViewController,
-        emailConfirmationFactory: @escaping (AttributeConfirmation, @escaping AttributeControllerClosure) -> UIViewController
-    ) {
-        self.emailFactory = emailFactory
-        self.termsFactory = termsFactory
-        self.phoneFactory = phoneFactory
-        self.searchFactory = searchFactory
-        self.welcomeFactory = welcomeFactory
-        self.successFactory = successFactory
-        self.usernameFactory = usernameFactory
-        self.chatListFactory = chatListFactory
-        self.countriesFactory = countriesFactory
-        self.restoreListFactory = restoreListFactory
-        self.phoneConfirmationFactory = phoneConfirmationFactory
-        self.emailConfirmationFactory = emailConfirmationFactory
-    }
-}
-
-public extension OnboardingCoordinator {
-    func toTerms(
-        ndf: String,
-        from parent: UIViewController
-    ) {
-        let screen = termsFactory(ndf)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toEmail(from parent: UIViewController) {
-        let screen = emailFactory()
-        replacePresenter.present(screen, from: parent)
-    }
-
-    func toPhone(from parent: UIViewController) {
-        let screen = phoneFactory()
-        replacePresenter.present(screen, from: parent)
-    }
-
-    func toWelcome(from parent: UIViewController) {
-        let screen = welcomeFactory()
-        replacePresenter.present(screen, from: parent)
-    }
-
-    func toRestoreList(with ndf: String, from parent: UIViewController) {
-        let screen = restoreListFactory(ndf)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toSuccess(with model: OnboardingSuccessModel, from parent: UIViewController) {
-        let screen = successFactory(model)
-        replacePresenter.present(screen, from: parent)
-    }
-
-    func toUsername(with ndf: String, from parent: UIViewController) {
-        let screen = usernameFactory(ndf)
-        replacePresenter.present(screen, from: parent)
-    }
-
-    func toDrawer(_ drawer: UIViewController, from parent: UIViewController) {
-        bottomPresenter.present(drawer, from: parent)
-    }
-
-    func toChats(from parent: UIViewController) {
-        let searchScreen = searchFactory(nil)
-        let chatListScreen = chatListFactory()
-        replacePresenter.present(chatListScreen, searchScreen, from: parent)
-    }
-
-    func toCountries(from parent: UIViewController, _ onChoose: @escaping (Country) -> Void) {
-        let screen = countriesFactory(onChoose)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toEmailConfirmation(
-        with confirmation: AttributeConfirmation,
-        from parent: UIViewController,
-        completion: @escaping (UIViewController) -> Void
-    ) {
-        let screen = emailConfirmationFactory(confirmation, completion)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toPhoneConfirmation(
-        with confirmation: AttributeConfirmation,
-        from parent: UIViewController,
-        completion: @escaping (UIViewController) -> Void
-    ) {
-        let screen = phoneConfirmationFactory(confirmation, completion)
-        pushPresenter.present(screen, from: parent)
-    }
-}
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingCodeViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingCodeViewModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..994b83b211a75eeb4bafb4e95ec683e263325881
--- /dev/null
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingCodeViewModel.swift
@@ -0,0 +1,98 @@
+import AppCore
+import Shared
+import Combine
+import Defaults
+import XXClient
+import InputField
+import Foundation
+import CombineSchedulers
+import XXMessengerClient
+import ComposableArchitecture
+
+final class OnboardingCodeViewModel {
+  struct ViewState: Equatable {
+    var input: String = ""
+    var status: InputField.ValidationStatus = .unknown(nil)
+    var resendDebouncer: Int = 0
+    var didConfirm: Bool = false
+  }
+
+  var statePublisher: AnyPublisher<ViewState, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
+
+  @Dependency(\.app.messenger) var messenger: Messenger
+  @Dependency(\.app.hudManager) var hudManager: HUDManager
+  @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue>
+
+  @KeyObject(.email, defaultValue: nil) var email: String?
+  @KeyObject(.phone, defaultValue: nil) var phone: String?
+
+  private var timer: Timer?
+  private let isEmail: Bool
+  private let content: String
+  private let confirmationId: String
+  private let stateSubject = CurrentValueSubject<ViewState, Never>(.init())
+
+  init(
+    isEmail: Bool,
+    content: String,
+    confirmationId: String
+  ) {
+    self.isEmail = isEmail
+    self.content = content
+    self.confirmationId = confirmationId
+    didTapResend()
+  }
+
+  func didInput(_ string: String) {
+    stateSubject.value.input = string
+    validate()
+  }
+
+  func didTapResend() {
+    guard stateSubject.value.resendDebouncer == 0 else { return }
+    stateSubject.value.resendDebouncer = 60
+    timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {  [weak self] in
+      guard let self, self.stateSubject.value.resendDebouncer > 0 else {
+        $0.invalidate()
+        return
+      }
+      self.stateSubject.value.resendDebouncer -= 1
+    }
+  }
+
+  func didTapNext() {
+    hudManager.show()
+    bgQueue.schedule { [weak self] in
+      guard let self else { return }
+      do {
+        try self.messenger.ud.get()!.confirmFact(
+          confirmationId: self.confirmationId,
+          code: self.stateSubject.value.input
+        )
+        if self.isEmail {
+          self.email = self.content
+        } else {
+          self.phone = self.content
+        }
+        self.timer?.invalidate()
+        self.hudManager.hide()
+        self.stateSubject.value.didConfirm = true
+      } catch {
+        self.hudManager.hide()
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.stateSubject.value.status = .invalid(xxError)
+      }
+    }
+  }
+
+  private func validate() {
+    switch Validator.code.validate(stateSubject.value.input) {
+    case .success:
+      stateSubject.value.status = .valid(nil)
+    case .failure(let error):
+      stateSubject.value.status = .invalid(error)
+    }
+  }
+}
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift
deleted file mode 100644
index b3871d6234517f1d493ab61da1466bc754446fd7..0000000000000000000000000000000000000000
--- a/Sources/OnboardingFeature/ViewModels/OnboardingEmailConfirmationViewModel.swift
+++ /dev/null
@@ -1,89 +0,0 @@
-import HUD
-import UIKit
-import Models
-import Shared
-import Combine
-import Defaults
-import InputField
-import Integration
-import CombineSchedulers
-import DependencyInjection
-
-struct OnboardingEmailConfirmationViewState: Equatable {
-    var input: String = ""
-    var status: InputField.ValidationStatus = .unknown(nil)
-    var resendDebouncer: Int = 0
-}
-
-final class OnboardingEmailConfirmationViewModel {
-    @Dependency private var session: SessionType
-
-    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
-        }
-    }
-
-    func didTapNext() {
-        hudRelay.send(.on)
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.session.confirm(
-                    code: self.stateRelay.value.input,
-                    confirmation: self.confirmation
-                )
-
-                self.timer?.invalidate()
-                self.hudRelay.send(.none)
-                self.completionRelay.send(self.confirmation)
-            } catch {
-                self.hudRelay.send(.error(.init(with: 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 c3cbbb897840964e15f908b9fa83d5d6537f7b23..a175a674fa981f7d48cf2409c8d84041f60da5dc 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingEmailViewModel.swift
@@ -1,69 +1,63 @@
-import HUD
-import UIKit
-import Models
+import AppCore
 import Shared
 import Combine
-import Defaults
+import XXClient
 import InputField
-import Integration
+import Foundation
 import CombineSchedulers
-import DependencyInjection
+import XXMessengerClient
+import ComposableArchitecture
 
-struct OnboardingEmailViewState: Equatable {
+final class OnboardingEmailViewModel {
+  struct ViewState: Equatable {
     var input: String = ""
-    var confirmation: AttributeConfirmation? = nil
+    var confirmationId: String?
     var status: InputField.ValidationStatus = .unknown(nil)
-}
-
-final class OnboardingEmailViewModel {
-    @KeyObject(.pushNotifications, defaultValue: false) private var pushNotifications
-
-    @Dependency private var session: SessionType
-
-    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(\.app.messenger) var messenger: Messenger
+  @Dependency(\.app.hudManager) var hudManager: HUDManager
+  @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue>
+
+  var statePublisher: AnyPublisher<ViewState, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
+
+  private let stateSubject = CurrentValueSubject<ViewState, Never>(.init())
+
+  func clearUp() {
+    stateSubject.value.confirmationId = nil
+  }
+
+  func didInput(_ string: String) {
+    stateSubject.value.input = string
+    validate()
+  }
+
+  func didTapNext() {
+    hudManager.show()
+    bgQueue.schedule { [weak self] in
+      guard let self else { return }
+      do {
+        let confirmationId = try self.messenger.ud.get()!.sendRegisterFact(
+          .init(type: .email, value: self.stateSubject.value.input)
+        )
+        self.hudManager.hide()
+        self.stateSubject.value.confirmationId = confirmationId
+      } catch {
+        self.hudManager.hide()
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.stateSubject.value.status = .invalid(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 }
-
-            self.session.register(.email, value: self.stateRelay.value.input) { [weak self] in
-                guard let self = self else { return }
-
-                switch $0 {
-                case .success(let confirmationId):
-                    self.hudRelay.send(.none)
-                    self.stateRelay.value.confirmation =
-                        .init(content: self.stateRelay.value.input, isEmail: true, confirmationId: confirmationId)
-                case .failure(let error):
-                    self.hudRelay.send(.error(.init(with: 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)
-        }
+  }
+
+  private func validate() {
+    switch Validator.email.validate(stateSubject.value.input) {
+    case .success:
+      stateSubject.value.status = .valid(nil)
+    case .failure(let error):
+      stateSubject.value.status = .invalid(error)
     }
+  }
 }
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift
deleted file mode 100644
index 2bd5a7ae35fbca7bf871479f998abeef9e4ce625..0000000000000000000000000000000000000000
--- a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneConfirmationViewModel.swift
+++ /dev/null
@@ -1,89 +0,0 @@
-import HUD
-import UIKit
-import Models
-import Shared
-import Combine
-import Defaults
-import InputField
-import Integration
-import CombineSchedulers
-import DependencyInjection
-
-struct OnboardingPhoneConfirmationViewState: Equatable {
-    var input: String = ""
-    var status: InputField.ValidationStatus = .unknown(nil)
-    var resendDebouncer: Int = 0
-}
-
-final class OnboardingPhoneConfirmationViewModel {
-    @Dependency private var session: SessionType
-
-    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()
-    }
-
-    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.session.confirm(
-                    code: self.stateRelay.value.input,
-                    confirmation: self.confirmation
-                )
-
-                self.timer?.invalidate()
-                self.hudRelay.send(.none)
-                self.completionRelay.send(self.confirmation)
-            } catch {
-                self.hudRelay.send(.error(.init(with: 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 0aff02f402420b959d03f166ba4eb456d1098bea..39d9f282be5757d19bc7ef47ff450dcfd2d1f48e 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingPhoneViewModel.swift
@@ -1,81 +1,73 @@
-import HUD
+import AppCore
 import Shared
-import Models
 import Combine
-import Countries
+import XXClient
 import InputField
-import Integration
+import Foundation
 import CombineSchedulers
-import DependencyInjection
+import XXMessengerClient
+import CountryListFeature
+import ComposableArchitecture
 
-struct OnboardingPhoneViewState: Equatable {
+final class OnboardingPhoneViewModel {
+  struct ViewState: Equatable {
     var input: String = ""
-    var confirmation: AttributeConfirmation?
+    var content: String?
+    var confirmationId: String?
     var status: InputField.ValidationStatus = .unknown(nil)
     var country: Country = .fromMyPhone()
-}
-
-final class OnboardingPhoneViewModel {
-    @Dependency private var session: SessionType
+  }
 
-    var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
+  @Dependency(\.app.messenger) var messenger: Messenger
+  @Dependency(\.app.hudManager) var hudManager: HUDManager
+  @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue>
 
-    var state: AnyPublisher<OnboardingPhoneViewState, Never> { stateRelay.eraseToAnyPublisher() }
-    private let stateRelay = CurrentValueSubject<OnboardingPhoneViewState, Never>(.init())
+  var statePublisher: AnyPublisher<ViewState, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
 
-    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
-    }
+  private let stateSubject = CurrentValueSubject<ViewState, Never>(.init())
 
-    func didTapNext() {
-        hudRelay.send(.on)
+  func clearUp() {
+    stateSubject.value.confirmationId = nil
+  }
 
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
+  func didInput(_ string: String) {
+    stateSubject.value.input = string
+    validate()
+  }
 
-            let content = "\(self.stateRelay.value.input)\(self.stateRelay.value.country.code)"
-            self.session.register(.phone, value: content) { [weak self] in
-                guard let self = self else { return }
+  func didChooseCountry(_ country: Country) {
+    stateSubject.value.country = country
+    validate()
+  }
 
-                switch $0 {
-                case .success(let confirmationId):
-                    self.hudRelay.send(.none)
-                    self.stateRelay.value.confirmation = .init(
-                        content: content,
-                        confirmationId: confirmationId
-                    )
-                case .failure(let error):
-                    self.hudRelay.send(.error(.init(with: error)))
-                }
-            }
-        }
+  func didTapNext() {
+    hudManager.show()
+    bgQueue.schedule { [weak self] in
+      guard let self else { return }
+      let content = "\(self.stateSubject.value.input)\(self.stateSubject.value.country.code)"
+      do {
+        let confirmationId = try self.messenger.ud.get()!.sendRegisterFact(
+          .init(type: .phone, value: content)
+        )
+        self.hudManager.hide()
+        self.stateSubject.value.content = content
+        self.stateSubject.value.confirmationId = confirmationId
+      } catch {
+        self.hudManager.hide()
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.stateSubject.value.status = .invalid(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((stateSubject.value.country.regex, stateSubject.value.input)) {
+    case .success:
+      stateSubject.value.status = .valid(nil)
+    case .failure(let error):
+      stateSubject.value.status = .invalid(error)
     }
+  }
 }
diff --git a/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift b/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift
index ecb64035e73e259bf147ce4ee7f427723d40b3f3..917e9fea3bcd5f21227f5a285ceafe620b271e9f 100644
--- a/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift
+++ b/Sources/OnboardingFeature/ViewModels/OnboardingUsernameViewModel.swift
@@ -1,78 +1,74 @@
-import HUD
+import AppCore
 import Shared
 import Combine
+import Defaults
+import XXModels
+import XXClient
 import InputField
-import Integration
-import CombineSchedulers
-import DependencyInjection
+import Foundation
+import XXMessengerClient
+import ComposableArchitecture
 
-struct OnboardingUsernameViewState: Equatable {
+final class OnboardingUsernameViewModel {
+  struct ViewState: Equatable {
     var input: String = ""
     var status: InputField.ValidationStatus = .unknown(nil)
-}
-
-final class OnboardingUsernameViewModel {
-
-    let ndf: String
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
-
-    var greenPublisher: AnyPublisher<Void, Never> { greenRelay.eraseToAnyPublisher() }
-    private let greenRelay = PassthroughSubject<Void, Never>()
+    var didConfirm: Bool = false
+  }
 
-    var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
+  @Dependency(\.app.dbManager) var dbManager: DBManager
+  @Dependency(\.app.messenger) var messenger: Messenger
+  @Dependency(\.app.hudManager) var hudManager: HUDManager
+  @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue>
 
-    var state: AnyPublisher<OnboardingUsernameViewState, Never> { stateRelay.eraseToAnyPublisher() }
-    private let stateRelay = CurrentValueSubject<OnboardingUsernameViewState, Never>(.init())
+  @KeyObject(.username, defaultValue: "") var username: String
 
-    init(ndf: String) {
-        self.ndf = ndf
-    }
+  var statePublisher: AnyPublisher<ViewState, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
 
-    func didInput(_ string: String) {
-        stateRelay.value.input = string.trimmingCharacters(in: .whitespacesAndNewlines)
+  private let stateSubject = CurrentValueSubject<ViewState, Never>(.init())
 
-        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 didInput(_ string: String) {
+    stateSubject.value.input = string.trimmingCharacters(in: .whitespacesAndNewlines)
+    switch Validator.username.validate(stateSubject.value.input) {
+    case .success(let text):
+      stateSubject.value.status = .valid(text)
+    case .failure(let error):
+      stateSubject.value.status = .invalid(error)
     }
-
-    func didTapRegister() {
-        hudRelay.send(.on)
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                var session: SessionType!
-
-                if let injectedSession = try? DependencyInjection.Container.shared.resolve() as SessionType {
-                    session = injectedSession
-                } else {
-                    session = try Session(ndf: self.ndf)
-                    DependencyInjection.Container.shared.register(session as SessionType)
-                }
-
-                session.register(.username, value: self.stateRelay.value.input) { [weak self] in
-                    guard let self = self else { return }
-
-                    switch $0 {
-                    case .success(_):
-                        self.hudRelay.send(.none)
-                        self.greenRelay.send()
-                    case .failure(let error):
-                        self.hudRelay.send(.none)
-                        self.stateRelay.value.status = .invalid(error.localizedDescription)
-                    }
-                }
-            } catch {
-                self.hudRelay.send(.none)
-                self.stateRelay.value.status = .invalid(error.localizedDescription)
-            }
-        }
+  }
+
+  func didTapRegister() {
+    hudManager.show()
+    bgQueue.schedule { [weak self] in
+      guard let self else { return }
+      do {
+        try self.messenger.register(
+          username: self.stateSubject.value.input
+        )
+        try self.dbManager.getDB().saveContact(.init(
+          id: self.messenger.e2e.get()!.getContact().getId(),
+          marshaled: self.messenger.e2e.get()!.getContact().data,
+          username: self.stateSubject.value.input,
+          email: nil,
+          phone: nil,
+          nickname: nil,
+          photo: nil,
+          authStatus: .friend,
+          isRecent: false,
+          isBlocked: false,
+          isBanned: false,
+          createdAt: Date()
+        ))
+        self.username = self.stateSubject.value.input
+        self.hudManager.hide()
+        self.stateSubject.value.didConfirm = true
+      } catch {
+        self.hudManager.hide()
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.stateSubject.value.status = .invalid(xxError)
+      }
     }
+  }
 }
diff --git a/Sources/OnboardingFeature/Views/OnboardingCodeView.swift b/Sources/OnboardingFeature/Views/OnboardingCodeView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..63a33f1bdcfe95b33f9335f32f3358bdbe3d354c
--- /dev/null
+++ b/Sources/OnboardingFeature/Views/OnboardingCodeView.swift
@@ -0,0 +1,118 @@
+import UIKit
+import Shared
+import InputField
+import AppResources
+
+final class OnboardingCodeView: UIView {
+  let titleLabel = UILabel()
+  let subtitleView = TextWithInfoView()
+  let inputField = InputField()
+  let nextButton = CapsuleButton()
+  let resendButton = UIButton()
+  let stackView = UIStackView()
+  var didTapInfo: (() -> Void)?
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    setupTitle(Localized.Onboarding.EmailConfirmation.title)
+
+    inputField.setup(
+      placeholder: Localized.Onboarding.EmailConfirmation.input,
+      subtitleColor: Asset.neutralWeak.color,
+      allowsEmptySpace: false,
+      keyboardType: .numberPad,
+      autocapitalization: .none,
+      contentType: .oneTimeCode
+    )
+
+    resendButton.setTitleColor(Asset.brandPrimary.color, for: .normal)
+    resendButton.setTitleColor(Asset.neutralWeak.color, for: .disabled)
+    resendButton.titleLabel?.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    resendButton.setTitle(Localized.Onboarding.EmailConfirmation.resend(""), for: .normal)
+
+    nextButton.set(style: .brandColored, title: Localized.Onboarding.EmailConfirmation.next)
+    nextButton.isEnabled = false
+
+    stackView.spacing = 15
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(nextButton)
+    stackView.addArrangedSubview(resendButton)
+
+    addSubview(titleLabel)
+    addSubview(subtitleView)
+    addSubview(inputField)
+    addSubview(stackView)
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(30)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
+    }
+    subtitleView.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
+    }
+    inputField.snp.makeConstraints {
+      $0.top.equalTo(subtitleView.snp.bottom).offset(24)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-38)
+    }
+    stackView.snp.makeConstraints {
+      $0.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20)
+      $0.left.equalToSuperview().offset(40)
+      $0.right.equalToSuperview().offset(-40)
+      $0.bottom.equalTo(safeAreaLayoutGuide).offset(-50)
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  func update(status: InputField.ValidationStatus) {
+    inputField.update(status: status)
+
+    switch status {
+    case .valid:
+      nextButton.isEnabled = true
+    case .invalid, .unknown:
+      nextButton.isEnabled = false
+    }
+  }
+
+  private func setupTitle(_ title: String) {
+    let attString = NSMutableAttributedString(string: title)
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.0
+
+    attString.addAttribute(.paragraphStyle, value: paragraph)
+    attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
+    attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
+
+    attString.addAttributes(attributes: [
+      .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
+      .foregroundColor: Asset.brandPrimary.color
+    ], betweenCharacters: "#")
+
+    titleLabel.numberOfLines = 0
+    titleLabel.attributedText = attString
+  }
+
+  public func setupSubtitle(_ subtitle: String) {
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.15
+
+    subtitleView.setup(
+      text: subtitle,
+      attributes: [
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+        .paragraphStyle: paragraph
+      ],
+      didTapInfo: { [weak self] in self?.didTapInfo?() }
+    )
+  }
+}
diff --git a/Sources/OnboardingFeature/Views/OnboardingEmailConfirmationView.swift b/Sources/OnboardingFeature/Views/OnboardingEmailConfirmationView.swift
deleted file mode 100644
index 8d41227472b0f0fdb7018f7f0dc02140f8900bdc..0000000000000000000000000000000000000000
--- a/Sources/OnboardingFeature/Views/OnboardingEmailConfirmationView.swift
+++ /dev/null
@@ -1,121 +0,0 @@
-import UIKit
-import Shared
-import InputField
-
-final class OnboardingEmailConfirmationView: UIView {
-    let titleLabel = UILabel()
-    let subtitleView = TextWithInfoView()
-    let inputField = InputField()
-    let nextButton = CapsuleButton()
-    let resendButton = UIButton()
-    let stackView = UIStackView()
-
-    var didTapInfo: (() -> Void)?
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        setupTitle(Localized.Onboarding.EmailConfirmation.title)
-
-        inputField.setup(
-            placeholder: Localized.Onboarding.EmailConfirmation.input,
-            subtitleColor: Asset.neutralWeak.color,
-            allowsEmptySpace: false,
-            keyboardType: .numberPad,
-            autocapitalization: .none,
-            contentType: .oneTimeCode
-        )
-
-        resendButton.setTitleColor(Asset.brandPrimary.color, for: .normal)
-        resendButton.setTitleColor(Asset.neutralWeak.color, for: .disabled)
-        resendButton.titleLabel?.font = Fonts.Mulish.semiBold.font(size: 14.0)
-        resendButton.setTitle(Localized.Onboarding.EmailConfirmation.resend(""), for: .normal)
-
-        nextButton.set(style: .brandColored, title: Localized.Onboarding.EmailConfirmation.next)
-        nextButton.isEnabled = false
-
-        stackView.spacing = 15
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(nextButton)
-        stackView.addArrangedSubview(resendButton)
-
-        addSubview(titleLabel)
-        addSubview(subtitleView)
-        addSubview(inputField)
-        addSubview(stackView)
-
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(30)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
-        }
-
-        subtitleView.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(8)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
-        }
-
-        inputField.snp.makeConstraints { make in
-            make.top.equalTo(subtitleView.snp.bottom).offset(24)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-38)
-        }
-
-        stackView.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20)
-            make.left.equalToSuperview().offset(40)
-            make.right.equalToSuperview().offset(-40)
-            make.bottom.equalTo(safeAreaLayoutGuide).offset(-50)
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    func update(status: InputField.ValidationStatus) {
-        inputField.update(status: status)
-
-        switch status {
-        case .valid:
-            nextButton.isEnabled = true
-        case .invalid, .unknown:
-            nextButton.isEnabled = false
-        }
-    }
-
-    private func setupTitle(_ title: String) {
-        let attString = NSMutableAttributedString(string: title)
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.0
-
-        attString.addAttribute(.paragraphStyle, value: paragraph)
-        attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
-        attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
-
-        attString.addAttributes(attributes: [
-            .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
-            .foregroundColor: Asset.brandPrimary.color
-        ], betweenCharacters: "#")
-
-        titleLabel.numberOfLines = 0
-        titleLabel.attributedText = attString
-    }
-
-    public func setupSubtitle(_ subtitle: String) {
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.15
-
-        subtitleView.setup(
-            text: subtitle,
-            attributes: [
-                .foregroundColor: Asset.neutralBody.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
-                .paragraphStyle: paragraph
-            ],
-            didTapInfo: { [weak self] in self?.didTapInfo?() }
-        )
-    }
-}
diff --git a/Sources/OnboardingFeature/Views/OnboardingEmailView.swift b/Sources/OnboardingFeature/Views/OnboardingEmailView.swift
index 760996abb549a1658c573b9602de6777f11022dc..e98117828c9de253fe169b3cb4e9a8fff67e92d1 100644
--- a/Sources/OnboardingFeature/Views/OnboardingEmailView.swift
+++ b/Sources/OnboardingFeature/Views/OnboardingEmailView.swift
@@ -1,120 +1,114 @@
 import UIKit
 import Shared
 import InputField
+import AppResources
 
 final class OnboardingEmailView: UIView {
-    let titleLabel = UILabel()
-    let subtitleView = TextWithInfoView()
-    let inputField = InputField()
-    let nextButton = CapsuleButton()
-    let skipButton = UIButton()
-    let stackView = UIStackView()
-
-    var didTapInfo: (() -> Void)?
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        setupTitle(Localized.Onboarding.Email.title)
-        setupSubtitle(Localized.Onboarding.Email.subtitle)
-
-        inputField.setup(
-            placeholder: Localized.Onboarding.Email.input,
-            subtitleColor: Asset.neutralWeak.color,
-            allowsEmptySpace: false,
-            keyboardType: .emailAddress,
-            autocapitalization: .none,
-            contentType: .emailAddress
-        )
-
-        skipButton.titleLabel?.font = Fonts.Mulish.bold.font(size: 14.0)
-        skipButton.setTitleColor(Asset.brandPrimary.color, for: .normal)
-        skipButton.setTitle(Localized.Onboarding.Email.skip, for: .normal)
-        nextButton.set(style: .brandColored, title: Localized.Onboarding.Email.action)
-        nextButton.isEnabled = false
-
-        stackView.spacing = 20
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(nextButton)
-        stackView.addArrangedSubview(skipButton)
-
-        addSubview(titleLabel)
-        addSubview(subtitleView)
-        addSubview(inputField)
-        addSubview(stackView)
-
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(30)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
-        }
-
-        subtitleView.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(8)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
-        }
-
-        inputField.snp.makeConstraints { make in
-            make.top.equalTo(subtitleView.snp.bottom).offset(24)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-38)
-        }
-
-        stackView.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20)
-            make.left.equalToSuperview().offset(40)
-            make.right.equalToSuperview().offset(-40)
-            make.bottom.equalTo(safeAreaLayoutGuide).offset(-50)
-        }
+  let titleLabel = UILabel()
+  let subtitleView = TextWithInfoView()
+  let inputField = InputField()
+  let nextButton = CapsuleButton()
+  let skipButton = UIButton()
+  let stackView = UIStackView()
+  var didTapInfo: (() -> Void)?
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    setupTitle(Localized.Onboarding.Email.title)
+    setupSubtitle(Localized.Onboarding.Email.subtitle)
+
+    inputField.setup(
+      placeholder: Localized.Onboarding.Email.input,
+      subtitleColor: Asset.neutralWeak.color,
+      allowsEmptySpace: false,
+      keyboardType: .emailAddress,
+      autocapitalization: .none,
+      contentType: .emailAddress
+    )
+
+    skipButton.titleLabel?.font = Fonts.Mulish.bold.font(size: 14.0)
+    skipButton.setTitleColor(Asset.brandPrimary.color, for: .normal)
+    skipButton.setTitle(Localized.Onboarding.Email.skip, for: .normal)
+    nextButton.set(style: .brandColored, title: Localized.Onboarding.Email.action)
+    nextButton.isEnabled = false
+
+    stackView.spacing = 20
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(nextButton)
+    stackView.addArrangedSubview(skipButton)
+
+    addSubview(titleLabel)
+    addSubview(subtitleView)
+    addSubview(inputField)
+    addSubview(stackView)
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(30)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    func update(status: InputField.ValidationStatus) {
-        inputField.update(status: status)
-
-        switch status {
-        case .valid:
-            nextButton.isEnabled = true
-        case .invalid, .unknown:
-            nextButton.isEnabled = false
-        }
+    subtitleView.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
     }
-
-    private func setupTitle(_ title: String) {
-        let attString = NSMutableAttributedString(string: title)
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.15
-
-        attString.addAttribute(.paragraphStyle, value: paragraph)
-        attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
-        attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
-
-        attString.addAttributes(attributes: [
-            .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
-            .foregroundColor: Asset.brandPrimary.color
-        ], betweenCharacters: "#")
-
-        titleLabel.numberOfLines = 0
-        titleLabel.attributedText = attString
+    inputField.snp.makeConstraints {
+      $0.top.equalTo(subtitleView.snp.bottom).offset(24)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-38)
+    }
+    stackView.snp.makeConstraints {
+      $0.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20)
+      $0.left.equalToSuperview().offset(40)
+      $0.right.equalToSuperview().offset(-40)
+      $0.bottom.equalTo(safeAreaLayoutGuide).offset(-50)
     }
+  }
 
-    private func setupSubtitle(_ subtitle: String) {
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.15
+  required init?(coder: NSCoder) { nil }
 
-        subtitleView.setup(
-            text: subtitle,
-            attributes: [
-                .foregroundColor: Asset.neutralBody.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
-                .paragraphStyle: paragraph
-            ],
-            didTapInfo: { [weak self] in self?.didTapInfo?() }
-        )
+  func update(status: InputField.ValidationStatus) {
+    inputField.update(status: status)
+    switch status {
+    case .valid:
+      nextButton.isEnabled = true
+    case .invalid, .unknown:
+      nextButton.isEnabled = false
     }
+  }
+
+  private func setupTitle(_ title: String) {
+    let attString = NSMutableAttributedString(string: title)
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.15
+    attString.addAttribute(.paragraphStyle, value: paragraph)
+    attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
+    attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
+    attString.addAttributes(attributes: [
+      .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
+      .foregroundColor: Asset.brandPrimary.color
+    ], betweenCharacters: "#")
+
+    titleLabel.numberOfLines = 0
+    titleLabel.attributedText = attString
+  }
+
+  private func setupSubtitle(_ subtitle: String) {
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.15
+
+    subtitleView.setup(
+      text: subtitle,
+      attributes: [
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+        .paragraphStyle: paragraph
+      ],
+      didTapInfo: { [weak self] in self?.didTapInfo?() }
+    )
+  }
 }
diff --git a/Sources/OnboardingFeature/Views/OnboardingPhoneConfirmationView.swift b/Sources/OnboardingFeature/Views/OnboardingPhoneConfirmationView.swift
index 0509f5d52517dfe4f0b78614eca764250f4fb285..45584a61af46433c46833141b1cae00a00333275 100644
--- a/Sources/OnboardingFeature/Views/OnboardingPhoneConfirmationView.swift
+++ b/Sources/OnboardingFeature/Views/OnboardingPhoneConfirmationView.swift
@@ -1,121 +1,112 @@
 import UIKit
 import Shared
 import InputField
+import AppResources
 
 final class OnboardingPhoneConfirmationView: UIView {
-    let titleLabel = UILabel()
-    let subtitleView = TextWithInfoView()
-    let inputField = InputField()
-    let nextButton = CapsuleButton()
-    let resendButton = UIButton()
-    let stackView = UIStackView()
-
-    var didTapInfo: (() -> Void)?
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        setupTitle(Localized.Onboarding.PhoneConfirmation.title)
-
-        inputField.setup(
-            placeholder: Localized.Onboarding.PhoneConfirmation.input,
-            subtitleColor: Asset.neutralWeak.color,
-            allowsEmptySpace: false,
-            keyboardType: .numberPad,
-            autocapitalization: .none,
-            contentType: .oneTimeCode
-        )
-
-        resendButton.setTitleColor(Asset.brandPrimary.color, for: .normal)
-        resendButton.setTitleColor(Asset.neutralWeak.color, for: .disabled)
-        resendButton.titleLabel?.font = Fonts.Mulish.semiBold.font(size: 14.0)
-        resendButton.setTitle(Localized.Onboarding.PhoneConfirmation.resend(""), for: .normal)
-
-        nextButton.set(style: .brandColored, title: Localized.Onboarding.PhoneConfirmation.next)
-        nextButton.isEnabled = false
-
-        stackView.spacing = 15
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(nextButton)
-        stackView.addArrangedSubview(resendButton)
-
-        addSubview(titleLabel)
-        addSubview(subtitleView)
-        addSubview(inputField)
-        addSubview(stackView)
-
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(30)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
-        }
-
-        subtitleView.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(8)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
-        }
-
-        inputField.snp.makeConstraints { make in
-            make.top.equalTo(subtitleView.snp.bottom).offset(24)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-38)
-        }
-
-        stackView.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20)
-            make.left.equalToSuperview().offset(40)
-            make.right.equalToSuperview().offset(-40)
-            make.bottom.equalTo(safeAreaLayoutGuide).offset(-50)
-        }
+  let titleLabel = UILabel()
+  let subtitleView = TextWithInfoView()
+  let inputField = InputField()
+  let nextButton = CapsuleButton()
+  let resendButton = UIButton()
+  let stackView = UIStackView()
+  var didTapInfo: (() -> Void)?
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+    setupTitle(Localized.Onboarding.PhoneConfirmation.title)
+
+    inputField.setup(
+      placeholder: Localized.Onboarding.PhoneConfirmation.input,
+      subtitleColor: Asset.neutralWeak.color,
+      allowsEmptySpace: false,
+      keyboardType: .numberPad,
+      autocapitalization: .none,
+      contentType: .oneTimeCode
+    )
+
+    resendButton.setTitleColor(Asset.brandPrimary.color, for: .normal)
+    resendButton.setTitleColor(Asset.neutralWeak.color, for: .disabled)
+    resendButton.titleLabel?.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    resendButton.setTitle(Localized.Onboarding.PhoneConfirmation.resend(""), for: .normal)
+    nextButton.set(style: .brandColored, title: Localized.Onboarding.PhoneConfirmation.next)
+    nextButton.isEnabled = false
+
+    stackView.spacing = 15
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(nextButton)
+    stackView.addArrangedSubview(resendButton)
+
+    addSubview(titleLabel)
+    addSubview(subtitleView)
+    addSubview(inputField)
+    addSubview(stackView)
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(30)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    func update(status: InputField.ValidationStatus) {
-        inputField.update(status: status)
-
-        switch status {
-        case .valid:
-            nextButton.isEnabled = true
-        case .invalid, .unknown:
-            nextButton.isEnabled = false
-        }
+    subtitleView.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
     }
-
-    private func setupTitle(_ title: String) {
-        let attString = NSMutableAttributedString(string: title)
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.0
-
-        attString.addAttribute(.paragraphStyle, value: paragraph)
-        attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
-        attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
-
-        attString.addAttributes(attributes: [
-            .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
-            .foregroundColor: Asset.brandPrimary.color
-        ], betweenCharacters: "#")
-
-        titleLabel.numberOfLines = 0
-        titleLabel.attributedText = attString
+    inputField.snp.makeConstraints {
+      $0.top.equalTo(subtitleView.snp.bottom).offset(24)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-38)
+    }
+    stackView.snp.makeConstraints {
+      $0.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20)
+      $0.left.equalToSuperview().offset(40)
+      $0.right.equalToSuperview().offset(-40)
+      $0.bottom.equalTo(safeAreaLayoutGuide).offset(-50)
     }
+  }
 
-    public func setupSubtitle(_ subtitle: String) {
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.15
+  required init?(coder: NSCoder) { nil }
 
-        subtitleView.setup(
-            text: subtitle,
-            attributes: [
-                .foregroundColor: Asset.neutralBody.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
-                .paragraphStyle: paragraph
-            ],
-            didTapInfo: { [weak self] in self?.didTapInfo?() }
-        )
+  func update(status: InputField.ValidationStatus) {
+    inputField.update(status: status)
+    switch status {
+    case .valid:
+      nextButton.isEnabled = true
+    case .invalid, .unknown:
+      nextButton.isEnabled = false
     }
+  }
+
+  private func setupTitle(_ title: String) {
+    let attString = NSMutableAttributedString(string: title)
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.0
+    attString.addAttribute(.paragraphStyle, value: paragraph)
+    attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
+    attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
+    attString.addAttributes(attributes: [
+      .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
+      .foregroundColor: Asset.brandPrimary.color
+    ], betweenCharacters: "#")
+
+    titleLabel.numberOfLines = 0
+    titleLabel.attributedText = attString
+  }
+  
+  public func setupSubtitle(_ subtitle: String) {
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.15
+    subtitleView.setup(
+      text: subtitle,
+      attributes: [
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+        .paragraphStyle: paragraph
+      ],
+      didTapInfo: { [weak self] in self?.didTapInfo?() }
+    )
+  }
 }
diff --git a/Sources/OnboardingFeature/Views/OnboardingPhoneView.swift b/Sources/OnboardingFeature/Views/OnboardingPhoneView.swift
index caec887d542c4d37f98486ef85c790d5bcda6ebb..f36c635ef4e0706297a6b40d328e9a387d3e556f 100644
--- a/Sources/OnboardingFeature/Views/OnboardingPhoneView.swift
+++ b/Sources/OnboardingFeature/Views/OnboardingPhoneView.swift
@@ -1,120 +1,113 @@
 import UIKit
 import Shared
 import InputField
+import AppResources
 
 final class OnboardingPhoneView: UIView {
-    let titleLabel = UILabel()
-    let subtitleView = TextWithInfoView()
-    let inputField = InputField()
-    let nextButton = CapsuleButton()
-    let skipButton = UIButton()
-    let stackView = UIStackView()
-
-    var didTapInfo: (() -> Void)?
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        setupTitle(Localized.Onboarding.Phone.title)
-        setupSubtitle(Localized.Onboarding.Phone.subtitle)
-
-        inputField.setup(
-            style: .phone,
-            placeholder: Localized.Onboarding.Phone.input,
-            subtitleColor: Asset.neutralWeak.color,
-            keyboardType: .phonePad,
-            contentType: .telephoneNumber,
-            codeAccessibility: Localized.Accessibility.Onboarding.Phone.code
-        )
-
-        skipButton.titleLabel?.font = Fonts.Mulish.bold.font(size: 14.0)
-        skipButton.setTitleColor(Asset.brandPrimary.color, for: .normal)
-        skipButton.setTitle(Localized.Onboarding.Phone.skip, for: .normal)
-        nextButton.set(style: .brandColored, title: Localized.Onboarding.Phone.action)
-        nextButton.isEnabled = false
-
-        stackView.spacing = 20
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(nextButton)
-        stackView.addArrangedSubview(skipButton)
-
-        addSubview(titleLabel)
-        addSubview(subtitleView)
-        addSubview(inputField)
-        addSubview(stackView)
-
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(30)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
-        }
-
-        subtitleView.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(8)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
-        }
-
-        inputField.snp.makeConstraints { make in
-            make.top.equalTo(subtitleView.snp.bottom).offset(24)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-38)
-        }
-
-        stackView.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20)
-            make.left.equalToSuperview().offset(40)
-            make.right.equalToSuperview().offset(-40)
-            make.bottom.equalTo(safeAreaLayoutGuide).offset(-50)
-        }
+  let titleLabel = UILabel()
+  let subtitleView = TextWithInfoView()
+  let inputField = InputField()
+  let nextButton = CapsuleButton()
+  let skipButton = UIButton()
+  let stackView = UIStackView()
+  var didTapInfo: (() -> Void)?
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    setupTitle(Localized.Onboarding.Phone.title)
+    setupSubtitle(Localized.Onboarding.Phone.subtitle)
+
+    inputField.setup(
+      style: .phone,
+      placeholder: Localized.Onboarding.Phone.input,
+      subtitleColor: Asset.neutralWeak.color,
+      keyboardType: .phonePad,
+      contentType: .telephoneNumber,
+      codeAccessibility: Localized.Accessibility.Onboarding.Phone.code
+    )
+
+    skipButton.titleLabel?.font = Fonts.Mulish.bold.font(size: 14.0)
+    skipButton.setTitleColor(Asset.brandPrimary.color, for: .normal)
+    skipButton.setTitle(Localized.Onboarding.Phone.skip, for: .normal)
+    nextButton.set(style: .brandColored, title: Localized.Onboarding.Phone.action)
+    nextButton.isEnabled = false
+
+    stackView.spacing = 20
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(nextButton)
+    stackView.addArrangedSubview(skipButton)
+
+    addSubview(titleLabel)
+    addSubview(subtitleView)
+    addSubview(inputField)
+    addSubview(stackView)
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(30)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    func update(status: InputField.ValidationStatus) {
-        inputField.update(status: status)
-
-        switch status {
-        case .valid:
-            nextButton.isEnabled = true
-        case .invalid, .unknown:
-            nextButton.isEnabled = false
-        }
+    subtitleView.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
     }
-
-    private func setupTitle(_ title: String) {
-        let attString = NSMutableAttributedString(string: title)
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.15
-
-        attString.addAttribute(.paragraphStyle, value: paragraph)
-        attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
-        attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
-
-        attString.addAttributes(attributes: [
-            .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
-            .foregroundColor: Asset.brandPrimary.color
-        ], betweenCharacters: "#")
-
-        titleLabel.numberOfLines = 0
-        titleLabel.attributedText = attString
+    inputField.snp.makeConstraints {
+      $0.top.equalTo(subtitleView.snp.bottom).offset(24)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-38)
+    }
+    stackView.snp.makeConstraints {
+      $0.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20)
+      $0.left.equalToSuperview().offset(40)
+      $0.right.equalToSuperview().offset(-40)
+      $0.bottom.equalTo(safeAreaLayoutGuide).offset(-50)
     }
+  }
 
-    private func setupSubtitle(_ subtitle: String) {
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.15
+  required init?(coder: NSCoder) { nil }
 
-        subtitleView.setup(
-            text: subtitle,
-            attributes: [
-                .foregroundColor: Asset.neutralBody.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
-                .paragraphStyle: paragraph
-            ],
-            didTapInfo: { [weak self] in self?.didTapInfo?() }
-        )
+  func update(status: InputField.ValidationStatus) {
+    inputField.update(status: status)
+    switch status {
+    case .valid:
+      nextButton.isEnabled = true
+    case .invalid, .unknown:
+      nextButton.isEnabled = false
     }
+  }
+
+  private func setupTitle(_ title: String) {
+    let attString = NSMutableAttributedString(string: title)
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.15
+    attString.addAttribute(.paragraphStyle, value: paragraph)
+    attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
+    attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
+    attString.addAttributes(attributes: [
+      .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
+      .foregroundColor: Asset.brandPrimary.color
+    ], betweenCharacters: "#")
+
+    titleLabel.numberOfLines = 0
+    titleLabel.attributedText = attString
+  }
+
+  private func setupSubtitle(_ subtitle: String) {
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.15
+    subtitleView.setup(
+      text: subtitle,
+      attributes: [
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+        .paragraphStyle: paragraph
+      ],
+      didTapInfo: { [weak self] in self?.didTapInfo?() }
+    )
+  }
 }
diff --git a/Sources/OnboardingFeature/Views/OnboardingStartView.swift b/Sources/OnboardingFeature/Views/OnboardingStartView.swift
index a3e953aad2108ae43036f43c56530b8c63937ec9..b0864bb32bb8bdd4557b0a677df73068e5c747d5 100644
--- a/Sources/OnboardingFeature/Views/OnboardingStartView.swift
+++ b/Sources/OnboardingFeature/Views/OnboardingStartView.swift
@@ -1,49 +1,49 @@
 import UIKit
 import Shared
+import AppResources
 
 final class OnboardingStartView: UIView {
-    let titleLabel = UILabel()
-    let stackView = UIStackView()
-    let logoImageView = UIImageView()
-    let startButton = CapsuleButton()
-    let bottomImageView = UIImageView()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-        logoImageView.image = Asset.onboardingLogoStart.image
-        bottomImageView.image = Asset.onboardingBottomLogoStart.image
-
-        titleLabel.textAlignment = .center
-        titleLabel.textColor = Asset.neutralWhite.color
-        titleLabel.font = Fonts.Mulish.bold.font(size: 18.0)
-        titleLabel.text = Localized.Onboarding.Start.title
-        startButton.set(style: .white, title: Localized.Onboarding.Start.action)
-
-        logoImageView.contentMode = .center
-        bottomImageView.contentMode = .center
-
-        stackView.spacing = 40
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(titleLabel)
-        stackView.addArrangedSubview(startButton)
-        stackView.addArrangedSubview(bottomImageView)
-        stackView.setCustomSpacing(70, after: startButton)
-
-        addSubview(logoImageView)
-        addSubview(stackView)
-
-        logoImageView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(130)
-            make.centerX.equalToSuperview()
-        }
-
-        stackView.snp.makeConstraints { make in
-            make.left.equalToSuperview().offset(40)
-            make.right.equalToSuperview().offset(-40)
-            make.bottom.equalTo(safeAreaLayoutGuide).offset(-40)
-        }
+  let titleLabel = UILabel()
+  let stackView = UIStackView()
+  let logoImageView = UIImageView()
+  let startButton = CapsuleButton()
+  let bottomImageView = UIImageView()
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+    logoImageView.image = Asset.onboardingLogoStart.image
+    bottomImageView.image = Asset.onboardingBottomLogoStart.image
+
+    titleLabel.textAlignment = .center
+    titleLabel.textColor = Asset.neutralWhite.color
+    titleLabel.font = Fonts.Mulish.bold.font(size: 18.0)
+    titleLabel.text = Localized.Onboarding.Start.title
+    startButton.set(style: .white, title: Localized.Onboarding.Start.action)
+
+    logoImageView.contentMode = .center
+    bottomImageView.contentMode = .center
+
+    stackView.spacing = 40
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(titleLabel)
+    stackView.addArrangedSubview(startButton)
+    stackView.addArrangedSubview(bottomImageView)
+    stackView.setCustomSpacing(70, after: startButton)
+
+    addSubview(logoImageView)
+    addSubview(stackView)
+
+    logoImageView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(130)
+      $0.centerX.equalToSuperview()
     }
+    stackView.snp.makeConstraints {
+      $0.left.equalToSuperview().offset(40)
+      $0.right.equalToSuperview().offset(-40)
+      $0.bottom.equalTo(safeAreaLayoutGuide).offset(-40)
+    }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/OnboardingFeature/Views/OnboardingSuccessView.swift b/Sources/OnboardingFeature/Views/OnboardingSuccessView.swift
deleted file mode 100644
index 7e02167c2786cb11c4fcc04d6ad4930b9fab92e2..0000000000000000000000000000000000000000
--- a/Sources/OnboardingFeature/Views/OnboardingSuccessView.swift
+++ /dev/null
@@ -1,75 +0,0 @@
-import UIKit
-import Shared
-
-final class OnboardingSuccessView: UIView {
-    let iconImageView = UIImageView()
-    let titleLabel = UILabel()
-    let subtitleLabel = UILabel()
-    let nextButton = CapsuleButton()
-
-    init() {
-        super.init(frame: .zero)
-
-        iconImageView.contentMode = .center
-        iconImageView.image = Asset.onboardingSuccess.image
-        nextButton.set(style: .white, title: Localized.Onboarding.Success.action)
-
-        subtitleLabel.numberOfLines = 0
-        subtitleLabel.textColor = Asset.neutralWhite.color
-        subtitleLabel.font = Fonts.Mulish.regular.font(size: 16.0)
-
-        addSubview(iconImageView)
-        addSubview(titleLabel)
-        addSubview(subtitleLabel)
-        addSubview(nextButton)
-
-        iconImageView.snp.makeConstraints { make in
-            make.top.equalTo(safeAreaLayoutGuide).offset(40)
-            make.left.equalToSuperview().offset(40)
-        }
-
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalTo(iconImageView.snp.bottom).offset(40)
-            make.left.equalToSuperview().offset(40)
-            make.right.equalToSuperview().offset(-90)
-        }
-
-        subtitleLabel.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(30)
-            make.left.equalToSuperview().offset(40)
-            make.right.equalToSuperview().offset(-90)
-        }
-
-        nextButton.snp.makeConstraints { make in
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-            make.bottom.equalToSuperview().offset(-60)
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    func setTitle(_ title: String) {
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.1
-
-        let attrString = NSMutableAttributedString(string: title)
-
-        attrString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 39.0))
-        attrString.addAttribute(.foregroundColor, value: Asset.neutralWhite.color)
-
-        attrString.addAttribute(
-            name: .foregroundColor,
-            value: Asset.neutralBody.color,
-            betweenCharacters: "#"
-        )
-
-        titleLabel.numberOfLines = 0
-        titleLabel.attributedText = attrString
-    }
-
-    func setSubtitle(_ subtitle: String?) {
-        subtitleLabel.text = subtitle
-    }
-}
diff --git a/Sources/OnboardingFeature/Views/OnboardingUsernameRestoreView.swift b/Sources/OnboardingFeature/Views/OnboardingUsernameRestoreView.swift
index f03437558a9157fd6846ce76436f1ed1c4d8822b..7a418f71686b90456b03f24af3f4165d416bd7a8 100644
--- a/Sources/OnboardingFeature/Views/OnboardingUsernameRestoreView.swift
+++ b/Sources/OnboardingFeature/Views/OnboardingUsernameRestoreView.swift
@@ -1,47 +1,46 @@
 import UIKit
 import Shared
+import AppResources
 
 final class OnboardingUsernameRestoreView: UIView {
-    let titleLabel = UILabel()
-    let restoreButton = CapsuleButton()
-    let separatorView = UIView()
-
-    init() {
-        super.init(frame: .zero)
-
-        titleLabel.text = Localized.Onboarding.Username.Restore.title
-        restoreButton.set(style: .seeThrough, title: Localized.Onboarding.Username.Restore.action)
-
-        titleLabel.numberOfLines = 0
-        titleLabel.textAlignment = .center
-        titleLabel.font = Fonts.Mulish.bold.font(size: 24)
-
-        addSubview(titleLabel)
-        addSubview(restoreButton)
-        addSubview(separatorView)
-
-        separatorView.backgroundColor = Asset.neutralLine.color
-
-        separatorView.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-            make.height.equalTo(1)
-        }
-
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalTo(separatorView.snp.bottom).offset(40)
-            make.left.equalToSuperview().offset(20)
-            make.right.equalToSuperview().offset(-20)
-        }
-
-        restoreButton.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(34)
-            make.left.equalToSuperview().offset(40)
-            make.right.equalToSuperview().offset(-40)
-            make.bottom.equalToSuperview()
-        }
+  let titleLabel = UILabel()
+  let restoreButton = CapsuleButton()
+  let separatorView = UIView()
+
+  init() {
+    super.init(frame: .zero)
+
+    titleLabel.text = Localized.Onboarding.Username.Restore.title
+    restoreButton.set(style: .seeThrough, title: Localized.Onboarding.Username.Restore.action)
+
+    titleLabel.numberOfLines = 0
+    titleLabel.textAlignment = .center
+    titleLabel.font = Fonts.Mulish.bold.font(size: 24)
+
+    addSubview(titleLabel)
+    addSubview(restoreButton)
+    addSubview(separatorView)
+
+    separatorView.backgroundColor = Asset.neutralLine.color
+
+    separatorView.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
+      $0.height.equalTo(1)
+    }
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(separatorView.snp.bottom).offset(40)
+      $0.left.equalToSuperview().offset(20)
+      $0.right.equalToSuperview().offset(-20)
+    }
+    restoreButton.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(34)
+      $0.left.equalToSuperview().offset(40)
+      $0.right.equalToSuperview().offset(-40)
+      $0.bottom.equalToSuperview()
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/OnboardingFeature/Views/OnboardingUsernameView.swift b/Sources/OnboardingFeature/Views/OnboardingUsernameView.swift
index 32ec1e43e312aee7880f1003c9e3f4ec0f18e899..8f5bf92b6a34377960f449c2d0a77aab28b3225c 100644
--- a/Sources/OnboardingFeature/Views/OnboardingUsernameView.swift
+++ b/Sources/OnboardingFeature/Views/OnboardingUsernameView.swift
@@ -1,116 +1,108 @@
 import UIKit
 import Shared
 import InputField
+import AppResources
 
 final class OnboardingUsernameView: UIView {
-    let titleLabel = UILabel()
-    let subtitleView = TextWithInfoView()
-    let inputField = InputField()
-    let nextButton = CapsuleButton()
-    let restoreView = OnboardingUsernameRestoreView()
-
-    var didTapInfo: (() -> Void)?
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        setupTitle(Localized.Onboarding.Username.title)
-        setupSubtitle(Localized.Onboarding.Username.subtitle)
-
-        inputField.setup(
-            placeholder: Localized.Onboarding.Username.input,
-            subtitleColor: Asset.neutralWeak.color,
-            allowsEmptySpace: false,
-            autocapitalization: .none
-        )
-
-        nextButton.set(style: .brandColored, title: Localized.Onboarding.Username.next)
-        nextButton.isEnabled = false
-
-        addSubview(titleLabel)
-        addSubview(subtitleView)
-        addSubview(inputField)
-        addSubview(nextButton)
-        addSubview(restoreView)
-
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(30)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
-        }
-
-        subtitleView.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(8)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
-        }
-
-        inputField.snp.makeConstraints { make in
-            make.top.equalTo(subtitleView.snp.bottom).offset(24)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-38)
-        }
-
-        nextButton.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20)
-            make.left.equalToSuperview().offset(40)
-            make.right.equalToSuperview().offset(-40)
-        }
-
-        restoreView.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualTo(nextButton.snp.bottom).offset(30)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.equalTo(safeAreaLayoutGuide).offset(-50)
-        }
+  let titleLabel = UILabel()
+  let subtitleView = TextWithInfoView()
+  let inputField = InputField()
+  let nextButton = CapsuleButton()
+  let restoreView = OnboardingUsernameRestoreView()
+  var didTapInfo: (() -> Void)?
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    setupTitle(Localized.Onboarding.Username.title)
+    setupSubtitle(Localized.Onboarding.Username.subtitle)
+
+    inputField.setup(
+      placeholder: Localized.Onboarding.Username.input,
+      subtitleColor: Asset.neutralWeak.color,
+      allowsEmptySpace: false,
+      autocapitalization: .none
+    )
+
+    nextButton.set(style: .brandColored, title: Localized.Onboarding.Username.next)
+    nextButton.isEnabled = false
+
+    addSubview(titleLabel)
+    addSubview(subtitleView)
+    addSubview(inputField)
+    addSubview(nextButton)
+    addSubview(restoreView)
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(30)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    func update(status: InputField.ValidationStatus) {
-        inputField.update(status: status)
-
-        switch status {
-        case .valid:
-            nextButton.isEnabled = true
-        case .invalid, .unknown:
-            nextButton.isEnabled = false
-        }
+    subtitleView.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
     }
-
-    private func setupTitle(_ title: String) {
-        let attString = NSMutableAttributedString(string: title)
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.15
-
-        attString.addAttribute(.paragraphStyle, value: paragraph)
-        attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
-        attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
-
-        attString.addAttributes(attributes: [
-            .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
-            .foregroundColor: Asset.brandPrimary.color
-        ], betweenCharacters: "#")
-
-        titleLabel.numberOfLines = 0
-        titleLabel.attributedText = attString
+    inputField.snp.makeConstraints {
+      $0.top.equalTo(subtitleView.snp.bottom).offset(24)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-38)
+    }
+    nextButton.snp.makeConstraints {
+      $0.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20)
+      $0.left.equalToSuperview().offset(40)
+      $0.right.equalToSuperview().offset(-40)
+    }
+    restoreView.snp.makeConstraints {
+      $0.top.greaterThanOrEqualTo(nextButton.snp.bottom).offset(30)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalTo(safeAreaLayoutGuide).offset(-50)
     }
+  }
 
-    private func setupSubtitle(_ subtitle: String) {
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.15
+  required init?(coder: NSCoder) { nil }
 
-        subtitleView.setup(
-            text: subtitle,
-            attributes: [
-                .foregroundColor: Asset.neutralBody.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
-                .paragraphStyle: paragraph
-            ],
-            didTapInfo: { [weak self] in self?.didTapInfo?() }
-        )
+  func update(status: InputField.ValidationStatus) {
+    inputField.update(status: status)
+    switch status {
+    case .valid:
+      nextButton.isEnabled = true
+    case .invalid, .unknown:
+      nextButton.isEnabled = false
     }
+  }
+
+  private func setupTitle(_ title: String) {
+    let attString = NSMutableAttributedString(string: title)
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.15
+    attString.addAttribute(.paragraphStyle, value: paragraph)
+    attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
+    attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
+    attString.addAttributes(attributes: [
+      .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
+      .foregroundColor: Asset.brandPrimary.color
+    ], betweenCharacters: "#")
+
+    titleLabel.numberOfLines = 0
+    titleLabel.attributedText = attString
+  }
+
+  private func setupSubtitle(_ subtitle: String) {
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.15
+    subtitleView.setup(
+      text: subtitle,
+      attributes: [
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+        .paragraphStyle: paragraph
+      ],
+      didTapInfo: { [weak self] in self?.didTapInfo?() }
+    )
+  }
 }
diff --git a/Sources/OnboardingFeature/Views/OnboardingWelcomeView.swift b/Sources/OnboardingFeature/Views/OnboardingWelcomeView.swift
index d3581a4d9f987e7e1e19f6f09831793cf57d5640..2daddf4b716a515abe8d17c61e9f6e2ee464aa6f 100644
--- a/Sources/OnboardingFeature/Views/OnboardingWelcomeView.swift
+++ b/Sources/OnboardingFeature/Views/OnboardingWelcomeView.swift
@@ -1,86 +1,81 @@
 import UIKit
 import Shared
+import AppResources
 
 final class OnboardingWelcomeView: UIView {
-    let titleLabel = UILabel()
-    let subtitleView = TextWithInfoView()
-    let stackView = UIStackView()
-    let continueButton = CapsuleButton()
-    let skipButton = CapsuleButton()
-
-    var didTapInfo: (() -> Void)?
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        setupSubtitle(Localized.Onboarding.Welcome.subtitle)
-
-        skipButton.set(style: .brandColored, title: Localized.Onboarding.Welcome.skip)
-        continueButton.set(style: .brandColored, title: Localized.Onboarding.Welcome.continue)
-
-        stackView.spacing = 15
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(continueButton)
-        stackView.addArrangedSubview(skipButton)
-
-        addSubview(titleLabel)
-        addSubview(subtitleView)
-        addSubview(stackView)
-
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalTo(safeAreaLayoutGuide).offset(30)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
-        }
-
-        subtitleView.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(8)
-            make.left.equalToSuperview().offset(38)
-            make.right.equalToSuperview().offset(-41)
-        }
-
-        stackView.snp.makeConstraints { make in
-            make.left.equalToSuperview().offset(40)
-            make.right.equalToSuperview().offset(-40)
-            make.bottom.equalTo(safeAreaLayoutGuide).offset(-50)
-        }
+  let titleLabel = UILabel()
+  let subtitleView = TextWithInfoView()
+  let stackView = UIStackView()
+  let continueButton = CapsuleButton()
+  let skipButton = CapsuleButton()
+  var didTapInfo: (() -> Void)?
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    setupSubtitle(Localized.Onboarding.Welcome.subtitle)
+
+    skipButton.set(style: .brandColored, title: Localized.Onboarding.Welcome.skip)
+    continueButton.set(style: .brandColored, title: Localized.Onboarding.Welcome.continue)
+
+    stackView.spacing = 15
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(continueButton)
+    stackView.addArrangedSubview(skipButton)
+
+    addSubview(titleLabel)
+    addSubview(subtitleView)
+    addSubview(stackView)
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(safeAreaLayoutGuide).offset(30)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    func setupTitle(_ title: String) {
-        let attString = NSMutableAttributedString(string: title)
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.1
-
-        attString.addAttribute(.paragraphStyle, value: paragraph)
-        attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
-        attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
-
-        attString.addAttributes(attributes: [
-            .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
-            .foregroundColor: Asset.brandPrimary.color
-        ], betweenCharacters: "#")
-
-        titleLabel.numberOfLines = 0
-        titleLabel.attributedText = attString
+    subtitleView.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
     }
-
-    private func setupSubtitle(_ subtitle: String) {
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.15
-
-        subtitleView.setup(
-            text: subtitle,
-            attributes: [
-                .foregroundColor: Asset.neutralBody.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
-                .paragraphStyle: paragraph
-            ],
-            didTapInfo: { [weak self] in self?.didTapInfo?() }
-        )
+    stackView.snp.makeConstraints {
+      $0.left.equalToSuperview().offset(40)
+      $0.right.equalToSuperview().offset(-40)
+      $0.bottom.equalTo(safeAreaLayoutGuide).offset(-50)
     }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  func setupTitle(_ title: String) {
+    let attString = NSMutableAttributedString(string: title)
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.1
+    attString.addAttribute(.paragraphStyle, value: paragraph)
+    attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
+    attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
+    attString.addAttributes(attributes: [
+      .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
+      .foregroundColor: Asset.brandPrimary.color
+    ], betweenCharacters: "#")
+
+    titleLabel.numberOfLines = 0
+    titleLabel.attributedText = attString
+  }
+
+  private func setupSubtitle(_ subtitle: String) {
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.15
+    subtitleView.setup(
+      text: subtitle,
+      attributes: [
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+        .paragraphStyle: paragraph
+      ],
+      didTapInfo: { [weak self] in self?.didTapInfo?() }
+    )
+  }
 }
diff --git a/Sources/Permissions/MockPermissionHandler.swift b/Sources/Permissions/MockPermissionHandler.swift
deleted file mode 100644
index d43dd9f1907f0faa671980f3ae0290828c0fc10d..0000000000000000000000000000000000000000
--- a/Sources/Permissions/MockPermissionHandler.swift
+++ /dev/null
@@ -1,38 +0,0 @@
-import AVFoundation
-
-public class MockPermissionHandler: PermissionHandling {
-    private var cameraStatus = false
-    private var photosStatus = false
-    private var biometricsStatus = false
-    private var microphoneStatus = false
-
-    public init() {}
-
-    public var isCameraAllowed: Bool { cameraStatus }
-
-    public var isPhotosAllowed: Bool { photosStatus }
-
-    public var isMicrophoneAllowed: Bool { microphoneStatus }
-
-    public var isBiometricsAvailable: Bool { biometricsStatus }
-
-    public func requestBiometrics(_ completion: @escaping (Result<Bool, Error>) -> Void) {
-        biometricsStatus = true
-        completion(.success(true))
-    }
-
-    public func requestCamera(_ completion: @escaping (Bool) -> Void) {
-        cameraStatus = true
-        completion(true)
-    }
-
-    public func requestMicrophone(_ completion: @escaping (Bool) -> Void) {
-        microphoneStatus = true
-        completion(true)
-    }
-
-    public func requestPhotos(_ completion: @escaping (Bool) -> Void) {
-        photosStatus = true
-        completion(true)
-    }
-}
diff --git a/Sources/Permissions/PermissionHandler.swift b/Sources/Permissions/PermissionHandler.swift
deleted file mode 100644
index 762a217f121daf85b61a172782ebef12dc8d6c21..0000000000000000000000000000000000000000
--- a/Sources/Permissions/PermissionHandler.swift
+++ /dev/null
@@ -1,68 +0,0 @@
-import Photos
-import AVFoundation
-import LocalAuthentication
-
-public protocol PermissionHandling {
-    var isCameraAllowed: Bool { get }
-    var isPhotosAllowed: Bool { get }
-    var isMicrophoneAllowed: Bool { get }
-    var isBiometricsAvailable: Bool { get }
-
-    func requestPhotos(_: @escaping (Bool) -> Void)
-    func requestCamera(_: @escaping (Bool) -> Void)
-    func requestMicrophone(_: @escaping (Bool) -> Void)
-    func requestBiometrics(_: @escaping (Result<Bool, Error>) -> Void)
-}
-
-public struct PermissionHandler: PermissionHandling {
-    public init() {}
-
-    public var isMicrophoneAllowed: Bool {
-        AVAudioSession.sharedInstance().recordPermission == .granted
-    }
-
-    public var isCameraAllowed: Bool {
-        AVCaptureDevice.authorizationStatus(for: .video) ==  .authorized
-    }
-
-    public var isPhotosAllowed: Bool {
-        PHPhotoLibrary.authorizationStatus() == .authorized
-    }
-
-    public var isBiometricsAvailable: Bool {
-        var error: NSError?
-        let context = LAContext()
-
-        if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) == true {
-            return true
-        } else {
-            let tooManyAttempts = LAError.Code.biometryLockout.rawValue
-            guard let error = error, error.code == tooManyAttempts else { return true }
-            return false
-        }
-    }
-
-    public func requestBiometrics(_ completion: @escaping (Result<Bool, Error>) -> Void) {
-        let reason = "Authentication is required to use xx messenger"
-        LAContext().evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason, reply: { success, error in
-            guard let error = error else {
-                completion(.success(success))
-                return
-            }
-
-            completion(.failure(error))
-        })
-    }
-
-    public func requestCamera(_ completion: @escaping (Bool) -> Void) {
-        AVCaptureDevice.requestAccess(for: .video, completionHandler: completion)
-    }
-
-    public func requestMicrophone(_ completion: @escaping (Bool) -> Void) {
-        AVAudioSession.sharedInstance().requestRecordPermission(completion)
-    }
-
-    public func requestPhotos(_ completion: @escaping (Bool) -> Void) {
-        PHPhotoLibrary.requestAuthorization { completion($0 == .authorized) }
-    }
-}
diff --git a/Sources/Permissions/RequestPermissionController.swift b/Sources/Permissions/RequestPermissionController.swift
deleted file mode 100644
index d892ab60e8722633e8d7c2930eb7ec97b4049d8c..0000000000000000000000000000000000000000
--- a/Sources/Permissions/RequestPermissionController.swift
+++ /dev/null
@@ -1,100 +0,0 @@
-import UIKit
-import Theme
-import Shared
-import Combine
-import DependencyInjection
-
-public enum PermissionType {
-    case camera
-    case library
-    case microphone
-}
-
-public final class RequestPermissionController: UIViewController {
-    @Dependency private var permissions: PermissionHandling
-    @Dependency private var statusBarController: StatusBarStyleControlling
-
-    lazy private var screenView = RequestPermissionView()
-
-    private var type: PermissionType!
-    private var cancellables = Set<AnyCancellable>()
-
-    public override func loadView() {
-        view = screenView
-    }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        statusBarController.style.send(.darkContent)
-        navigationController?.navigationBar.customize(backgroundColor: Asset.neutralWhite.color)
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupBindings()
-    }
-
-    public func setup(type: PermissionType) {
-        self.type = type
-
-        switch type {
-        case .camera:
-            screenView.setup(
-                title: Localized.Chat.Actions.Permission.Camera.title,
-                subtitle: Localized.Chat.Actions.Permission.Camera.subtitle,
-                image: Asset.permissionCamera.image
-            )
-        case .library:
-            screenView.setup(
-                title: Localized.Chat.Actions.Permission.Library.title,
-                subtitle: Localized.Chat.Actions.Permission.Library.subtitle,
-                image: Asset.permissionLibrary.image
-            )
-        case .microphone:
-            screenView.setup(
-                title: Localized.Chat.Actions.Permission.Microphone.title,
-                subtitle: Localized.Chat.Actions.Permission.Microphone.subtitle,
-                image: Asset.permissionMicrophone.image
-            )
-        }
-    }
-
-    private func setupBindings() {
-        screenView.notNowButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [weak self] in
-                self?.navigationController?.popViewController(animated: true)
-            }.store(in: &cancellables)
-
-        screenView.continueButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                switch type {
-                case .camera:
-                    permissions.requestCamera { [weak self] _ in
-                        DispatchQueue.main.async {
-                            self?.navigationController?.popViewController(animated: true)
-                        }
-                    }
-                case .library:
-                    permissions.requestPhotos { [weak self] _ in
-                        DispatchQueue.main.async {
-                            self?.navigationController?.popViewController(animated: true)
-                        }
-                    }
-                case .microphone:
-                    permissions.requestMicrophone { [weak self] _ in
-                        DispatchQueue.main.async {
-                            self?.navigationController?.popViewController(animated: true)
-                        }
-                    }
-                case .none:
-                    break
-                }
-            }.store(in: &cancellables)
-    }
-
-}
diff --git a/Sources/Permissions/RequestPermissionView.swift b/Sources/Permissions/RequestPermissionView.swift
deleted file mode 100644
index 52fd14da4f3d250ee5e8be9c9dd1efce9cd21e3a..0000000000000000000000000000000000000000
--- a/Sources/Permissions/RequestPermissionView.swift
+++ /dev/null
@@ -1,106 +0,0 @@
-import UIKit
-import Shared
-
-final class RequestPermissionView: UIView {
-    let titleLabel = UILabel()
-    let iconImage = UIImageView()
-    let subtitleLabel = UILabel()
-    let littleLogo = UIImageView()
-    private(set) var notNowButton = UIButton()
-    private(set) var continueButton = CapsuleButton()
-
-    init() {
-        super.init(frame: .zero)
-        setup()
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    func setup(title: String, subtitle: String, image: UIImage) {
-        iconImage.image = image
-        titleLabel.text = title
-
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.lineHeightMultiple = 1.5
-        paragraph.alignment = .center
-
-        subtitleLabel.attributedText = NSAttributedString(
-            string: subtitle,
-            attributes: [
-                .paragraphStyle: paragraph,
-                .font: Fonts.Mulish.regular.font(size: 14.0),
-                .foregroundColor: Asset.neutralBody.color,
-            ]
-        )
-    }
-
-    private func setup() {
-        littleLogo.image = Asset.permissionLogo.image
-        notNowButton.setTitle(Localized.Chat.Actions.Permission.notnow, for: .normal)
-        continueButton.set(style: .brandColored, title: Localized.Chat.Actions.Permission.continue)
-
-        titleLabel.textAlignment = .center
-
-        backgroundColor = Asset.neutralWhite.color
-        titleLabel.textColor = Asset.neutralActive.color
-        notNowButton.setTitleColor(Asset.neutralWeak.color, for: .normal)
-
-        subtitleLabel.numberOfLines = 0
-
-        titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
-        notNowButton.titleLabel?.font = Fonts.Mulish.semiBold.font(size: 16)
-
-        let actionsContainer = UIView()
-        actionsContainer.addSubview(continueButton)
-        actionsContainer.addSubview(notNowButton)
-
-        addSubview(iconImage)
-        addSubview(titleLabel)
-        addSubview(littleLogo)
-        addSubview(subtitleLabel)
-        addSubview(actionsContainer)
-
-        iconImage.snp.makeConstraints { make in
-            make.centerX.equalToSuperview()
-        }
-
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalTo(iconImage.snp.bottom).offset(34)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-        }
-
-        subtitleLabel.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(8)
-            make.left.equalToSuperview().offset(32)
-            make.right.equalToSuperview().offset(-32)
-            make.bottom.equalTo(snp.centerY)
-        }
-
-        littleLogo.snp.makeConstraints { make in
-            make.centerX.equalToSuperview()
-            make.bottom.equalTo(safeAreaLayoutGuide).offset(-15)
-        }
-
-        actionsContainer.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualTo(subtitleLabel.snp.bottom)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.lessThanOrEqualTo(littleLogo.snp.top)
-        }
-
-        continueButton.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualToSuperview()
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-            make.bottom.equalTo(actionsContainer.snp.centerY).offset(-5)
-        }
-
-        notNowButton.snp.makeConstraints { make in
-            make.top.equalTo(actionsContainer.snp.centerY).offset(5)
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-            make.bottom.lessThanOrEqualToSuperview()
-        }
-    }
-}
diff --git a/Sources/PermissionsFeature/Dependency.swift b/Sources/PermissionsFeature/Dependency.swift
new file mode 100644
index 0000000000000000000000000000000000000000..766f239ff492ca00b4ed0fc9a8afaa879818812b
--- /dev/null
+++ b/Sources/PermissionsFeature/Dependency.swift
@@ -0,0 +1,13 @@
+import Dependencies
+
+private enum PermissionsDependencyKey: DependencyKey {
+  static let liveValue: PermissionsManager = .live
+  static let testValue: PermissionsManager = .unimplemented
+}
+
+extension DependencyValues {
+  public var permissions: PermissionsManager {
+    get { self[PermissionsDependencyKey.self] }
+    set { self[PermissionsDependencyKey.self] = newValue }
+  }
+}
diff --git a/Sources/PermissionsFeature/PermissionBiometrics.swift b/Sources/PermissionsFeature/PermissionBiometrics.swift
new file mode 100644
index 0000000000000000000000000000000000000000..066d835af9b179233a89bc9bcebf57d8ce5a3fe4
--- /dev/null
+++ b/Sources/PermissionsFeature/PermissionBiometrics.swift
@@ -0,0 +1,65 @@
+import LocalAuthentication
+import XCTestDynamicOverlay
+
+public struct PermissionBiometrics {
+  public var status: PermissionBiometricsStatus
+  public var request: PermissionBiometricsRequest
+
+  public static let live = PermissionBiometrics(
+    status: .live,
+    request: .live
+  )
+  public static let unimplemented = PermissionBiometrics(
+    status: .unimplemented,
+    request: .unimplemented
+  )
+}
+
+public struct PermissionBiometricsStatus {
+  public var run: () -> Bool
+
+  public func callAsFunction() -> Bool {
+    run()
+  }
+
+  public static let live = PermissionBiometricsStatus {
+    var error: NSError?
+    let context = LAContext()
+
+    if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) == true {
+      return true
+    } else {
+      let tooManyAttempts = LAError.Code.biometryLockout.rawValue
+      guard let error = error, error.code == tooManyAttempts else { return true }
+      return false
+    }
+  }
+
+  public static let unimplemented = PermissionBiometricsStatus(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
+
+public struct PermissionBiometricsRequest {
+  public var run: (@escaping (Bool) -> Void) -> Void
+
+  public func callAsFunction(_ completion: @escaping (Bool) -> Void) -> Void {
+    run(completion)
+  }
+
+  public static let live = PermissionBiometricsRequest { completion in
+    let reason = "Authentication is required to use xx messenger"
+    LAContext().evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, error in
+      if let error {
+        completion(false)
+        return
+      }
+
+      completion(success)
+    }
+  }
+
+  public static let unimplemented = PermissionBiometricsRequest(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/PermissionsFeature/PermissionCamera.swift b/Sources/PermissionsFeature/PermissionCamera.swift
new file mode 100644
index 0000000000000000000000000000000000000000..1c4e263087b5428af0f7d85a54b00f83a072f71c
--- /dev/null
+++ b/Sources/PermissionsFeature/PermissionCamera.swift
@@ -0,0 +1,48 @@
+import AVFoundation
+import XCTestDynamicOverlay
+
+public struct PermissionCamera {
+  public var status: PermissionCameraStatus
+  public var request: PermissionCameraRequest
+
+  public static let live = PermissionCamera(
+    status: .live,
+    request: .live
+  )
+  public static let unimplemented = PermissionCamera(
+    status: .unimplemented,
+    request: .unimplemented
+  )
+}
+
+public struct PermissionCameraStatus {
+  public var run: () -> Bool
+
+  public func callAsFunction() -> Bool {
+    run()
+  }
+
+  public static let live = PermissionCameraStatus {
+    AVCaptureDevice.authorizationStatus(for: .video) == .authorized
+  }
+
+  public static let unimplemented = PermissionCameraStatus(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
+
+public struct PermissionCameraRequest {
+  public var run: (@escaping (Bool) -> Void) -> Void
+
+  public func callAsFunction(_ completion: @escaping (Bool) -> Void) -> Void {
+    run(completion)
+  }
+
+  public static let live = PermissionCameraRequest {
+    AVCaptureDevice.requestAccess(for: .video, completionHandler: $0)
+  }
+
+  public static let unimplemented = PermissionCameraRequest(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/PermissionsFeature/PermissionLibrary.swift b/Sources/PermissionsFeature/PermissionLibrary.swift
new file mode 100644
index 0000000000000000000000000000000000000000..485a666f58a7a6c04ff3cd425454798c6c711eda
--- /dev/null
+++ b/Sources/PermissionsFeature/PermissionLibrary.swift
@@ -0,0 +1,48 @@
+import Photos
+import XCTestDynamicOverlay
+
+public struct PermissionLibrary {
+  public var status: PermissionLibraryStatus
+  public var request: PermissionLibraryRequest
+
+  public static let live = PermissionLibrary(
+    status: .live,
+    request: .live
+  )
+  public static let unimplemented = PermissionLibrary(
+    status: .unimplemented,
+    request: .unimplemented
+  )
+}
+
+public struct PermissionLibraryStatus {
+  public var run: () -> Bool
+
+  public func callAsFunction() -> Bool {
+    run()
+  }
+
+  public static let live = PermissionLibraryStatus {
+    PHPhotoLibrary.authorizationStatus() == .authorized
+  }
+
+  public static let unimplemented = PermissionLibraryStatus(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
+
+public struct PermissionLibraryRequest {
+  public var run: (@escaping (Bool) -> Void) -> Void
+
+  public func callAsFunction(_ completion: @escaping (Bool) -> Void) -> Void {
+    run(completion)
+  }
+
+  public static let live = PermissionLibraryRequest { completion in
+    PHPhotoLibrary.requestAuthorization { completion($0 == .authorized) }
+  }
+
+  public static let unimplemented = PermissionLibraryRequest(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/PermissionsFeature/PermissionMicrophone.swift b/Sources/PermissionsFeature/PermissionMicrophone.swift
new file mode 100644
index 0000000000000000000000000000000000000000..0f5727837cbea30f05d920156d1dfd155f2f6bd4
--- /dev/null
+++ b/Sources/PermissionsFeature/PermissionMicrophone.swift
@@ -0,0 +1,56 @@
+import AVFoundation
+import XCTestDynamicOverlay
+
+public struct PermissionMicrophone {
+  public var status: PermissionMicrophoneStatus
+  public var request: PermissionMicrophoneRequest
+
+  public static let live = PermissionMicrophone(
+    status: .live,
+    request: .live
+  )
+  public static let unimplemented = PermissionMicrophone(
+    status: .unimplemented,
+    request: .unimplemented
+  )
+}
+
+public struct PermissionMicrophoneRequest {
+  public var run: (@escaping (Bool) -> Void) -> Void
+
+  public func callAsFunction(_ completion: @escaping (Bool) -> Void) -> Void {
+    run(completion)
+  }
+}
+
+extension PermissionMicrophoneRequest {
+  public static let live = PermissionMicrophoneRequest {
+    AVAudioSession.sharedInstance().requestRecordPermission($0)
+  }
+}
+
+extension PermissionMicrophoneRequest {
+  public static let unimplemented = PermissionMicrophoneRequest(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
+
+public struct PermissionMicrophoneStatus {
+  public var run: () -> Bool
+
+  public func callAsFunction() -> Bool {
+    run()
+  }
+}
+
+extension PermissionMicrophoneStatus {
+  public static let live = PermissionMicrophoneStatus {
+    AVAudioSession.sharedInstance().recordPermission == .granted
+  }
+}
+
+extension PermissionMicrophoneStatus {
+  public static let unimplemented = PermissionMicrophoneStatus(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/PermissionsFeature/PermissionPush.swift b/Sources/PermissionsFeature/PermissionPush.swift
new file mode 100644
index 0000000000000000000000000000000000000000..a4be4209b7656f59615ca95b6a89ce187ccf5515
--- /dev/null
+++ b/Sources/PermissionsFeature/PermissionPush.swift
@@ -0,0 +1,66 @@
+import UserNotifications
+import XCTestDynamicOverlay
+
+public struct PermissionPush {
+  public var status: PermissionPushStatus
+  public var request: PermissionPushRequest
+
+  public static let live = PermissionPush(
+    status: .live,
+    request: .live
+  )
+  public static let unimplemented = PermissionPush(
+    status: .unimplemented,
+    request: .unimplemented
+  )
+}
+
+public struct PermissionPushRequest {
+  public var run: (@escaping (Bool) -> Void) -> Void
+
+  public func callAsFunction(_ completion: @escaping (Bool) -> Void) -> Void {
+    run(completion)
+  }
+}
+
+extension PermissionPushRequest {
+  public static let live = PermissionPushRequest { completion in
+    let current = UNUserNotificationCenter.current()
+    current.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
+      if error != nil {
+        completion(false)
+        return
+      }
+      completion(granted)
+    }
+  }
+}
+
+extension PermissionPushRequest {
+  public static let unimplemented = PermissionPushRequest(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
+
+public struct PermissionPushStatus {
+  public var run: (@escaping (Bool) -> Void) -> Void
+
+  public func callAsFunction(_ completion: @escaping (Bool) -> Void) -> Void {
+    run(completion)
+  }
+}
+
+extension PermissionPushStatus {
+  public static let live = PermissionPushStatus { completion in
+    let current = UNUserNotificationCenter.current()
+    current.getNotificationSettings {
+      completion($0.authorizationStatus == .authorized)
+    }
+  }
+}
+
+extension PermissionPushStatus {
+  public static let unimplemented = PermissionPushStatus(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/PermissionsFeature/PermissionsManager.swift b/Sources/PermissionsFeature/PermissionsManager.swift
new file mode 100644
index 0000000000000000000000000000000000000000..33373e6e3fbf9faf738cbd80f89a24c2ee13f7eb
--- /dev/null
+++ b/Sources/PermissionsFeature/PermissionsManager.swift
@@ -0,0 +1,22 @@
+public struct PermissionsManager {
+  public var push: PermissionPush
+  public var camera: PermissionCamera
+  public var library: PermissionLibrary
+  public var microphone: PermissionMicrophone
+  public var biometrics: PermissionBiometrics
+
+  public static let live = PermissionsManager(
+    push: .live,
+    camera: .live,
+    library: .live,
+    microphone: .live,
+    biometrics: .live
+  )
+  public static let unimplemented = PermissionsManager(
+    push: .unimplemented,
+    camera: .unimplemented,
+    library: .unimplemented,
+    microphone: .unimplemented,
+    biometrics: .unimplemented
+  )
+}
diff --git a/Sources/Presentation/BottomPresenter.swift b/Sources/Presentation/BottomPresenter.swift
deleted file mode 100644
index d4d5778b09e00ffaedb3357b1d621f6017da339a..0000000000000000000000000000000000000000
--- a/Sources/Presentation/BottomPresenter.swift
+++ /dev/null
@@ -1,33 +0,0 @@
-import UIKit
-
-public final class BottomPresenter: NSObject, Presenting {
-    private var transition: BottomTransition?
-    
-    public func present(_ viewControllers: UIViewController..., from parent: UIViewController) {
-        guard let screen = viewControllers.first else {
-            fatalError("Tried to present empty list of view controllers")
-        }
-
-        screen.modalPresentationStyle = .overFullScreen
-        screen.transitioningDelegate = self
-        parent.present(screen, animated: true)
-    }
-}
-
-// MARK: UIViewControllerTransitioningDelegate
-extension BottomPresenter: UIViewControllerTransitioningDelegate {
-    public func animationController(forPresented presented: UIViewController,
-                                    presenting: UIViewController,
-                                    source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
-        transition = BottomTransition(onDismissal: { [weak self] in
-            self?.transition = nil
-        })
-        
-        return transition
-    }
-    
-    public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
-        transition?.direction = .dismiss
-        return transition
-    }
-}
diff --git a/Sources/Presentation/BottomTransition.swift b/Sources/Presentation/BottomTransition.swift
deleted file mode 100644
index 28f41a490b2607e813ecd9123b373b673e579da2..0000000000000000000000000000000000000000
--- a/Sources/Presentation/BottomTransition.swift
+++ /dev/null
@@ -1,122 +0,0 @@
-import UIKit
-import Combine
-import SnapKit
-import Shared
-
-final class BottomTransition: NSObject, UIViewControllerAnimatedTransitioning {
-    enum Direction {
-        case present
-        case dismiss
-    }
-
-    var direction: Direction = .present
-    private let onDismissal: EmptyClosure
-    private weak var darkOverlayView: UIControl?
-    private weak var topConstraint: Constraint?
-    private weak var bottomConstraint: Constraint?
-    private var cancellables = Set<AnyCancellable>()
-
-    private var presentedConstraints: [NSLayoutConstraint] = []
-    private var dismissedConstraints: [NSLayoutConstraint] = []
-
-    init(onDismissal: @escaping EmptyClosure) {
-        self.onDismissal = onDismissal
-        super.init()
-    }
-
-    func transitionDuration(using context: UIViewControllerContextTransitioning?) -> TimeInterval { 0.5 }
-
-    func animateTransition(using context: UIViewControllerContextTransitioning) {
-        switch direction {
-        case .present:
-            present(using: context)
-        case .dismiss:
-            dismiss(using: context)
-        }
-    }
-
-    private func present(using context: UIViewControllerContextTransitioning) {
-        guard let presentingController = context.viewController(forKey: .from),
-              let presentedView = context.view(forKey: .to) else {
-            context.completeTransition(false)
-            return
-        }
-
-        let darkOverlayView = UIControl()
-        self.darkOverlayView = darkOverlayView
-
-        darkOverlayView.alpha = 0.0
-        darkOverlayView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
-        context.containerView.addSubview(darkOverlayView)
-        darkOverlayView.frame = context.containerView.bounds
-
-        darkOverlayView
-            .publisher(for: .touchUpInside)
-            .sink { [weak presentingController] _ in
-                presentingController?.dismiss(animated: true)
-            }.store(in: &cancellables)
-
-        context.containerView.addSubview(presentedView)
-        presentedView.translatesAutoresizingMaskIntoConstraints = false
-
-        presentedConstraints = [
-            presentedView.leftAnchor.constraint(equalTo: context.containerView.leftAnchor),
-            presentedView.rightAnchor.constraint(equalTo: context.containerView.rightAnchor),
-            presentedView.bottomAnchor.constraint(equalTo: context.containerView.bottomAnchor),
-            presentedView.topAnchor.constraint(
-                greaterThanOrEqualTo: context.containerView.safeAreaLayoutGuide.topAnchor,
-                constant: 60
-            )
-        ]
-
-        dismissedConstraints = [
-            presentedView.leftAnchor.constraint(equalTo: context.containerView.leftAnchor),
-            presentedView.rightAnchor.constraint(equalTo: context.containerView.rightAnchor),
-            presentedView.topAnchor.constraint(equalTo: context.containerView.bottomAnchor)
-        ]
-
-        NSLayoutConstraint.activate(dismissedConstraints)
-
-        context.containerView.setNeedsLayout()
-        context.containerView.layoutIfNeeded()
-
-        NSLayoutConstraint.deactivate(dismissedConstraints)
-        NSLayoutConstraint.activate(presentedConstraints)
-
-        UIView.animate(
-            withDuration: transitionDuration(using: context),
-            delay: 0,
-            usingSpringWithDamping: 1,
-            initialSpringVelocity: 0,
-            options: .curveEaseInOut,
-            animations: {
-                darkOverlayView.alpha = 1.0
-                context.containerView.setNeedsLayout()
-                context.containerView.layoutIfNeeded()
-            },
-            completion: { _ in
-                context.completeTransition(true)
-            })
-    }
-
-    private func dismiss(using context: UIViewControllerContextTransitioning) {
-        NSLayoutConstraint.deactivate(presentedConstraints)
-        NSLayoutConstraint.activate(dismissedConstraints)
-
-        UIView.animate(
-            withDuration: transitionDuration(using: context),
-            delay: 0,
-            usingSpringWithDamping: 1,
-            initialSpringVelocity: 0,
-            options: .curveEaseInOut,
-            animations: { [weak darkOverlayView] in
-                darkOverlayView?.alpha = 0.0
-                context.containerView.setNeedsLayout()
-                context.containerView.layoutIfNeeded()
-            },
-            completion: { [weak self] _ in
-                context.completeTransition(true)
-                self?.onDismissal()
-            })
-    }
-}
diff --git a/Sources/Presentation/CenterPresenter.swift b/Sources/Presentation/CenterPresenter.swift
deleted file mode 100644
index 99277dc5708680bcd74482edc5cc88d542072381..0000000000000000000000000000000000000000
--- a/Sources/Presentation/CenterPresenter.swift
+++ /dev/null
@@ -1,35 +0,0 @@
-import UIKit
-
-public protocol CenterPresenterNonDismissingTarget: UIViewController {}
-
-public final class CenterPresenter: NSObject, Presenting {
-    private var transition: CenterTransition?
-
-    public func present(_ viewControllers: UIViewController..., from parent: UIViewController) {
-        guard let screen = viewControllers.first else {
-            fatalError("Tried to present empty list of view controllers")
-        }
-
-        screen.modalPresentationStyle = .overFullScreen
-        screen.transitioningDelegate = self
-        parent.present(screen, animated: true)
-    }
-}
-
-// MARK: UIViewControllerTransitioningDelegate
-extension CenterPresenter: UIViewControllerTransitioningDelegate {
-    public func animationController(forPresented presented: UIViewController,
-                             presenting: UIViewController,
-                             source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
-        transition = CenterTransition(
-            onDismissal: { [weak self] in self?.transition = nil },
-            dismissable: (presented is CenterPresenterNonDismissingTarget) == false)
-
-        return transition
-    }
-
-    public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
-        transition?.direction = .dismiss
-        return transition
-    }
-}
diff --git a/Sources/Presentation/CenterTransition.swift b/Sources/Presentation/CenterTransition.swift
deleted file mode 100644
index ad94401d4e69f15d116fe5a98966e7d268914985..0000000000000000000000000000000000000000
--- a/Sources/Presentation/CenterTransition.swift
+++ /dev/null
@@ -1,136 +0,0 @@
-import UIKit
-import Combine
-import SnapKit
-import Shared
-
-final class CenterTransition: NSObject, UIViewControllerAnimatedTransitioning {
-    enum Direction {
-        case present
-        case dismiss
-    }
-
-    let dismissable: Bool
-    var direction: Direction = .present
-    private let onDismissal: EmptyClosure
-    private weak var darkOverlayView: UIControl?
-    private weak var topConstraint: Constraint?
-    private weak var bottomConstraint: Constraint?
-    private var cancellables = Set<AnyCancellable>()
-
-    private var presentedConstraints: [NSLayoutConstraint] = []
-    private var dismissedConstraints: [NSLayoutConstraint] = []
-
-    init(onDismissal: @escaping EmptyClosure,
-         dismissable: Bool = true) {
-        self.dismissable = dismissable
-        self.onDismissal = onDismissal
-        super.init()
-    }
-
-    func transitionDuration(using context: UIViewControllerContextTransitioning?) -> TimeInterval { 0.25 }
-
-    func animateTransition(using context: UIViewControllerContextTransitioning) {
-        switch direction {
-        case .present:
-            present(using: context)
-        case .dismiss:
-            dismiss(using: context)
-        }
-    }
-
-    private func present(using context: UIViewControllerContextTransitioning) {
-        guard let presentingController = context.viewController(forKey: .from),
-              let presentedView = context.view(forKey: .to) else {
-            context.completeTransition(false)
-            return
-        }
-
-        let darkOverlayView = UIControl()
-        self.darkOverlayView = darkOverlayView
-
-        darkOverlayView.alpha = 0.0
-        darkOverlayView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
-        context.containerView.addSubview(darkOverlayView)
-        darkOverlayView.frame = context.containerView.bounds
-
-        if dismissable {
-            darkOverlayView
-                .publisher(for: .touchUpInside)
-                .sink { [weak presentingController] _ in
-                    presentingController?.dismiss(animated: true)
-                }
-                .store(in: &cancellables)
-        }
-
-        context.containerView.addSubview(presentedView)
-        presentedView.translatesAutoresizingMaskIntoConstraints = false
-        presentedView.alpha = 0.0
-
-        presentedView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
-
-        presentedConstraints = [
-            presentedView.leftAnchor.constraint(equalTo: context.containerView.leftAnchor, constant: 40),
-            presentedView.rightAnchor.constraint(equalTo: context.containerView.rightAnchor, constant: -40),
-            presentedView.centerYAnchor.constraint(equalTo: context.containerView.centerYAnchor)
-        ]
-
-        dismissedConstraints = [
-            presentedView.leftAnchor.constraint(equalTo: context.containerView.leftAnchor, constant: 40),
-            presentedView.rightAnchor.constraint(equalTo: context.containerView.rightAnchor, constant: -40),
-            presentedView.centerYAnchor.constraint(equalTo: context.containerView.centerYAnchor)
-        ]
-
-        NSLayoutConstraint.activate(dismissedConstraints)
-
-        context.containerView.setNeedsLayout()
-        context.containerView.layoutIfNeeded()
-
-        NSLayoutConstraint.deactivate(dismissedConstraints)
-        NSLayoutConstraint.activate(presentedConstraints)
-
-        UIView.animate(
-            withDuration: transitionDuration(using: context),
-            delay: 0,
-            usingSpringWithDamping: 1,
-            initialSpringVelocity: 0,
-            options: .curveEaseInOut,
-            animations: {
-                darkOverlayView.alpha = 1.0
-                presentedView.alpha = 1.0
-                context.containerView.setNeedsLayout()
-                context.containerView.layoutIfNeeded()
-                presentedView.transform = .identity
-            },
-            completion: { _ in
-                context.completeTransition(true)
-            })
-    }
-
-    private func dismiss(using context: UIViewControllerContextTransitioning) {
-        NSLayoutConstraint.deactivate(presentedConstraints)
-        NSLayoutConstraint.activate(dismissedConstraints)
-
-        guard let presentedView = context.view(forKey: .from) else {
-            context.completeTransition(false)
-            return
-        }
-
-        UIView.animate(
-            withDuration: transitionDuration(using: context),
-            delay: 0,
-            usingSpringWithDamping: 1,
-            initialSpringVelocity: 0,
-            options: .curveEaseInOut,
-            animations: { [weak darkOverlayView] in
-                darkOverlayView?.alpha = 0.0
-                presentedView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
-                presentedView.alpha = 0.0
-                context.containerView.setNeedsLayout()
-                context.containerView.layoutIfNeeded()
-            },
-            completion: { [weak self] _ in
-                context.completeTransition(true)
-                self?.onDismissal()
-            })
-    }
-}
diff --git a/Sources/Presentation/FadePresenter.swift b/Sources/Presentation/FadePresenter.swift
deleted file mode 100644
index d4d27c85ec5af65355d97487b06fb39f4c4fa2e3..0000000000000000000000000000000000000000
--- a/Sources/Presentation/FadePresenter.swift
+++ /dev/null
@@ -1,29 +0,0 @@
-import UIKit
-
-public final class FadePresenter: NSObject, Presenting {
-    private var transition: FadeTransition?
-
-    public func present(_ viewControllers: UIViewController..., from parent: UIViewController) {
-        guard let screen = viewControllers.first else {
-            fatalError("Tried to present empty list of view controllers")
-        }
-
-        screen.modalPresentationStyle = .overFullScreen
-        screen.transitioningDelegate = self
-        parent.present(screen, animated: true)
-    }
-}
-
-extension FadePresenter: UIViewControllerTransitioningDelegate {
-    public func animationController(forPresented presented: UIViewController,
-                                    presenting: UIViewController,
-                                    source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
-        transition = FadeTransition(didDismiss: { [weak self] in self?.transition = nil })
-        return transition
-    }
-
-    public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
-        transition?.direction = .dismiss
-        return transition
-    }
-}
diff --git a/Sources/Presentation/FadeTransition.swift b/Sources/Presentation/FadeTransition.swift
deleted file mode 100644
index 71841ccd9b4433c8d95066be3e835bffe4f49c41..0000000000000000000000000000000000000000
--- a/Sources/Presentation/FadeTransition.swift
+++ /dev/null
@@ -1,90 +0,0 @@
-import UIKit
-import Combine
-import SnapKit
-import Shared
-
-final class FadeTransition: NSObject, UIViewControllerAnimatedTransitioning {
-    enum Direction {
-        case present
-        case dismiss
-    }
-
-    var direction: Direction = .present
-    private let didDismiss: EmptyClosure
-    private weak var darkOverlayView: UIControl?
-
-    init(didDismiss: @escaping EmptyClosure) {
-        self.didDismiss = didDismiss
-        super.init()
-    }
-
-    func transitionDuration(using context: UIViewControllerContextTransitioning?) -> TimeInterval { 0.25 }
-
-    func animateTransition(using context: UIViewControllerContextTransitioning) {
-        switch direction {
-        case .present:
-            present(using: context)
-        case .dismiss:
-            dismiss(using: context)
-        }
-    }
-
-    private func present(using context: UIViewControllerContextTransitioning) {
-        guard let presentedView = context.view(forKey: .to) else {
-            context.completeTransition(false)
-            return
-        }
-
-        let darkOverlayView = UIControl()
-        self.darkOverlayView = darkOverlayView
-
-        darkOverlayView.alpha = 0.0
-        darkOverlayView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
-        context.containerView.addSubview(darkOverlayView)
-        darkOverlayView.frame = context.containerView.bounds
-
-        context.containerView.addSubview(presentedView)
-        presentedView.alpha = 0.0
-
-        presentedView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
-        presentedView.snp.makeConstraints { $0.edges.equalToSuperview() }
-
-        UIView.animate(
-            withDuration: transitionDuration(using: context),
-            delay: 0,
-            usingSpringWithDamping: 1,
-            initialSpringVelocity: 0,
-            options: .curveEaseInOut,
-            animations: {
-                darkOverlayView.alpha = 1.0
-                presentedView.alpha = 1.0
-                presentedView.transform = .identity
-            },
-            completion: { _ in
-                context.completeTransition(true)
-            })
-    }
-
-    private func dismiss(using context: UIViewControllerContextTransitioning) {
-        guard let presentedView = context.view(forKey: .from) else {
-            context.completeTransition(false)
-            return
-        }
-
-        UIView.animate(
-            withDuration: transitionDuration(using: context),
-            delay: 0,
-            usingSpringWithDamping: 1,
-            initialSpringVelocity: 0,
-            options: .curveEaseInOut,
-            animations: { [weak darkOverlayView] in
-                darkOverlayView?.alpha = 0.0
-                presentedView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
-                presentedView.alpha = 0.0
-            },
-            completion: { [weak self] _ in
-                context.completeTransition(true)
-                self?.didDismiss()
-            })
-    }
-}
diff --git a/Sources/Presentation/FullscreenPresenter.swift b/Sources/Presentation/FullscreenPresenter.swift
deleted file mode 100644
index 207c77e66aabce11e6682b32701fc6a2e483befc..0000000000000000000000000000000000000000
--- a/Sources/Presentation/FullscreenPresenter.swift
+++ /dev/null
@@ -1,33 +0,0 @@
-import UIKit
-
-public final class FullscreenPresenter: NSObject, Presenting {
-    private var transition: FullscreenTransition?
-
-    public func present(_ viewControllers: UIViewController..., from parent: UIViewController) {
-        guard let screen = viewControllers.first else {
-            fatalError("Tried to present empty list of view controllers")
-        }
-
-        screen.modalPresentationStyle = .overFullScreen
-        screen.transitioningDelegate = self
-        parent.present(screen, animated: true)
-    }
-}
-
-// MARK: UIViewControllerTransitioningDelegate
-extension FullscreenPresenter: UIViewControllerTransitioningDelegate {
-    public func animationController(forPresented presented: UIViewController,
-                                    presenting: UIViewController,
-                                    source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
-        transition = FullscreenTransition(onDismissal: { [weak self] in
-            self?.transition = nil
-        })
-
-        return transition
-    }
-
-    public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
-        transition?.direction = .dismiss
-        return transition
-    }
-}
diff --git a/Sources/Presentation/FullscreenTransition.swift b/Sources/Presentation/FullscreenTransition.swift
deleted file mode 100644
index 7a36e5beb64cbe2227890c37cd7215b87088193e..0000000000000000000000000000000000000000
--- a/Sources/Presentation/FullscreenTransition.swift
+++ /dev/null
@@ -1,120 +0,0 @@
-import UIKit
-import Combine
-import SnapKit
-import Shared
-
-final class FullscreenTransition: NSObject, UIViewControllerAnimatedTransitioning {
-    enum Direction {
-        case present
-        case dismiss
-    }
-
-    var direction: Direction = .present
-    private let onDismissal: EmptyClosure
-    private weak var darkOverlayView: UIControl?
-    private weak var topConstraint: Constraint?
-    private weak var bottomConstraint: Constraint?
-    private var cancellables = Set<AnyCancellable>()
-
-    private var presentedConstraints: [NSLayoutConstraint] = []
-    private var dismissedConstraints: [NSLayoutConstraint] = []
-
-    init(onDismissal: @escaping EmptyClosure) {
-        self.onDismissal = onDismissal
-        super.init()
-    }
-
-    func transitionDuration(using context: UIViewControllerContextTransitioning?) -> TimeInterval { 0.5 }
-
-    func animateTransition(using context: UIViewControllerContextTransitioning) {
-        switch direction {
-        case .present:
-            present(using: context)
-        case .dismiss:
-            dismiss(using: context)
-        }
-    }
-
-    private func present(using context: UIViewControllerContextTransitioning) {
-        guard let presentingController = context.viewController(forKey: .from),
-              let presentedView = context.view(forKey: .to) else {
-                  context.completeTransition(false)
-                  return
-              }
-
-        let darkOverlayView = UIControl()
-        self.darkOverlayView = darkOverlayView
-
-        darkOverlayView.alpha = 0.0
-        darkOverlayView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
-        context.containerView.addSubview(darkOverlayView)
-        darkOverlayView.frame = context.containerView.bounds
-
-        darkOverlayView
-            .publisher(for: .touchUpInside)
-            .sink { [weak presentingController] _ in
-                presentingController?.dismiss(animated: true)
-            }.store(in: &cancellables)
-
-        context.containerView.addSubview(presentedView)
-        presentedView.translatesAutoresizingMaskIntoConstraints = false
-
-        presentedConstraints = [
-            presentedView.topAnchor.constraint(equalTo: context.containerView.topAnchor),
-            presentedView.leftAnchor.constraint(equalTo: context.containerView.leftAnchor),
-            presentedView.rightAnchor.constraint(equalTo: context.containerView.rightAnchor),
-            presentedView.bottomAnchor.constraint(equalTo: context.containerView.bottomAnchor)
-        ]
-
-        dismissedConstraints = [
-            presentedView.leftAnchor.constraint(equalTo: context.containerView.leftAnchor),
-            presentedView.rightAnchor.constraint(equalTo: context.containerView.rightAnchor),
-            presentedView.topAnchor.constraint(equalTo: context.containerView.bottomAnchor),
-            presentedView.heightAnchor.constraint(equalTo: context.containerView.heightAnchor)
-        ]
-
-        NSLayoutConstraint.activate(dismissedConstraints)
-
-        context.containerView.setNeedsLayout()
-        context.containerView.layoutIfNeeded()
-
-        NSLayoutConstraint.deactivate(dismissedConstraints)
-        NSLayoutConstraint.activate(presentedConstraints)
-
-        UIView.animate(
-            withDuration: transitionDuration(using: context),
-            delay: 0,
-            usingSpringWithDamping: 1,
-            initialSpringVelocity: 0,
-            options: .curveEaseInOut,
-            animations: {
-                darkOverlayView.alpha = 1.0
-                context.containerView.setNeedsLayout()
-                context.containerView.layoutIfNeeded()
-            },
-            completion: { _ in
-                context.completeTransition(true)
-            })
-    }
-
-    private func dismiss(using context: UIViewControllerContextTransitioning) {
-        NSLayoutConstraint.deactivate(presentedConstraints)
-        NSLayoutConstraint.activate(dismissedConstraints)
-
-        UIView.animate(
-            withDuration: transitionDuration(using: context),
-            delay: 0,
-            usingSpringWithDamping: 1,
-            initialSpringVelocity: 0,
-            options: .curveEaseInOut,
-            animations: { [weak darkOverlayView] in
-                darkOverlayView?.alpha = 0.0
-                context.containerView.setNeedsLayout()
-                context.containerView.layoutIfNeeded()
-            },
-            completion: { [weak self] _ in
-                context.completeTransition(true)
-                self?.onDismissal()
-            })
-    }
-}
diff --git a/Sources/Presentation/Presenting.swift b/Sources/Presentation/Presenting.swift
deleted file mode 100644
index 1baf98d11fe94e90217c41b39c081175cd00bcb9..0000000000000000000000000000000000000000
--- a/Sources/Presentation/Presenting.swift
+++ /dev/null
@@ -1,100 +0,0 @@
-import UIKit
-import Theme
-
-public protocol Presenting {
-    func present(_ target: UIViewController..., from parent: UIViewController)
-    func dismiss(from parent: UIViewController)
-}
-
-public extension Presenting {
-    func dismiss(from parent: UIViewController) {
-        parent.dismiss(animated: true)
-    }
-}
-
-public struct PushPresenter: Presenting {
-    public init() {}
-
-    public func present(_ target: UIViewController..., from parent: UIViewController) {
-        parent.navigationController?.pushViewController(target.first!, animated: true)
-    }
-}
-
-public struct ModalPresenter: Presenting {
-    public init() {}
-
-    public func present(_ target: UIViewController..., from parent: UIViewController) {
-        let statusBarVC = StatusBarViewController(target.first!)
-        statusBarVC.modalPresentationStyle = .fullScreen
-        parent.present(statusBarVC, animated: true)
-    }
-}
-
-public struct ReplacePresenter: Presenting {
-    public enum Mode {
-        case replaceAll
-        case replaceLast
-        case replaceBackwards(AnyObject.Type)
-    }
-
-    var mode: Mode
-
-    public init(mode: Mode = .replaceAll) {
-        self.mode = mode
-    }
-
-    public func present(_ target: UIViewController..., from parent: UIViewController) {
-        guard let navigationController = parent.navigationController else { return }
-
-        switch mode {
-        case .replaceAll:
-            navigationController.setViewControllers(target, animated: true)
-
-        case .replaceBackwards(let OlderInStack):
-            if let oldScreen = navigationController.viewControllers.filter({ $0.isKind(of: OlderInStack.self) }).first,
-               let index = navigationController.viewControllers.firstIndex(of: oldScreen) {
-
-                let viewControllersBefore =
-                    navigationController.viewControllers.dropLast(
-                        navigationController.viewControllers.count - index
-                    )
-
-                if let coordinator = navigationController.transitionCoordinator {
-                    coordinator.animate(alongsideTransition: nil) { _ in
-                        navigationController.setViewControllers(viewControllersBefore + target , animated: true)
-                    }
-                } else {
-                    navigationController.setViewControllers(viewControllersBefore + target , animated: true)
-                }
-
-            } else {
-                navigationController.pushViewController(target.first!, animated: true)
-            }
-        case .replaceLast:
-            let viewControllersBefore = navigationController.viewControllers.dropLast()
-
-            func replace() {
-                navigationController.setViewControllers(viewControllersBefore + target , animated: true)
-            }
-
-            if let coordinator = navigationController.transitionCoordinator {
-                coordinator.animate(alongsideTransition: nil) { _ in
-                    replace()
-                }
-            } else {
-                replace()
-            }
-        }
-    }
-}
-
-public struct PopReplacePresenter: Presenting {
-    public init() {}
-
-    public func present(_ target: UIViewController..., from parent: UIViewController) {
-        if let lastViewController = parent.navigationController?.viewControllers.last {
-            parent.navigationController?.setViewControllers([target.first!, lastViewController], animated: false)
-            parent.navigationController?.setViewControllers([target.first!], animated: true)
-        }
-    }
-}
diff --git a/Sources/Presentation/SideMenuAnimator.swift b/Sources/Presentation/SideMenuAnimator.swift
deleted file mode 100644
index 02f302616920022d137f73b8bc704fac75883382..0000000000000000000000000000000000000000
--- a/Sources/Presentation/SideMenuAnimator.swift
+++ /dev/null
@@ -1,26 +0,0 @@
-import UIKit
-
-public protocol SideMenuAnimating {
-    func animate(in containerView: UIView, to progress: CGFloat)
-}
-
-public struct SideMenuAnimator: SideMenuAnimating {
-    public init() {}
-
-    public func animate(in containerView: UIView, to progress: CGFloat) {
-        guard let fromView = containerView.viewWithTag(SideMenuPresentTransition.fromViewTag)
-        else { return }
-
-        let cornerRadius = progress * 24
-        let shadowOpacity = Float(progress)
-        let offsetX = containerView.bounds.size.width * 0.5 * progress
-        let offsetY = containerView.bounds.size.height * 0.08 * progress
-        let scale = 1 - (0.25 * progress)
-
-        fromView.subviews.first?.layer.cornerRadius = cornerRadius
-        fromView.layer.shadowOpacity = shadowOpacity
-        fromView.transform = CGAffineTransform.identity
-            .translatedBy(x: offsetX, y: offsetY)
-            .scaledBy(x: scale, y: scale)
-    }
-}
diff --git a/Sources/Presentation/SideMenuDismissInteractor.swift b/Sources/Presentation/SideMenuDismissInteractor.swift
deleted file mode 100644
index d170ebce1aff83b1e75641ee6970a51497ccb44c..0000000000000000000000000000000000000000
--- a/Sources/Presentation/SideMenuDismissInteractor.swift
+++ /dev/null
@@ -1,73 +0,0 @@
-import UIKit
-import Shared
-
-public protocol SideMenuDismissInteracting: UIViewControllerInteractiveTransitioning {
-    var interactionInProgress: Bool { get }
-
-    func setup(view: UIView, action: @escaping EmptyClosure)
-}
-
-public final class SideMenuDismissInteractor: UIPercentDrivenInteractiveTransition, SideMenuDismissInteracting {
-    private var action: EmptyClosure?
-    private var shouldFinishTransition = false
-
-    // MARK: SideMenuDismissInteracting
-
-    public var interactionInProgress = false
-
-    public func setup(view: UIView, action: @escaping EmptyClosure) {
-        let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
-        view.addGestureRecognizer(panRecognizer)
-
-        let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
-        view.addGestureRecognizer(tapRecognizer)
-
-        self.action = action
-    }
-
-    // MARK: Gesture handling
-
-    @objc
-    private func handleTapGesture(_ recognizer: UITapGestureRecognizer) {
-        action?()
-    }
-
-    @objc
-    private func handlePanGesture(_ recognizer: UIPanGestureRecognizer) {
-        guard let view = recognizer.view,
-              let containerView = view.superview
-        else { return }
-
-        let viewWidth = containerView.bounds.size.width
-        guard viewWidth > 0 else { return }
-
-        let translation = recognizer.translation(in: view)
-        let progress = min(1, max(0, -translation.x / (viewWidth * 0.8)))
-
-        switch recognizer.state {
-        case .possible, .failed:
-            interactionInProgress = false
-
-        case .began:
-            interactionInProgress = true
-            shouldFinishTransition = false
-            action?()
-
-        case .changed:
-            shouldFinishTransition = progress >= 0.5
-            update(progress)
-
-        case .cancelled:
-            interactionInProgress = false
-            cancel()
-
-        case .ended:
-            interactionInProgress = false
-            shouldFinishTransition ? finish() : cancel()
-
-        @unknown default:
-            interactionInProgress = false
-            cancel()
-        }
-    }
-}
diff --git a/Sources/Presentation/SideMenuDismissTransition.swift b/Sources/Presentation/SideMenuDismissTransition.swift
deleted file mode 100644
index 829367fec8d564210b9f018eba1a0c6a4ac9a04b..0000000000000000000000000000000000000000
--- a/Sources/Presentation/SideMenuDismissTransition.swift
+++ /dev/null
@@ -1,31 +0,0 @@
-import UIKit
-
-final class SideMenuDismissTransition: NSObject, UIViewControllerAnimatedTransitioning {
-
-    init(menuAnimator: SideMenuAnimating,
-         viewAnimator: UIViewAnimating.Type) {
-        self.menuAnimator = menuAnimator
-        self.viewAnimator = viewAnimator
-        super.init()
-    }
-
-    let menuAnimator: SideMenuAnimating
-    let viewAnimator: UIViewAnimating.Type
-
-    // MARK: UIViewControllerAnimatedTransitioning
-
-    func transitionDuration(using context: UIViewControllerContextTransitioning?) -> TimeInterval { 0.25 }
-
-    func animateTransition(using context: UIViewControllerContextTransitioning) {
-        viewAnimator.animate(
-            withDuration: transitionDuration(using: context),
-            animations: {
-                self.menuAnimator.animate(in: context.containerView, to: 0)
-            },
-            completion: { _ in
-                let isCancelled = context.transitionWasCancelled
-                context.completeTransition(isCancelled == false)
-            }
-        )
-    }
-}
diff --git a/Sources/Presentation/SideMenuPresentTransition.swift b/Sources/Presentation/SideMenuPresentTransition.swift
deleted file mode 100644
index a29d7aea09e7e6414116bfb4ebd38da78b7d07c8..0000000000000000000000000000000000000000
--- a/Sources/Presentation/SideMenuPresentTransition.swift
+++ /dev/null
@@ -1,67 +0,0 @@
-import UIKit
-import Shared
-
-final class SideMenuPresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
-    static let fromViewTag = UUID().hashValue
-
-    init(
-        dismissInteractor: SideMenuDismissInteracting,
-        menuAnimator: SideMenuAnimating,
-        viewAnimator: UIViewAnimating.Type
-    ) {
-        self.dismissInteractor = dismissInteractor
-        self.menuAnimator = menuAnimator
-        self.viewAnimator = viewAnimator
-        super.init()
-    }
-
-    let dismissInteractor: SideMenuDismissInteracting
-    let menuAnimator: SideMenuAnimating
-    let viewAnimator: UIViewAnimating.Type
-
-    // MARK: UIViewControllerAnimatedTransitioning
-
-    func transitionDuration(using context: UIViewControllerContextTransitioning?) -> TimeInterval { 0.25 }
-
-    func animateTransition(using context: UIViewControllerContextTransitioning) {
-        guard let fromVC = context.viewController(forKey: .from),
-              let fromSnapshot = fromVC.view.snapshotView(afterScreenUpdates: true),
-              let toVC = context.viewController(forKey: .to)
-        else {
-            context.completeTransition(false)
-            return
-        }
-
-        context.containerView.addSubview(toVC.view)
-        toVC.view.frame = context.containerView.bounds
-
-        let fromView = UIView()
-        fromView.tag = Self.fromViewTag
-        context.containerView.addSubview(fromView)
-        fromView.frame = context.containerView.bounds
-        fromView.layer.shadowColor = UIColor.black.cgColor
-        fromView.layer.shadowOpacity = 1
-        fromView.layer.shadowOffset = .zero
-        fromView.layer.shadowRadius = 32
-        fromView.addSubview(fromSnapshot)
-        fromSnapshot.frame = fromView.bounds
-        fromSnapshot.layer.cornerRadius = 0
-        fromSnapshot.layer.masksToBounds = true
-
-        dismissInteractor.setup(
-            view: fromView,
-            action: { fromVC.dismiss(animated: true) }
-        )
-
-        viewAnimator.animate(
-            withDuration: transitionDuration(using: context),
-            animations: {
-                self.menuAnimator.animate(in: context.containerView, to: 1)
-            },
-            completion: { _ in
-                let isCancelled = context.transitionWasCancelled
-                context.completeTransition(isCancelled == false)
-            }
-        )
-    }
-}
diff --git a/Sources/Presentation/SideMenuPresenter.swift b/Sources/Presentation/SideMenuPresenter.swift
deleted file mode 100644
index 5d9036dee7ef729973eeef8cf4f5d56e7b3d0bab..0000000000000000000000000000000000000000
--- a/Sources/Presentation/SideMenuPresenter.swift
+++ /dev/null
@@ -1,56 +0,0 @@
-import UIKit
-
-public final class SideMenuPresenter: NSObject,
-                                      Presenting,
-                                      UIViewControllerTransitioningDelegate {
-
-    public init(dismissInteractor: SideMenuDismissInteracting = SideMenuDismissInteractor(),
-                menuAnimator: SideMenuAnimating = SideMenuAnimator(),
-                viewAnimator: UIViewAnimating.Type = UIView.self) {
-        self.dismissInteractor = dismissInteractor
-        self.menuAnimator = menuAnimator
-        self.viewAnimator = viewAnimator
-        super.init()
-    }
-
-    let dismissInteractor: SideMenuDismissInteracting
-    let menuAnimator: SideMenuAnimating
-    let viewAnimator: UIViewAnimating.Type
-    
-    // MARK: Presenting
-
-    public func present(_ viewControllers: UIViewController..., from parent: UIViewController) {
-        guard let screen = viewControllers.first else {
-            fatalError("Tried to present empty list of view controllers")
-        }
-
-        screen.modalPresentationStyle = .overFullScreen
-        screen.transitioningDelegate = self
-        parent.present(screen, animated: true)
-    }
-
-    // MARK: UIViewControllerTransitioningDelegate
-
-    public func animationController(
-        forPresented presented: UIViewController,
-        presenting: UIViewController,
-        source: UIViewController
-    ) -> UIViewControllerAnimatedTransitioning? {
-        SideMenuPresentTransition(dismissInteractor: dismissInteractor,
-                                  menuAnimator: menuAnimator,
-                                  viewAnimator: viewAnimator)
-    }
-
-    public func animationController(
-        forDismissed dismissed: UIViewController
-    ) -> UIViewControllerAnimatedTransitioning? {
-        SideMenuDismissTransition(menuAnimator: menuAnimator,
-                                  viewAnimator: viewAnimator)
-    }
-
-    public func interactionControllerForDismissal(
-        using animator: UIViewControllerAnimatedTransitioning
-    ) -> UIViewControllerInteractiveTransitioning? {
-        dismissInteractor.interactionInProgress ? dismissInteractor : nil
-    }
-}
diff --git a/Sources/Presentation/UIViewAnimating.swift b/Sources/Presentation/UIViewAnimating.swift
deleted file mode 100644
index 63210a2c3ef1b1cd921540d0f994418c18f089a5..0000000000000000000000000000000000000000
--- a/Sources/Presentation/UIViewAnimating.swift
+++ /dev/null
@@ -1,12 +0,0 @@
-import UIKit
-import Shared
-
-public protocol UIViewAnimating {
-  static func animate(
-    withDuration duration: TimeInterval,
-    animations: @escaping EmptyClosure,
-    completion: ((Bool) -> Void)?
-  )
-}
-
-extension UIView: UIViewAnimating {}
diff --git a/Sources/ProcessBannedList/Dependency.swift b/Sources/ProcessBannedList/Dependency.swift
new file mode 100644
index 0000000000000000000000000000000000000000..1f02756a5541c2907e9cb6f23fe45510c43afa16
--- /dev/null
+++ b/Sources/ProcessBannedList/Dependency.swift
@@ -0,0 +1,13 @@
+import Dependencies
+
+private enum ProcessBannedListDependencyKey: DependencyKey {
+  static let liveValue: ProcessBannedList = .live
+  static let testValue: ProcessBannedList = .unimplemented
+}
+
+extension DependencyValues {
+  public var processBannedList: ProcessBannedList {
+    get { self[ProcessBannedListDependencyKey.self] }
+    set { self[ProcessBannedListDependencyKey.self] = newValue }
+  }
+}
diff --git a/Sources/ProcessBannedList/ProcessBannedList.swift b/Sources/ProcessBannedList/ProcessBannedList.swift
new file mode 100644
index 0000000000000000000000000000000000000000..900738b323abf85587789fa040e891563cd9f85a
--- /dev/null
+++ b/Sources/ProcessBannedList/ProcessBannedList.swift
@@ -0,0 +1,64 @@
+import Foundation
+import SwiftCSV
+import XCTestDynamicOverlay
+
+public struct ProcessBannedList {
+  public enum ElementError: Swift.Error {
+    case missingUserId
+    case invalidUserId(String)
+  }
+
+  public enum Error: Swift.Error {
+    case invalidData
+    case csv(Swift.Error)
+  }
+
+  public typealias ForEach = (Result<Data, ElementError>) -> Void
+  public typealias Completion = (Result<Void, Error>) -> Void
+
+  public var run: (Data, ForEach, Completion) -> Void
+
+  public func callAsFunction(
+    data: Data,
+    forEach: ForEach,
+    completion: Completion
+  ) {
+    run(data, forEach, completion)
+  }
+}
+
+extension ProcessBannedList {
+  public static let live = ProcessBannedList { data, forEach, completion in
+    guard let csvString = String(data: data, encoding: .utf8) else {
+      completion(.failure(.invalidData))
+      return
+    }
+    let csv: EnumeratedCSV
+    do {
+      csv = try EnumeratedCSV(string: csvString)
+    }
+    catch {
+      completion(.failure(.csv(error)))
+      return
+    }
+    csv.rows.forEach { row in
+      guard let userIdString = row.first else {
+        forEach(.failure(.missingUserId))
+        return
+      }
+      guard let userId = Data(base64Encoded: userIdString) else {
+        forEach(.failure(.invalidUserId(userIdString)))
+        return
+      }
+      forEach(.success(userId))
+    }
+    completion(.success(()))
+  }
+}
+
+extension ProcessBannedList {
+  public static let unimplemented = ProcessBannedList { _, _, _ in
+    let run: () -> Void = XCTUnimplemented("\(Self.self)")
+    run()
+  }
+}
diff --git a/Sources/ProfileFeature/Controllers/ProfileCodeController.swift b/Sources/ProfileFeature/Controllers/ProfileCodeController.swift
index d612b9e9e79c4dbb362b5a0c7782eb1463dc4fe6..d5d640db0263cf29abda90a9b1ec962e89e9081c 100644
--- a/Sources/ProfileFeature/Controllers/ProfileCodeController.swift
+++ b/Sources/ProfileFeature/Controllers/ProfileCodeController.swift
@@ -1,123 +1,137 @@
-import HUD
 import UIKit
-import Models
 import Shared
 import Combine
-import Countries
-import DependencyInjection
+import AppCore
+import AppResources
+import Dependencies
+import AppNavigation
 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()
-
-    private let completion: ControllerClosure
-    private let confirmation: AttributeConfirmation
-    private var cancellables = Set<AnyCancellable>()
-    lazy private var viewModel = ProfileCodeViewModel(confirmation)
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        navigationController?.navigationBar
-            .customize(backgroundColor: Asset.neutralWhite.color)
-    }
-
-    public init(
-        _ confirmation: AttributeConfirmation,
-        _ completion: @escaping ControllerClosure
-    ) {
-        self.completion = completion
-        self.confirmation = confirmation
-        super.init(nibName: nil, bundle: nil)
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+
+  private lazy var screenView = ProfileCodeView()
+  private lazy var scrollViewController = ScrollViewController()
+
+  private let isEmail: Bool
+  private let content: String
+  private let viewModel: ProfileCodeViewModel
+  private var cancellables = Set<AnyCancellable>()
+
+  public init(
+    _ isEmail: Bool,
+    _ content: String,
+    _ confirmationId: String
+  ) {
+    self.viewModel = .init(
+      isEmail: isEmail,
+      content: content,
+      confirmationId: confirmationId
+    )
+    self.isEmail = isEmail
+    self.content = content
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    navigationController?.navigationBar
+      .customize(backgroundColor: Asset.neutralWhite.color)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupScrollView()
+    setupBindings()
+
+    if isEmail {
+      screenView.set(content, isEmail: true)
+    } else {
+      let country = Country.findFrom(content)
+      screenView.set(
+        "\(country.prefix)\(content.dropLast(2))",
+        isEmail: false
+      )
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupScrollView()
-        setupBindings()
-        setupDetail()
-    }
-
-    private func setupScrollView() {
-        addChild(scrollViewController)
-        view.addSubview(scrollViewController.view)
-        scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
-        scrollViewController.didMove(toParent: self)
-        scrollViewController.contentView = screenView
-        scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color
+  private func setupScrollView() {
+    addChild(scrollViewController)
+    view.addSubview(scrollViewController.view)
+    scrollViewController.view.snp.makeConstraints {
+      $0.edges.equalToSuperview()
     }
-
-    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)
-
-        viewModel.state
-            .map(\.status)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                switch $0 {
-                case .valid:
-                    screenView.saveButton.isEnabled = true
-                case .invalid, .unknown:
-                    screenView.saveButton.isEnabled = false
-                }
-            }.store(in: &cancellables)
-
-        screenView.saveButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in viewModel.didTapNext() }
-            .store(in: &cancellables)
-
-        viewModel.state
-            .map(\.resendDebouncer)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                screenView.resendButton.isEnabled = $0 == 0
-
-                if $0 == 0 {
-                    screenView.resendButton.setTitle(Localized.Profile.Code.resend(""), for: .normal)
-                } else {
-                    screenView.resendButton.setTitle(Localized.Profile.Code.resend("(\($0))"), for: .disabled)
-                }
-            }.store(in: &cancellables)
-
-        screenView.resendButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in viewModel.didTapResend() }
-            .store(in: &cancellables)
-
-        viewModel.completionPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in completion(self, $0) }
-            .store(in: &cancellables)
-    }
-
-    private func setupDetail() {
-        var content: String!
-
-        if confirmation.isEmail {
-            content = confirmation.content
+    scrollViewController.didMove(toParent: self)
+    scrollViewController.contentView = screenView
+    scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color
+  }
+
+  private func setupBindings() {
+    screenView
+      .inputField
+      .textPublisher
+      .sink { [unowned self] in
+        viewModel.didInput($0)
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.status)
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        switch $0 {
+        case .valid:
+          screenView.saveButton.isEnabled = true
+        case .invalid, .unknown:
+          screenView.saveButton.isEnabled = false
+        }
+      }.store(in: &cancellables)
+
+    screenView
+      .saveButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        viewModel.didTapNext()
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.resendDebouncer)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.resendButton.isEnabled = $0 == 0
+        if $0 == 0 {
+          screenView.resendButton.setTitle(
+            Localized.Profile.Code.resend(""), for: .normal
+          )
         } else {
-            let country = Country.findFrom(confirmation.content)
-            content = "\(country.prefix)\(confirmation.content.dropLast(2))"
+          screenView.resendButton.setTitle(
+            Localized.Profile.Code.resend("(\($0))"), for: .disabled
+          )
         }
-
-        screenView.set(content, isEmail: confirmation.isEmail)
-    }
+      }.store(in: &cancellables)
+
+    screenView
+      .resendButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        viewModel.didTapResend()
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.didConfirm)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        guard let navigationController, $0 == true else { return }
+        navigator.perform(PopToRoot(on: navigationController))
+      }.store(in: &cancellables)
+  }
 }
diff --git a/Sources/ProfileFeature/Controllers/ProfileController.swift b/Sources/ProfileFeature/Controllers/ProfileController.swift
index e3138f275dd967d73d58548649e4156409178e57..23dca61bf795b7ede333f78d8ed0c2d7fdfa2588 100644
--- a/Sources/ProfileFeature/Controllers/ProfileController.swift
+++ b/Sources/ProfileFeature/Controllers/ProfileController.swift
@@ -1,225 +1,228 @@
-import HUD
-import DrawerFeature
 import UIKit
-import Theme
 import Shared
 import Combine
-import DependencyInjection
+import AppCore
+import AppResources
+import AppNavigation
+import DrawerFeature
+import ComposableArchitecture
 
 public final class ProfileController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: ProfileCoordinating
-    @Dependency private var statusBarController: StatusBarStyleControlling
-
-    lazy private var screenView = ProfileView()
-
-    private let viewModel = ProfileViewModel()
-    private var cancellables = Set<AnyCancellable>()
-    private var drawerCancellables = Set<AnyCancellable>()
-
-    public override func loadView() {
-        view = screenView
-    }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        statusBarController.style.send(.lightContent)
-        navigationController?.navigationBar
-            .customize(backgroundColor: Asset.neutralBody.color)
-        viewModel.refresh()
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        screenView.cardComponent.nameLabel.text = viewModel.username!
-        setupNavigationBar()
-        setupBindings()
-    }
-
-    private func setupNavigationBar() {
-        navigationItem.backButtonTitle = ""
-
-        let menuButton = UIButton()
-        menuButton.tintColor = Asset.neutralWhite.color
-        menuButton.setImage(Asset.chatListMenu.image, for: .normal)
-        menuButton.addTarget(self, action: #selector(didTapMenu), for: .touchUpInside)
-        menuButton.snp.makeConstraints { $0.width.equalTo(50) }
-
-        navigationItem.leftBarButtonItem = UIBarButtonItem(customView: menuButton)
-    }
-
-    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)
-            .sink { [unowned self] in
-                if screenView.emailView.currentValue != nil {
-                    presentDrawer(
-                        title: Localized.Profile.Delete.title(
-                            Localized.Profile.Email.title.capitalized
-                        ),
-                        subtitle: Localized.Profile.Delete.subtitle(
-                            Localized.Profile.Email.title.lowercased(), Localized.Profile.Email.title.lowercased()
-                        ),
-                        actionTitle: Localized.Profile.Delete.action(
-                            Localized.Profile.Email.title
-                        )) {
-                            viewModel.didTapDelete(isEmail: true)
-                        }
-                } else {
-                    coordinator.toEmail(from: self)
-                }
-            }.store(in: &cancellables)
-
-        screenView.phoneView.actionButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                if screenView.phoneView.currentValue != nil {
-                    presentDrawer(
-                        title: Localized.Profile.Delete.title(
-                            Localized.Profile.Phone.title.capitalized
-                        ),
-                        subtitle: Localized.Profile.Delete.subtitle(
-                            Localized.Profile.Phone.title.lowercased(), Localized.Profile.Phone.title.lowercased()
-                        ),
-                        actionTitle: Localized.Profile.Delete.action(
-                            Localized.Profile.Phone.title
-                        )) {
-                            viewModel.didTapDelete(isEmail: false)
-                        }
-                } else {
-                    coordinator.toPhone(from: self)
-                }
-            }.store(in: &cancellables)
-
-        screenView.cardComponent.avatarView.editButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in viewModel.didRequestLibraryAccess() }
-            .store(in: &cancellables)
-
-        viewModel.navigation
-            .receive(on: DispatchQueue.main)
-            .removeDuplicates()
-            .sink { [unowned self] in
-                switch $0 {
-                case .library:
-                    presentDrawer(
-                        title: Localized.Profile.Photo.title,
-                        subtitle: Localized.Profile.Photo.subtitle,
-                        actionTitle: Localized.Profile.Photo.continue) {
-                            coordinator.toPhotos(from: self)
-                        }
-                case .libraryPermission:
-                    coordinator.toPermission(type: .library, from: self)
-                case .none:
-                    break
-                }
-
-                viewModel.didNavigateSomewhere()
-            }.store(in: &cancellables)
-
-        viewModel.state
-            .map(\.email)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.emailView.set(value: $0) }
-            .store(in: &cancellables)
-
-        viewModel.state
-            .map(\.phone)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.phoneView.set(value: $0) }
-            .store(in: &cancellables)
-
-        viewModel.state
-            .map(\.photo)
-            .compactMap { $0 }
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.cardComponent.image = $0 }
-            .store(in: &cancellables)
-    }
-
-    private func presentDrawer(
-        title: String,
-        subtitle: String,
-        actionTitle: String,
-        action: @escaping () -> Void
-    ) {
-        let actionButton = DrawerCapsuleButton(model: .init(
-            title: actionTitle,
-            style: .red
-        ))
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+
+  private lazy var screenView = ProfileView()
+
+  private let viewModel = ProfileViewModel()
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
+
+  public override func loadView() {
+    view = screenView
+  }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    statusBar.set(.lightContent)
+    navigationController?.navigationBar
+      .customize(backgroundColor: Asset.neutralBody.color)
+    viewModel.refresh()
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    screenView.cardComponent.nameLabel.text = viewModel.username!
+    setupNavigationBar()
+    setupBindings()
+  }
+
+  private func setupNavigationBar() {
+    navigationItem.backButtonTitle = ""
+
+    let menuButton = UIButton()
+    menuButton.tintColor = Asset.neutralWhite.color
+    menuButton.setImage(Asset.chatListMenu.image, for: .normal)
+    menuButton.addTarget(self, action: #selector(didTapMenu), for: .touchUpInside)
+    menuButton.snp.makeConstraints { $0.width.equalTo(50) }
+
+    navigationItem.leftBarButtonItem = UIBarButtonItem(customView: menuButton)
+  }
+
+  private func setupBindings() {
+    screenView.emailView.actionButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        if screenView.emailView.currentValue != nil {
+          presentDrawer(
+            title: Localized.Profile.Delete.title(
+              Localized.Profile.Email.title.capitalized
             ),
-            DrawerText(
-                font: Fonts.Mulish.regular.font(size: 16.0),
-                text: subtitle,
-                color: Asset.neutralBody.color,
-                alignment: .left,
-                lineHeightMultiple: 1.1,
-                spacingAfter: 37
+            subtitle: Localized.Profile.Delete.subtitle(
+              Localized.Profile.Email.title.lowercased(), Localized.Profile.Email.title.lowercased()
             ),
-            actionButton
-        ])
-
-        actionButton.action
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-
-                    action()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
-
-    @objc private func didTapMenu() {
-        coordinator.toSideMenu(from: self)
-    }
+            actionTitle: Localized.Profile.Delete.action(
+              Localized.Profile.Email.title
+            )) {
+              self.viewModel.didTapDelete(isEmail: true)
+            }
+        } else {
+          navigator.perform(PresentProfileEmail(on: navigationController!))
+        }
+      }.store(in: &cancellables)
+
+    screenView.phoneView.actionButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        if screenView.phoneView.currentValue != nil {
+          presentDrawer(
+            title: Localized.Profile.Delete.title(
+              Localized.Profile.Phone.title.capitalized
+            ),
+            subtitle: Localized.Profile.Delete.subtitle(
+              Localized.Profile.Phone.title.lowercased(), Localized.Profile.Phone.title.lowercased()
+            ),
+            actionTitle: Localized.Profile.Delete.action(
+              Localized.Profile.Phone.title
+            )) {
+              self.viewModel.didTapDelete(isEmail: false)
+            }
+        } else {
+          navigator.perform(PresentProfilePhone(on: navigationController!))
+        }
+      }.store(in: &cancellables)
+
+    screenView
+      .cardComponent
+      .avatarView
+      .editButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        viewModel.didRequestLibraryAccess()
+      }.store(in: &cancellables)
+
+    viewModel
+      .navigation
+      .receive(on: DispatchQueue.main)
+      .removeDuplicates()
+      .sink { [unowned self] in
+        switch $0 {
+        case .library:
+          presentDrawer(
+            title: Localized.Profile.Photo.title,
+            subtitle: Localized.Profile.Photo.subtitle,
+            actionTitle: Localized.Profile.Photo.continue) {
+              self.navigator.perform(PresentPhotoLibrary(from: self))
+          }
+        case .libraryPermission:
+          self.navigator.perform(PresentPermissionRequest(type: .library, from: self))
+        case .none:
+          break
+        }
+        viewModel.didNavigateSomewhere()
+      }.store(in: &cancellables)
+
+    viewModel
+      .state
+      .map(\.email)
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.emailView.set(value: $0)
+      }.store(in: &cancellables)
+
+    viewModel
+      .state
+      .map(\.phone)
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.phoneView.set(value: $0)
+      }.store(in: &cancellables)
+
+    viewModel
+      .state
+      .map(\.photo)
+      .compactMap { $0 }
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.cardComponent.image = $0
+      }.store(in: &cancellables)
+  }
+
+  private func presentDrawer(
+    title: String,
+    subtitle: String,
+    actionTitle: String,
+    action: @escaping () -> Void
+  ) {
+    let actionButton = DrawerCapsuleButton(model: .init(
+      title: actionTitle,
+      style: .red
+    ))
+
+    actionButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          action()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: title,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 19
+      ),
+      DrawerText(
+        font: Fonts.Mulish.regular.font(size: 16.0),
+        text: subtitle,
+        color: Asset.neutralBody.color,
+        alignment: .left,
+        lineHeightMultiple: 1.1,
+        spacingAfter: 37
+      ),
+      actionButton
+    ], isDismissable: true, from: self))
+  }
+
+  @objc private func didTapMenu() {
+    navigator.perform(PresentMenu(currentItem: .profile, from: self))
+  }
 }
 
 extension ProfileController: UIImagePickerControllerDelegate {
-    public func imagePickerController(
-        _ picker: UIImagePickerController,
-        didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]
-    ) {
-        var image: UIImage?
-
-        if let originalImage = info[.originalImage] as? UIImage {
-            image = originalImage
-        }
-
-        if let croppedImage = info[.editedImage] as? UIImage {
-            image = croppedImage
-        }
+  public func imagePickerController(
+    _ picker: UIImagePickerController,
+    didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]
+  ) {
+    var image: UIImage?
+
+    if let originalImage = info[.originalImage] as? UIImage {
+      image = originalImage
+    }
 
-        guard let image = image else {
-            picker.dismiss(animated: true)
-            return
-        }
+    if let croppedImage = info[.editedImage] as? UIImage {
+      image = croppedImage
+    }
 
-        picker.dismiss(animated: true)
-        viewModel.didChoosePhoto(image)
+    guard let image = image else {
+      picker.dismiss(animated: true)
+      return
     }
+
+    picker.dismiss(animated: true)
+    viewModel.didChoosePhoto(image)
+  }
 }
 
 extension ProfileController: UINavigationControllerDelegate {}
diff --git a/Sources/ProfileFeature/Controllers/ProfileEmailController.swift b/Sources/ProfileFeature/Controllers/ProfileEmailController.swift
index 3fb88d2b649ed6f9f20b8189467a021563188c13..ef9ef2f8f501ae6160d96cd72376d33222d9cdac 100644
--- a/Sources/ProfileFeature/Controllers/ProfileEmailController.swift
+++ b/Sources/ProfileFeature/Controllers/ProfileEmailController.swift
@@ -1,84 +1,91 @@
-import HUD
 import UIKit
 import Shared
 import Combine
-import Theme
-import DependencyInjection
+import AppCore
+import AppResources
+import AppNavigation
 import ScrollViewController
+import ComposableArchitecture
 
 public final class ProfileEmailController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: ProfileCoordinating
-    @Dependency private var statusBarController: StatusBarStyleControlling
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
 
-    lazy private var screenView = ProfileEmailView()
-    lazy private var scrollViewController = ScrollViewController()
+  private lazy var screenView = ProfileEmailView()
+  private lazy var scrollViewController = ScrollViewController()
 
-    private let viewModel = ProfileEmailViewModel()
-    private var cancellables = Set<AnyCancellable>()
+  private let viewModel = ProfileEmailViewModel()
+  private var cancellables = Set<AnyCancellable>()
 
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        statusBarController.style.send(.darkContent)
-        navigationController?.navigationBar
-            .customize(backgroundColor: Asset.neutralWhite.color)
-    }
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    statusBar.set(.darkContent)
+    navigationController?.navigationBar.customize(backgroundColor: Asset.neutralWhite.color)
+  }
 
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupScrollView()
-        setupBindings()
-    }
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupScrollView()
+    setupBindings()
+  }
 
-    private func setupScrollView() {
-        addChild(scrollViewController)
-        view.addSubview(scrollViewController.view)
-        scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
-        scrollViewController.didMove(toParent: self)
-        scrollViewController.contentView = screenView
-        scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color
+  private func setupScrollView() {
+    addChild(scrollViewController)
+    view.addSubview(scrollViewController.view)
+    scrollViewController.view.snp.makeConstraints {
+      $0.edges.equalToSuperview()
     }
+    scrollViewController.didMove(toParent: self)
+    scrollViewController.contentView = screenView
+    scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color
+  }
 
-    private func setupBindings() {
-        viewModel.hud
-            .receive(on: DispatchQueue.main)
-            .sink { [hud] in hud.update(with: $0) }
-            .store(in: &cancellables)
+  private func setupBindings() {
+    screenView
+      .inputField
+      .textPublisher
+      .sink { [unowned self] in
+        viewModel.didInput($0)
+      }.store(in: &cancellables)
 
-        screenView.inputField.textPublisher
-            .sink { [unowned self] in viewModel.didInput($0) }
-            .store(in: &cancellables)
+    screenView
+      .inputField
+      .returnPublisher
+      .sink { [unowned self] in
+        screenView.inputField.endEditing(true)
+      }.store(in: &cancellables)
 
-        screenView.inputField.returnPublisher
-            .sink { [unowned self] in screenView.inputField.endEditing(true) }
-            .store(in: &cancellables)
+    viewModel
+      .statePublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        guard let id = $0.confirmationId else { return }
+        viewModel.clearUp()
+        navigator.perform(
+          PresentProfileCode(
+            isEmail: true,
+            content: $0.input,
+            confirmationId: id,
+            on: navigationController!
+          )
+        )
+      }.store(in: &cancellables)
 
-        viewModel.state
-            .map(\.confirmation)
-            .receive(on: DispatchQueue.main)
-            .compactMap { $0 }
-            .sink { [unowned self] in
-                viewModel.clearUp()
-                coordinator.toCode(with: $0, from: self) { _, _ in
-                    if let viewControllers = navigationController?.viewControllers {
-                        navigationController?.popToViewController(
-                            viewControllers[viewControllers.count - 3],
-                            animated: true
-                        )
-                    }
-                }
-            }
-            .store(in: &cancellables)
+    viewModel
+      .statePublisher
+      .map(\.status)
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.update(status: $0)
+      }.store(in: &cancellables)
 
-        viewModel.state.map(\.status)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.update(status: $0) }
-            .store(in: &cancellables)
-
-        screenView.saveButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewModel.didTapNext() }
-            .store(in: &cancellables)
-    }
+    screenView
+      .saveButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        viewModel.didTapNext()
+      }.store(in: &cancellables)
+  }
 }
diff --git a/Sources/ProfileFeature/Controllers/ProfilePhoneController.swift b/Sources/ProfileFeature/Controllers/ProfilePhoneController.swift
index 01737802adc9611476892ca38675b5dd8ff6d743..99ac70b864328dd362fe88a2487fc3ba8fec2ce2 100644
--- a/Sources/ProfileFeature/Controllers/ProfilePhoneController.swift
+++ b/Sources/ProfileFeature/Controllers/ProfilePhoneController.swift
@@ -1,102 +1,113 @@
-import HUD
 import UIKit
 import Shared
 import Combine
-import Theme
-import DependencyInjection
+import AppCore
+import AppResources
+import Dependencies
+import AppNavigation
 import ScrollViewController
 
-#warning("TODO: Merge ProfilePhoneController/ProfileEmailController")
-
 public final class ProfilePhoneController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: ProfileCoordinating
-    @Dependency private var statusBarController: StatusBarStyleControlling
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
 
-    lazy private var screenView = ProfilePhoneView()
-    lazy private var scrollViewController = ScrollViewController()
+  private lazy var screenView = ProfilePhoneView()
+  private lazy var scrollViewController = ScrollViewController()
 
-    private let viewModel = ProfilePhoneViewModel()
-    private var cancellables = Set<AnyCancellable>()
+  private let viewModel = ProfilePhoneViewModel()
+  private var cancellables = Set<AnyCancellable>()
 
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        statusBarController.style.send(.darkContent)
-        navigationController?.navigationBar
-            .customize(backgroundColor: Asset.neutralWhite.color)
-    }
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    statusBar.set(.darkContent)
+    navigationController?.navigationBar
+      .customize(backgroundColor: Asset.neutralWhite.color)
+  }
 
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupScrollView()
-        setupBindings()
-    }
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupScrollView()
+    setupBindings()
+  }
 
-    private func setupScrollView() {
-        addChild(scrollViewController)
-        view.addSubview(scrollViewController.view)
-        scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
-        scrollViewController.didMove(toParent: self)
-        scrollViewController.contentView = screenView
-        scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color
+  private func setupScrollView() {
+    addChild(scrollViewController)
+    view.addSubview(scrollViewController.view)
+    scrollViewController.view.snp.makeConstraints {
+      $0.edges.equalToSuperview()
     }
+    scrollViewController.didMove(toParent: self)
+    scrollViewController.contentView = screenView
+    scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color
+  }
 
-    private func setupBindings() {
-        viewModel.hud
-            .receive(on: DispatchQueue.main)
-            .sink { [hud] in hud.update(with: $0) }
-            .store(in: &cancellables)
+  private func setupBindings() {
+    screenView
+      .inputField
+      .textPublisher
+      .sink { [unowned self] in
+        viewModel.didInput($0)
+      }.store(in: &cancellables)
 
-        screenView.inputField.textPublisher
-            .sink { [unowned self] in viewModel.didInput($0) }
-            .store(in: &cancellables)
+    screenView
+      .inputField
+      .returnPublisher
+      .sink { [unowned self] in
+        screenView.inputField.endEditing(true)
+      }.store(in: &cancellables)
 
-        screenView.inputField.returnPublisher
-            .sink { [unowned self] in screenView.inputField.endEditing(true) }
-            .store(in: &cancellables)
+    screenView
+      .inputField
+      .codePublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentCountryList(completion: { [weak self] in
+          guard let self else { return }
+          self.viewModel.didChooseCountry($0 as! Country)
+        }, from: self))
+      }.store(in: &cancellables)
 
-        screenView.inputField.codePublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                coordinator.toCountries(from: self) { viewModel.didChooseCountry($0) }
-            }.store(in: &cancellables)
+    viewModel
+      .statePublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        guard let id = $0.confirmationId, let content = $0.content else { return }
+        viewModel.clearUp()
+        navigator.perform(
+          PresentProfileCode(
+            isEmail: false,
+            content: content,
+            confirmationId: id,
+            on: navigationController!
+          )
+        )
+      }.store(in: &cancellables)
 
-        viewModel.state
-            .map(\.confirmation)
-            .receive(on: DispatchQueue.main)
-            .compactMap { $0 }
-            .sink { [unowned self] in
-                viewModel.clearUp()
-                coordinator.toCode(with: $0, from: self) { _, _ in
-                    if let viewControllers = navigationController?.viewControllers {
-                        navigationController?.popToViewController(
-                            viewControllers[viewControllers.count - 3],
-                            animated: true
-                        )
-                    }
-                }
-            }.store(in: &cancellables)
+    viewModel
+      .statePublisher
+      .map(\.country)
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.inputField.set(prefix: $0.prefixWithFlag)
+        screenView.inputField.update(placeholder: $0.example)
+      }.store(in: &cancellables)
 
-        viewModel.state
-            .map(\.country)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                screenView.inputField.set(prefix: $0.prefixWithFlag)
-                screenView.inputField.update(placeholder: $0.example)
-            }
-            .store(in: &cancellables)
+    viewModel
+      .statePublisher
+      .map(\.status)
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.update(status: $0)
+      }.store(in: &cancellables)
 
-        viewModel.state
-            .map(\.status)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.update(status: $0) }
-            .store(in: &cancellables)
-
-        screenView.saveButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewModel.didTapNext() }
-            .store(in: &cancellables)
-    }
+    screenView
+      .saveButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        viewModel.didTapNext()
+      }.store(in: &cancellables)
+  }
 }
diff --git a/Sources/ProfileFeature/Coordinator/ProfileCoordinator.swift b/Sources/ProfileFeature/Coordinator/ProfileCoordinator.swift
deleted file mode 100644
index 947937b403a4f29cee289e590908589a66c57754..0000000000000000000000000000000000000000
--- a/Sources/ProfileFeature/Coordinator/ProfileCoordinator.swift
+++ /dev/null
@@ -1,108 +0,0 @@
-import UIKit
-import Shared
-import Models
-import Countries
-import Permissions
-import MenuFeature
-import Presentation
-
-public protocol ProfileCoordinating {
-    func toEmail(from: UIViewController)
-    func toPhone(from: UIViewController)
-    func toPhotos(from: UIViewController)
-    func toSideMenu(from: UIViewController)
-    func toDrawer(_: UIViewController, from: UIViewController)
-    func toPermission(type: PermissionType, from: UIViewController)
-
-    func toCode(
-        with: AttributeConfirmation,
-        from: UIViewController,
-        _: @escaping ControllerClosure
-    )
-
-    func toCountries(
-        from: UIViewController,
-        _: @escaping (Country) -> Void
-    )
-}
-
-public struct ProfileCoordinator: ProfileCoordinating {
-    var pushPresenter: Presenting = PushPresenter()
-    var modalPresenter: Presenting = ModalPresenter()
-    var sidePresenter: Presenting = SideMenuPresenter()
-    var bottomPresenter: Presenting = BottomPresenter()
-
-    var emailFactory: () -> UIViewController
-    var phoneFactory: () -> UIViewController
-    var imagePickerFactory: () -> UIImagePickerController
-    var permissionFactory: () -> RequestPermissionController
-    var sideMenuFactory: (MenuItem, UIViewController) -> UIViewController
-    var countriesFactory: (@escaping (Country) -> Void) -> UIViewController
-    var codeFactory: (AttributeConfirmation, @escaping ControllerClosure) -> UIViewController
-
-    public init(
-        emailFactory: @escaping () -> UIViewController,
-        phoneFactory: @escaping () -> UIViewController,
-        imagePickerFactory: @escaping () -> UIImagePickerController,
-        permissionFactory: @escaping () -> RequestPermissionController, // ⚠️
-        sideMenuFactory: @escaping (MenuItem, UIViewController) -> UIViewController,
-        countriesFactory: @escaping (@escaping (Country) -> Void) -> UIViewController,
-        codeFactory: @escaping (AttributeConfirmation, @escaping ControllerClosure) -> UIViewController
-    ) {
-        self.codeFactory = codeFactory
-        self.emailFactory = emailFactory
-        self.phoneFactory = phoneFactory
-        self.sideMenuFactory = sideMenuFactory
-        self.countriesFactory = countriesFactory
-        self.permissionFactory = permissionFactory
-        self.imagePickerFactory = imagePickerFactory
-    }
-}
-
-public extension ProfileCoordinator {
-    func toEmail(from parent: UIViewController) {
-        let screen = emailFactory()
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toPhone(from parent: UIViewController) {
-        let screen = phoneFactory()
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toCode(
-        with confirmation: AttributeConfirmation,
-        from parent: UIViewController,
-        _ completion: @escaping ControllerClosure
-    ) {
-        let screen = codeFactory(confirmation, completion)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toPermission(type: PermissionType, from parent: UIViewController) {
-        let screen = permissionFactory()
-        screen.setup(type: type)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toDrawer(_ drawer: UIViewController, from parent: UIViewController) {
-        bottomPresenter.present(drawer, from: parent)
-    }
-
-    func toCountries(from parent: UIViewController, _ onChoose: @escaping (Country) -> Void) {
-        let screen = countriesFactory(onChoose)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toPhotos(from parent: UIViewController) {
-        let screen = imagePickerFactory()
-        screen.delegate = (parent as? (UIImagePickerControllerDelegate & UINavigationControllerDelegate))
-        screen.allowsEditing = true
-        modalPresenter.present(screen, from: parent)
-    }
-
-    func toSideMenu(from parent: UIViewController) {
-        let screen = sideMenuFactory(.profile, parent)
-        sidePresenter.present(screen, from: parent)
-    }
-}
diff --git a/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift
index d9763e989e23e37c9736582a41fcf1b63aef03b2..a018da77f237fc1c4b604ea791aa0289452638d0 100644
--- a/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift
@@ -1,88 +1,96 @@
-import HUD
 import Shared
-import Models
 import Combine
+import AppCore
+import Defaults
+import XXClient
 import InputField
-import Integration
-import CombineSchedulers
-import DependencyInjection
+import Foundation
+import XXMessengerClient
+import ComposableArchitecture
 
-struct ProfileCodeViewState: Equatable {
+final class ProfileCodeViewModel {
+  struct ViewState: Equatable {
     var input: String = ""
     var status: InputField.ValidationStatus = .unknown(nil)
     var resendDebouncer: Int = 0
-}
-
-final class ProfileCodeViewModel {
-    @Dependency private var session: SessionType
-
-    let confirmation: AttributeConfirmation
-
-    var timer: Timer?
-
-    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())
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
-
-    init(_ confirmation: AttributeConfirmation) {
-        self.confirmation = confirmation
-        didTapResend()
-    }
-
-    func didInput(_ string: String) {
-        stateRelay.value.input = string
-        validate()
+    var didConfirm: Bool = false
+  }
+
+  var statePublisher: AnyPublisher<ViewState, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
+
+  @Dependency(\.app.messenger) var messenger: Messenger
+  @Dependency(\.app.hudManager) var hudManager: HUDManager
+  @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue>
+
+  @KeyObject(.email, defaultValue: nil) var email: String?
+  @KeyObject(.phone, defaultValue: nil) var phone: String?
+
+  private var timer: Timer?
+  private let isEmail: Bool
+  private let content: String
+  private let confirmationId: String
+  private let stateSubject = CurrentValueSubject<ViewState, Never>(.init())
+
+  init(
+    isEmail: Bool,
+    content: String,
+    confirmationId: String
+  ) {
+    self.isEmail = isEmail
+    self.content = content
+    self.confirmationId = confirmationId
+    didTapResend()
+  }
+
+  func didInput(_ string: String) {
+    stateSubject.value.input = string
+    validate()
+  }
+
+  func didTapResend() {
+    guard stateSubject.value.resendDebouncer == 0 else { return }
+    stateSubject.value.resendDebouncer = 60
+    timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {  [weak self] in
+      guard let self, self.stateSubject.value.resendDebouncer > 0 else {
+        $0.invalidate()
+        return
+      }
+      self.stateSubject.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() {
-        hudRelay.send(.on)
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.session.confirm(
-                    code: self.stateRelay.value.input,
-                    confirmation: self.confirmation
-                )
-
-                self.timer?.invalidate()
-                self.hudRelay.send(.none)
-                self.completionRelay.send(self.confirmation)
-            } catch {
-                self.hudRelay.send(.error(.init(with: error)))
-            }
+  }
+
+  func didTapNext() {
+    hudManager.show()
+    bgQueue.schedule { [weak self] in
+      guard let self else { return }
+      do {
+        try self.messenger.ud.get()!.confirmFact(
+          confirmationId: self.confirmationId,
+          code: self.stateSubject.value.input
+        )
+        if self.isEmail {
+          self.email = self.content
+        } else {
+          self.phone = self.content
         }
+        self.timer?.invalidate()
+        self.hudManager.hide()
+        self.stateSubject.value.didConfirm = true
+      } catch {
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.hudManager.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(stateSubject.value.input) {
+    case .success:
+      stateSubject.value.status = .valid(nil)
+    case .failure(let error):
+      stateSubject.value.status = .invalid(error)
     }
+  }
 }
diff --git a/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift
index 6b57bc81fe4040faaccaeefff057a275a90deaac..96210b7dc00867639c7861d377d868df11bb8b62 100644
--- a/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfileEmailViewModel.swift
@@ -1,76 +1,63 @@
-import HUD
-import Models
 import Shared
 import Combine
+import AppCore
+import XXClient
+import Foundation
 import InputField
-import Integration
 import CombineSchedulers
-import DependencyInjection
+import XXMessengerClient
+import ComposableArchitecture
 
-struct ProfileEmailViewState: Equatable {
+final class ProfileEmailViewModel {
+  struct ViewState: Equatable {
     var input: String = ""
-    var confirmation: AttributeConfirmation? = nil
+    var confirmationId: String?
     var status: InputField.ValidationStatus = .unknown(nil)
-}
-
-final class ProfileEmailViewModel {
-    // MARK: Injected
-
-    @Dependency private var session: SessionType
-
-    // 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()
-    }
-
-    func clearUp() {
-        stateRelay.value.confirmation = nil
-    }
-
-    func didTapNext() {
-        hudRelay.send(.on)
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            self.session.register(.email, value: self.stateRelay.value.input) { [weak self] in
-                guard let self = self else { return }
-
-                switch $0 {
-                case .success(let confirmationId):
-                    self.hudRelay.send(.none)
-                    self.stateRelay.value.confirmation = .init(
-                        content: self.stateRelay.value.input,
-                        isEmail: true,
-                        confirmationId: confirmationId
-                    )
-                case .failure(let error):
-                    self.hudRelay.send(.error(.init(with: error)))
-                }
-            }
-        }
+  }
+
+  @Dependency(\.app.messenger) var messenger: Messenger
+  @Dependency(\.app.hudManager) var hudManager: HUDManager
+  @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue>
+
+  var statePublisher: AnyPublisher<ViewState, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
+
+  private let stateSubject = CurrentValueSubject<ViewState, Never>(.init())
+
+  func clearUp() {
+    stateSubject.value.confirmationId = nil
+  }
+
+  func didInput(_ string: String) {
+    stateSubject.value.input = string
+    validate()
+  }
+
+  func didTapNext() {
+    hudManager.show()
+    bgQueue.schedule { [weak self] in
+      guard let self else { return }
+      do {
+        let confirmationId = try self.messenger.ud.get()!.sendRegisterFact(
+          .init(type: .email, value: self.stateSubject.value.input)
+        )
+        self.hudManager.hide()
+        self.stateSubject.value.confirmationId = confirmationId
+      } catch {
+        self.hudManager.hide()
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.stateSubject.value.status = .invalid(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(stateSubject.value.input) {
+    case .success:
+      stateSubject.value.status = .valid(nil)
+    case .failure(let error):
+      stateSubject.value.status = .invalid(error)
     }
+  }
 }
diff --git a/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift
index 1013725b419a118c4e437d3e8959c50748447d72..90387a69dde095491775b47cd4e138ae6786f483 100644
--- a/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfilePhoneViewModel.swift
@@ -1,84 +1,73 @@
-import HUD
 import Shared
-import Models
 import Combine
-import Countries
+import AppCore
+import XXClient
 import InputField
-import Integration
+import Foundation
+import Dependencies
 import CombineSchedulers
-import DependencyInjection
+import XXMessengerClient
+import CountryListFeature
 
-struct ProfilePhoneViewState: Equatable {
+final class ProfilePhoneViewModel {
+  struct ViewState: Equatable {
     var input: String = ""
-    var confirmation: AttributeConfirmation? = nil
+    var content: String?
+    var confirmationId: String?
     var status: InputField.ValidationStatus = .unknown(nil)
     var country: Country = .fromMyPhone()
-}
-
-final class ProfilePhoneViewModel {
-    // MARK: Injected
-
-    @Dependency private var session: SessionType
-
-    // MARK: Properties
-
-    var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-
-    var state: AnyPublisher<ProfilePhoneViewState, Never> { stateRelay.eraseToAnyPublisher() }
-    private let stateRelay = CurrentValueSubject<ProfilePhoneViewState, Never>(.init())
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
-
-    // MARK: Public
-
-    func didInput(_ string: String) {
-        stateRelay.value.input = string
-        validate()
+  }
+
+  @Dependency(\.app.messenger) var messenger: Messenger
+  @Dependency(\.app.hudManager) var hudManager: HUDManager
+  @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue>
+
+  var statePublisher: AnyPublisher<ViewState, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
+
+  private let stateSubject = CurrentValueSubject<ViewState, Never>(.init())
+
+  func didInput(_ string: String) {
+    stateSubject.value.input = string
+    validate()
+  }
+
+  func clearUp() {
+    stateSubject.value.confirmationId = nil
+  }
+
+  func didChooseCountry(_ country: Country) {
+    stateSubject.value.country = country
+    validate()
+  }
+
+  func didTapNext() {
+    hudManager.show()
+    bgQueue.schedule { [weak self] in
+      guard let self else { return }
+      let content = "\(self.stateSubject.value.input)\(self.stateSubject.value.country.code)"
+      do {
+        let confirmationId = try self.messenger.ud.get()!.sendRegisterFact(
+          .init(type: .phone, value: content)
+        )
+
+        self.hudManager.hide()
+        self.stateSubject.value.content = content
+        self.stateSubject.value.confirmationId = confirmationId
+      } catch {
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.hudManager.show(.init(content: xxError))
+      }
     }
-
-    func clearUp() {
-        stateRelay.value.confirmation = nil
-    }
-
-    func didChooseCountry(_ country: Country) {
-        stateRelay.value.country = country
-        validate()
-    }
-
-    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)"
-
-            self.session.register(.phone, value: content) { [weak self] in
-                guard let self = self else { return }
-
-                switch $0 {
-                case .success(let confirmationId):
-                    self.hudRelay.send(.none)
-                    self.stateRelay.value.confirmation = .init(
-                        content: content,
-                        confirmationId: confirmationId
-                    )
-                case .failure(let error):
-                    self.hudRelay.send(.error(.init(with: error)))
-                }
-            }
-        }
-    }
-
-    // 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((stateSubject.value.country.regex, stateSubject.value.input)) {
+    case .success:
+      stateSubject.value.status = .valid(nil)
+    case .failure(let error):
+      stateSubject.value.status = .invalid(error)
     }
+  }
 }
diff --git a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift
index a7066eccfaada6e74960beb7a5b3b8b7e72548cb..07ab592ebd904e0a1dc91314e7b616a5cea7a646 100644
--- a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift
+++ b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift
@@ -1,101 +1,116 @@
-import HUD
 import UIKit
 import Shared
+import AppCore
 import Combine
 import Defaults
-import Countries
-import Foundation
-import Permissions
-import Integration
+import XXClient
+import BackupFeature
+import XXMessengerClient
 import CombineSchedulers
-import DependencyInjection
+import CountryListFeature
+import PermissionsFeature
+import ComposableArchitecture
 
 enum ProfileNavigationRoutes {
-    case none
-    case library
-    case libraryPermission
+  case none
+  case library
+  case libraryPermission
 }
 
 struct ProfileViewState: Equatable {
-    var email: String?
-    var phone: String?
-    var photo: UIImage?
+  var email: String?
+  var phone: String?
+  var photo: UIImage?
 }
 
 final class ProfileViewModel {
-    @KeyObject(.avatar, defaultValue: nil) var avatar: Data?
-    @KeyObject(.email, defaultValue: nil) var emailStored: String?
-    @KeyObject(.phone, defaultValue: nil) var phoneStored: String?
-    @KeyObject(.username, defaultValue: nil) var username: String?
-    @KeyObject(.sharingEmail, defaultValue: false) var isEmailSharing: Bool
-    @KeyObject(.sharingPhone, defaultValue: false) var isPhoneSharing: Bool
-
-    @Dependency private var session: SessionType
-    @Dependency private var permissions: PermissionHandling
-
-    var name: String { username! }
-
-    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>()
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
-
-    init() {
-        refresh()
+  @KeyObject(.avatar, defaultValue: nil) var avatar: Data?
+  @KeyObject(.email, defaultValue: nil) var emailStored: String?
+  @KeyObject(.phone, defaultValue: nil) var phoneStored: String?
+  @KeyObject(.username, defaultValue: nil) var username: String?
+  @KeyObject(.sharingEmail, defaultValue: false) var isEmailSharing: Bool
+  @KeyObject(.sharingPhone, defaultValue: false) var isPhoneSharing: Bool
+
+  @Dependency(\.app.messenger) var messenger: Messenger
+  @Dependency(\.app.hudManager) var hudManager: HUDManager
+  @Dependency(\.backupService) var backupService: BackupService
+  @Dependency(\.permissions) var permissions: PermissionsManager
+  @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue>
+
+  var name: String { username! }
+
+  var state: AnyPublisher<ProfileViewState, Never> {
+    stateRelay.eraseToAnyPublisher()
+  }
+  private let stateRelay = CurrentValueSubject<ProfileViewState, Never>(.init())
+
+  var navigation: AnyPublisher<ProfileNavigationRoutes, Never> {
+    navigationRoutes.eraseToAnyPublisher()
+  }
+  private let navigationRoutes = PassthroughSubject<ProfileNavigationRoutes, Never>()
+
+  init() {
+    refresh()
+  }
+
+  func refresh() {
+    var cleanPhone = phoneStored
+
+    if let phone = cleanPhone {
+      let country = Country.findFrom(phone)
+      cleanPhone = "\(country.prefix)\(phone.dropLast(2))"
     }
 
-    func refresh() {
-        var cleanPhone = phoneStored
-
-        if let phone = cleanPhone {
-            let country = Country.findFrom(phone)
-            cleanPhone = "\(country.prefix)\(phone.dropLast(2))"
-        }
-
-        stateRelay.value = .init(
-            email: emailStored,
-            phone: cleanPhone,
-            photo: avatar != nil ? UIImage(data: avatar!) : nil
-        )
+    stateRelay.value = .init(
+      email: emailStored,
+      phone: cleanPhone,
+      photo: avatar != nil ? UIImage(data: avatar!) : nil
+    )
+  }
+
+  func didRequestLibraryAccess() {
+    if permissions.library.status() {
+      navigationRoutes.send(.library)
+    } else {
+      navigationRoutes.send(.libraryPermission)
     }
-
-    func didRequestLibraryAccess() {
-        if permissions.isPhotosAllowed {
-            navigationRoutes.send(.library)
+  }
+
+  func didNavigateSomewhere() {
+    navigationRoutes.send(.none)
+  }
+
+  func didChoosePhoto(_ photo: UIImage) {
+    stateRelay.value.photo = photo
+    avatar = photo.jpegData(compressionQuality: 0.0)
+  }
+
+  func didTapDelete(isEmail: Bool) {
+    hudManager.show()
+
+    bgQueue.schedule { [weak self] in
+      guard let self else { return }
+      do {
+        try self.messenger.ud.get()!.removeFact(
+          .init(
+            type: isEmail ? .email : .phone,
+            value: isEmail ? self.emailStored! : self.phoneStored!
+          )
+        )
+        if isEmail {
+          self.emailStored = nil
+          self.isEmailSharing = false
         } else {
-            navigationRoutes.send(.libraryPermission)
-        }
-    }
-
-    func didNavigateSomewhere() {
-        navigationRoutes.send(.none)
-    }
-
-    func didChoosePhoto(_ photo: UIImage) {
-        stateRelay.value.photo = photo
-        avatar = photo.jpegData(compressionQuality: 0.0)
-    }
-
-    func didTapDelete(isEmail: Bool) {
-        hudRelay.send(.on)
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.session.unregister(fact: isEmail ? .email : .phone)
-
-                self.hudRelay.send(.none)
-                self.refresh()
-            } catch {
-                self.hudRelay.send(.error(.init(with: error)))
-            }
+          self.phoneStored = nil
+          self.isPhoneSharing = false
         }
+        self.backupService.didUpdateFacts()
+        self.hudManager.hide()
+        self.refresh()
+      } catch {
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.hudManager.show(.init(content: xxError))
+      }
     }
+  }
 }
diff --git a/Sources/ProfileFeature/Views/ProfileCodeView.swift b/Sources/ProfileFeature/Views/ProfileCodeView.swift
index f8d7199bb22c5b25af077cacd7315774b83ff5fe..dc4fb0310ff4bcbdf5736878b5c4e984a7472686 100644
--- a/Sources/ProfileFeature/Views/ProfileCodeView.swift
+++ b/Sources/ProfileFeature/Views/ProfileCodeView.swift
@@ -1,6 +1,7 @@
 import UIKit
 import Shared
 import InputField
+import AppResources
 
 final class ProfileCodeView: UIView {
     let titleLabel = UILabel()
diff --git a/Sources/ProfileFeature/Views/ProfileEmailView.swift b/Sources/ProfileFeature/Views/ProfileEmailView.swift
index 185c0ef797ec8574a31e722dc3478bac3d12fe5c..6c689f68169a7b55261c7444f6c7e3a4e5fe2026 100644
--- a/Sources/ProfileFeature/Views/ProfileEmailView.swift
+++ b/Sources/ProfileFeature/Views/ProfileEmailView.swift
@@ -1,74 +1,75 @@
 import UIKit
 import Shared
 import InputField
+import AppResources
 
 final class ProfileEmailView: UIView {
-    let titleLabel = UILabel()
-    let imageView = UIImageView()
-    let inputField = InputField()
-    let saveButton = CapsuleButton()
+  let titleLabel = UILabel()
+  let imageView = UIImageView()
+  let inputField = InputField()
+  let saveButton = CapsuleButton()
 
-    init() {
-        super.init(frame: .zero)
+  init() {
+    super.init(frame: .zero)
 
-        titleLabel.text = Localized.Profile.EmailScreen.title
-        titleLabel.textAlignment = .center
-        titleLabel.textColor = Asset.neutralActive.color
-        titleLabel.font = Fonts.Mulish.bold.font(size: 32.0)
-        imageView.contentMode = .center
-        imageView.image = Asset.profileEmail.image
-        saveButton.setStyle(.brandColored)
-        saveButton.setTitle(Localized.Profile.EmailScreen.action, for: .normal)
+    titleLabel.text = Localized.Profile.EmailScreen.title
+    titleLabel.textAlignment = .center
+    titleLabel.textColor = Asset.neutralActive.color
+    titleLabel.font = Fonts.Mulish.bold.font(size: 32.0)
+    imageView.contentMode = .center
+    imageView.image = Asset.profileEmail.image
+    saveButton.setStyle(.brandColored)
+    saveButton.setTitle(Localized.Profile.EmailScreen.action, for: .normal)
 
-        inputField.setup(
-            title: Localized.Profile.EmailScreen.input,
-            placeholder: Localized.Profile.EmailScreen.input,
-            subtitleColor: Asset.neutralWeak.color,
-            allowsEmptySpace: false,
-            keyboardType: .emailAddress,
-            autocapitalization: .none,
-            contentType: .emailAddress
-        )
+    inputField.setup(
+      title: Localized.Profile.EmailScreen.input,
+      placeholder: Localized.Profile.EmailScreen.input,
+      subtitleColor: Asset.neutralWeak.color,
+      allowsEmptySpace: false,
+      keyboardType: .emailAddress,
+      autocapitalization: .none,
+      contentType: .emailAddress
+    )
 
-        addSubview(imageView)
-        addSubview(titleLabel)
-        addSubview(inputField)
-        addSubview(saveButton)
+    addSubview(imageView)
+    addSubview(titleLabel)
+    addSubview(inputField)
+    addSubview(saveButton)
 
-        imageView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(60)
-            make.centerX.equalToSuperview()
-        }
+    imageView.snp.makeConstraints { make in
+      make.top.equalToSuperview().offset(60)
+      make.centerX.equalToSuperview()
+    }
 
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalTo(imageView.snp.bottom).offset(39)
-            make.centerX.equalToSuperview()
-        }
+    titleLabel.snp.makeConstraints { make in
+      make.top.equalTo(imageView.snp.bottom).offset(39)
+      make.centerX.equalToSuperview()
+    }
 
-        inputField.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(35)
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-        }
+    inputField.snp.makeConstraints { make in
+      make.top.equalTo(titleLabel.snp.bottom).offset(35)
+      make.left.equalToSuperview().offset(24)
+      make.right.equalToSuperview().offset(-24)
+    }
 
-        saveButton.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(40)
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-            make.bottom.equalTo(safeAreaLayoutGuide).offset(-40)
-        }
+    saveButton.snp.makeConstraints { make in
+      make.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(40)
+      make.left.equalToSuperview().offset(24)
+      make.right.equalToSuperview().offset(-24)
+      make.bottom.equalTo(safeAreaLayoutGuide).offset(-40)
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 
-    func update(status: InputField.ValidationStatus) {
-        inputField.update(status: status)
+  func update(status: InputField.ValidationStatus) {
+    inputField.update(status: status)
 
-        switch status {
-        case .valid:
-            saveButton.isEnabled = true
-        case .invalid, .unknown:
-            saveButton.isEnabled = false
-        }
+    switch status {
+    case .valid:
+      saveButton.isEnabled = true
+    case .invalid, .unknown:
+      saveButton.isEnabled = false
     }
+  }
 }
diff --git a/Sources/ProfileFeature/Views/ProfilePhoneView.swift b/Sources/ProfileFeature/Views/ProfilePhoneView.swift
index 12f5062fd269d5092a036685943d260304b5b4a0..1dccecfa1276e2983520b01e792ff83d762d527c 100644
--- a/Sources/ProfileFeature/Views/ProfilePhoneView.swift
+++ b/Sources/ProfileFeature/Views/ProfilePhoneView.swift
@@ -1,73 +1,74 @@
 import UIKit
 import Shared
 import InputField
+import AppResources
 
 final class ProfilePhoneView: UIView {
-    let titleLabel = UILabel()
-    let imageView = UIImageView()
-    let inputField = InputField()
-    let saveButton = CapsuleButton()
-    
-    init() {
-        super.init(frame: .zero)
-        
-        titleLabel.text = "Add Phone"
-        titleLabel.textAlignment = .center
-        titleLabel.textColor = Asset.neutralActive.color
-        titleLabel.font = Fonts.Mulish.bold.font(size: 32.0)
-        imageView.contentMode = .center
-        imageView.image = Asset.profilePhone.image
-        saveButton.setStyle(.brandColored)
-        saveButton.setTitle(Localized.Profile.PhoneScreen.action, for: .normal)
-        
-        inputField.setup(
-            style: .phone,
-            title: Localized.Profile.PhoneScreen.input,
-            placeholder: "10651613216",
-            subtitleColor: Asset.neutralWeak.color,
-            keyboardType: .phonePad,
-            contentType: .telephoneNumber
-        )
-        
-        addSubview(imageView)
-        addSubview(titleLabel)
-        addSubview(inputField)
-        addSubview(saveButton)
-        
-        imageView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(60)
-            make.centerX.equalToSuperview()
-        }
-        
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalTo(imageView.snp.bottom).offset(39)
-            make.centerX.equalToSuperview()
-        }
-        
-        inputField.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(35)
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-        }
-        
-        saveButton.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(40)
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-            make.bottom.equalTo(safeAreaLayoutGuide).offset(-40)
-        }
+  let titleLabel = UILabel()
+  let imageView = UIImageView()
+  let inputField = InputField()
+  let saveButton = CapsuleButton()
+
+  init() {
+    super.init(frame: .zero)
+
+    titleLabel.text = "Add Phone"
+    titleLabel.textAlignment = .center
+    titleLabel.textColor = Asset.neutralActive.color
+    titleLabel.font = Fonts.Mulish.bold.font(size: 32.0)
+    imageView.contentMode = .center
+    imageView.image = Asset.profilePhone.image
+    saveButton.setStyle(.brandColored)
+    saveButton.setTitle(Localized.Profile.PhoneScreen.action, for: .normal)
+
+    inputField.setup(
+      style: .phone,
+      title: Localized.Profile.PhoneScreen.input,
+      placeholder: "10651613216",
+      subtitleColor: Asset.neutralWeak.color,
+      keyboardType: .phonePad,
+      contentType: .telephoneNumber
+    )
+
+    addSubview(imageView)
+    addSubview(titleLabel)
+    addSubview(inputField)
+    addSubview(saveButton)
+
+    imageView.snp.makeConstraints { make in
+      make.top.equalToSuperview().offset(60)
+      make.centerX.equalToSuperview()
+    }
+
+    titleLabel.snp.makeConstraints { make in
+      make.top.equalTo(imageView.snp.bottom).offset(39)
+      make.centerX.equalToSuperview()
     }
-    
-    required init?(coder: NSCoder) { nil }
-    
-    func update(status: InputField.ValidationStatus) {
-        inputField.update(status: status)
-        
-        switch status {
-        case .valid:
-            saveButton.isEnabled = true
-        case .invalid, .unknown:
-            saveButton.isEnabled = false
-        }
+
+    inputField.snp.makeConstraints { make in
+      make.top.equalTo(titleLabel.snp.bottom).offset(35)
+      make.left.equalToSuperview().offset(24)
+      make.right.equalToSuperview().offset(-24)
+    }
+
+    saveButton.snp.makeConstraints { make in
+      make.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(40)
+      make.left.equalToSuperview().offset(24)
+      make.right.equalToSuperview().offset(-24)
+      make.bottom.equalTo(safeAreaLayoutGuide).offset(-40)
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  func update(status: InputField.ValidationStatus) {
+    inputField.update(status: status)
+
+    switch status {
+    case .valid:
+      saveButton.isEnabled = true
+    case .invalid, .unknown:
+      saveButton.isEnabled = false
     }
+  }
 }
diff --git a/Sources/ProfileFeature/Views/ProfileView.swift b/Sources/ProfileFeature/Views/ProfileView.swift
index e95e3e63c08f97b705a4da31c9e3264d8c1ba1f4..55081d330c759c7bcc6fc798c9bc92a33969990d 100644
--- a/Sources/ProfileFeature/Views/ProfileView.swift
+++ b/Sources/ProfileFeature/Views/ProfileView.swift
@@ -1,43 +1,44 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ProfileView: UIView {
-    let stackView = UIStackView()
-    let cardComponent = AvatarCardComponent()
-    let emailView = AttributeComponent()
-    let phoneView = AttributeComponent()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        let emailTitle = Localized.Profile.Email.title
-        let phoneTitle = Localized.Profile.Phone.title
-
-        emailView.set(title: emailTitle, style: .interactive)
-        phoneView.set(title: phoneTitle, style: .interactive)
-
-        stackView.spacing = 41
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(emailView)
-        stackView.addArrangedSubview(phoneView)
-
-        addSubview(stackView)
-        addSubview(cardComponent)
-
-        cardComponent.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-        }
-
-        stackView.snp.makeConstraints { make in
-            make.top.equalTo(cardComponent.snp.bottom).offset(24)
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-26)
-            make.bottom.lessThanOrEqualToSuperview()
-        }
+  let stackView = UIStackView()
+  let cardComponent = AvatarCardComponent()
+  let emailView = AttributeComponent()
+  let phoneView = AttributeComponent()
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    let emailTitle = Localized.Profile.Email.title
+    let phoneTitle = Localized.Profile.Phone.title
+
+    emailView.set(title: emailTitle, style: .interactive)
+    phoneView.set(title: phoneTitle, style: .interactive)
+
+    stackView.spacing = 41
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(emailView)
+    stackView.addArrangedSubview(phoneView)
+
+    addSubview(stackView)
+    addSubview(cardComponent)
+
+    cardComponent.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+    }
+
+    stackView.snp.makeConstraints {
+      $0.top.equalTo(cardComponent.snp.bottom).offset(24)
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-26)
+      $0.bottom.lessThanOrEqualToSuperview()
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/PushFeature/ContentsBuilder.swift b/Sources/PushFeature/ContentsBuilder.swift
deleted file mode 100644
index 9aa75041564c08334bf2f8068d118128c3a8b52f..0000000000000000000000000000000000000000
--- a/Sources/PushFeature/ContentsBuilder.swift
+++ /dev/null
@@ -1,23 +0,0 @@
-import UserNotifications
-
-public struct ContentsBuilder {
-    enum Constants {
-        static let threadIdentifier = "new_message_identifier"
-    }
-
-    public var build: (String, Push) -> UNMutableNotificationContent
-}
-
-public extension ContentsBuilder {
-    static let live = ContentsBuilder { title, push in
-        let content = UNMutableNotificationContent()
-        content.badge = 1
-        content.body = title
-        content.title = title
-        content.sound = .default
-        content.userInfo["source"] = push.source
-        content.userInfo["type"] = push.type.rawValue
-        content.threadIdentifier = Constants.threadIdentifier
-        return content
-    }
-}
diff --git a/Sources/PushFeature/MockPushHandler.swift b/Sources/PushFeature/MockPushHandler.swift
deleted file mode 100644
index 41aced17ade52d9c4b02ec092feec29276fc21fb..0000000000000000000000000000000000000000
--- a/Sources/PushFeature/MockPushHandler.swift
+++ /dev/null
@@ -1,39 +0,0 @@
-import UIKit
-
-public struct MockPushHandler: PushHandling {
-    public init() {}
-
-    public func registerToken(_ token: Data) {
-        // TODO
-    }
-
-    public func requestAuthorization(
-        _ completion: @escaping (Result<Bool, Error>) -> Void
-    ) {
-        completion(.success(true))
-    }
-
-    public func handlePush(
-        _ notification: [AnyHashable : Any],
-        _ completion: @escaping (UIBackgroundFetchResult) -> Void
-    ) {
-        completion(.noData)
-    }
-
-    public func handlePush(
-        _ request: UNNotificationRequest,
-        _ completion: @escaping (UNNotificationContent) -> Void
-    ) {
-        let content = UNMutableNotificationContent()
-        content.title = String(describing: Self.self)
-        completion(content)
-    }
-
-    public func handleAction(
-        _ router: PushRouter,
-        _ userInfo: [AnyHashable : Any],
-        _ completion: @escaping () -> Void
-    ) {
-        completion()
-    }
-}
diff --git a/Sources/PushFeature/Push.swift b/Sources/PushFeature/Push.swift
deleted file mode 100644
index 51c25bd52f513816ca850930e07a4c0a72204798..0000000000000000000000000000000000000000
--- a/Sources/PushFeature/Push.swift
+++ /dev/null
@@ -1,15 +0,0 @@
-import Foundation
-
-public struct Push {
-    public let type: PushType
-    public let source: Data?
-
-    public init?(type: String, source: Data?) {
-        guard let pushType = PushType(rawValue: type) else {
-            return nil
-        }
-
-        self.type = pushType
-        self.source = source
-    }
-}
diff --git a/Sources/PushFeature/PushExtractor.swift b/Sources/PushFeature/PushExtractor.swift
deleted file mode 100644
index 045f0e898276350914d9c0d335a58997d4fbdfa1..0000000000000000000000000000000000000000
--- a/Sources/PushFeature/PushExtractor.swift
+++ /dev/null
@@ -1,38 +0,0 @@
-import Foundation
-import Integration
-
-public struct PushExtractor {
-    enum Constants {
-        static let preImage = "preImage"
-        static let appGroup = "group.elixxir.messenger"
-        static let notificationData = "notificationData"
-    }
-
-    public var extractFrom: ([AnyHashable: Any]) -> Result<[Push]?, Error>
-}
-
-public extension PushExtractor {
-    static let live = PushExtractor { dictionary in
-        var error: NSError?
-
-        guard let data = dictionary[Constants.notificationData] as? String,
-              let defaults = UserDefaults(suiteName: Constants.appGroup),
-              let preImage = defaults.value(forKey: Constants.preImage) as? String,
-              let reports = evaluateNotification(data, preImage, &error) else {
-            return .success(nil)
-        }
-
-        if let error = error {
-            return .failure(error)
-        }
-
-        let pushes = (0..<reports.len())
-            .compactMap { try? reports.get(index: $0) }
-            .filter { $0.forMe() }
-            .filter { $0.type() != PushType.silent.rawValue }
-            .filter { $0.type() != PushType.default.rawValue }
-            .compactMap { Push(type: $0.type(), source: $0.source()) }
-
-        return .success(pushes)
-    }
-}
diff --git a/Sources/PushFeature/PushHandler.swift b/Sources/PushFeature/PushHandler.swift
deleted file mode 100644
index b090664f1f7bb55086cc4909afd8bf2639cf7e59..0000000000000000000000000000000000000000
--- a/Sources/PushFeature/PushHandler.swift
+++ /dev/null
@@ -1,173 +0,0 @@
-import UIKit
-import Models
-import Defaults
-import XXModels
-import Integration
-import ReportingFeature
-import DependencyInjection
-
-public final class PushHandler: PushHandling {
-    private enum Constants {
-        static let appGroup = "group.elixxir.messenger"
-        static let usernamesSetting = "isShowingUsernames"
-    }
-
-    @Dependency var reportingStatus: ReportingStatus
-
-    @KeyObject(.pushNotifications, defaultValue: false) var isPushEnabled: Bool
-
-    let requestAuth: RequestAuth
-    public static let defaultRequestAuth = UNUserNotificationCenter.current().requestAuthorization
-    public typealias RequestAuth = (UNAuthorizationOptions, @escaping (Bool, Error?) -> Void) -> Void
-
-    public var pushExtractor: PushExtractor
-    public var contentsBuilder: ContentsBuilder
-    public var applicationState: () -> UIApplication.State
-
-    public init(
-        requestAuth: @escaping RequestAuth = defaultRequestAuth,
-        pushExtractor: PushExtractor = .live,
-        contentsBuilder: ContentsBuilder = .live,
-        applicationState: @escaping () -> UIApplication.State = { UIApplication.shared.applicationState }
-    ) {
-        self.requestAuth = requestAuth
-        self.pushExtractor = pushExtractor
-        self.contentsBuilder = contentsBuilder
-        self.applicationState = applicationState
-    }
-
-    public func registerToken(_ token: Data) {
-        do {
-            let session = try DependencyInjection.Container.shared.resolve() as SessionType
-            try session.registerNotifications(token)
-        } catch {
-            isPushEnabled = false
-        }
-    }
-
-    public func requestAuthorization(
-        _ completion: @escaping (Result<Bool, Error>) -> Void
-    ) {
-        let options: UNAuthorizationOptions = [.alert, .sound, .badge]
-
-        requestAuth(options) { granted, error in
-            guard let error = error else {
-                completion(.success(granted))
-                return
-            }
-
-            completion(.failure(error))
-        }
-    }
-
-    public func handlePush(
-        _ userInfo: [AnyHashable: Any],
-        _ completion: @escaping (UIBackgroundFetchResult) -> Void
-    ) {
-        do {
-            guard
-                let pushes = try pushExtractor.extractFrom(userInfo).get(),
-                applicationState() == .background,
-                pushes.isEmpty == false
-            else {
-                completion(.noData)
-                return
-            }
-
-            let content = contentsBuilder.build("New Messages Available", pushes.first!)
-            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
-            let request = UNNotificationRequest(identifier: Bundle.main.bundleIdentifier!, content: content, trigger: trigger)
-
-            UNUserNotificationCenter.current().add(request) { error in
-                if error == nil {
-                    completion(.newData)
-                } else {
-                    completion(.failed)
-                }
-            }
-        } catch {
-            completion(.failed)
-        }
-    }
-
-    public func handlePush(
-        _ request: UNNotificationRequest,
-        _ completion: @escaping (UNNotificationContent) -> Void
-    ) {
-        guard let pushes = try? pushExtractor.extractFrom(request.content.userInfo).get(), !pushes.isEmpty,
-              let defaults = UserDefaults(suiteName: Constants.appGroup) else {
-            return
-        }
-
-        let dbPath = FileManager.default
-            .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
-            .appendingPathComponent("xxm_database")
-            .appendingPathExtension("sqlite").path
-
-        let tuples: [(String, Push)] = pushes.compactMap {
-            guard let userId = $0.source,
-                  let dbManager = try? Database.onDisk(path: dbPath),
-                  let contact = try? dbManager.fetchContacts(.init(id: [userId])).first else {
-                return ($0.type.unknownSenderContent!, $0)
-            }
-
-            if reportingStatus.isEnabled(), (contact.isBlocked || contact.isBanned) {
-                return nil
-            }
-
-            if let showSender = defaults.value(forKey: Constants.usernamesSetting) as? Bool, showSender == true {
-                let name = (contact.nickname ?? contact.username) ?? ""
-                return ($0.type.knownSenderContent(name)!, $0)
-            } else {
-                return ($0.type.unknownSenderContent!, $0)
-            }
-        }
-
-        tuples
-            .map(contentsBuilder.build)
-            .forEach { completion($0) }
-    }
-
-    public func handleAction(
-        _ router: PushRouter,
-        _ userInfo: [AnyHashable : Any],
-        _ completion: @escaping () -> Void
-    ) {
-        guard let typeString = userInfo["type"] as? String,
-              let type = PushType(rawValue: typeString) else {
-            completion()
-            return
-        }
-
-        let route: PushRouter.Route
-
-        switch type {
-        case .e2e:
-            guard let source = userInfo["source"] as? Data else {
-                completion()
-                return
-            }
-
-            route = .contactChat(id: source)
-
-        case .group:
-            guard let source = userInfo["source"] as? Data else {
-                completion()
-                return
-            }
-
-            route = .groupChat(id: source)
-
-        case .request, .groupRq:
-            route = .requests
-
-        case .silent, .`default`:
-            fatalError("Silent/Default push types should be filtered at this point")
-
-        case .reset, .endFT, .confirm:
-            route = .requests
-        }
-
-        router.navigateTo(route, completion)
-    }
-}
diff --git a/Sources/PushFeature/PushHandling.swift b/Sources/PushFeature/PushHandling.swift
deleted file mode 100644
index c17c7e600d9c8f3ed222f3e7b8e1d6db035d85ce..0000000000000000000000000000000000000000
--- a/Sources/PushFeature/PushHandling.swift
+++ /dev/null
@@ -1,70 +0,0 @@
-import UIKit
-
-public protocol PushHandling {
-
-    /// Submits the APNS token to a 3rd-party service.
-    /// This should be called whenever the user accepts
-    /// receiving remote push notifications.
-    ///
-    /// - Parameters:
-    ///   - token: The APNS provided token
-    ///
-    func registerToken(
-        _ token: Data
-    )
-
-    /// Prompts a system alert to the user requesting
-    /// permission for receiving remote push notifications
-    ///
-    /// - Parameters:
-    ///   - completion: Async result closure containing the user reponse
-    ///
-    func requestAuthorization(
-        _ completion: @escaping (Result<Bool, Error>) -> Void
-    )
-
-    /// Evaluates if the notification should be displayed or not
-    /// and if yes, how should it look like.
-    ///
-    /// - Note: This function should be called by the main app target
-    /// - Warning: The notifications should only appear if the app is in background
-    ///
-    /// - Parameters:
-    ///   - userInfo: Dictionary contaning the payload of the remote push
-    ///   - completion: Async closure containing the operation chosed
-    ///
-    func handlePush(
-        _ userInfo: [AnyHashable: Any],
-        _ completion: @escaping (UIBackgroundFetchResult) -> Void
-    )
-
-    /// Evaluates if the notification should be displayed or not
-    ///  and if yes, how it should look like and who is it from
-    ///
-    /// - Note: This function should be called by the `NotificationExtension`
-    ///
-    /// - Parameters:
-    ///   - request: The notification request that arrived for the `NotificationExtension`
-    ///   - completion: Async closure containing the operation chosed
-    ///
-    func handlePush(
-        _ request: UNNotificationRequest,
-        _ completion: @escaping (UNNotificationContent) -> Void
-    )
-
-    /// Deeplinks to any UI flow set within the notification.
-    /// It can get called either when the user starts the app
-    /// from a notification or when the user has the app in
-    /// background and resumes the app by tapping on a push
-    ///
-    /// - Parameters:
-    ///   - router: Router instance that will decide the correct UI flow
-    ///   - userInfo: Dictionary contaning the payload of the notification
-    ///   - completion: Async empty closure
-    ///
-    func handleAction(
-        _ router: PushRouter,
-        _ userInfo: [AnyHashable: Any],
-        _ completion: @escaping () -> Void
-    )
-}
diff --git a/Sources/PushFeature/PushRouter.swift b/Sources/PushFeature/PushRouter.swift
deleted file mode 100644
index 05885d5196b6025f24d18bd47f5922f1164d1f43..0000000000000000000000000000000000000000
--- a/Sources/PushFeature/PushRouter.swift
+++ /dev/null
@@ -1,23 +0,0 @@
-import Foundation
-
-public struct PushRouter {
-    public typealias NavigateTo = (Route, @escaping () -> Void) -> Void
-
-    public enum Route {
-        case requests
-        case groupChat(id: Data)
-        case contactChat(id: Data)
-        case search(username: String)
-    }
-
-    public var navigateTo: NavigateTo
-
-    public init(navigateTo: @escaping NavigateTo) {
-        self.navigateTo = navigateTo
-    }
-}
-
-public extension PushRouter {
-    static let noop = PushRouter { _, _ in }
-}
-
diff --git a/Sources/PushFeature/PushType.swift b/Sources/PushFeature/PushType.swift
deleted file mode 100644
index 7cd2fefefcbce0968a7d4478aa4e5cc01d771f98..0000000000000000000000000000000000000000
--- a/Sources/PushFeature/PushType.swift
+++ /dev/null
@@ -1,53 +0,0 @@
-public enum PushType: String {
-    case e2e
-    case reset
-    case endFT
-    case group
-    case silent
-    case groupRq
-    case confirm
-    case request
-    case `default`
-
-    var unknownSenderContent: String? {
-        switch self {
-        case .silent, .`default`:
-            return nil
-        case .endFT:
-            return "New media received"
-        case .group:
-            return "New group message"
-        case .groupRq:
-            return "Group request received"
-        case .e2e:
-            return "New private message"
-        case .reset:
-            return "One of your contacts has restored their account"
-        case .request:
-            return "Request received"
-        case .confirm:
-            return "Request accepted"
-        }
-    }
-
-    var knownSenderContent: (String) -> String? {
-        switch self {
-        case .silent, .`default`:
-            return { _ in nil }
-        case .e2e:
-            return { String(format: "%@ sent you a private message", $0) }
-        case .reset:
-            return { String(format: "%@ restored their account", $0) }
-        case .endFT:
-            return { String(format: "%@ sent you a file", $0) }
-        case .group:
-            return { String(format: "%@ sent you a group message", $0) }
-        case .groupRq:
-            return { String(format: "%@ sent you a group request", $0) }
-        case .confirm:
-            return { String(format: "%@ confirmed your contact request", $0) }
-        case .request:
-            return { String(format: "%@ sent you a contact request", $0) }
-        }
-    }
-}
diff --git a/Sources/ReportingFeature/Dependency.swift b/Sources/ReportingFeature/Dependency.swift
new file mode 100644
index 0000000000000000000000000000000000000000..33e19ae1c98c65fba2b084c9dc0bbf5e10c03c33
--- /dev/null
+++ b/Sources/ReportingFeature/Dependency.swift
@@ -0,0 +1,25 @@
+import Dependencies
+
+private enum ReportingStatusDependencyKey: DependencyKey {
+  static let liveValue: ReportingStatus = .live()
+  static let testValue: ReportingStatus = .unimplemented
+}
+
+extension DependencyValues {
+  public var reportingStatus: ReportingStatus {
+    get { self[ReportingStatusDependencyKey.self] }
+    set { self[ReportingStatusDependencyKey.self] = newValue }
+  }
+}
+  
+private enum SendReportDependencyKey: DependencyKey {
+  static let liveValue: SendReport = .live
+  static let testValue: SendReport = .unimplemented
+}
+
+extension DependencyValues {
+  public var sendReport: SendReport {
+    get { self[SendReportDependencyKey.self] }
+    set { self[SendReportDependencyKey.self] = newValue }
+  }
+}
diff --git a/Sources/ReportingFeature/FetchBannedList.swift b/Sources/ReportingFeature/FetchBannedList.swift
deleted file mode 100644
index 2620b15c84e5d31316f663a363ff6a4526d1880d..0000000000000000000000000000000000000000
--- a/Sources/ReportingFeature/FetchBannedList.swift
+++ /dev/null
@@ -1,46 +0,0 @@
-import Foundation
-import XCTestDynamicOverlay
-
-public struct FetchBannedList {
-    public enum Error: Swift.Error, Equatable {
-        case network(URLError)
-        case invalidResponse
-    }
-
-    public typealias Completion = (Result<Data, Error>) -> Void
-
-    public var run: (@escaping Completion) -> Void
-
-    public func callAsFunction(completion: @escaping Completion) {
-        run(completion)
-    }
-}
-
-extension FetchBannedList {
-    public static let live = FetchBannedList { completion in
-        let url = URL(string: "https://elixxir-bins.s3.us-west-1.amazonaws.com/client/bannedUsers/banned.csv")!
-        let session = URLSession.shared
-        let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData)
-        let task = session.dataTask(with: request) { data, response, error in
-            if let error = error {
-                completion(.failure(.network(error as! URLError)))
-                return
-            }
-            guard let response = response as? HTTPURLResponse,
-                  (200..<300).contains(response.statusCode),
-                  let data = data
-            else {
-                completion(.failure(.invalidResponse))
-                return
-            }
-            completion(.success(data))
-        }
-        task.resume()
-    }
-}
-
-extension FetchBannedList {
-    public static let unimplemented = FetchBannedList(
-        run: XCTUnimplemented("\(Self.self)")
-    )
-}
diff --git a/Sources/ReportingFeature/MakeAppScreenshot.swift b/Sources/ReportingFeature/MakeAppScreenshot.swift
index 7d44af879f35d792685f3e278cc6ce9c044d8a07..75a1fc31e6139e9a3fc2510eb811f00ffe37a628 100644
--- a/Sources/ReportingFeature/MakeAppScreenshot.swift
+++ b/Sources/ReportingFeature/MakeAppScreenshot.swift
@@ -3,51 +3,51 @@ import UIKit
 import XCTestDynamicOverlay
 
 public struct MakeAppScreenshot {
-    public enum Error: Swift.Error, Equatable {
-        case unableToGetForegroundWindowScene
-        case unableToGetKeyWindow
-    }
-
-    public var run: () throws -> UIImage
-
-    public func callAsFunction() throws -> UIImage {
-        try run()
-    }
+  public enum Error: Swift.Error, Equatable {
+    case unableToGetForegroundWindowScene
+    case unableToGetKeyWindow
+  }
+  
+  public var run: () throws -> UIImage
+  
+  public func callAsFunction() throws -> UIImage {
+    try run()
+  }
 }
 
 extension MakeAppScreenshot {
-    public static let live = MakeAppScreenshot {
-        let scene: UIWindowScene? = UIApplication.shared.connectedScenes
-            .filter { $0.activationState == .foregroundActive }
-            .compactMap { $0 as? UIWindowScene }
-            .first
-
-        guard let scene = scene else {
-            throw Error.unableToGetForegroundWindowScene
-        }
-
-        let window: UIWindow? = scene.windows.first(where: \.isKeyWindow)
-
-        guard let keyWindow = window else {
-            throw Error.unableToGetKeyWindow
-        }
-
-        let rendererFormat = UIGraphicsImageRendererFormat()
-        rendererFormat.scale = scene.screen.scale
-
-        let renderer = UIGraphicsImageRenderer(
-            bounds: keyWindow.bounds,
-            format: rendererFormat
-        )
-
-        return renderer.image { ctx in
-            keyWindow.layer.render(in: ctx.cgContext)
-        }
+  public static let live = MakeAppScreenshot {
+    let scene: UIWindowScene? = UIApplication.shared.connectedScenes
+      .filter { $0.activationState == .foregroundActive }
+      .compactMap { $0 as? UIWindowScene }
+      .first
+    
+    guard let scene = scene else {
+      throw Error.unableToGetForegroundWindowScene
+    }
+    
+    let window: UIWindow? = scene.windows.first(where: \.isKeyWindow)
+    
+    guard let keyWindow = window else {
+      throw Error.unableToGetKeyWindow
     }
+    
+    let rendererFormat = UIGraphicsImageRendererFormat()
+    rendererFormat.scale = scene.screen.scale
+    
+    let renderer = UIGraphicsImageRenderer(
+      bounds: keyWindow.bounds,
+      format: rendererFormat
+    )
+    
+    return renderer.image { ctx in
+      keyWindow.layer.render(in: ctx.cgContext)
+    }
+  }
 }
 
 extension MakeAppScreenshot {
-    public static let unimplemented = MakeAppScreenshot(
-        run: XCTUnimplemented("\(Self.self)")
-    )
+  public static let unimplemented = MakeAppScreenshot(
+    run: XCTUnimplemented("\(Self.self)")
+  )
 }
diff --git a/Sources/ReportingFeature/MakeReportDrawer.swift b/Sources/ReportingFeature/MakeReportDrawer.swift
index b8b4aa394481d3e292ef236711b1f31f82704d3e..249683a0de30030e305fc399fcdc841dbee1bd75 100644
--- a/Sources/ReportingFeature/MakeReportDrawer.swift
+++ b/Sources/ReportingFeature/MakeReportDrawer.swift
@@ -1,86 +1,87 @@
-import DrawerFeature
-import Shared
 import UIKit
+import Shared
+import AppResources
+import DrawerFeature
 import XCTestDynamicOverlay
 
 public struct MakeReportDrawer {
-    public struct Config {
-        public init(
-            onReport: @escaping () -> Void = {},
-            onCancel: @escaping () -> Void = {}
-        ) {
-            self.onReport = onReport
-            self.onCancel = onCancel
-        }
-
-        public var onReport: () -> Void
-        public var onCancel: () -> Void
+  public struct Config {
+    public init(
+      onReport: @escaping () -> Void = {},
+      onCancel: @escaping () -> Void = {}
+    ) {
+      self.onReport = onReport
+      self.onCancel = onCancel
     }
 
-    public var run: (Config) -> UIViewController
+    public var onReport: () -> Void
+    public var onCancel: () -> Void
+  }
 
-    public func callAsFunction(_ config: Config) -> UIViewController {
-        run(config)
-    }
+  public var run: (Config) -> UIViewController
+
+  public func callAsFunction(_ config: Config) -> UIViewController {
+    run(config)
+  }
 }
 
 extension MakeReportDrawer {
-    public static let live = MakeReportDrawer { config in
-        let cancelButton = CapsuleButton()
-        cancelButton.setStyle(.seeThrough)
-        cancelButton.setTitle(Localized.Chat.Report.cancel, for: .normal)
+  public static let live = MakeReportDrawer { config in
+    let cancelButton = CapsuleButton()
+    cancelButton.setStyle(.seeThrough)
+    cancelButton.setTitle(Localized.Chat.Report.cancel, for: .normal)
 
-        let reportButton = CapsuleButton()
-        reportButton.setStyle(.red)
-        reportButton.setTitle(Localized.Chat.Report.action, for: .normal)
+    let reportButton = CapsuleButton()
+    reportButton.setStyle(.red)
+    reportButton.setTitle(Localized.Chat.Report.action, for: .normal)
 
-        let drawer = DrawerController(with: [
-            DrawerImage(
-                image: Asset.drawerNegative.image
-            ),
-            DrawerText(
-                font: Fonts.Mulish.semiBold.font(size: 18.0),
-                text: Localized.Chat.Report.title,
-                color: Asset.neutralActive.color
-            ),
-            DrawerText(
-                font: Fonts.Mulish.semiBold.font(size: 14.0),
-                text: Localized.Chat.Report.subtitle,
-                color: Asset.neutralWeak.color,
-                lineHeightMultiple: 1.35,
-                spacingAfter: 25
-            ),
-            DrawerStack(
-                axis: .vertical,
-                spacing: 20.0,
-                views: [reportButton, cancelButton]
-            )
-        ])
+    let drawer = DrawerController([
+      DrawerImage(
+        image: Asset.drawerNegative.image
+      ),
+      DrawerText(
+        font: Fonts.Mulish.semiBold.font(size: 18.0),
+        text: Localized.Chat.Report.title,
+        color: Asset.neutralActive.color
+      ),
+      DrawerText(
+        font: Fonts.Mulish.semiBold.font(size: 14.0),
+        text: Localized.Chat.Report.subtitle,
+        color: Asset.neutralWeak.color,
+        lineHeightMultiple: 1.35,
+        spacingAfter: 25
+      ),
+      DrawerStack(
+        axis: .vertical,
+        spacing: 20.0,
+        views: [reportButton, cancelButton]
+      )
+    ])
 
-        reportButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned drawer] in
-                drawer.dismiss(animated: true) {
-                    config.onReport()
-                }
-            }
-            .store(in: &drawer.cancellables)
+    reportButton.publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned drawer] in
+        drawer.dismiss(animated: true) {
+          config.onReport()
+        }
+      }
+      .store(in: &drawer.cancellables)
 
-        cancelButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned drawer] in
-                drawer.dismiss(animated: true) {
-                    config.onCancel()
-                }
-            }
-            .store(in: &drawer.cancellables)
+    cancelButton.publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned drawer] in
+        drawer.dismiss(animated: true) {
+          config.onCancel()
+        }
+      }
+      .store(in: &drawer.cancellables)
 
-        return drawer
-    }
+    return drawer
+  }
 }
 
 extension MakeReportDrawer {
-    public static let unimplemented = MakeReportDrawer(
-        run: XCTUnimplemented("\(Self.self)")
-    )
+  public static let unimplemented = MakeReportDrawer(
+    run: XCTUnimplemented("\(Self.self)")
+  )
 }
diff --git a/Sources/ReportingFeature/ProcessBannedList.swift b/Sources/ReportingFeature/ProcessBannedList.swift
deleted file mode 100644
index 3399a34ee3614bc41df393251d1e21a9309cdf52..0000000000000000000000000000000000000000
--- a/Sources/ReportingFeature/ProcessBannedList.swift
+++ /dev/null
@@ -1,64 +0,0 @@
-import Foundation
-import SwiftCSV
-import XCTestDynamicOverlay
-
-public struct ProcessBannedList {
-    public enum ElementError: Swift.Error {
-        case missingUserId
-        case invalidUserId(String)
-    }
-
-    public enum Error: Swift.Error {
-        case invalidData
-        case csv(Swift.Error)
-    }
-
-    public typealias ForEach = (Result<Data, ElementError>) -> Void
-    public typealias Completion = (Result<Void, Error>) -> Void
-
-    public var run: (Data, ForEach, Completion) -> Void
-
-    public func callAsFunction(
-        data: Data,
-        forEach: ForEach,
-        completion: Completion
-    ) {
-        run(data, forEach, completion)
-    }
-}
-
-extension ProcessBannedList {
-    public static let live = ProcessBannedList { data, forEach, completion in
-        guard let csvString = String(data: data, encoding: .utf8) else {
-            completion(.failure(.invalidData))
-            return
-        }
-        let csv: EnumeratedCSV
-        do {
-            csv = try EnumeratedCSV(string: csvString)
-        }
-        catch {
-            completion(.failure(.csv(error)))
-            return
-        }
-        csv.rows.forEach { row in
-            guard let userIdString = row.first else {
-                forEach(.failure(.missingUserId))
-                return
-            }
-            guard let userId = Data(base64Encoded: userIdString) else {
-                forEach(.failure(.invalidUserId(userIdString)))
-                return
-            }
-            forEach(.success(userId))
-        }
-        completion(.success(()))
-    }
-}
-
-extension ProcessBannedList {
-    public static let unimplemented = ProcessBannedList { _, _, _ in
-        let run: () -> Void = XCTUnimplemented("\(Self.self)")
-        run()
-    }
-}
diff --git a/Sources/ReportingFeature/Report.swift b/Sources/ReportingFeature/Report.swift
index c2032b6a6b87d096f8f6883085d4c6e887630600..56ff6192eeee64004e4cb5a3b5d978017d719998 100644
--- a/Sources/ReportingFeature/Report.swift
+++ b/Sources/ReportingFeature/Report.swift
@@ -1,52 +1,52 @@
 import Foundation
 
 public struct Report: Encodable {
-    public init(
-        sender: ReportUser,
-        recipient: ReportUser,
-        type: ReportType,
-        screenshot: Data,
-        partyName: String? = nil,
-        partyBlob: String? = nil,
-        partyMembers: [ReportUser]? = nil
-    ) {
-        self.sender = sender
-        self.recipient = recipient
-        self.type = type
-        self.screenshot = screenshot
-        self.partyName = partyName
-        self.partyBlob = partyBlob
-        self.partyMembers = partyMembers
-    }
-
-    public var sender: ReportUser
-    public var recipient: ReportUser
-    public var type: ReportType
-    public var screenshot: Data
-    public var partyName: String?
-    public var partyBlob: String?
-    public var partyMembers: [ReportUser]?
+  public init(
+    sender: ReportUser,
+    recipient: ReportUser,
+    type: ReportType,
+    screenshot: Data,
+    partyName: String? = nil,
+    partyBlob: String? = nil,
+    partyMembers: [ReportUser]? = nil
+  ) {
+    self.sender = sender
+    self.recipient = recipient
+    self.type = type
+    self.screenshot = screenshot
+    self.partyName = partyName
+    self.partyBlob = partyBlob
+    self.partyMembers = partyMembers
+  }
+  
+  public var sender: ReportUser
+  public var recipient: ReportUser
+  public var type: ReportType
+  public var screenshot: Data
+  public var partyName: String?
+  public var partyBlob: String?
+  public var partyMembers: [ReportUser]?
 }
 
 extension Report {
-    public struct ReportUser: Encodable {
-        public init(
-            userId: String,
-            username: String
-        ) {
-            self.userId = userId
-            self.username = username
-        }
-
-        public var userId: String
-        public var username: String
+  public struct ReportUser: Encodable {
+    public init(
+      userId: String,
+      username: String
+    ) {
+      self.userId = userId
+      self.username = username
     }
+    
+    public var userId: String
+    public var username: String
+  }
 }
 
 extension Report {
-    public enum ReportType: String, Encodable {
-        case dm
-        case group
-        case channel
-    }
+  public enum ReportType: String, Encodable {
+    case dm
+    case group
+    case channel
+  }
 }
diff --git a/Sources/ReportingFeature/ReportingStatus.swift b/Sources/ReportingFeature/ReportingStatus.swift
index 1bb72983430de28a6d46c47cd7ec2a3a21612659..2da0592e647cc059be8d2152e72a4e71c81d5689 100644
--- a/Sources/ReportingFeature/ReportingStatus.swift
+++ b/Sources/ReportingFeature/ReportingStatus.swift
@@ -1,51 +1,58 @@
 import Combine
 
 public struct ReportingStatus {
-    public var isOptional: () -> Bool
-    public var isEnabled: () -> Bool
-    public var isEnabledPublisher: () -> AnyPublisher<Bool, Never>
-    public var enable: (Bool) -> Void
+  public var isOptional: () -> Bool
+  public var isEnabled: () -> Bool
+  public var isEnabledPublisher: () -> AnyPublisher<Bool, Never>
+  public var enable: (Bool) -> Void
 }
 
 extension ReportingStatus {
-    public static func live(
-        isOptional: ReportingStatusIsOptional = .live(),
-        isEnabled: ReportingStatusIsEnabled = .live()
-    ) -> ReportingStatus {
-        ReportingStatus(
-            isOptional: {
-                isOptional.get()
-            },
-            isEnabled: {
-                if isOptional.get() == false {
-                    return true
-                }
+  public static func live(
+    isOptional: ReportingStatusIsOptional = .live(),
+    isEnabled: ReportingStatusIsEnabled = .live()
+  ) -> ReportingStatus {
+    ReportingStatus(
+      isOptional: {
+        isOptional.get()
+      },
+      isEnabled: {
+        if isOptional.get() == false {
+          return true
+        }
+        
+        return isEnabled.get()
+      },
+      isEnabledPublisher: {
+        if isOptional.get() == false {
+          return Just(true).eraseToAnyPublisher()
+        }
+        
+        return isEnabled.publisher()
+      },
+      enable: { enabled in
+        isEnabled.set(enabled)
+      }
+    )
+  }
+  
+  public static func mock(
+    isEnabled: Bool = false,
+    isOptional: Bool = true
+  ) -> ReportingStatus {
+    let isEnabledSubject = CurrentValueSubject<Bool, Never>(isEnabled)
+    return ReportingStatus(
+      isOptional: { isOptional },
+      isEnabled: { isEnabledSubject.value },
+      isEnabledPublisher: { isEnabledSubject.eraseToAnyPublisher() },
+      enable: { isEnabledSubject.send($0) }
+    )
+  }
 
-                return isEnabled.get()
-            },
-            isEnabledPublisher: {
-                if isOptional.get() == false {
-                    return Just(true).eraseToAnyPublisher()
-                }
-
-                return isEnabled.publisher()
-            },
-            enable: { enabled in
-                isEnabled.set(enabled)
-            }
-        )
-    }
-
-    public static func mock(
-        isEnabled: Bool = false,
-        isOptional: Bool = true
-    ) -> ReportingStatus {
-        let isEnabledSubject = CurrentValueSubject<Bool, Never>(isEnabled)
-        return ReportingStatus(
-            isOptional: { isOptional },
-            isEnabled: { isEnabledSubject.value },
-            isEnabledPublisher: { isEnabledSubject.eraseToAnyPublisher() },
-            enable: { isEnabledSubject.send($0) }
-        )
-    }
+  public static let unimplemented = ReportingStatus(
+    isOptional: { fatalError() },
+    isEnabled: { fatalError() },
+    isEnabledPublisher: { fatalError() },
+    enable: { _ in }
+  )
 }
diff --git a/Sources/ReportingFeature/ReportingStatusIsEnabled.swift b/Sources/ReportingFeature/ReportingStatusIsEnabled.swift
index 32f23fcb78a3b5e8fe7f6dc15a95a1b27e278bfb..ecf02aba50fff9f2334ad50dee4a6d2b8aa2dbb8 100644
--- a/Sources/ReportingFeature/ReportingStatusIsEnabled.swift
+++ b/Sources/ReportingFeature/ReportingStatusIsEnabled.swift
@@ -2,37 +2,37 @@ import Combine
 import Foundation
 
 public struct ReportingStatusIsEnabled {
-    public var get: () -> Bool
-    public var set: (Bool) -> Void
-    public var publisher: () -> AnyPublisher<Bool, Never>
+  public var get: () -> Bool
+  public var set: (Bool) -> Void
+  public var publisher: () -> AnyPublisher<Bool, Never>
 }
 
 extension ReportingStatusIsEnabled {
-    public static func live(
-        userDefaults: UserDefaults = .standard
-    ) -> ReportingStatusIsEnabled {
-        ReportingStatusIsEnabled(
-            get: {
-                userDefaults.isReportingEnabled
-            },
-            set: { enabled in
-                userDefaults.isReportingEnabled = enabled
-            },
-            publisher: {
-                userDefaults.publisher(for: \.isReportingEnabled).eraseToAnyPublisher()
-            }
-        )
-    }
+  public static func live(
+    userDefaults: UserDefaults = .standard
+  ) -> ReportingStatusIsEnabled {
+    ReportingStatusIsEnabled(
+      get: {
+        userDefaults.isReportingEnabled
+      },
+      set: { enabled in
+        userDefaults.isReportingEnabled = enabled
+      },
+      publisher: {
+        userDefaults.publisher(for: \.isReportingEnabled).eraseToAnyPublisher()
+      }
+    )
+  }
 }
 
 private extension UserDefaults {
-    static let isReportingEnabledKey = "isReportingEnabled"
-
-    @objc var isReportingEnabled: Bool {
-        get {
-            bool(forKey: Self.isReportingEnabledKey)
-        } set {
-            set(newValue, forKey: Self.isReportingEnabledKey)
-        }
+  static let isReportingEnabledKey = "isReportingEnabled"
+  
+  @objc var isReportingEnabled: Bool {
+    get {
+      bool(forKey: Self.isReportingEnabledKey)
+    } set {
+      set(newValue, forKey: Self.isReportingEnabledKey)
     }
+  }
 }
diff --git a/Sources/ReportingFeature/ReportingStatusIsOptional.swift b/Sources/ReportingFeature/ReportingStatusIsOptional.swift
index e0cc6591bf1d13c68d022a2f8587cc495403f89d..ca5ad5654ba72678496581b7ac3aae56626bc577 100644
--- a/Sources/ReportingFeature/ReportingStatusIsOptional.swift
+++ b/Sources/ReportingFeature/ReportingStatusIsOptional.swift
@@ -1,24 +1,24 @@
 import Foundation
 
 public struct ReportingStatusIsOptional {
-    public var get: () -> Bool
+  public var get: () -> Bool
 }
 
 extension ReportingStatusIsOptional {
-    public static func live(
-        plist url: URL = Bundle.main.url(forResource: "Info", withExtension: "plist")!
-    ) -> ReportingStatusIsOptional {
-        ReportingStatusIsOptional {
-            struct Plist: Decodable {
-                let isReportingOptional: Bool
-            }
-
-            guard let data = try? Data(contentsOf: url),
-                  let infoPlist = try? PropertyListDecoder().decode(Plist.self, from: data) else {
-                return true
-            }
-
-            return infoPlist.isReportingOptional
-        }
+  public static func live(
+    plist url: URL = Bundle.main.url(forResource: "Info", withExtension: "plist")!
+  ) -> ReportingStatusIsOptional {
+    ReportingStatusIsOptional {
+      struct Plist: Decodable {
+        let isReportingOptional: Bool
+      }
+      
+      guard let data = try? Data(contentsOf: url),
+            let infoPlist = try? PropertyListDecoder().decode(Plist.self, from: data) else {
+        return true
+      }
+      
+      return infoPlist.isReportingOptional
     }
+  }
 }
diff --git a/Sources/ReportingFeature/SendReport.swift b/Sources/ReportingFeature/SendReport.swift
index 4793d998d49c43426012ac9760cd28e39e8eb8e6..678bfbc3d1ce2a8339975e74b56330591b6ff06d 100644
--- a/Sources/ReportingFeature/SendReport.swift
+++ b/Sources/ReportingFeature/SendReport.swift
@@ -2,93 +2,93 @@ import Foundation
 import XCTestDynamicOverlay
 
 public struct SendReport {
-    public typealias Completion = (Result<Void, Error>) -> Void
+  public typealias Completion = (Result<Void, Error>) -> Void
 
-    public var run: (Report, @escaping Completion) -> Void
+  public var run: (Report, @escaping Completion) -> Void
 
-    public func callAsFunction(_ report: Report, completion: @escaping Completion) {
-        run(report, completion)
-    }
+  public func callAsFunction(_ report: Report, completion: @escaping Completion) {
+    run(report, completion)
+  }
 }
 
 extension SendReport {
-    public static let live = SendReport { report, completion in
-        let url = URL(string: "https://3.74.237.181:11420/report")!
-        var request = URLRequest(url: url)
-        request.httpMethod = "POST"
-        do {
-            request.httpBody = try JSONEncoder().encode(report)
-        } catch {
-            completion(.failure(error))
-            return
-        }
-        let session = URLSession(
-            configuration: .default,
-            delegate: SessionDelegate(),
-            delegateQueue: nil
-        )
-        let task = session.dataTask(with: request) { _, _, error in
-            defer { session.invalidateAndCancel() }
-            if let error = error {
-                completion(.failure(error))
-                return
-            }
-            completion(.success(()))
-        }
-        task.resume()
+  public static let live = SendReport { report, completion in
+    let url = URL(string: "https://3.74.237.181:11420/report")!
+    var request = URLRequest(url: url)
+    request.httpMethod = "POST"
+    do {
+      request.httpBody = try JSONEncoder().encode(report)
+    } catch {
+      completion(.failure(error))
+      return
+    }
+    let session = URLSession(
+      configuration: .default,
+      delegate: SessionDelegate(),
+      delegateQueue: nil
+    )
+    let task = session.dataTask(with: request) { _, _, error in
+      defer { session.invalidateAndCancel() }
+      if let error = error {
+        completion(.failure(error))
+        return
+      }
+      completion(.success(()))
     }
+    task.resume()
+  }
 
-    public static func mock(
-        result: Result<Void, Error> = .success(())
-    ) -> SendReport {
-        SendReport { report, completion in
-            print("[SendReport.mock] Sending report: \(report)")
-            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
-                print("[SendReport.mock] Sending report finished")
-                completion(result)
-            }
-        }
+  public static func mock(
+    result: Result<Void, Error> = .success(())
+  ) -> SendReport {
+    SendReport { report, completion in
+      print("[SendReport.mock] Sending report: \(report)")
+      DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
+        print("[SendReport.mock] Sending report finished")
+        completion(result)
+      }
     }
+  }
 }
 
 extension SendReport {
-    public static let unimplemented = SendReport(
-        run: XCTUnimplemented("\(Self.self)")
-    )
+  public static let unimplemented = SendReport(
+    run: XCTUnimplemented("\(Self.self)")
+  )
 }
 
 private final class SessionDelegate: NSObject, URLSessionDelegate {
-    func urlSession(
-        _ session: URLSession,
-        didReceive challenge: URLAuthenticationChallenge,
-        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
-    ) {
-        let authMethod = challenge.protectionSpace.authenticationMethod
-        guard authMethod == NSURLAuthenticationMethodServerTrust else {
-            return completionHandler(.cancelAuthenticationChallenge, nil)
-        }
-
-        guard let serverTrust = challenge.protectionSpace.serverTrust else {
-            return completionHandler(.cancelAuthenticationChallenge, nil)
-        }
+  func urlSession(
+    _ session: URLSession,
+    didReceive challenge: URLAuthenticationChallenge,
+    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
+  ) {
+    let authMethod = challenge.protectionSpace.authenticationMethod
+    guard authMethod == NSURLAuthenticationMethodServerTrust else {
+      return completionHandler(.cancelAuthenticationChallenge, nil)
+    }
 
-        guard let serverCert = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
-            return completionHandler(.cancelAuthenticationChallenge, nil)
-        }
+    guard let serverTrust = challenge.protectionSpace.serverTrust else {
+      return completionHandler(.cancelAuthenticationChallenge, nil)
+    }
 
-        let serverCertCFData = SecCertificateCopyData(serverCert)
-        let serverCertData = Data(
-            bytes: CFDataGetBytePtr(serverCertCFData),
-            count: CFDataGetLength(serverCertCFData)
-        )
+    guard let serverCert = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
+      return completionHandler(.cancelAuthenticationChallenge, nil)
+    }
 
-        let localCertURL = Bundle.module.url(forResource: "report_cert", withExtension: "der")!
-        let localCertData = try! Data(contentsOf: localCertURL)
+    let serverCertCFData = SecCertificateCopyData(serverCert)
+    let serverCertData = Data(
+      bytes: CFDataGetBytePtr(serverCertCFData),
+      count: CFDataGetLength(serverCertCFData)
+    )
 
-        guard serverCertData == localCertData else {
-            return completionHandler(.cancelAuthenticationChallenge, nil)
-        }
+    let localCertURL = Bundle.module.url(forResource: "report_cert", withExtension: "der")!
+    let localCertData = try! Data(contentsOf: localCertURL)
 
-        completionHandler(.useCredential, URLCredential(trust: serverTrust))
+    guard serverCertData == localCertData else {
+      return completionHandler(.cancelAuthenticationChallenge, nil)
     }
+
+    completionHandler(.useCredential, URLCredential(trust: serverTrust))
+  }
 }
diff --git a/Sources/RequestPermissionFeature/RequestPermissionController.swift b/Sources/RequestPermissionFeature/RequestPermissionController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..9c12752c92e20763c81647fe501e749d014eca3e
--- /dev/null
+++ b/Sources/RequestPermissionFeature/RequestPermissionController.swift
@@ -0,0 +1,98 @@
+import UIKit
+import Shared
+import Combine
+import AppCore
+import Dependencies
+import AppResources
+import AppNavigation
+import PermissionsFeature
+
+public final class RequestPermissionController: UIViewController {
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+  @Dependency(\.permissions) var permissions: PermissionsManager
+
+  private lazy var screenView = RequestPermissionView()
+
+  private let permissionType: PermissionType
+  private var cancellables = Set<AnyCancellable>()
+
+  public override func loadView() {
+    view = screenView
+  }
+
+  public init(_ permissionType: PermissionType) {
+    self.permissionType = permissionType
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    statusBar.set(.darkContent)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+
+    switch permissionType {
+    case .camera:
+      screenView.setup(
+        title: Localized.Chat.Actions.Permission.Camera.title,
+        subtitle: Localized.Chat.Actions.Permission.Camera.subtitle,
+        image: Asset.permissionCamera.image
+      )
+    case .library:
+      screenView.setup(
+        title: Localized.Chat.Actions.Permission.Library.title,
+        subtitle: Localized.Chat.Actions.Permission.Library.subtitle,
+        image: Asset.permissionLibrary.image
+      )
+    case .microphone:
+      screenView.setup(
+        title: Localized.Chat.Actions.Permission.Microphone.title,
+        subtitle: Localized.Chat.Actions.Permission.Microphone.subtitle,
+        image: Asset.permissionMicrophone.image
+      )
+    }
+
+    screenView
+      .notNowButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        dismiss(animated: true)
+      }.store(in: &cancellables)
+
+    screenView
+      .continueButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        switch permissionType {
+        case .camera:
+          permissions.camera.request { [weak self] _ in
+            guard let self else { return }
+            self.shouldDismissModal()
+          }
+        case .library:
+          permissions.library.request { [weak self] _ in
+            guard let self else { return }
+            self.shouldDismissModal()
+          }
+        case .microphone:
+          permissions.microphone.request { [weak self] _ in
+            guard let self else { return }
+            self.shouldDismissModal()
+          }
+        }
+      }.store(in: &cancellables)
+  }
+
+  private func shouldDismissModal() {
+    DispatchQueue.main.async { [weak self] in
+      guard let self else { return }
+      self.dismiss(animated: true)
+    }
+  }
+}
diff --git a/Sources/RequestPermissionFeature/RequestPermissionView.swift b/Sources/RequestPermissionFeature/RequestPermissionView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..ab4405bd0844fce7bf2273f8e8a0944106e4c894
--- /dev/null
+++ b/Sources/RequestPermissionFeature/RequestPermissionView.swift
@@ -0,0 +1,103 @@
+import UIKit
+import Shared
+import AppResources
+
+final class RequestPermissionView: UIView {
+  let titleLabel = UILabel()
+  let iconImage = UIImageView()
+  let subtitleLabel = UILabel()
+  let littleLogo = UIImageView()
+  let notNowButton = UIButton()
+  let continueButton = CapsuleButton()
+
+  init() {
+    super.init(frame: .zero)
+    littleLogo.image = Asset.permissionLogo.image
+    notNowButton.setTitle(Localized.Chat.Actions.Permission.notnow, for: .normal)
+    continueButton.set(style: .brandColored, title: Localized.Chat.Actions.Permission.continue)
+
+    titleLabel.textAlignment = .center
+
+    backgroundColor = Asset.neutralWhite.color
+    titleLabel.textColor = Asset.neutralActive.color
+    notNowButton.setTitleColor(Asset.neutralWeak.color, for: .normal)
+
+    subtitleLabel.numberOfLines = 0
+
+    titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
+    notNowButton.titleLabel?.font = Fonts.Mulish.semiBold.font(size: 16)
+
+    let actionsContainer = UIView()
+    actionsContainer.addSubview(continueButton)
+    actionsContainer.addSubview(notNowButton)
+
+    addSubview(iconImage)
+    addSubview(titleLabel)
+    addSubview(littleLogo)
+    addSubview(subtitleLabel)
+    addSubview(actionsContainer)
+
+    iconImage.snp.makeConstraints {
+      $0.centerX.equalToSuperview()
+    }
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(iconImage.snp.bottom).offset(34)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+    }
+
+    subtitleLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(32)
+      $0.right.equalToSuperview().offset(-32)
+      $0.bottom.equalTo(snp.centerY)
+    }
+
+    littleLogo.snp.makeConstraints {
+      $0.centerX.equalToSuperview()
+      $0.bottom.equalTo(safeAreaLayoutGuide).offset(-15)
+    }
+
+    actionsContainer.snp.makeConstraints {
+      $0.top.greaterThanOrEqualTo(subtitleLabel.snp.bottom)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.lessThanOrEqualTo(littleLogo.snp.top)
+    }
+
+    continueButton.snp.makeConstraints {
+      $0.top.greaterThanOrEqualToSuperview()
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
+      $0.bottom.equalTo(actionsContainer.snp.centerY).offset(-5)
+    }
+
+    notNowButton.snp.makeConstraints {
+      $0.top.equalTo(actionsContainer.snp.centerY).offset(5)
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
+      $0.bottom.lessThanOrEqualToSuperview()
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  func setup(title: String, subtitle: String, image: UIImage) {
+    iconImage.image = image
+    titleLabel.text = title
+
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.lineHeightMultiple = 1.5
+    paragraph.alignment = .center
+
+    subtitleLabel.attributedText = NSAttributedString(
+      string: subtitle,
+      attributes: [
+        .paragraphStyle: paragraph,
+        .font: Fonts.Mulish.regular.font(size: 14.0),
+        .foregroundColor: Asset.neutralBody.color,
+      ]
+    )
+  }
+}
diff --git a/Sources/RequestsFeature/Controllers/RequestsContainerController.swift b/Sources/RequestsFeature/Controllers/RequestsContainerController.swift
index f3f7b3937059b0b30185e139b79b597f338061ca..49ab70bc1481be2c6797d9af64c2c3162c2026b3 100644
--- a/Sources/RequestsFeature/Controllers/RequestsContainerController.swift
+++ b/Sources/RequestsFeature/Controllers/RequestsContainerController.swift
@@ -1,118 +1,121 @@
 import UIKit
-import Theme
 import Shared
 import Combine
+import AppCore
+import AppResources
+import Dependencies
+import AppNavigation
 import ContactFeature
-import DependencyInjection
 
 public final class RequestsContainerController: UIViewController {
-    @Dependency private var coordinator: RequestsCoordinating
-    @Dependency private var statusBarController: StatusBarStyleControlling
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
 
-    lazy private var screenView = RequestsContainerView()
-    private var cancellables = Set<AnyCancellable>()
+  private lazy var screenView = RequestsContainerView()
+  private var cancellables = Set<AnyCancellable>()
 
-    public override func loadView() {
-        view = screenView
-        screenView.scrollView.delegate = self
+  public override func loadView() {
+    view = screenView
+    screenView.scrollView.delegate = self
 
-        addChild(screenView.sentController)
-        addChild(screenView.failedController)
-        addChild(screenView.receivedController)
+    addChild(screenView.sentController)
+    addChild(screenView.failedController)
+    addChild(screenView.receivedController)
 
-        screenView.sentController.didMove(toParent: self)
-        screenView.failedController.didMove(toParent: self)
-        screenView.receivedController.didMove(toParent: self)
+    screenView.sentController.didMove(toParent: self)
+    screenView.failedController.didMove(toParent: self)
+    screenView.receivedController.didMove(toParent: self)
 
-        screenView.bringSubviewToFront(screenView.segmentedControl)
-    }
+    screenView.bringSubviewToFront(screenView.segmentedControl)
+  }
 
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        statusBarController.style.send(.darkContent)
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    statusBar.set(.darkContent)
 
-        navigationController?.navigationBar
-            .customize(backgroundColor: Asset.neutralWhite.color)
-    }
+    navigationController?.navigationBar
+      .customize(backgroundColor: Asset.neutralWhite.color)
+  }
 
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupNavigationBar()
-        setupBindings()
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupNavigationBar()
+    setupBindings()
 
-        if let stack = navigationController?.viewControllers, stack.count > 1 {
-            if stack[stack.count - 2].isKind(of: ContactController.self) {
-                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
-                    guard let self = self else { return }
+    if let stack = navigationController?.viewControllers, stack.count > 1 {
+      if stack[stack.count - 2].isKind(of: ContactController.self) {
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
+          guard let self else { return }
 
-                    let point = CGPoint(x: self.screenView.frame.width, y: 0.0)
-                    self.screenView.scrollView.setContentOffset(point, animated: true)
-                }
-            }
+          let point = CGPoint(x: self.screenView.frame.width, y: 0.0)
+          self.screenView.scrollView.setContentOffset(point, animated: true)
         }
+      }
     }
-
-    private func setupNavigationBar() {
-        navigationItem.backButtonTitle = ""
-
-        let titleLabel = UILabel()
-        titleLabel.text = Localized.Requests.title
-        titleLabel.textColor = Asset.neutralActive.color
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 18.0)
-
-        let menuButton = UIButton()
-        menuButton.tintColor = Asset.neutralDark.color
-        menuButton.setImage(Asset.chatListMenu.image, for: .normal)
-        menuButton.addTarget(self, action: #selector(didTapMenu), for: .touchUpInside)
-        menuButton.snp.makeConstraints { $0.width.equalTo(50) }
-
-        navigationItem.leftBarButtonItem = UIBarButtonItem(
-            customView: UIStackView(arrangedSubviews: [menuButton, titleLabel])
-        )
-    }
-
-    private func setupBindings() {
-        screenView
-            .sentController
-            .connectionsPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toSearch(from: self) }
-            .store(in: &cancellables)
-
-        screenView
-            .segmentedControl
-            .receivedRequestsButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] _ in
-                screenView.scrollView.setContentOffset(.zero, animated: true)
-            }.store(in: &cancellables)
-
-        screenView
-            .segmentedControl
-            .sentRequestsButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] _ in
-                let point = CGPoint(x: screenView.frame.width, y: 0.0)
-                screenView.scrollView.setContentOffset(point, animated: true)
-            }.store(in: &cancellables)
-
-        screenView
-            .segmentedControl
-            .failedRequestsButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] _ in
-                let point = CGPoint(x: screenView.frame.width * 2.0, y: 0.0)
-                screenView.scrollView.setContentOffset(point, animated: true)
-            }.store(in: &cancellables)
-    }
-
-    @objc private func didTapMenu() {
-        coordinator.toSideMenu(from: self)
-    }
+  }
+
+  private func setupNavigationBar() {
+    navigationItem.backButtonTitle = ""
+
+    let titleLabel = UILabel()
+    titleLabel.text = Localized.Requests.title
+    titleLabel.textColor = Asset.neutralActive.color
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 18.0)
+
+    let menuButton = UIButton()
+    menuButton.tintColor = Asset.neutralDark.color
+    menuButton.setImage(Asset.chatListMenu.image, for: .normal)
+    menuButton.addTarget(self, action: #selector(didTapMenu), for: .touchUpInside)
+    menuButton.snp.makeConstraints { $0.width.equalTo(50) }
+
+    navigationItem.leftBarButtonItem = UIBarButtonItem(
+      customView: UIStackView(arrangedSubviews: [menuButton, titleLabel])
+    )
+  }
+
+  private func setupBindings() {
+    screenView
+      .sentController
+      .connectionsPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentSearch(on: navigationController!))
+      }.store(in: &cancellables)
+
+    screenView
+      .segmentedControl
+      .receivedRequestsButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] _ in
+        screenView.scrollView.setContentOffset(.zero, animated: true)
+      }.store(in: &cancellables)
+
+    screenView
+      .segmentedControl
+      .sentRequestsButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] _ in
+        let point = CGPoint(x: screenView.frame.width, y: 0.0)
+        screenView.scrollView.setContentOffset(point, animated: true)
+      }.store(in: &cancellables)
+
+    screenView
+      .segmentedControl
+      .failedRequestsButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] _ in
+        let point = CGPoint(x: screenView.frame.width * 2.0, y: 0.0)
+        screenView.scrollView.setContentOffset(point, animated: true)
+      }.store(in: &cancellables)
+  }
+
+  @objc private func didTapMenu() {
+    navigator.perform(PresentMenu(currentItem: .requests, from: self))
+  }
 }
 
 extension RequestsContainerController: UIScrollViewDelegate {
-    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
-        screenView.segmentedControl.updateSwipePercentage(scrollView.contentOffset.x / view.frame.width)
-    }
+  public func scrollViewDidScroll(_ scrollView: UIScrollView) {
+    screenView.segmentedControl.updateSwipePercentage(scrollView.contentOffset.x / view.frame.width)
+  }
 }
diff --git a/Sources/RequestsFeature/Controllers/RequestsFailedController.swift b/Sources/RequestsFeature/Controllers/RequestsFailedController.swift
index c0af65d5f83a4bc1528065eef1551e597a52f6c9..a7e65e87d510deaac71d9a0b3a2024c8d58f99b3 100644
--- a/Sources/RequestsFeature/Controllers/RequestsFailedController.swift
+++ b/Sources/RequestsFeature/Controllers/RequestsFailedController.swift
@@ -1,13 +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 lazy var screenView = RequestsFailedView()
     private var cancellables = Set<AnyCancellable>()
     private let viewModel = RequestsFailedViewModel()
     private var dataSource: UICollectionViewDiffableDataSource<Section, Request>?
@@ -27,7 +22,7 @@ final class RequestsFailedController: UIViewController {
             let cell: RequestCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
             cell.setupFor(requestFailed: request)
             cell.didTapStateButton = { [weak self] in
-                guard let self = self else { return }
+                guard let self else { return }
                 self.viewModel.didTapStateButtonFor(request: request)
             }
             return cell
@@ -39,10 +34,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 0a30d05fb32b5ec4d3eb54b1fdeb9ced674a17ac..3ee84bb9e1b29b539448cb11e222fbcf3c452ce8 100644
--- a/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift
+++ b/Sources/RequestsFeature/Controllers/RequestsReceivedController.swift
@@ -1,572 +1,596 @@
-import HUD
 import UIKit
-import Models
 import Shared
 import Combine
 import XXModels
-import Countries
-import ToastFeature
+import AppCore
+import AppResources
+import Dependencies
+import AppNavigation
 import DrawerFeature
-import DependencyInjection
+import CountryListFeature
 
 final class RequestsReceivedController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var toaster: ToastController
-    @Dependency private var coordinator: RequestsCoordinating
-
-    lazy private var screenView = RequestsReceivedView()
-    private var cancellables = Set<AnyCancellable>()
-    private let viewModel = RequestsReceivedViewModel()
-    private var drawerCancellables = Set<AnyCancellable>()
-    private var dataSource: UICollectionViewDiffableDataSource<Section, RequestReceived>?
-
-    override func loadView() {
-        view = screenView
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.toastManager) var toaster: ToastManager
+
+  private lazy var screenView = RequestsReceivedView()
+  private var cancellables = Set<AnyCancellable>()
+  private let viewModel = RequestsReceivedViewModel()
+  private var drawerCancellables = Set<AnyCancellable>()
+  private var dataSource: UICollectionViewDiffableDataSource<Section, RequestReceived>?
+
+  override func loadView() {
+    view = screenView
+  }
+
+  override func viewDidLoad() {
+    super.viewDidLoad()
+
+    screenView.collectionView.delegate = self
+    screenView.collectionView.register(RequestCell.self)
+    screenView.collectionView.register(RequestReceivedEmptyCell.self)
+    screenView.collectionView.registerSectionHeader(RequestsBlankSectionHeader.self)
+    screenView.collectionView.registerSectionHeader(RequestsHiddenSectionHeader.self)
+
+    dataSource = UICollectionViewDiffableDataSource<Section, RequestReceived>(
+      collectionView: screenView.collectionView
+    ) { collectionView, indexPath, requestReceived in
+      guard let request = requestReceived.request else {
+        let cell: RequestReceivedEmptyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+        return cell
+      }
+
+      let cell: RequestCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
+      cell.setupFor(requestReceived: requestReceived, isHidden: indexPath.section == 1)
+      cell.didTapStateButton = { [weak self] in
+        guard let self else { return }
+        self.viewModel.didTapStateButtonFor(request: request)
+      }
+
+      return cell
     }
 
-    override func viewDidLoad() {
-        super.viewDidLoad()
-
-        screenView.collectionView.delegate = self
-        screenView.collectionView.register(RequestCell.self)
-        screenView.collectionView.register(RequestReceivedEmptyCell.self)
-        screenView.collectionView.registerSectionHeader(RequestsBlankSectionHeader.self)
-        screenView.collectionView.registerSectionHeader(RequestsHiddenSectionHeader.self)
-
-        dataSource = UICollectionViewDiffableDataSource<Section, RequestReceived>(
-            collectionView: screenView.collectionView
-        ) { collectionView, indexPath, requestReceived in
-            guard let request = requestReceived.request else {
-                let cell: RequestReceivedEmptyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-                return cell
-            }
-
-            let cell: RequestCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
-            cell.setupFor(requestReceived: requestReceived, isHidden: indexPath.section == 1)
-            cell.didTapStateButton = { [weak self] in
-                guard let self = self else { return }
-                self.viewModel.didTapStateButtonFor(request: request)
-            }
-
-            return cell
-        }
-
-        dataSource?.supplementaryViewProvider = { [weak self] collectionView, kind, indexPath in
-            let reuseIdentifier: String
+    dataSource?.supplementaryViewProvider = { [weak self] collectionView, kind, indexPath in
+      let reuseIdentifier: String
 
-            if indexPath.section == Section.appearing.rawValue {
-                reuseIdentifier = String(describing: RequestsBlankSectionHeader.self)
-            } else {
-                reuseIdentifier = String(describing: RequestsHiddenSectionHeader.self)
-            }
+      if indexPath.section == Section.appearing.rawValue {
+        reuseIdentifier = String(describing: RequestsBlankSectionHeader.self)
+      } else {
+        reuseIdentifier = String(describing: RequestsHiddenSectionHeader.self)
+      }
 
-            let cell = collectionView.dequeueReusableSupplementaryView(
-                ofKind: kind,
-                withReuseIdentifier: reuseIdentifier,
-                for: indexPath
-            )
+      let cell = collectionView.dequeueReusableSupplementaryView(
+        ofKind: kind,
+        withReuseIdentifier: reuseIdentifier,
+        for: indexPath
+      )
 
-            if let cell = cell as? RequestsHiddenSectionHeader, let self = self {
-                cell.switcherView.setOn(self.viewModel.isShowingHiddenRequests, animated: true)
+      if let cell = cell as? RequestsHiddenSectionHeader, let self = self {
+        cell.switcherView.setOn(self.viewModel.isShowingHiddenRequests, animated: true)
 
-                cell.switcherView
-                    .publisher(for: .valueChanged)
-                    .sink { self.viewModel.didToggleHiddenRequestsSwitcher() }
-                    .store(in: &cell.cancellables)
-            }
+        cell.switcherView
+          .publisher(for: .valueChanged)
+          .sink { self.viewModel.didToggleHiddenRequestsSwitcher() }
+          .store(in: &cell.cancellables)
+      }
 
-            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() }
-            .store(in: &cancellables)
-
-        viewModel.itemsPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in dataSource?.apply($0, animatingDifferences: true) }
-            .store(in: &cancellables)
-
-        viewModel.contactConfirmationPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in presentSingleRequestSuccessDrawer(forContact: $0) }
-            .store(in: &cancellables)
-
-        viewModel.groupConfirmationPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in presentGroupRequestSuccessDrawer(forGroup: $0) }
-            .store(in: &cancellables)
+      return cell
     }
+
+    viewModel
+      .verifyingPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        presentVerifyingDrawer()
+      }.store(in: &cancellables)
+
+    viewModel
+      .itemsPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        dataSource?.apply($0, animatingDifferences: true)
+      }.store(in: &cancellables)
+
+    viewModel
+      .contactConfirmationPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        presentSingleRequestSuccessDrawer(forContact: $0)
+      }.store(in: &cancellables)
+
+    viewModel
+      .groupConfirmationPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        presentGroupRequestSuccessDrawer(forGroup: $0)
+      }.store(in: &cancellables)
+  }
 }
 
 extension RequestsReceivedController: UICollectionViewDelegate {
-    func collectionView(_: UICollectionView, didSelectItemAt indexPath: IndexPath) {
-        guard let request = dataSource?.itemIdentifier(for: indexPath)?.request else { return }
-
-        switch request {
-        case .group(let group):
-            guard group.authStatus == .pending || group.authStatus == .hidden else { return }
-            presentGroupRequestDrawer(forGroup: group)
-        case .contact(let contact):
-            guard contact.authStatus == .verified || contact.authStatus == .hidden else { return }
-            presentSingleRequestDrawer(forContact: contact)
-        }
+  func collectionView(_: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+    guard let request = dataSource?.itemIdentifier(for: indexPath)?.request else { return }
+
+    switch request {
+    case .group(let group):
+      guard group.authStatus == .pending || group.authStatus == .hidden else { return }
+      presentGroupRequestDrawer(forGroup: group)
+    case .contact(let contact):
+      guard contact.authStatus == .verified || contact.authStatus == .hidden else { return }
+      presentSingleRequestDrawer(forContact: contact)
     }
+  }
 }
 
 // MARK: - Group Request Success Drawer
 
 extension RequestsReceivedController {
-    func presentGroupRequestSuccessDrawer(forGroup group: Group) {
-        drawerCancellables.removeAll()
-
-        var items: [DrawerItem] = []
-
-        let drawerTitle = DrawerText(
-            font: Fonts.Mulish.bold.font(size: 12.0),
-            text: Localized.Requests.Drawer.Group.Success.title,
-            color: Asset.accentSuccess.color,
-            spacingAfter: 20,
-            leftImage: Asset.requestAccepted.image
-        )
-
-        let drawerNickname = DrawerText(
-            font: Fonts.Mulish.extraBold.font(size: 26.0),
-            text: group.name,
-            color: Asset.neutralDark.color,
-            spacingAfter: 20
-        )
-
-        let drawerSubtitle = DrawerText(
-            font: Fonts.Mulish.regular.font(size: 16.0),
-            text: Localized.Requests.Drawer.Group.Success.subtitle,
-            color: Asset.neutralDark.color,
-            spacingAfter: 20
-        )
-
-        items.append(contentsOf: [
-            drawerTitle,
-            drawerNickname,
-            drawerSubtitle
-        ])
-
-        let drawerSendButton = DrawerCapsuleButton(
-            model: .init(
-                title: Localized.Requests.Drawer.Group.Success.send,
-                style: .brandColored
-            ), spacingAfter: 5
-        )
-
-        let drawerLaterButton = DrawerCapsuleButton(
-            model: .init(
-                title: Localized.Requests.Drawer.Group.Success.later,
-                style: .simplestColoredBrand
-            )
-        )
-
-        items.append(contentsOf: [
-            drawerSendButton,
-            drawerLaterButton
-        ])
-
-        let drawer = DrawerController(with: items)
-
-        drawerSendButton.action
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                drawer.dismiss(animated: true) {
-                    let chatInfo = self.viewModel.groupChatWith(group: group)
-                    self.coordinator.toGroupChat(with: chatInfo, from: self)
-                }
-            }.store(in: &drawerCancellables)
-
-        drawerLaterButton.action
-            .sink { drawer.dismiss(animated: true) }
-            .store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
+  func presentGroupRequestSuccessDrawer(forGroup group: Group) {
+    drawerCancellables.removeAll()
+
+    var items: [DrawerItem] = []
+
+    let drawerTitle = DrawerText(
+      font: Fonts.Mulish.bold.font(size: 12.0),
+      text: Localized.Requests.Drawer.Group.Success.title,
+      color: Asset.accentSuccess.color,
+      spacingAfter: 20,
+      leftImage: Asset.requestAccepted.image
+    )
+
+    let drawerNickname = DrawerText(
+      font: Fonts.Mulish.extraBold.font(size: 26.0),
+      text: group.name,
+      color: Asset.neutralDark.color,
+      spacingAfter: 20
+    )
+
+    let drawerSubtitle = DrawerText(
+      font: Fonts.Mulish.regular.font(size: 16.0),
+      text: Localized.Requests.Drawer.Group.Success.subtitle,
+      color: Asset.neutralDark.color,
+      spacingAfter: 20
+    )
+
+    items.append(contentsOf: [
+      drawerTitle,
+      drawerNickname,
+      drawerSubtitle
+    ])
+
+    let drawerSendButton = DrawerCapsuleButton(
+      model: .init(
+        title: Localized.Requests.Drawer.Group.Success.send,
+        style: .brandColored
+      ), spacingAfter: 5
+    )
+
+    let drawerLaterButton = DrawerCapsuleButton(
+      model: .init(
+        title: Localized.Requests.Drawer.Group.Success.later,
+        style: .simplestColoredBrand
+      )
+    )
+
+    items.append(contentsOf: [
+      drawerSendButton,
+      drawerLaterButton
+    ])
+
+    drawerSendButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          self.navigator.perform(PresentGroupChat(
+            groupInfo: self.viewModel.groupChatWith(group: group),
+            on: self.navigationController!
+          ))
+        }
+      }.store(in: &drawerCancellables)
+
+    drawerLaterButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: items, isDismissable: true, from: self))
+  }
 }
 
 // MARK: - Single Request Success Drawer
 
 extension RequestsReceivedController {
-    func presentSingleRequestSuccessDrawer(forContact contact: Contact) {
-        drawerCancellables.removeAll()
-
-        var items: [DrawerItem] = []
-
-        let drawerTitle = DrawerText(
-            font: Fonts.Mulish.bold.font(size: 12.0),
-            text: Localized.Requests.Drawer.Single.Success.title,
-            color: Asset.accentSuccess.color,
-            spacingAfter: 20,
-            leftImage: Asset.requestAccepted.image
-        )
-
-        let drawerNickname = DrawerText(
-            font: Fonts.Mulish.extraBold.font(size: 26.0),
-            text: (contact.nickname ?? contact.username) ?? "",
-            color: Asset.neutralDark.color,
-            spacingAfter: 20
-        )
-
-        let drawerSubtitle = DrawerText(
-            font: Fonts.Mulish.regular.font(size: 16.0),
-            text: Localized.Requests.Drawer.Single.Success.subtitle,
-            color: Asset.neutralDark.color,
-            spacingAfter: 20
-        )
-
-        items.append(contentsOf: [
-            drawerTitle,
-            drawerNickname,
-            drawerSubtitle
-        ])
-
-        let drawerSendButton = DrawerCapsuleButton(
-            model: .init(
-                title: Localized.Requests.Drawer.Single.Success.send,
-                style: .brandColored
-            ), spacingAfter: 5
-        )
-
-        let drawerLaterButton = DrawerCapsuleButton(
-            model: .init(
-                title: Localized.Requests.Drawer.Single.Success.later,
-                style: .simplestColoredBrand
-            )
-        )
-
-        items.append(contentsOf: [
-            drawerSendButton,
-            drawerLaterButton
-        ])
-
-        let drawer = DrawerController(with: items)
-
-        drawerSendButton.action
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                drawer.dismiss(animated: true) {
-                    self.coordinator.toSingleChat(with: contact, from: self)
-                }
-            }.store(in: &drawerCancellables)
-
-        drawerLaterButton.action
-            .receive(on: DispatchQueue.main)
-            .sink { drawer.dismiss(animated: true) }
-            .store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
+  func presentSingleRequestSuccessDrawer(forContact contact: Contact) {
+    drawerCancellables.removeAll()
+
+    var items: [DrawerItem] = []
+
+    let drawerTitle = DrawerText(
+      font: Fonts.Mulish.bold.font(size: 12.0),
+      text: Localized.Requests.Drawer.Single.Success.title,
+      color: Asset.accentSuccess.color,
+      spacingAfter: 20,
+      leftImage: Asset.requestAccepted.image
+    )
+
+    let drawerNickname = DrawerText(
+      font: Fonts.Mulish.extraBold.font(size: 26.0),
+      text: (contact.nickname ?? contact.username) ?? "",
+      color: Asset.neutralDark.color,
+      spacingAfter: 20
+    )
+
+    let drawerSubtitle = DrawerText(
+      font: Fonts.Mulish.regular.font(size: 16.0),
+      text: Localized.Requests.Drawer.Single.Success.subtitle,
+      color: Asset.neutralDark.color,
+      spacingAfter: 20
+    )
+
+    items.append(contentsOf: [
+      drawerTitle,
+      drawerNickname,
+      drawerSubtitle
+    ])
+
+    let drawerSendButton = DrawerCapsuleButton(
+      model: .init(
+        title: Localized.Requests.Drawer.Single.Success.send,
+        style: .brandColored
+      ), spacingAfter: 5
+    )
+
+    let drawerLaterButton = DrawerCapsuleButton(
+      model: .init(
+        title: Localized.Requests.Drawer.Single.Success.later,
+        style: .simplestColoredBrand
+      )
+    )
+
+    items.append(contentsOf: [
+      drawerSendButton,
+      drawerLaterButton
+    ])
+
+    drawerSendButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          self.navigator.perform(PresentChat(contact: contact, on: self.navigationController!))
+        }
+      }.store(in: &drawerCancellables)
+
+    drawerLaterButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: items, isDismissable: true,  from: self))
+  }
 }
 
 // MARK: - Group Request Drawer
 
 extension RequestsReceivedController {
-    func presentGroupRequestDrawer(forGroup group: Group) {
-        drawerCancellables.removeAll()
-
-        var items: [DrawerItem] = []
-
-        let drawerTitle = DrawerText(
-            font: Fonts.Mulish.bold.font(size: 12.0),
-            text: Localized.Requests.Drawer.Group.title,
-            spacingAfter: 20
-        )
-
-        let drawerGroupName = DrawerText(
-            font: Fonts.Mulish.extraBold.font(size: 26.0),
-            text: group.name,
-            color: Asset.neutralDark.color,
-            spacingAfter: 25
-        )
-
-        items.append(contentsOf: [
-            drawerTitle,
-            drawerGroupName
-        ])
-
-        let drawerLoading = DrawerLoadingRetry()
-        drawerLoading.startSpinning()
-
-        items.append(drawerLoading)
+  func presentGroupRequestDrawer(forGroup group: Group) {
+    drawerCancellables.removeAll()
 
-        let drawerTable = DrawerTable(spacingAfter: 23)
+    var items: [DrawerItem] = []
 
-        drawerLoading.retryPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                drawerLoading.startSpinning()
+    let drawerTitle = DrawerText(
+      font: Fonts.Mulish.bold.font(size: 12.0),
+      text: Localized.Requests.Drawer.Group.title,
+      spacingAfter: 20
+    )
 
-                viewModel.fetchMembers(group) { [weak self] in
-                    guard let _ = self else { return }
+    let drawerGroupName = DrawerText(
+      font: Fonts.Mulish.extraBold.font(size: 26.0),
+      text: group.name,
+      color: Asset.neutralDark.color,
+      spacingAfter: 25
+    )
 
-                    switch $0 {
-                    case .success(let models):
-                        DispatchQueue.main.async {
-                            drawerTable.update(models: models)
-                            drawerLoading.stopSpinning(withRetry: false)
-                        }
-                    case .failure:
-                        drawerLoading.stopSpinning(withRetry: true)
-                    }
-                }
-            }.store(in: &drawerCancellables)
+    items.append(contentsOf: [
+      drawerTitle,
+      drawerGroupName
+    ])
 
-        viewModel.fetchMembers(group) { [weak self] in
-            guard let _ = self else { return }
-
-            switch $0 {
-            case .success(let models):
-                DispatchQueue.main.async {
-                    drawerTable.update(models: models)
-                    drawerLoading.stopSpinning(withRetry: false)
-                }
-            case .failure:
-                drawerLoading.stopSpinning(withRetry: true)
-            }
-        }
-
-        items.append(drawerTable)
+    let drawerLoading = DrawerLoadingRetry()
+    drawerLoading.startSpinning()
 
-        let drawerAcceptButton = DrawerCapsuleButton(
-            model: .init(
-                title: Localized.Requests.Drawer.Group.accept,
-                style: .brandColored
-            ), spacingAfter: 5
-        )
+    items.append(drawerLoading)
 
-        let drawerHideButton = DrawerCapsuleButton(
-            model: .init(
-                title: Localized.Requests.Drawer.Group.hide,
-                style: .simplestColoredBrand
-            ), spacingAfter: 5
-        )
+    let drawerTable = DrawerTable(spacingAfter: 23)
 
-        items.append(contentsOf: [drawerAcceptButton, drawerHideButton])
+    drawerLoading.retryPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        drawerLoading.startSpinning()
 
-        let drawer = DrawerController(with: items)
+        viewModel.fetchMembers(group) { [weak self] in
+          guard let _ = self else { return }
 
-        drawerAcceptButton.action
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                drawer.dismiss(animated: true) {
-                    self.viewModel.didRequestAccept(group: group)
-                }
-            }
-            .store(in: &drawerCancellables)
-
-        drawerHideButton.action
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                drawer.dismiss(animated: true) {
-                    self.viewModel.didRequestHide(group: group)
-                }
+          switch $0 {
+          case .success(let models):
+            DispatchQueue.main.async {
+              drawerTable.update(models: models)
+              drawerLoading.stopSpinning(withRetry: false)
             }
-            .store(in: &drawerCancellables)
-
-        coordinator.toDrawerBottom(drawer, from: self)
-    }
-}
+          case .failure:
+            drawerLoading.stopSpinning(withRetry: true)
+          }
+        }
+      }.store(in: &drawerCancellables)
 
-// MARK: - Single Request Drawer
+    viewModel.fetchMembers(group) { [weak self] in
+      guard let _ = self else { return }
 
-extension RequestsReceivedController {
-    func presentSingleRequestDrawer(forContact contact: Contact) {
-        drawerCancellables.removeAll()
-
-        var items: [DrawerItem] = []
-
-        let drawerTitle = DrawerText(
-            font: Fonts.Mulish.bold.font(size: 12.0),
-            text: Localized.Requests.Drawer.Single.title,
-            spacingAfter: 20
-        )
-
-        let drawerUsername = DrawerText(
-            font: Fonts.Mulish.extraBold.font(size: 26.0),
-            text: contact.username ?? "",
-            color: Asset.neutralDark.color,
-            spacingAfter: 25
-        )
-
-        items.append(contentsOf: [
-            drawerTitle,
-            drawerUsername
-        ])
-
-        let drawerEmailTitle = DrawerText(
-            font: Fonts.Mulish.bold.font(size: 12.0),
-            text: Localized.Requests.Drawer.Single.email,
-            color: Asset.neutralWeak.color,
-            spacingAfter: 5
-        )
-
-        if let email = contact.email {
-            let drawerEmailContent = DrawerText(
-                font: Fonts.Mulish.regular.font(size: 16.0),
-                text: email,
-                spacingAfter: 25
-            )
-
-            items.append(contentsOf: [
-                drawerEmailTitle,
-                drawerEmailContent
-            ])
+      switch $0 {
+      case .success(let models):
+        DispatchQueue.main.async {
+          drawerTable.update(models: models)
+          drawerLoading.stopSpinning(withRetry: false)
         }
+      case .failure:
+        drawerLoading.stopSpinning(withRetry: true)
+      }
+    }
 
-        let drawerPhoneTitle = DrawerText(
-            font: Fonts.Mulish.bold.font(size: 12.0),
-            text: Localized.Requests.Drawer.Single.phone,
-            color: Asset.neutralWeak.color,
-            spacingAfter: 5
-        )
-
-        if let phone = contact.phone {
-            let drawerPhoneContent = DrawerText(
-                font: Fonts.Mulish.regular.font(size: 16.0),
-                text: "\(Country.findFrom(phone).prefix) \(phone.dropLast(2))",
-                spacingAfter: 30
-            )
-
-            items.append(contentsOf: [
-                drawerPhoneTitle,
-                drawerPhoneContent
-            ])
+    items.append(drawerTable)
+
+    let drawerAcceptButton = DrawerCapsuleButton(
+      model: .init(
+        title: Localized.Requests.Drawer.Group.accept,
+        style: .brandColored
+      ), spacingAfter: 5
+    )
+
+    let drawerHideButton = DrawerCapsuleButton(
+      model: .init(
+        title: Localized.Requests.Drawer.Group.hide,
+        style: .simplestColoredBrand
+      ), spacingAfter: 5
+    )
+
+    items.append(contentsOf: [drawerAcceptButton, drawerHideButton])
+
+    drawerAcceptButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          self.viewModel.didRequestAccept(group: group)
         }
+      }.store(in: &drawerCancellables)
+
+    drawerHideButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          self.viewModel.didRequestHide(group: group)
+        }
+      }.store(in: &drawerCancellables)
 
-        let drawerNicknameTitle = DrawerText(
-            font: Fonts.Mulish.bold.font(size: 16.0),
-            text: Localized.Requests.Drawer.Single.nickname,
-            color: Asset.neutralDark.color,
-            spacingAfter: 21
-        )
-
-        items.append(drawerNicknameTitle)
-
-        let drawerNicknameInput = DrawerInput(
-            placeholder: contact.username ?? "",
-            validator: .init(
-                wrongIcon: .image(Asset.sharedError.image),
-                correctIcon: .image(Asset.sharedSuccess.image),
-                shouldAcceptPlaceholder: true
-            ),
-            spacingAfter: 29
-        )
-
-        items.append(drawerNicknameInput)
-
-        let drawerAcceptButton = DrawerCapsuleButton(
-            model: .init(
-                title: Localized.Requests.Drawer.Single.accept,
-                style: .brandColored
-            ), spacingAfter: 5
-        )
-
-        let drawerHideButton = DrawerCapsuleButton(
-            model: .init(
-                title: Localized.Requests.Drawer.Single.hide,
-                style: .simplestColoredBrand
-            ), spacingAfter: 5
-        )
-
-        items.append(contentsOf: [drawerAcceptButton, drawerHideButton])
-
-        let drawer = DrawerController(with: items)
-
-        var nickname: String?
-        var allowsSave = true
-
-        drawerNicknameInput.validationPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { allowsSave = $0 }
-            .store(in: &drawerCancellables)
-
-        drawerNicknameInput.inputPublisher
-            .receive(on: DispatchQueue.main)
-            .sink {
-                guard !$0.isEmpty else {
-                    nickname = contact.username
-                    return
-                }
-
-                nickname = $0
-            }
-            .store(in: &drawerCancellables)
-
-        drawerAcceptButton.action
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                guard allowsSave else { return }
-
-                drawer.dismiss(animated: true) {
-                    self.viewModel.didRequestAccept(contact: contact, nickname: nickname)
-                }
-            }
-            .store(in: &drawerCancellables)
-
-        drawerHideButton.action
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                drawer.dismiss(animated: true) {
-                    self.viewModel.didRequestHide(contact: contact)
-                }
-            }
-            .store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
+    navigator.perform(PresentDrawer(items: items, isDismissable: true, from: self))
+  }
 }
 
-// MARK: - Verifying Drawer
+// MARK: - Single Request Drawer
 
 extension RequestsReceivedController {
-    func presentVerifyingDrawer() {
-        drawerCancellables.removeAll()
-
-        var items: [DrawerItem] = []
-
-        let drawerTitle = DrawerText(
-            font: Fonts.Mulish.extraBold.font(size: 26.0),
-            text: Localized.Requests.Received.Verifying.title,
-            spacingAfter: 20
-        )
+  func presentSingleRequestDrawer(forContact contact: Contact) {
+    drawerCancellables.removeAll()
+
+    var items: [DrawerItem] = []
+
+    let drawerTitle = DrawerText(
+      font: Fonts.Mulish.bold.font(size: 12.0),
+      text: Localized.Requests.Drawer.Single.title,
+      spacingAfter: 20
+    )
+
+    let drawerUsername = DrawerText(
+      font: Fonts.Mulish.extraBold.font(size: 26.0),
+      text: contact.username ?? "",
+      color: Asset.neutralDark.color,
+      spacingAfter: 25
+    )
+
+    items.append(contentsOf: [
+      drawerTitle,
+      drawerUsername
+    ])
+
+    let drawerEmailTitle = DrawerText(
+      font: Fonts.Mulish.bold.font(size: 12.0),
+      text: Localized.Requests.Drawer.Single.email,
+      color: Asset.neutralWeak.color,
+      spacingAfter: 5
+    )
+
+    if let email = contact.email {
+      let drawerEmailContent = DrawerText(
+        font: Fonts.Mulish.regular.font(size: 16.0),
+        text: email,
+        spacingAfter: 25
+      )
+
+      items.append(contentsOf: [
+        drawerEmailTitle,
+        drawerEmailContent
+      ])
+    }
 
-        let drawerSubtitle = DrawerText(
-            font: Fonts.Mulish.regular.font(size: 16.0),
-            text: Localized.Requests.Received.Verifying.subtitle,
-            spacingAfter: 40
-        )
+    let drawerPhoneTitle = DrawerText(
+      font: Fonts.Mulish.bold.font(size: 12.0),
+      text: Localized.Requests.Drawer.Single.phone,
+      color: Asset.neutralWeak.color,
+      spacingAfter: 5
+    )
+
+    if let phone = contact.phone {
+      let drawerPhoneContent = DrawerText(
+        font: Fonts.Mulish.regular.font(size: 16.0),
+        text: "\(Country.findFrom(phone).prefix) \(phone.dropLast(2))",
+        spacingAfter: 30
+      )
+
+      items.append(contentsOf: [
+        drawerPhoneTitle,
+        drawerPhoneContent
+      ])
+    }
 
-        items.append(contentsOf: [
-            drawerTitle,
-            drawerSubtitle
-        ])
+    let drawerNicknameTitle = DrawerText(
+      font: Fonts.Mulish.bold.font(size: 16.0),
+      text: Localized.Requests.Drawer.Single.nickname,
+      color: Asset.neutralDark.color,
+      spacingAfter: 21
+    )
+
+    items.append(drawerNicknameTitle)
+
+    let drawerNicknameInput = DrawerInput(
+      placeholder: contact.username ?? "",
+      validator: .init(
+        wrongIcon: .image(Asset.sharedError.image),
+        correctIcon: .image(Asset.sharedSuccess.image),
+        shouldAcceptPlaceholder: true
+      ),
+      spacingAfter: 29
+    )
+
+    items.append(drawerNicknameInput)
+
+    let drawerAcceptButton = DrawerCapsuleButton(
+      model: .init(
+        title: Localized.Requests.Drawer.Single.accept,
+        style: .brandColored
+      ), spacingAfter: 5
+    )
+
+    let drawerHideButton = DrawerCapsuleButton(
+      model: .init(
+        title: Localized.Requests.Drawer.Single.hide,
+        style: .simplestColoredBrand
+      ), spacingAfter: 5
+    )
+
+    items.append(contentsOf: [drawerAcceptButton, drawerHideButton])
+
+    var nickname: String?
+    var allowsSave = true
+
+    drawerNicknameInput
+      .validationPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { allowsSave = $0 }
+      .store(in: &drawerCancellables)
+
+    drawerNicknameInput
+      .inputPublisher
+      .receive(on: DispatchQueue.main)
+      .sink {
+        guard !$0.isEmpty else {
+          nickname = contact.username
+          return
+        }
 
-        let drawerDoneButton = DrawerCapsuleButton(
-            model: .init(
-                title: Localized.Requests.Received.Verifying.action,
-                style: .brandColored
-            ), spacingAfter: 5
-        )
+        nickname = $0
+      }.store(in: &drawerCancellables)
+
+    drawerAcceptButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        guard allowsSave else { return }
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          self.viewModel.didRequestAccept(contact: contact, nickname: nickname)
+        }
+      }.store(in: &drawerCancellables)
+
+    drawerHideButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          self.viewModel.didRequestHide(contact: contact)
+        }
+      }.store(in: &drawerCancellables)
 
-        items.append(drawerDoneButton)
+    navigator.perform(PresentDrawer(items: items, isDismissable: true, from: self))
+  }
+}
 
-        let drawer = DrawerController(with: items)
+// MARK: - Verifying Drawer
 
-        drawerDoneButton.action
-            .receive(on: DispatchQueue.main)
-            .sink { drawer.dismiss(animated: true) }
-            .store(in: &drawerCancellables)
+extension RequestsReceivedController {
+  func presentVerifyingDrawer() {
+    drawerCancellables.removeAll()
+
+    var items: [DrawerItem] = []
+
+    let drawerTitle = DrawerText(
+      font: Fonts.Mulish.extraBold.font(size: 26.0),
+      text: Localized.Requests.Received.Verifying.title,
+      spacingAfter: 20
+    )
+
+    let drawerSubtitle = DrawerText(
+      font: Fonts.Mulish.regular.font(size: 16.0),
+      text: Localized.Requests.Received.Verifying.subtitle,
+      spacingAfter: 40
+    )
+
+    items.append(contentsOf: [
+      drawerTitle,
+      drawerSubtitle
+    ])
+
+    let drawerDoneButton = DrawerCapsuleButton(
+      model: .init(
+        title: Localized.Requests.Received.Verifying.action,
+        style: .brandColored
+      ), spacingAfter: 5
+    )
+
+    items.append(drawerDoneButton)
+
+    drawerDoneButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
 
-        coordinator.toDrawer(drawer, from: self)
-    }
+    navigator.perform(PresentDrawer(items: items, isDismissable: true, from: self))
+  }
 }
diff --git a/Sources/RequestsFeature/Controllers/RequestsSentController.swift b/Sources/RequestsFeature/Controllers/RequestsSentController.swift
index ceed2cc28989b51d72d0191e63e81ade2f930bdd..a2000f74cd532e7c90f404c79e1f4225ea919a7f 100644
--- a/Sources/RequestsFeature/Controllers/RequestsSentController.swift
+++ b/Sources/RequestsFeature/Controllers/RequestsSentController.swift
@@ -1,17 +1,12 @@
-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()
     }
 
-    lazy private var screenView = RequestsSentView()
+    private lazy var screenView = RequestsSentView()
     private let viewModel = RequestsSentViewModel()
     private var cancellables = Set<AnyCancellable>()
     private let tapSubject = PassthroughSubject<Request, Never>()
@@ -33,7 +28,7 @@ final class RequestsSentController: UIViewController {
             let cell: RequestCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
             cell.setupFor(requestSent: requestSent)
             cell.didTapStateButton = { [weak self] in
-                guard let self = self else { return }
+                guard let self else { return }
                 self.viewModel.didTapStateButtonFor(request: requestSent)
             }
             return cell
@@ -46,11 +41,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/Coordinator/RequestsCoordinator.swift b/Sources/RequestsFeature/Coordinator/RequestsCoordinator.swift
deleted file mode 100644
index 571bf625fc1a98b1cf2905154a660fd4cbebd92e..0000000000000000000000000000000000000000
--- a/Sources/RequestsFeature/Coordinator/RequestsCoordinator.swift
+++ /dev/null
@@ -1,120 +0,0 @@
-import UIKit
-import Shared
-import Models
-import XXModels
-import MenuFeature
-import Presentation
-import ContactFeature
-import ScrollViewController
-
-public protocol RequestsCoordinating {
-    func toSearch(from: UIViewController)
-    func toSideMenu(from: UIViewController)
-    func toContact(_: Contact, from: UIViewController)
-    func toSingleChat(with: Contact, from: UIViewController)
-    func toGroupChat(with: GroupInfo, from: UIViewController)
-    func toDrawer(_:  UIViewController, from: UIViewController)
-    func toDrawerBottom(_:  UIViewController, from: UIViewController)
-    func toNickname(from: UIViewController, prefilled: String, _: @escaping StringClosure)
-}
-
-public struct RequestsCoordinator: RequestsCoordinating {
-    var pushPresenter: Presenting = PushPresenter()
-    var sidePresenter: Presenting = SideMenuPresenter()
-    var bottomPresenter: Presenting = BottomPresenter()
-    var fullscreenPresenter: Presenting = FullscreenPresenter()
-
-    var searchFactory: (String?) -> UIViewController
-    var contactFactory: (Contact) -> UIViewController
-    var singleChatFactory: (Contact) -> UIViewController
-    var groupChatFactory: (GroupInfo) -> UIViewController
-    var sideMenuFactory: (MenuItem, UIViewController) -> UIViewController
-    var nicknameFactory: (String, @escaping StringClosure) -> UIViewController
-
-    public init(
-        searchFactory: @escaping (String?) -> UIViewController,
-        contactFactory: @escaping (Contact) -> UIViewController,
-        singleChatFactory: @escaping (Contact) -> UIViewController,
-        groupChatFactory: @escaping (GroupInfo) -> UIViewController,
-        sideMenuFactory: @escaping (MenuItem, UIViewController) -> UIViewController,
-        nicknameFactory: @escaping (String, @escaping StringClosure) -> UIViewController
-    ) {
-        self.searchFactory = searchFactory
-        self.contactFactory = contactFactory
-        self.nicknameFactory = nicknameFactory
-        self.sideMenuFactory = sideMenuFactory
-        self.groupChatFactory = groupChatFactory
-        self.singleChatFactory = singleChatFactory
-    }
-}
-
-public extension RequestsCoordinator {
-    func toSingleChat(
-        with contact: Contact,
-        from parent: UIViewController
-    ) {
-        let screen = singleChatFactory(contact)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toGroupChat(
-        with info: GroupInfo,
-        from parent: UIViewController
-    ) {
-        let screen = groupChatFactory(info)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toDrawer(
-        _ drawer: UIViewController,
-        from parent: UIViewController
-    ) {
-        let target = ScrollViewController.embedding(drawer)
-        fullscreenPresenter.present(target, from: parent)
-    }
-
-    func toDrawerBottom(
-        _ drawer: UIViewController,
-        from parent: UIViewController
-    ) {
-        bottomPresenter.present(drawer, from: parent)
-    }
-
-    func toSearch(from parent: UIViewController) {
-        let screen = searchFactory(nil)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toNickname(
-        from parent: UIViewController,
-        prefilled: String,
-        _ completion: @escaping StringClosure
-    ) {
-        let screen = nicknameFactory(prefilled, completion)
-        bottomPresenter.present(screen, from: parent)
-    }
-
-    func toContact(_ contact: Contact, from parent: UIViewController) {
-        let screen = contactFactory(contact)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toSideMenu(from parent: UIViewController) {
-        let screen = sideMenuFactory(.requests, parent)
-        sidePresenter.present(screen, from: parent)
-    }
-}
-
-extension ScrollViewController {
-    static func embedding(_ viewController: UIViewController) -> ScrollViewController {
-        let scrollViewController = ScrollViewController()
-        scrollViewController.addChild(viewController)
-        scrollViewController.contentView = viewController.view
-        scrollViewController.wrapperView.handlesTouchesOutsideContent = false
-        scrollViewController.wrapperView.alignContentToBottom = true
-        scrollViewController.scrollView.bounces = false
-
-        viewController.didMove(toParent: scrollViewController)
-        return scrollViewController
-    }
-}
diff --git a/Sources/RequestsFeature/Models/Request.swift b/Sources/RequestsFeature/Models/Request.swift
index 595410d43257294a139a0f7b77a717992ed6ffcf..aac0fd8c068a3e5c873e2d4d3edef334202c749b 100644
--- a/Sources/RequestsFeature/Models/Request.swift
+++ b/Sources/RequestsFeature/Models/Request.swift
@@ -1,4 +1,3 @@
-import Models
 import XXModels
 import Foundation
 
diff --git a/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift
index b7d81db3a65b791e001a897506418295b56f8b3a..6481b67c4a5c25a148980b6a3ed2d454d61f7822 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsFailedViewModel.swift
@@ -1,53 +1,93 @@
-import HUD
 import UIKit
-import Models
+import Shared
+import AppCore
 import Combine
-import Integration
+import XXModels
+import Defaults
+import XXClient
+import Dependencies
 import CombineSchedulers
-import DependencyInjection
+import XXMessengerClient
 
 final class RequestsFailedViewModel {
-    @Dependency private var session: SessionType
+  @Dependency(\.app.dbManager) var dbManager: DBManager
+  @Dependency(\.app.messenger) var messenger: Messenger
+  @Dependency(\.app.hudManager) var hudManager: HUDManager
+  
+  @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 itemsSubject = CurrentValueSubject<NSDiffableDataSourceSnapshot<Section, Request>, Never>(.init())
+  
+  var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
+  
+  init() {
+    try! dbManager.getDB().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
     }
-
-    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() {
-        session.dbManager.fetchContactsPublisher(.init(authStatus: [.requestFailed]))
-            .assertNoFailure()
-            .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 let .contact(contact) = request, request.status == .failedToRequest else { return }
-
-        hudSubject.send(.on)
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.session.retryRequest(contact)
-                self.hudSubject.send(.none)
-            } catch {
-                self.hudSubject.send(.error(.init(with: error)))
-            }
+    
+    hudManager.show()
+    backgroundScheduler.schedule { [weak self] in
+      guard let 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.dbManager.getDB().saveContact(contact)
+        self.hudManager.hide()
+      } catch {
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.hudManager.show(.init(content: xxError))
+      }
     }
+  }
 }
diff --git a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
index 3f4be9cbe3541dd0b2a2431967c1e4ec8df5ce93..46d4fe29bc99ebb71207264a27663a1f16de7731 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsReceivedViewModel.swift
@@ -1,242 +1,286 @@
-import HUD
 import UIKit
-import Models
 import Shared
+import AppCore
 import Combine
 import Defaults
 import XXModels
-import Integration
+import XXClient
+import Dependencies
 import DrawerFeature
 import ReportingFeature
 import CombineSchedulers
-import DependencyInjection
+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 private var session: SessionType
-    @Dependency private var reportingStatus: ReportingStatus
-
-    @KeyObject(.isShowingHiddenRequests, defaultValue: false) var isShowingHiddenRequests: Bool
-
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
-
-    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<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<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 = session.dbManager.fetchGroupsPublisher(groupsQuery).assertNoFailure()
-        let contactsStream = session.dbManager.fetchContactsPublisher(contactsQuery).assertNoFailure()
-
-        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 let .contact(contact) = request else { return }
-
-        if request.status == .failedToVerify {
-            backgroundScheduler.schedule { [weak self] in
-                self?.session.verify(contact: contact)
+  @Dependency(\.app.dbManager) var dbManager
+  @Dependency(\.app.messenger) var messenger
+  @Dependency(\.app.hudManager) var hudManager
+  @Dependency(\.reportingStatus) var 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 = try! dbManager.getDB()
+      .fetchGroupsPublisher(groupsQuery)
+      .replaceError(with: [])
+    
+    let contactsStream = try! dbManager.getDB()
+      .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]"
             }
-        } else if request.status == .verifying {
-            verifyingSubject.send()
+          }
+          
+          return RequestReceived(
+            request: request,
+            isHidden: group.authStatus == .hidden,
+            leader: leaderName()
+          )
+        case let .contact(contact):
+          return RequestReceived(
+            request: request,
+            isHidden: contact.authStatus == .hidden,
+            leader: nil
+          )
         }
-    }
-
-    func didRequestHide(group: Group) {
-        if var group = try? session.dbManager.fetchGroups(.init(id: [group.id])).first {
-            group.authStatus = .hidden
-            _ = try? session.dbManager.saveGroup(group)
+      }
+      
+      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 else { return }
+        
+        do {
+          contact.authStatus = .verificationInProgress
+          try self.dbManager.getDB().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.dbManager.getDB().saveContact(contact)
+          } else {
+            print(">>> [messenger.verifyContact] is fake")
+            
+            try self.dbManager.getDB().deleteContact(contact)
+          }
+        } catch {
+          print(">>> [messenger.verifyContact] thrown an exception: \(error.localizedDescription)")
+          
+          contact.authStatus = .verificationFailed
+          _ = try? self.dbManager.getDB().saveContact(contact)
         }
+      }
+    } else if request.status == .verifying {
+      verifyingSubject.send()
     }
-
-    func didRequestAccept(group: Group) {
-        hudSubject.send(.on)
-
-        backgroundScheduler.schedule { [weak self] in
-            do {
-                try self?.session.join(group: group)
-                self?.hudSubject.send(.none)
-                self?.groupConfirmationSubject.send(group)
-            } catch {
-                self?.hudSubject.send(.error(.init(with: error)))
-            }
-        }
+  }
+  
+  func didRequestHide(group: Group) {
+    if var group = try? dbManager.getDB().fetchGroups(.init(id: [group.id])).first {
+      group.authStatus = .hidden
+      _ = try? dbManager.getDB().saveGroup(group)
     }
-
-    func fetchMembers(
-        _ group: Group,
-        _ completion: @escaping (Result<[DrawerTableCellModel], Error>) -> Void
-    ) {
-        if let info = try? session.dbManager.fetchGroupInfos(.init(groupId: group.id)).first {
-            session.dbManager.fetchContactsPublisher(.init(id: Set(info.members.map(\.id))))
-                .assertNoFailure()
-                .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 didRequestAccept(group: Group) {
+    hudManager.show()
+    
+    backgroundScheduler.schedule { [weak self] in
+      guard let self else { return }
+      
+      do {
+        try self.messenger.groupChat()!.joinGroup(serializedGroupData: group.serialized)
+
+        var group = group
+        group.authStatus = .participating
+        try self.dbManager.getDB().saveGroup(group)
+        
+        self.hudManager.hide()
+        self.groupConfirmationSubject.send(group)
+      } catch {
+        self.hudManager.show(.init(error: error))
+      }
     }
-
-    func didRequestHide(contact: Contact) {
-        if var contact = try? session.dbManager.fetchContacts(.init(id: [contact.id])).first {
-            contact.authStatus = .hidden
-            _ = try? session.dbManager.saveContact(contact)
-        }
-    }
-
-    func didRequestAccept(contact: Contact, nickname: String? = nil) {
-        hudSubject.send(.on)
-
-        var contact = contact
-        contact.nickname = nickname ?? contact.username
-
-        backgroundScheduler.schedule { [weak self] in
-            do {
-                try self?.session.confirm(contact)
-                self?.hudSubject.send(.none)
-                self?.contactConfirmationSubject.send(contact)
-            } catch {
-                self?.hudSubject.send(.error(.init(with: error)))
+  }
+  
+  func fetchMembers(
+    _ group: Group,
+    _ completion: @escaping (Result<[DrawerTableCellModel], Error>) -> Void
+  ) {
+    if let info = try? dbManager.getDB().fetchGroupInfos(.init(groupId: group.id)).first {
+      try! dbManager.getDB().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 groupChatWith(group: Group) -> GroupInfo {
-        guard let info = try? session.dbManager.fetchGroupInfos(.init(groupId: group.id)).first else {
-            fatalError()
-        }
-
-        return info
+  }
+  
+  func didRequestHide(contact: XXModels.Contact) {
+    if var contact = try? dbManager.getDB().fetchContacts(.init(id: [contact.id])).first {
+      contact.authStatus = .hidden
+      _ = try? dbManager.getDB().saveContact(contact)
+    }
+  }
+  
+  func didRequestAccept(contact: XXModels.Contact, nickname: String? = nil) {
+    hudManager.show()
+    
+    var contact = contact
+    contact.authStatus = .confirming
+    contact.nickname = nickname ?? contact.username
+    
+    backgroundScheduler.schedule { [weak self] in
+      guard let self else { return }
+      
+      do {
+        try self.dbManager.getDB().saveContact(contact)
+        
+        let _ = try self.messenger.e2e.get()!.confirmReceivedRequest(partner: .live(contact.marshaled!))
+        contact.authStatus = .friend
+        try self.dbManager.getDB().saveContact(contact)
+        
+        self.hudManager.hide()
+        self.contactConfirmationSubject.send(contact)
+      } catch {
+        contact.authStatus = .confirmationFailed
+        _ = try? self.dbManager.getDB().saveContact(contact)
+        self.hudManager.show(.init(error: error))
+      }
+    }
+  }
+  
+  func groupChatWith(group: Group) -> GroupInfo {
+    guard let info = try? dbManager.getDB().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 40964837a7a4254badcc48c5750389def82a3fda..abe6ba7b6c2e20c14115ce6f54535f841c693930 100644
--- a/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
+++ b/Sources/RequestsFeature/ViewModels/RequestsSentViewModel.swift
@@ -1,97 +1,129 @@
-import HUD
 import UIKit
-import Models
 import Shared
+import AppCore
 import Combine
 import Defaults
 import XXModels
-import Integration
-import ToastFeature
+import Defaults
+import XXClient
+import AppResources
+import Dependencies
 import ReportingFeature
 import CombineSchedulers
-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 private var session: SessionType
-    @Dependency private var reportingStatus: ReportingStatus
-    @Dependency private var toastController: ToastController
-
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
-
-    var itemsPublisher: AnyPublisher<NSDiffableDataSourceSnapshot<Section, RequestSent>, Never> {
-        itemsSubject.eraseToAnyPublisher()
+  @Dependency(\.app.dbManager) var dbManager: DBManager
+  @Dependency(\.app.messenger) var messenger: Messenger
+  @Dependency(\.app.hudManager) var hudManager: HUDManager
+  @Dependency(\.app.toastManager) var toastManager: ToastManager
+  @Dependency(\.reportingStatus) var reportingStatus: ReportingStatus
+  
+  @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
+    )
+    
+    try! dbManager.getDB().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
     }
-
-    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
+    
+    let name = (contact.nickname ?? contact.username) ?? ""
+    
+    hudManager.show()
+    backgroundScheduler.schedule { [weak self] in
+      guard let 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
         )
-
-        session.dbManager.fetchContactsPublisher(query)
-            .assertNoFailure()
-            .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 else { return }
-
-        hudSubject.send(.on)
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                try self.session.retryRequest(contact)
-                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)
-
-                let name = (contact.nickname ?? contact.username) ?? ""
-
-                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.hudSubject.send(.error(.init(with: error)))
-            }
+        
+        self.hudManager.hide()
+        
+        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.toastManager.enqueue(.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.toastManager.enqueue(.init(
+          title: Localized.Requests.Sent.Toast.resentFailed(name),
+          leftImage: Asset.requestFailedToaster.image
+        ))
+        
+        let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+        self.hudManager.show(.init(content: xxError))
+      }
     }
+  }
 }
diff --git a/Sources/RequestsFeature/Views/RequestCell.swift b/Sources/RequestsFeature/Views/RequestCell.swift
index e0af9ec6017ef1241ed0831ee0dbad71af7dc3d4..217e969783cff7cfe659711861ebee6a5ef51d99 100644
--- a/Sources/RequestsFeature/Views/RequestCell.swift
+++ b/Sources/RequestsFeature/Views/RequestCell.swift
@@ -1,259 +1,260 @@
 import UIKit
 import Shared
 import Combine
-import Countries
+import AppResources
+import CountryListFeature
 
 final class RequestCell: UICollectionViewCell {
-    let titleLabel = UILabel()
-    let leaderLabel = UILabel()
-    let emailLabel = UILabel()
-    let phoneLabel = UILabel()
-    let dateLabel = UILabel()
-    let stackView = UIStackView()
-    let avatarView = AvatarView()
-    let stateButton = RequestCellButton()
-
-    var cancellables = Set<AnyCancellable>()
-    var didTapStateButton: (() -> Void)!
-
-    override init(frame: CGRect) {
-        super.init(frame: frame)
-        backgroundColor = Asset.neutralWhite.color
-
-        titleLabel.textColor = Asset.neutralActive.color
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
-
-        emailLabel.font = Fonts.Mulish.regular.font(size: 14.0)
-        phoneLabel.font = Fonts.Mulish.regular.font(size: 14.0)
-        leaderLabel.font = Fonts.Mulish.regular.font(size: 14.0)
-
-        emailLabel.textColor = Asset.neutralSecondaryAlternative.color
-        phoneLabel.textColor = Asset.neutralSecondaryAlternative.color
-        leaderLabel.textColor = Asset.neutralSecondaryAlternative.color
-
-        dateLabel.font = Fonts.Mulish.regular.font(size: 10.0)
-        dateLabel.textColor = Asset.neutralWeak.color
-
-        stackView.axis = .vertical
-        stackView.spacing = 4
-        stackView.addArrangedSubview(titleLabel)
-        stackView.addArrangedSubview(leaderLabel)
-        stackView.addArrangedSubview(emailLabel)
-        stackView.addArrangedSubview(phoneLabel)
-        stackView.addArrangedSubview(dateLabel)
-
-        contentView.addSubview(avatarView)
-        contentView.addSubview(stateButton)
-        contentView.addSubview(stackView)
-
-        avatarView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(15)
-            $0.width.height.equalTo(36)
-            $0.left.equalToSuperview().offset(27)
-            $0.bottom.lessThanOrEqualToSuperview().offset(-15)
-        }
-
-        stackView.snp.makeConstraints {
-            $0.top.equalTo(avatarView).offset(-5)
-            $0.left.equalTo(avatarView.snp.right).offset(20)
-            $0.right.lessThanOrEqualTo(stateButton.snp.left).offset(-20)
-            $0.bottom.lessThanOrEqualToSuperview().offset(-15)
-        }
-
-        stateButton.snp.makeConstraints {
-            $0.centerY.equalTo(stackView)
-            $0.right.equalToSuperview().offset(-24)
-        }
+  let titleLabel = UILabel()
+  let leaderLabel = UILabel()
+  let emailLabel = UILabel()
+  let phoneLabel = UILabel()
+  let dateLabel = UILabel()
+  let stackView = UIStackView()
+  let avatarView = AvatarView()
+  let stateButton = RequestCellButton()
+
+  var cancellables = Set<AnyCancellable>()
+  var didTapStateButton: (() -> Void)!
+
+  override init(frame: CGRect) {
+    super.init(frame: frame)
+    backgroundColor = Asset.neutralWhite.color
+
+    titleLabel.textColor = Asset.neutralActive.color
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
+
+    emailLabel.font = Fonts.Mulish.regular.font(size: 14.0)
+    phoneLabel.font = Fonts.Mulish.regular.font(size: 14.0)
+    leaderLabel.font = Fonts.Mulish.regular.font(size: 14.0)
+
+    emailLabel.textColor = Asset.neutralSecondaryAlternative.color
+    phoneLabel.textColor = Asset.neutralSecondaryAlternative.color
+    leaderLabel.textColor = Asset.neutralSecondaryAlternative.color
+
+    dateLabel.font = Fonts.Mulish.regular.font(size: 10.0)
+    dateLabel.textColor = Asset.neutralWeak.color
+
+    stackView.axis = .vertical
+    stackView.spacing = 4
+    stackView.addArrangedSubview(titleLabel)
+    stackView.addArrangedSubview(leaderLabel)
+    stackView.addArrangedSubview(emailLabel)
+    stackView.addArrangedSubview(phoneLabel)
+    stackView.addArrangedSubview(dateLabel)
+
+    contentView.addSubview(avatarView)
+    contentView.addSubview(stateButton)
+    contentView.addSubview(stackView)
+
+    avatarView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(15)
+      $0.width.height.equalTo(36)
+      $0.left.equalToSuperview().offset(27)
+      $0.bottom.lessThanOrEqualToSuperview().offset(-15)
     }
 
-    required init?(coder: NSCoder) { nil }
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        titleLabel.text = nil
-        dateLabel.text = nil
-        phoneLabel.text = nil
-        emailLabel.text = nil
-        leaderLabel.text = nil
-        avatarView.prepareForReuse()
-        cancellables.removeAll()
+    stackView.snp.makeConstraints {
+      $0.top.equalTo(avatarView).offset(-5)
+      $0.left.equalTo(avatarView.snp.right).offset(20)
+      $0.right.lessThanOrEqualTo(stateButton.snp.left).offset(-20)
+      $0.bottom.lessThanOrEqualToSuperview().offset(-15)
     }
 
-    func setupFor(requestSent: RequestSent) {
-        cancellables.removeAll()
-        guard case .contact(let contact) = requestSent.request else { fatalError("A sent request -must- be of type contact") }
-
-        var phone: String?
-        if let contactPhone = contact.phone {
-            phone = "\(Country.findFrom(contactPhone).prefix) \(contactPhone.dropLast(2))"
-        }
-
-        setupContact(
-            title: (contact.nickname ?? contact.username) ?? "",
-            photo: contact.photo,
-            phone: phone,
-            email: contact.email,
-            createdAt: contact.createdAt,
-            backgroundColor: Asset.brandPrimary.color
-        )
-
-        var buttonTitle: String? = nil
-        var buttonImage: UIImage? = nil
-        var buttonTitleColor: UIColor? = nil
-
-        if requestSent.isResent {
-            buttonTitle = Localized.Requests.Cell.resent
-            buttonImage = Asset.requestsResent.image
-            buttonTitleColor = Asset.neutralWeak.color
-        } else {
-            buttonTitle = Localized.Requests.Cell.requested
-            buttonImage = Asset.requestsResend.image
-            buttonTitleColor = Asset.brandPrimary.color
-        }
-
-        setupStateButton(
-            image: buttonImage,
-            title: buttonTitle,
-            color: buttonTitleColor
-        )
+    stateButton.snp.makeConstraints {
+      $0.centerY.equalTo(stackView)
+      $0.right.equalToSuperview().offset(-24)
     }
-
-    func setupFor(requestFailed request: Request) {
-        cancellables.removeAll()
-        guard case .contact(let contact) = request else { fatalError("A failed request -must- be of type contact") }
-
-        var phone: String?
-        if let contactPhone = contact.phone {
-            phone = "\(Country.findFrom(contactPhone).prefix) \(contactPhone.dropLast(2))"
-        }
-
-        setupContact(
-            title: (contact.nickname ?? contact.username) ?? "",
-            photo: contact.photo,
-            phone: phone,
-            email: contact.email,
-            createdAt: contact.createdAt,
-            backgroundColor: Asset.brandPrimary.color
-        )
-
-        setupStateButton(
-            image: Asset.requestsResend.image,
-            title: Localized.Requests.Cell.failedRequest,
-            color: Asset.brandPrimary.color
-        )
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  override func prepareForReuse() {
+    super.prepareForReuse()
+    titleLabel.text = nil
+    dateLabel.text = nil
+    phoneLabel.text = nil
+    emailLabel.text = nil
+    leaderLabel.text = nil
+    avatarView.prepareForReuse()
+    cancellables.removeAll()
+  }
+
+  func setupFor(requestSent: RequestSent) {
+    cancellables.removeAll()
+    guard case .contact(let contact) = requestSent.request else { fatalError("A sent request -must- be of type contact") }
+
+    var phone: String?
+    if let contactPhone = contact.phone {
+      phone = "\(Country.findFrom(contactPhone).prefix) \(contactPhone.dropLast(2))"
     }
 
-    func setupFor(requestReceived: RequestReceived, isHidden: Bool = false) {
-        cancellables.removeAll()
-        guard let request = requestReceived.request else { return }
-        let color = isHidden ? Asset.neutralDisabled.color : Asset.brandPrimary.color
-
-        switch request {
-        case .group(let group):
-            setupGroup(
-                name: group.name,
-                createdAt: group.createdAt,
-                leader: requestReceived.leader,
-                backgroundColor: color
-            )
-
-        case .contact(let contact):
-
-            var phone: String?
-            if let contactPhone = contact.phone {
-                phone = "\(Country.findFrom(contactPhone).prefix) \(contactPhone.dropLast(2))"
-            }
-
-            setupContact(
-                title: (contact.nickname ?? contact.username) ?? "",
-                photo: contact.photo,
-                phone: phone,
-                email: contact.email,
-                createdAt: contact.createdAt,
-                backgroundColor: color
-            )
-
-            var buttonTitle: String? = nil
-            var buttonImage: UIImage? = nil
-            var buttonTitleColor: UIColor? = nil
-
-            switch request.status {
-            case .verified, .confirming, .failedToConfirm:
-                break // TODO: These statuses don't need UI
-
-            case .verifying:
-                buttonTitle = Localized.Requests.Cell.verifying
-                buttonTitleColor = Asset.neutralWeak.color
-
-            case .failedToVerify:
-                buttonTitle = Localized.Requests.Cell.failedVerification
-                buttonImage = Asset.requestsVerificationFailed.image
-                buttonTitleColor = Asset.accentDanger.color
-
-            case .requesting, .requested, .failedToRequest:
-                fatalError("A receivedRequest can never have the statuses: .requesting, .requested or .failedToRequest")
-            }
-
-            setupStateButton(
-                image: buttonImage,
-                title: buttonTitle,
-                color: buttonTitleColor
-            )
-        }
+    setupContact(
+      title: (contact.nickname ?? contact.username) ?? "",
+      photo: contact.photo,
+      phone: phone,
+      email: contact.email,
+      createdAt: contact.createdAt,
+      backgroundColor: Asset.brandPrimary.color
+    )
+
+    var buttonTitle: String? = nil
+    var buttonImage: UIImage? = nil
+    var buttonTitleColor: UIColor? = nil
+
+    if requestSent.isResent {
+      buttonTitle = Localized.Requests.Cell.resent
+      buttonImage = Asset.requestsResent.image
+      buttonTitleColor = Asset.neutralWeak.color
+    } else {
+      buttonTitle = Localized.Requests.Cell.requested
+      buttonImage = Asset.requestsResend.image
+      buttonTitleColor = Asset.brandPrimary.color
     }
 
-    private func setupContact(
-        title: String,
-        photo: Data?,
-        phone: String?,
-        email: String?,
-        createdAt: Date,
-        backgroundColor: UIColor
-    ) {
-        titleLabel.text = title
-        phoneLabel.text = phone
-        emailLabel.text = email
-        dateLabel.text = createdAt.asRelativeFromNow()
-        avatarView.setupProfile(title: title, image: photo, size: .small)
-
-        leaderLabel.isHidden = true
-        phoneLabel.isHidden = phone == nil
-        emailLabel.isHidden = email == nil
-        avatarView.backgroundColor = backgroundColor
-    }
+    setupStateButton(
+      image: buttonImage,
+      title: buttonTitle,
+      color: buttonTitleColor
+    )
+  }
+
+  func setupFor(requestFailed request: Request) {
+    cancellables.removeAll()
+    guard case .contact(let contact) = request else { fatalError("A failed request -must- be of type contact") }
 
-    private func setupGroup(
-        name: String,
-        createdAt: Date,
-        leader: String?,
-        backgroundColor: UIColor
-    ) {
-        titleLabel.text = name
-        stateButton.imageView.image = nil
-        stateButton.titleLabel.text = nil
-        avatarView.setupGroup(size: .small)
-        dateLabel.text = createdAt.asRelativeFromNow()
-
-        leaderLabel.text = leader
-        leaderLabel.isHidden = false
-        phoneLabel.isHidden = true
-        emailLabel.isHidden = true
-        avatarView.backgroundColor = backgroundColor
+    var phone: String?
+    if let contactPhone = contact.phone {
+      phone = "\(Country.findFrom(contactPhone).prefix) \(contactPhone.dropLast(2))"
     }
 
-    private func setupStateButton(
-        image: UIImage?,
-        title: String?,
-        color: UIColor?
-    ) {
-        stateButton.imageView.image = image
-        stateButton.titleLabel.text = title
-        stateButton.titleLabel.textColor = color
-
-        stateButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in didTapStateButton() }
-            .store(in: &cancellables)
+    setupContact(
+      title: (contact.nickname ?? contact.username) ?? "",
+      photo: contact.photo,
+      phone: phone,
+      email: contact.email,
+      createdAt: contact.createdAt,
+      backgroundColor: Asset.brandPrimary.color
+    )
+
+    setupStateButton(
+      image: Asset.requestsResend.image,
+      title: Localized.Requests.Cell.failedRequest,
+      color: Asset.brandPrimary.color
+    )
+  }
+
+  func setupFor(requestReceived: RequestReceived, isHidden: Bool = false) {
+    cancellables.removeAll()
+    guard let request = requestReceived.request else { return }
+    let color = isHidden ? Asset.neutralDisabled.color : Asset.brandPrimary.color
+
+    switch request {
+    case .group(let group):
+      setupGroup(
+        name: group.name,
+        createdAt: group.createdAt,
+        leader: requestReceived.leader,
+        backgroundColor: color
+      )
+
+    case .contact(let contact):
+
+      var phone: String?
+      if let contactPhone = contact.phone {
+        phone = "\(Country.findFrom(contactPhone).prefix) \(contactPhone.dropLast(2))"
+      }
+
+      setupContact(
+        title: (contact.nickname ?? contact.username) ?? "",
+        photo: contact.photo,
+        phone: phone,
+        email: contact.email,
+        createdAt: contact.createdAt,
+        backgroundColor: color
+      )
+
+      var buttonTitle: String? = nil
+      var buttonImage: UIImage? = nil
+      var buttonTitleColor: UIColor? = nil
+
+      switch request.status {
+      case .verified, .confirming, .failedToConfirm:
+        break // TODO: These statuses don't need UI
+
+      case .verifying:
+        buttonTitle = Localized.Requests.Cell.verifying
+        buttonTitleColor = Asset.neutralWeak.color
+
+      case .failedToVerify:
+        buttonTitle = Localized.Requests.Cell.failedVerification
+        buttonImage = Asset.requestsVerificationFailed.image
+        buttonTitleColor = Asset.accentDanger.color
+
+      case .requesting, .requested, .failedToRequest:
+        fatalError("A receivedRequest can never have the statuses: .requesting, .requested or .failedToRequest")
+      }
+
+      setupStateButton(
+        image: buttonImage,
+        title: buttonTitle,
+        color: buttonTitleColor
+      )
     }
+  }
+
+  private func setupContact(
+    title: String,
+    photo: Data?,
+    phone: String?,
+    email: String?,
+    createdAt: Date,
+    backgroundColor: UIColor
+  ) {
+    titleLabel.text = title
+    phoneLabel.text = phone
+    emailLabel.text = email
+    dateLabel.text = createdAt.asRelativeFromNow()
+    avatarView.setupProfile(title: title, image: photo, size: .small)
+
+    leaderLabel.isHidden = true
+    phoneLabel.isHidden = phone == nil
+    emailLabel.isHidden = email == nil
+    avatarView.backgroundColor = backgroundColor
+  }
+
+  private func setupGroup(
+    name: String,
+    createdAt: Date,
+    leader: String?,
+    backgroundColor: UIColor
+  ) {
+    titleLabel.text = name
+    stateButton.imageView.image = nil
+    stateButton.titleLabel.text = nil
+    avatarView.setupGroup(size: .small)
+    dateLabel.text = createdAt.asRelativeFromNow()
+
+    leaderLabel.text = leader
+    leaderLabel.isHidden = false
+    phoneLabel.isHidden = true
+    emailLabel.isHidden = true
+    avatarView.backgroundColor = backgroundColor
+  }
+
+  private func setupStateButton(
+    image: UIImage?,
+    title: String?,
+    color: UIColor?
+  ) {
+    stateButton.imageView.image = image
+    stateButton.titleLabel.text = title
+    stateButton.titleLabel.textColor = color
+
+    stateButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in didTapStateButton() }
+      .store(in: &cancellables)
+  }
 }
 
diff --git a/Sources/RequestsFeature/Views/RequestCellButton.swift b/Sources/RequestsFeature/Views/RequestCellButton.swift
index 22332912310da3912439621b425f614b1123a32d..005197063e8220863d70edec1cfc1c9a5e93bf5c 100644
--- a/Sources/RequestsFeature/Views/RequestCellButton.swift
+++ b/Sources/RequestsFeature/Views/RequestCellButton.swift
@@ -1,35 +1,36 @@
 import UIKit
 import Shared
+import AppResources
 
 final class RequestCellButton: UIControl {
-    let titleLabel = UILabel()
-    let imageView = UIImageView()
-
-    init() {
-        super.init(frame: .zero)
-        titleLabel.numberOfLines = 0
-        titleLabel.textAlignment = .right
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
-
-        addSubview(imageView)
-        addSubview(titleLabel)
-
-        imageView.snp.makeConstraints {
-            $0.top.greaterThanOrEqualToSuperview()
-            $0.left.equalToSuperview()
-            $0.centerY.equalToSuperview()
-            $0.bottom.lessThanOrEqualToSuperview()
-        }
-
-        titleLabel.snp.makeConstraints {
-            $0.top.greaterThanOrEqualToSuperview()
-            $0.left.equalTo(imageView.snp.right).offset(5)
-            $0.centerY.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.width.equalTo(60)
-            $0.bottom.lessThanOrEqualToSuperview()
-        }
+  let titleLabel = UILabel()
+  let imageView = UIImageView()
+  
+  init() {
+    super.init(frame: .zero)
+    titleLabel.numberOfLines = 0
+    titleLabel.textAlignment = .right
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
+    
+    addSubview(imageView)
+    addSubview(titleLabel)
+    
+    imageView.snp.makeConstraints {
+      $0.top.greaterThanOrEqualToSuperview()
+      $0.left.equalToSuperview()
+      $0.centerY.equalToSuperview()
+      $0.bottom.lessThanOrEqualToSuperview()
     }
-
-    required init?(coder: NSCoder) { nil }
+    
+    titleLabel.snp.makeConstraints {
+      $0.top.greaterThanOrEqualToSuperview()
+      $0.left.equalTo(imageView.snp.right).offset(5)
+      $0.centerY.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.width.equalTo(60)
+      $0.bottom.lessThanOrEqualToSuperview()
+    }
+  }
+  
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/RequestsFeature/Views/RequestReceivedEmptyCell.swift b/Sources/RequestsFeature/Views/RequestReceivedEmptyCell.swift
index bf706b39c5eded757bcb02eeac4c0092c9f409e1..4239feca2ff52a7db5573e39b3dca1bff438bbe1 100644
--- a/Sources/RequestsFeature/Views/RequestReceivedEmptyCell.swift
+++ b/Sources/RequestsFeature/Views/RequestReceivedEmptyCell.swift
@@ -1,34 +1,35 @@
 import UIKit
 import Shared
+import AppResources
 
 final class RequestReceivedEmptyCell: UICollectionViewCell {
-    private let titleLabel = UILabel()
-
-    override init(frame: CGRect) {
-        super.init(frame: frame)
-        titleLabel.textAlignment = .center
-        titleLabel.textColor = Asset.neutralWeak.color
-        titleLabel.font = Fonts.Mulish.regular.font(size: 14.0)
-        titleLabel.text = Localized.Requests.Received.placeholder
-
-        contentView.addSubview(titleLabel)
-
-        titleLabel.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(50)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview().offset(-50)
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        titleLabel.text = nil
-    }
-
-    func setup(title: String) {
-        titleLabel.text = title
+  private let titleLabel = UILabel()
+  
+  override init(frame: CGRect) {
+    super.init(frame: frame)
+    titleLabel.textAlignment = .center
+    titleLabel.textColor = Asset.neutralWeak.color
+    titleLabel.font = Fonts.Mulish.regular.font(size: 14.0)
+    titleLabel.text = Localized.Requests.Received.placeholder
+    
+    contentView.addSubview(titleLabel)
+    
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(50)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview().offset(-50)
     }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  override func prepareForReuse() {
+    super.prepareForReuse()
+    titleLabel.text = nil
+  }
+  
+  func setup(title: String) {
+    titleLabel.text = title
+  }
 }
diff --git a/Sources/RequestsFeature/Views/RequestSegmentedButton.swift b/Sources/RequestsFeature/Views/RequestSegmentedButton.swift
index 22bd81e2da7387b54c70f93f8c8528cb4d0dc031..85f49138de97d52b9551ed2f8a659ddf562a9565 100644
--- a/Sources/RequestsFeature/Views/RequestSegmentedButton.swift
+++ b/Sources/RequestsFeature/Views/RequestSegmentedButton.swift
@@ -1,32 +1,33 @@
 import UIKit
 import Shared
+import AppResources
 
 final class RequestSegmentedButton: UIControl {
-    let titleLabel = UILabel()
-    let imageView = UIImageView()
-
-    init() {
-        super.init(frame: .zero)
-
-        titleLabel.textAlignment = .center
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
-        imageView.setContentCompressionResistancePriority(.required, for: .vertical)
-        titleLabel.setContentCompressionResistancePriority(.required, for: .vertical)
-
-        addSubview(titleLabel)
-        addSubview(imageView)
-
-        imageView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(7.5)
-            $0.centerX.equalTo(titleLabel)
-        }
-
-        titleLabel.snp.makeConstraints {
-            $0.top.equalTo(imageView.snp.bottom).offset(2)
-            $0.centerX.equalToSuperview()
-            $0.bottom.equalToSuperview().offset(-7.5)
-        }
+  let titleLabel = UILabel()
+  let imageView = UIImageView()
+  
+  init() {
+    super.init(frame: .zero)
+    
+    titleLabel.textAlignment = .center
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    imageView.setContentCompressionResistancePriority(.required, for: .vertical)
+    titleLabel.setContentCompressionResistancePriority(.required, for: .vertical)
+    
+    addSubview(titleLabel)
+    addSubview(imageView)
+    
+    imageView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(7.5)
+      $0.centerX.equalTo(titleLabel)
     }
-
-    required init?(coder: NSCoder) { nil }
+    
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(imageView.snp.bottom).offset(2)
+      $0.centerX.equalToSuperview()
+      $0.bottom.equalToSuperview().offset(-7.5)
+    }
+  }
+  
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/RequestsFeature/Views/RequestsContainerView.swift b/Sources/RequestsFeature/Views/RequestsContainerView.swift
index 80cd844a803dd38d4cce568d5d077bb0b2e1a66d..222a9eb74e6ac3075d63fe34d6917db8bb6cc4d3 100644
--- a/Sources/RequestsFeature/Views/RequestsContainerView.swift
+++ b/Sources/RequestsFeature/Views/RequestsContainerView.swift
@@ -1,58 +1,59 @@
 import UIKit
 import Shared
+import AppResources
 
 final class RequestsContainerView: UIView {
-    let scrollView = UIScrollView()
-    let sentController = RequestsSentController()
-    let failedController = RequestsFailedController()
-    let receivedController = RequestsReceivedController()
-    let segmentedControl = RequestsSegmentedControl()
-
-    init() {
-        super.init(frame: .zero)
-        scrollView.bounces = false
-        scrollView.isScrollEnabled = false
-        backgroundColor = Asset.neutralWhite.color
-
-        scrollView.addSubview(sentController.view)
-        scrollView.addSubview(failedController.view)
-        scrollView.addSubview(receivedController.view)
-        addSubview(segmentedControl)
-        addSubview(scrollView)
-
-        scrollView.snp.makeConstraints {
-            $0.edges.equalToSuperview()
-        }
-
-        segmentedControl.snp.makeConstraints {
-            $0.top.equalTo(safeAreaLayoutGuide).offset(10)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.height.equalTo(60)
-        }
-
-        receivedController.view.snp.makeConstraints {
-            $0.top.equalTo(segmentedControl.snp.bottom)
-            $0.left.equalToSuperview()
-            $0.right.equalTo(sentController.view.snp.left)
-            $0.bottom.equalTo(self)
-            $0.width.equalTo(self)
-        }
-
-        sentController.view.snp.makeConstraints {
-            $0.top.equalTo(segmentedControl.snp.bottom)
-            $0.right.equalTo(failedController.view.snp.left)
-            $0.bottom.equalTo(self)
-            $0.width.equalTo(self)
-        }
-
-        failedController.view.snp.makeConstraints {
-            $0.top.equalTo(segmentedControl.snp.bottom)
-            $0.right.equalToSuperview()
-            $0.bottom.equalTo(self)
-            $0.width.equalTo(self)
-        }
+  let scrollView = UIScrollView()
+  let sentController = RequestsSentController()
+  let failedController = RequestsFailedController()
+  let receivedController = RequestsReceivedController()
+  let segmentedControl = RequestsSegmentedControl()
+  
+  init() {
+    super.init(frame: .zero)
+    scrollView.bounces = false
+    scrollView.isScrollEnabled = false
+    backgroundColor = Asset.neutralWhite.color
+    
+    scrollView.addSubview(sentController.view)
+    scrollView.addSubview(failedController.view)
+    scrollView.addSubview(receivedController.view)
+    addSubview(segmentedControl)
+    addSubview(scrollView)
+    
+    scrollView.snp.makeConstraints {
+      $0.edges.equalToSuperview()
     }
-
-    required init?(coder: NSCoder) { nil }
+    
+    segmentedControl.snp.makeConstraints {
+      $0.top.equalTo(safeAreaLayoutGuide).offset(10)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.height.equalTo(60)
+    }
+    
+    receivedController.view.snp.makeConstraints {
+      $0.top.equalTo(segmentedControl.snp.bottom)
+      $0.left.equalToSuperview()
+      $0.right.equalTo(sentController.view.snp.left)
+      $0.bottom.equalTo(self)
+      $0.width.equalTo(self)
+    }
+    
+    sentController.view.snp.makeConstraints {
+      $0.top.equalTo(segmentedControl.snp.bottom)
+      $0.right.equalTo(failedController.view.snp.left)
+      $0.bottom.equalTo(self)
+      $0.width.equalTo(self)
+    }
+    
+    failedController.view.snp.makeConstraints {
+      $0.top.equalTo(segmentedControl.snp.bottom)
+      $0.right.equalToSuperview()
+      $0.bottom.equalTo(self)
+      $0.width.equalTo(self)
+    }
+  }
+  
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/RequestsFeature/Views/RequestsFailedView.swift b/Sources/RequestsFeature/Views/RequestsFailedView.swift
index 76f4adadd7e15d27781e944bd957427629c74379..b3ded76b653c0d8053e5476f42f0c1bfb62b9c08 100644
--- a/Sources/RequestsFeature/Views/RequestsFailedView.swift
+++ b/Sources/RequestsFeature/Views/RequestsFailedView.swift
@@ -1,40 +1,41 @@
 import UIKit
 import Shared
+import AppResources
 
 final class RequestsFailedView: UIView {
-    let titleLabel = UILabel()
-
-    lazy var collectionView: UICollectionView = {
-        var config = UICollectionLayoutListConfiguration(appearance: .plain)
-        config.backgroundColor = Asset.neutralWhite.color
-        config.showsSeparators = false
-        let layout = UICollectionViewCompositionalLayout.list(using: config)
-        let collectionView = UICollectionView(frame: bounds, collectionViewLayout: layout)
-        collectionView.contentInset = .init(top: 15, left: 0, bottom: 0, right: 0)
-        return collectionView
-    }()
-
-    init() {
-        super.init(frame: .zero)
-
-        titleLabel.textAlignment = .center
-        titleLabel.text = Localized.Requests.Failed.empty
-        titleLabel.textColor = Asset.neutralWeak.color
-        titleLabel.font = Fonts.Mulish.regular.font(size: 14.0)
-
-        addSubview(titleLabel)
-        addSubview(collectionView)
-
-        titleLabel.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(48.5)
-            $0.left.equalToSuperview().offset(24)
-            $0.right.equalToSuperview().offset(-24)
-        }
-
-        collectionView.snp.makeConstraints {
-            $0.edges.equalToSuperview()
-        }
+  let titleLabel = UILabel()
+  
+  lazy var collectionView: UICollectionView = {
+    var config = UICollectionLayoutListConfiguration(appearance: .plain)
+    config.backgroundColor = Asset.neutralWhite.color
+    config.showsSeparators = false
+    let layout = UICollectionViewCompositionalLayout.list(using: config)
+    let collectionView = UICollectionView(frame: bounds, collectionViewLayout: layout)
+    collectionView.contentInset = .init(top: 15, left: 0, bottom: 0, right: 0)
+    return collectionView
+  }()
+  
+  init() {
+    super.init(frame: .zero)
+    
+    titleLabel.textAlignment = .center
+    titleLabel.text = Localized.Requests.Failed.empty
+    titleLabel.textColor = Asset.neutralWeak.color
+    titleLabel.font = Fonts.Mulish.regular.font(size: 14.0)
+    
+    addSubview(titleLabel)
+    addSubview(collectionView)
+    
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(48.5)
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
     }
-
-    required init?(coder: NSCoder) { nil }
+    
+    collectionView.snp.makeConstraints {
+      $0.edges.equalToSuperview()
+    }
+  }
+  
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/RequestsFeature/Views/RequestsHiddenSectionHeader.swift b/Sources/RequestsFeature/Views/RequestsHiddenSectionHeader.swift
index f6fab012ee3b934ae1adbd31dd54c10857fe9e7d..9442a7a55097f411933b048522893da51afb0763 100644
--- a/Sources/RequestsFeature/Views/RequestsHiddenSectionHeader.swift
+++ b/Sources/RequestsFeature/Views/RequestsHiddenSectionHeader.swift
@@ -1,64 +1,65 @@
 import UIKit
 import Shared
 import Combine
+import AppResources
 
 final class RequestsHiddenSectionHeader: UICollectionReusableView {
-    let titleLabel = UILabel()
-    let separatorView = UIView()
-    let switcherView = UISwitch()
-    var cancellables = Set<AnyCancellable>()
-
-    override func prepareForReuse() {
-        super.prepareForReuse()
-        cancellables.removeAll()
+  let titleLabel = UILabel()
+  let separatorView = UIView()
+  let switcherView = UISwitch()
+  var cancellables = Set<AnyCancellable>()
+  
+  override func prepareForReuse() {
+    super.prepareForReuse()
+    cancellables.removeAll()
+  }
+  
+  override init(frame: CGRect) {
+    super.init(frame: frame)
+    
+    titleLabel.text = Localized.Requests.Received.hidden
+    titleLabel.textColor = Asset.neutralActive.color
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 16.0)
+    separatorView.backgroundColor = Asset.neutralLine.color
+    switcherView.onTintColor = Asset.brandPrimary.color
+    
+    addSubview(titleLabel)
+    addSubview(switcherView)
+    addSubview(separatorView)
+    
+    separatorView.snp.makeConstraints {
+      $0.height.equalTo(1)
+      $0.top.equalToSuperview().offset(10)
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
     }
-
-    override init(frame: CGRect) {
-        super.init(frame: frame)
-
-        titleLabel.text = Localized.Requests.Received.hidden
-        titleLabel.textColor = Asset.neutralActive.color
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 16.0)
-        separatorView.backgroundColor = Asset.neutralLine.color
-        switcherView.onTintColor = Asset.brandPrimary.color
-
-        addSubview(titleLabel)
-        addSubview(switcherView)
-        addSubview(separatorView)
-
-        separatorView.snp.makeConstraints {
-            $0.height.equalTo(1)
-            $0.top.equalToSuperview().offset(10)
-            $0.left.equalToSuperview().offset(24)
-            $0.right.equalToSuperview().offset(-24)
-        }
-
-        titleLabel.snp.makeConstraints {
-            $0.top.equalTo(separatorView.snp.bottom).offset(30)
-            $0.left.equalToSuperview().offset(24)
-            $0.bottom.equalToSuperview().offset(-20)
-        }
-
-        switcherView.snp.makeConstraints {
-            $0.centerY.equalTo(titleLabel)
-            $0.right.equalToSuperview().offset(-24)
-        }
+    
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(separatorView.snp.bottom).offset(30)
+      $0.left.equalToSuperview().offset(24)
+      $0.bottom.equalToSuperview().offset(-20)
     }
-
-    required init?(coder: NSCoder) { nil }
+    
+    switcherView.snp.makeConstraints {
+      $0.centerY.equalTo(titleLabel)
+      $0.right.equalToSuperview().offset(-24)
+    }
+  }
+  
+  required init?(coder: NSCoder) { nil }
 }
 
 final class RequestsBlankSectionHeader: UICollectionReusableView {
-    private let view = UIView()
-
-    override init(frame: CGRect) {
-        super.init(frame: frame)
-        addSubview(view)
-        view.snp.makeConstraints {
-            $0.edges.equalToSuperview()
-            $0.height.equalTo(1)
-        }
+  private let view = UIView()
+  
+  override init(frame: CGRect) {
+    super.init(frame: frame)
+    addSubview(view)
+    view.snp.makeConstraints {
+      $0.edges.equalToSuperview()
+      $0.height.equalTo(1)
     }
-
-    required init?(coder: NSCoder) { nil }
+  }
+  
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/RequestsFeature/Views/RequestsReceivedView.swift b/Sources/RequestsFeature/Views/RequestsReceivedView.swift
index d4df66fea6dae47ee46dac61451f45a05fa2b4e7..5e60e04ac94f513c3e96018e2dd3762fedb8ca56 100644
--- a/Sources/RequestsFeature/Views/RequestsReceivedView.swift
+++ b/Sources/RequestsFeature/Views/RequestsReceivedView.swift
@@ -1,52 +1,53 @@
 import UIKit
 import Shared
+import AppResources
 
 final class RequestsReceivedView: UIView {
-    lazy var collectionView: UICollectionView = {
-        let itemSize = NSCollectionLayoutSize(
-            widthDimension: .fractionalWidth(1),
-            heightDimension: .estimated(1)
-        )
-
-        let item = NSCollectionLayoutItem(layoutSize: itemSize)
-
-        let groupSize = NSCollectionLayoutSize(
-            widthDimension: .fractionalWidth(1),
-            heightDimension: .estimated(1)
-        )
-
-        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
-
-        let section = NSCollectionLayoutSection(group: group)
-        section.interGroupSpacing = 5
-        section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)
-
-        let headerFooterSize = NSCollectionLayoutSize(
-            widthDimension: .fractionalWidth(1.0),
-            heightDimension: .estimated(44)
-        )
-
-        let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
-            layoutSize: headerFooterSize,
-            elementKind: UICollectionView.elementKindSectionHeader,
-            alignment: .top
-        )
-
-        section.boundarySupplementaryItems = [sectionHeader]
-        let layout = UICollectionViewCompositionalLayout(section: section)
-
-        let collectionView = UICollectionView(frame: bounds, collectionViewLayout: layout)
-        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
-        collectionView.backgroundColor = Asset.neutralWhite.color
-        collectionView.contentInset = .init(top: 15, left: 0, bottom: 0, right: 0)
-        return collectionView
-    }()
-
-    init() {
-        super.init(frame: .zero)
-        addSubview(collectionView)
-    }
-
-    required init?(coder: NSCoder) { nil }
+  lazy var collectionView: UICollectionView = {
+    let itemSize = NSCollectionLayoutSize(
+      widthDimension: .fractionalWidth(1),
+      heightDimension: .estimated(1)
+    )
+    
+    let item = NSCollectionLayoutItem(layoutSize: itemSize)
+    
+    let groupSize = NSCollectionLayoutSize(
+      widthDimension: .fractionalWidth(1),
+      heightDimension: .estimated(1)
+    )
+    
+    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
+    
+    let section = NSCollectionLayoutSection(group: group)
+    section.interGroupSpacing = 5
+    section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)
+    
+    let headerFooterSize = NSCollectionLayoutSize(
+      widthDimension: .fractionalWidth(1.0),
+      heightDimension: .estimated(44)
+    )
+    
+    let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
+      layoutSize: headerFooterSize,
+      elementKind: UICollectionView.elementKindSectionHeader,
+      alignment: .top
+    )
+    
+    section.boundarySupplementaryItems = [sectionHeader]
+    let layout = UICollectionViewCompositionalLayout(section: section)
+    
+    let collectionView = UICollectionView(frame: bounds, collectionViewLayout: layout)
+    collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+    collectionView.backgroundColor = Asset.neutralWhite.color
+    collectionView.contentInset = .init(top: 15, left: 0, bottom: 0, right: 0)
+    return collectionView
+  }()
+  
+  init() {
+    super.init(frame: .zero)
+    addSubview(collectionView)
+  }
+  
+  required init?(coder: NSCoder) { nil }
 }
 
diff --git a/Sources/RequestsFeature/Views/RequestsSegmentedControl.swift b/Sources/RequestsFeature/Views/RequestsSegmentedControl.swift
index 0bdea739207c2b2a73cba676d40df85eac85d946..6467704207b98661ee3158a471264bb073b1aa01 100644
--- a/Sources/RequestsFeature/Views/RequestsSegmentedControl.swift
+++ b/Sources/RequestsFeature/Views/RequestsSegmentedControl.swift
@@ -1,94 +1,95 @@
 import UIKit
 import Shared
 import SnapKit
+import AppResources
 
 final class RequestsSegmentedControl: UIView {
-    private let trackView = UIView()
-    private let stackView = UIStackView()
-    private var leftConstraint: Constraint?
-    private let trackIndicatorView = UIView()
-    private(set) var sentRequestsButton = RequestSegmentedButton()
-    private(set) var failedRequestsButton = RequestSegmentedButton()
-    private(set) var receivedRequestsButton = RequestSegmentedButton()
-
-    init() {
-        super.init(frame: .zero)
-        trackView.backgroundColor = Asset.neutralLine.color
-        trackIndicatorView.backgroundColor = Asset.brandPrimary.color
-
-        sentRequestsButton.titleLabel.text = Localized.Requests.Sent.title
-        failedRequestsButton.titleLabel.text = Localized.Requests.Failed.title
-        receivedRequestsButton.titleLabel.text = Localized.Requests.Received.title
-
-        sentRequestsButton.titleLabel.textColor = Asset.neutralDisabled.color
-        failedRequestsButton.titleLabel.textColor = Asset.neutralDisabled.color
-        receivedRequestsButton.titleLabel.textColor = Asset.brandPrimary.color
-
-        sentRequestsButton.imageView.tintColor = Asset.neutralDisabled.color
-        failedRequestsButton.imageView.tintColor = Asset.neutralDisabled.color
-        receivedRequestsButton.imageView.tintColor = Asset.brandPrimary.color
-
-        sentRequestsButton.imageView.image = Asset.requestsTabSent.image
-        failedRequestsButton.imageView.image = Asset.requestsTabFailed.image
-        receivedRequestsButton.imageView.image = Asset.requestsTabReceived.image
-        
-        stackView.addArrangedSubview(receivedRequestsButton)
-        stackView.addArrangedSubview(sentRequestsButton)
-        stackView.addArrangedSubview(failedRequestsButton)
-        stackView.distribution = .fillEqually
-        stackView.backgroundColor = Asset.neutralWhite.color
-
-        addSubview(stackView)
-        addSubview(trackView)
-        trackView.addSubview(trackIndicatorView)
-
-        stackView.snp.makeConstraints {
-            $0.edges.equalToSuperview()
-        }
-
-        trackView.snp.makeConstraints {
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-            $0.height.equalTo(2)
-        }
-
-        trackIndicatorView.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            leftConstraint = $0.left.equalToSuperview().constraint
-            $0.width.equalToSuperview().dividedBy(3)
-            $0.bottom.equalToSuperview()
-        }
-
-        sentRequestsButton.accessibilityIdentifier = Localized.Accessibility.Requests.Sent.tab
-        failedRequestsButton.accessibilityIdentifier = Localized.Accessibility.Requests.Failed.tab
-        receivedRequestsButton.accessibilityIdentifier = Localized.Accessibility.Requests.Received.tab
+  private let trackView = UIView()
+  private let stackView = UIStackView()
+  private var leftConstraint: Constraint?
+  private let trackIndicatorView = UIView()
+  private(set) var sentRequestsButton = RequestSegmentedButton()
+  private(set) var failedRequestsButton = RequestSegmentedButton()
+  private(set) var receivedRequestsButton = RequestSegmentedButton()
+  
+  init() {
+    super.init(frame: .zero)
+    trackView.backgroundColor = Asset.neutralLine.color
+    trackIndicatorView.backgroundColor = Asset.brandPrimary.color
+    
+    sentRequestsButton.titleLabel.text = Localized.Requests.Sent.title
+    failedRequestsButton.titleLabel.text = Localized.Requests.Failed.title
+    receivedRequestsButton.titleLabel.text = Localized.Requests.Received.title
+    
+    sentRequestsButton.titleLabel.textColor = Asset.neutralDisabled.color
+    failedRequestsButton.titleLabel.textColor = Asset.neutralDisabled.color
+    receivedRequestsButton.titleLabel.textColor = Asset.brandPrimary.color
+    
+    sentRequestsButton.imageView.tintColor = Asset.neutralDisabled.color
+    failedRequestsButton.imageView.tintColor = Asset.neutralDisabled.color
+    receivedRequestsButton.imageView.tintColor = Asset.brandPrimary.color
+    
+    sentRequestsButton.imageView.image = Asset.requestsTabSent.image
+    failedRequestsButton.imageView.image = Asset.requestsTabFailed.image
+    receivedRequestsButton.imageView.image = Asset.requestsTabReceived.image
+    
+    stackView.addArrangedSubview(receivedRequestsButton)
+    stackView.addArrangedSubview(sentRequestsButton)
+    stackView.addArrangedSubview(failedRequestsButton)
+    stackView.distribution = .fillEqually
+    stackView.backgroundColor = Asset.neutralWhite.color
+    
+    addSubview(stackView)
+    addSubview(trackView)
+    trackView.addSubview(trackIndicatorView)
+    
+    stackView.snp.makeConstraints {
+      $0.edges.equalToSuperview()
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    func updateSwipePercentage(_ percentageScrolled: CGFloat) {
-        let amountOfTabs = 3.0
-        let tabWidth = bounds.width / amountOfTabs
-        let leftOffset = percentageScrolled * tabWidth
-
-        leftConstraint?.update(offset: leftOffset)
-
-        let receivedPercentage = percentageScrolled > 1 ? 1 : percentageScrolled
-        let failedPercentage = percentageScrolled <= 1 ? 0 : percentageScrolled - 1
-        let sentPercentage = percentageScrolled > 1 ? 1 - (percentageScrolled-1) : percentageScrolled
-
-        let sentColor = UIColor.fade(from: Asset.neutralDisabled.color, to: Asset.brandPrimary.color, pcent: sentPercentage)
-        let failedColor = UIColor.fade(from: Asset.neutralDisabled.color, to: Asset.brandPrimary.color, pcent: failedPercentage)
-        let receivedColor = UIColor.fade(from: Asset.brandPrimary.color, to: Asset.neutralDisabled.color, pcent: receivedPercentage)
-
-        sentRequestsButton.imageView.tintColor = sentColor
-        sentRequestsButton.titleLabel.textColor = sentColor
-
-        failedRequestsButton.imageView.tintColor = failedColor
-        failedRequestsButton.titleLabel.textColor = failedColor
-
-        receivedRequestsButton.imageView.tintColor = receivedColor
-        receivedRequestsButton.titleLabel.textColor = receivedColor
+    
+    trackView.snp.makeConstraints {
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+      $0.height.equalTo(2)
+    }
+    
+    trackIndicatorView.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      leftConstraint = $0.left.equalToSuperview().constraint
+      $0.width.equalToSuperview().dividedBy(3)
+      $0.bottom.equalToSuperview()
     }
+    
+    sentRequestsButton.accessibilityIdentifier = Localized.Accessibility.Requests.Sent.tab
+    failedRequestsButton.accessibilityIdentifier = Localized.Accessibility.Requests.Failed.tab
+    receivedRequestsButton.accessibilityIdentifier = Localized.Accessibility.Requests.Received.tab
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  func updateSwipePercentage(_ percentageScrolled: CGFloat) {
+    let amountOfTabs = 3.0
+    let tabWidth = bounds.width / amountOfTabs
+    let leftOffset = percentageScrolled * tabWidth
+    
+    leftConstraint?.update(offset: leftOffset)
+    
+    let receivedPercentage = percentageScrolled > 1 ? 1 : percentageScrolled
+    let failedPercentage = percentageScrolled <= 1 ? 0 : percentageScrolled - 1
+    let sentPercentage = percentageScrolled > 1 ? 1 - (percentageScrolled-1) : percentageScrolled
+    
+    let sentColor = UIColor.fade(from: Asset.neutralDisabled.color, to: Asset.brandPrimary.color, pcent: sentPercentage)
+    let failedColor = UIColor.fade(from: Asset.neutralDisabled.color, to: Asset.brandPrimary.color, pcent: failedPercentage)
+    let receivedColor = UIColor.fade(from: Asset.brandPrimary.color, to: Asset.neutralDisabled.color, pcent: receivedPercentage)
+    
+    sentRequestsButton.imageView.tintColor = sentColor
+    sentRequestsButton.titleLabel.textColor = sentColor
+    
+    failedRequestsButton.imageView.tintColor = failedColor
+    failedRequestsButton.titleLabel.textColor = failedColor
+    
+    receivedRequestsButton.imageView.tintColor = receivedColor
+    receivedRequestsButton.titleLabel.textColor = receivedColor
+  }
 }
diff --git a/Sources/RequestsFeature/Views/RequestsSentView.swift b/Sources/RequestsFeature/Views/RequestsSentView.swift
index 9150c06bcb745e56036ff809568a4a4fbd2f351e..1035850ca0165d44893bef7fc3750c0679ee6bcf 100644
--- a/Sources/RequestsFeature/Views/RequestsSentView.swift
+++ b/Sources/RequestsFeature/Views/RequestsSentView.swift
@@ -1,53 +1,54 @@
 import UIKit
 import Shared
+import AppResources
 
 final class RequestsSentView: UIView {
-    let titleLabel = UILabel()
-    let connectionsButton = CapsuleButton()
-
-    lazy var collectionView: UICollectionView = {
-        var config = UICollectionLayoutListConfiguration(appearance: .plain)
-        config.backgroundColor = Asset.neutralWhite.color
-        config.showsSeparators = false
-        let layout = UICollectionViewCompositionalLayout.list(using: config)
-        let collectionView = UICollectionView(frame: bounds, collectionViewLayout: layout)
-        collectionView.contentInset = .init(top: 15, left: 0, bottom: 0, right: 0)
-        return collectionView
-    }()
-
-    init() {
-        super.init(frame: .zero)
-
-        titleLabel.textAlignment = .center
-        titleLabel.text = Localized.Requests.Sent.empty
-        titleLabel.textColor = Asset.neutralWeak.color
-        titleLabel.font = Fonts.Mulish.regular.font(size: 14.0)
-
-        connectionsButton.set(
-            style: .brandColored,
-            title: Localized.Requests.Sent.action
-        )
-
-        addSubview(titleLabel)
-        addSubview(connectionsButton)
-        addSubview(collectionView)
-
-        titleLabel.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(48.5)
-            $0.left.equalToSuperview().offset(24)
-            $0.right.equalToSuperview().offset(-24)
-        }
-
-        connectionsButton.snp.makeConstraints {
-            $0.left.equalToSuperview().offset(24)
-            $0.right.equalToSuperview().offset(-24)
-            $0.bottom.equalTo(safeAreaLayoutGuide).offset(-16)
-        }
-
-        collectionView.snp.makeConstraints {
-            $0.edges.equalToSuperview()
-        }
+  let titleLabel = UILabel()
+  let connectionsButton = CapsuleButton()
+
+  lazy var collectionView: UICollectionView = {
+    var config = UICollectionLayoutListConfiguration(appearance: .plain)
+    config.backgroundColor = Asset.neutralWhite.color
+    config.showsSeparators = false
+    let layout = UICollectionViewCompositionalLayout.list(using: config)
+    let collectionView = UICollectionView(frame: bounds, collectionViewLayout: layout)
+    collectionView.contentInset = .init(top: 15, left: 0, bottom: 0, right: 0)
+    return collectionView
+  }()
+
+  init() {
+    super.init(frame: .zero)
+
+    titleLabel.textAlignment = .center
+    titleLabel.text = Localized.Requests.Sent.empty
+    titleLabel.textColor = Asset.neutralWeak.color
+    titleLabel.font = Fonts.Mulish.regular.font(size: 14.0)
+
+    connectionsButton.set(
+      style: .brandColored,
+      title: Localized.Requests.Sent.action
+    )
+
+    addSubview(titleLabel)
+    addSubview(connectionsButton)
+    addSubview(collectionView)
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(48.5)
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
     }
 
-    required init?(coder: NSCoder) { nil }
+    connectionsButton.snp.makeConstraints {
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
+      $0.bottom.equalTo(safeAreaLayoutGuide).offset(-16)
+    }
+
+    collectionView.snp.makeConstraints {
+      $0.edges.equalToSuperview()
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/RestoreFeature/Controllers/RestoreController.swift b/Sources/RestoreFeature/Controllers/RestoreController.swift
index f2081aaef8eca0773f552bf685976dbfe67d75c9..ac6f6ae1b3778411c35f750734fe2498371e3f90 100644
--- a/Sources/RestoreFeature/Controllers/RestoreController.swift
+++ b/Sources/RestoreFeature/Controllers/RestoreController.swift
@@ -1,128 +1,138 @@
 import UIKit
-import Models
 import Shared
-import DrawerFeature
 import Combine
-import DependencyInjection
+import AppResources
+import AppNavigation
+import DrawerFeature
+import ComposableArchitecture
 
 public final class RestoreController: UIViewController {
-    @Dependency private var coordinator: RestoreCoordinating
-
-    lazy private var screenView = RestoreView()
-
-    private let viewModel: RestoreViewModel
-    private var cancellables = Set<AnyCancellable>()
-    private var drawerCancellables = Set<AnyCancellable>()
-
-    public init(_ ndf: String, _ settings: RestoreSettings) {
-        viewModel = .init(ndf: ndf, settings: settings)
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func loadView() {
-        view = screenView
-        presentWarning()
-    }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        navigationController?.navigationBar.customize()
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupNavigationBar()
-        setupBindings()
-    }
-
-    private func setupNavigationBar() {
-        let title = UILabel()
-        title.text = Localized.AccountRestore.header
-        title.textColor = Asset.neutralActive.color
-        title.font = Fonts.Mulish.semiBold.font(size: 18.0)
-
-        navigationItem.leftBarButtonItem = UIBarButtonItem(customView: title)
-        navigationItem.leftItemsSupplementBackButton = true
-    }
-
-    private func setupBindings() {
-        viewModel.step
-            .receive(on: DispatchQueue.main)
-            .removeDuplicates()
-            .sink { [unowned self] in
-                screenView.updateFor(step: $0)
-
-                if $0 == .wrongPass {
-                    coordinator.toPassphrase(from: self) { pass in
-                        self.viewModel.retryWith(passphrase: pass)
-                    }
-
-                    return
-                }
-
-                if $0 == .done {
-                    coordinator.toSuccess(from: self)
-                }
-            }.store(in: &cancellables)
-
-        screenView.backButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in didTapBack() }
-            .store(in: &cancellables)
-
-        screenView.cancelButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in didTapBack() }
-            .store(in: &cancellables)
-
-        screenView.restoreButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                coordinator.toPassphrase(from: self) { passphrase in
-                    self.viewModel.didTapRestore(passphrase: passphrase)
-                }
-            }.store(in: &cancellables)
-    }
-
-    @objc private func didTapBack() {
-        navigationController?.popViewController(animated: true)
-    }
+  @Dependency(\.navigator) var navigator: Navigator
+
+  private lazy var screenView = RestoreView()
+
+  private let viewModel: RestoreViewModel
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
+
+  public init(_ details: RestorationDetails) {
+    viewModel = .init(details: details)
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func loadView() {
+    view = screenView
+    presentWarning()
+  }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    navigationController?.navigationBar.customize(translucent: true)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupNavigationBar()
+    setupBindings()
+  }
+
+  private func setupNavigationBar() {
+    let title = UILabel()
+    title.text = Localized.AccountRestore.header
+    title.textColor = Asset.neutralActive.color
+    title.font = Fonts.Mulish.semiBold.font(size: 18.0)
+    navigationItem.leftBarButtonItem = UIBarButtonItem(customView: title)
+    navigationItem.leftItemsSupplementBackButton = true
+  }
+
+  private func setupBindings() {
+    viewModel
+      .stepPublisher
+      .receive(on: DispatchQueue.main)
+      .removeDuplicates()
+      .sink { [unowned self] in
+        screenView.updateFor(step: $0)
+        if $0 == .wrongPass {
+          navigator.perform(PresentPassphrase(onCancel: { [weak self] in
+            guard let self else { return }
+            self.navigator.perform(DismissModal(from: self))
+          }, onPassphrase: { [weak self] passphrase in
+            guard let self else { return }
+            self.viewModel.retryWith(passphrase: passphrase)
+          }))
+          return
+        }
+        if $0 == .done {
+//          coordinator.toSuccess(from: self)
+        }
+      }.store(in: &cancellables)
+
+    screenView
+      .backButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        didTapBack()
+      }.store(in: &cancellables)
+
+    screenView
+      .cancelButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        didTapBack()
+      }.store(in: &cancellables)
+
+    screenView
+      .restoreButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        navigator.perform(PresentPassphrase(onCancel: { [weak self] in
+          guard let self else { return }
+          self.navigator.perform(DismissModal(from: self))
+        }, onPassphrase: { [weak self] passphrase in
+          guard let self else { return }
+          self.viewModel.didTapRestore(passphrase: passphrase)
+        }))
+      }.store(in: &cancellables)
+  }
+
+  @objc private func didTapBack() {
+    navigationController?.popViewController(animated: true)
+  }
 }
 
 extension RestoreController {
-    private func presentWarning() {
-        let actionButton = DrawerCapsuleButton(model: .init(
-            title: Localized.AccountRestore.Warning.action,
-            style: .brandColored
-        ))
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: Localized.AccountRestore.Warning.title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
-            ),
-            DrawerText(
-                text: Localized.AccountRestore.Warning.subtitle,
-                spacingAfter: 37
-            ),
-            actionButton
-        ])
-
-        actionButton.action
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
+  private func presentWarning() {
+    let actionButton = DrawerCapsuleButton(model: .init(
+      title: Localized.AccountRestore.Warning.action,
+      style: .brandColored
+    ))
+
+    actionButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: Localized.AccountRestore.Warning.title,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 19
+      ),
+      DrawerText(
+        text: Localized.AccountRestore.Warning.subtitle,
+        spacingAfter: 37
+      ),
+      actionButton
+    ], isDismissable: true, from: self))
+  }
 }
diff --git a/Sources/RestoreFeature/Controllers/RestoreListController.swift b/Sources/RestoreFeature/Controllers/RestoreListController.swift
index 33470e44212a67e1ee6833cfd70664aca840321f..cbce48e53d9b5943a84495906dfd64cc38bfd484 100644
--- a/Sources/RestoreFeature/Controllers/RestoreListController.swift
+++ b/Sources/RestoreFeature/Controllers/RestoreListController.swift
@@ -1,117 +1,122 @@
-import HUD
 import UIKit
 import Shared
 import Combine
+import AppResources
+import AppNavigation
 import DrawerFeature
-import DependencyInjection
+import ComposableArchitecture
 
 public final class RestoreListController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: RestoreCoordinating
-
-    lazy private var screenView = RestoreListView()
-
-    private let ndf: String
-    private let viewModel = RestoreListViewModel()
-    private var cancellables = Set<AnyCancellable>()
-    private var drawerCancellables = Set<AnyCancellable>()
-
-    public override func loadView() {
-        view = screenView
-        presentWarning()
-    }
-
-    public init(_ ndf: String) {
-        self.ndf = ndf
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    public required init?(coder: NSCoder) { nil }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        navigationController?.navigationBar.customize(translucent: true)
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-
-        viewModel.hudPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [hud] in hud.update(with: $0) }
-            .store(in: &cancellables)
-
-        viewModel.backupPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                coordinator.toRestore(using: ndf, with: $0, from: self)
-            }.store(in: &cancellables)
-
-        screenView.cancelButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in didTapBack() }
-            .store(in: &cancellables)
-
-        screenView.driveButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                viewModel.didTapCloud(.drive, from: self)
-            }.store(in: &cancellables)
-
-        screenView.icloudButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                viewModel.didTapCloud(.icloud, from: self)
-            }.store(in: &cancellables)
-
-        screenView.dropboxButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                viewModel.didTapCloud(.dropbox, from: self)
-            }.store(in: &cancellables)
-
-        screenView.sftpButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                viewModel.didTapCloud(.sftp, from: self)
-            }.store(in: &cancellables)
-    }
-
-    @objc private func didTapBack() {
-        navigationController?.popViewController(animated: true)
-    }
+  @Dependency(\.navigator) var navigator: Navigator
+
+  private lazy var screenView = RestoreListView()
+
+  private let viewModel = RestoreListViewModel()
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
+
+  public override func loadView() {
+    view = screenView
+    presentWarning()
+  }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    navigationController?.navigationBar.customize(translucent: true)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+
+    viewModel
+      .sftpPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] _ in
+        navigator.perform(PresentSFTP(completion: { [weak self] host, username, password in
+          guard let self else { return }
+          self.viewModel.setupSFTP(host: host, username: username, password: password)
+        }, on: navigationController!))
+      }.store(in: &cancellables)
+
+    viewModel.detailsPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] _ in
+//        coordinator.toRestore(with: $0, from: self)
+      }.store(in: &cancellables)
+
+    screenView.cancelButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in didTapBack() }
+      .store(in: &cancellables)
+
+    screenView.driveButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        viewModel.link(provider: .drive, from: self) { [weak self] in
+          guard let self else { return }
+          self.viewModel.fetch(provider: .drive)
+        }
+      }.store(in: &cancellables)
+
+    screenView.icloudButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        viewModel.link(provider: .icloud, from: self) { [weak self] in
+          guard let self else { return }
+          self.viewModel.fetch(provider: .icloud)
+        }
+      }.store(in: &cancellables)
+
+    screenView.dropboxButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        viewModel.link(provider: .dropbox, from: self) { [weak self] in
+          guard let self else { return }
+          self.viewModel.fetch(provider: .dropbox)
+        }
+      }.store(in: &cancellables)
+
+    screenView.sftpButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        viewModel.link(provider: .sftp, from: self) {}
+      }.store(in: &cancellables)
+  }
+
+  @objc private func didTapBack() {
+    navigationController?.popViewController(animated: true)
+  }
 }
 
 extension RestoreListController {
-    private func presentWarning() {
-        let actionButton = DrawerCapsuleButton(model: .init(
-            title: Localized.AccountRestore.Warning.action,
-            style: .brandColored
-        ))
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: Localized.AccountRestore.Warning.title,
-                spacingAfter: 19
-            ),
-            DrawerText(
-                text: Localized.AccountRestore.Warning.subtitle,
-                spacingAfter: 37
-            ),
-            actionButton
-        ])
-
-        actionButton.action
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
+  private func presentWarning() {
+    let actionButton = DrawerCapsuleButton(model: .init(
+      title: Localized.AccountRestore.Warning.action,
+      style: .brandColored
+    ))
+
+    actionButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: Localized.AccountRestore.Warning.title,
+        spacingAfter: 19
+      ),
+      DrawerText(
+        text: Localized.AccountRestore.Warning.subtitle,
+        spacingAfter: 37
+      ),
+      actionButton
+    ], isDismissable: true, from: self))
+  }
 }
diff --git a/Sources/RestoreFeature/Controllers/RestorePassphraseController.swift b/Sources/RestoreFeature/Controllers/RestorePassphraseController.swift
index 4d19e356729508ea5b688bb8435e09c602b5c8a6..660287c6ca004e36beb04bfeb60a4ad839f55e1f 100644
--- a/Sources/RestoreFeature/Controllers/RestorePassphraseController.swift
+++ b/Sources/RestoreFeature/Controllers/RestorePassphraseController.swift
@@ -2,92 +2,71 @@ import UIKit
 import Shared
 import Combine
 import InputField
-import ScrollViewController
 
 public final class RestorePassphraseController: UIViewController {
-    lazy private var screenView = RestorePassphraseView()
-
-    private var passphrase = "" {
-        didSet {
-            switch Validator.backupPassphrase.validate(passphrase) {
-            case .success:
-                screenView.continueButton.isEnabled = true
-            case .failure:
-                screenView.continueButton.isEnabled = false
-            }
-        }
-    }
-
-    private let completion: StringClosure
-    private var cancellables = Set<AnyCancellable>()
-    private let keyboardListener = KeyboardFrameChangeListener(notificationCenter: .default)
-
-    public init(_ completion: @escaping StringClosure) {
-        self.completion = completion
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func loadView() {
-        let view = UIView()
-        view.addSubview(screenView)
-
-        screenView.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.equalToSuperview().offset(0)
-        }
-
-        self.view = view
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupKeyboard()
-        setupBindings()
-
+  private lazy var screenView = RestorePassphraseView()
+
+  private var passphrase = "" {
+    didSet {
+      switch Validator.backupPassphrase.validate(passphrase) {
+      case .success:
+        screenView.continueButton.isEnabled = true
+      case .failure:
         screenView.continueButton.isEnabled = false
+      }
     }
-
-    private func setupKeyboard() {
-        keyboardListener.keyboardFrameWillChange = { [weak self] keyboard in
-            guard let self = self else { return }
-
-            let inset = self.view.frame.height - self.view.convert(keyboard.frame, from: nil).minY
-
-            self.screenView.snp.updateConstraints {
-                $0.bottom.equalToSuperview().offset(-inset)
-            }
-
-            self.view.setNeedsLayout()
-
-            UIView.animate(withDuration: keyboard.animationDuration) {
-                self.view.layoutIfNeeded()
-            }
+  }
+
+  private let cancelClosure: () -> Void
+  private let stringClosure: (String) -> Void
+  private var cancellables = Set<AnyCancellable>()
+
+  public init(
+    _ cancelClosure: @escaping () -> Void,
+    _ stringClosure: @escaping (String) -> Void
+  ) {
+    self.stringClosure = stringClosure
+    self.cancelClosure = cancelClosure
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func loadView() {
+    view = screenView
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+
+    screenView
+      .inputField
+      .returnPublisher
+      .sink { [unowned self] in
+        screenView.inputField.endEditing(true)
+      }.store(in: &cancellables)
+
+    screenView
+      .inputField
+      .textPublisher
+      .sink { [unowned self] in
+        passphrase = $0.trimmingCharacters(in: .whitespacesAndNewlines)
+      }.store(in: &cancellables)
+
+    screenView
+      .continueButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        dismiss(animated: true) {
+          self.stringClosure(self.passphrase)
         }
-    }
-
-    private func setupBindings() {
-        screenView.inputField.returnPublisher
-            .sink { [unowned self] in screenView.inputField.endEditing(true) }
-            .store(in: &cancellables)
-
-        screenView.cancelButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in dismiss(animated: true) }
-            .store(in: &cancellables)
-
-        screenView.inputField
-            .textPublisher
-            .sink { [unowned self] in passphrase = $0.trimmingCharacters(in: .whitespacesAndNewlines) }
-            .store(in: &cancellables)
-
-        screenView.continueButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                dismiss(animated: true, completion: { self.completion(self.passphrase) })
-            }.store(in: &cancellables)
-    }
+      }.store(in: &cancellables)
+
+    screenView
+      .cancelButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        dismiss(animated: true)
+      }.store(in: &cancellables)
+  }
 }
diff --git a/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift b/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..1cfe3d251c7826bcad639009778dc9b26837f4a1
--- /dev/null
+++ b/Sources/RestoreFeature/Controllers/RestoreSFTPController.swift
@@ -0,0 +1,80 @@
+import UIKit
+import Combine
+import ScrollViewController
+
+public final class RestoreSFTPController: UIViewController {
+  private lazy var screenView = RestoreSFTPView()
+  private lazy var scrollViewController = ScrollViewController()
+
+  private let completion: (String, String, String) -> Void
+  private let viewModel = RestoreSFTPViewModel()
+  private var cancellables = Set<AnyCancellable>()
+
+  public init(_ completion: @escaping (String, String, String) -> Void) {
+    self.completion = completion
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    navigationController?.navigationBar.customize(translucent: true)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupScrollView()
+    setupBindings()
+  }
+
+  private func setupScrollView() {
+    scrollViewController.scrollView.backgroundColor = .white
+
+    addChild(scrollViewController)
+    view.addSubview(scrollViewController.view)
+    scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
+    scrollViewController.didMove(toParent: self)
+    scrollViewController.contentView = screenView
+  }
+
+  private func setupBindings() {
+    viewModel.authPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] params in
+        dismiss(animated: true) {
+          self.completion(params.0, params.1, params.2)
+        }
+      }.store(in: &cancellables)
+
+    screenView.hostField
+      .textPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in viewModel.didEnterHost($0) }
+      .store(in: &cancellables)
+
+    screenView.usernameField
+      .textPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in viewModel.didEnterUsername($0) }
+      .store(in: &cancellables)
+
+    screenView.passwordField
+      .textPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in viewModel.didEnterPassword($0) }
+      .store(in: &cancellables)
+
+    viewModel.statePublisher
+      .receive(on: DispatchQueue.main)
+      .map(\.isButtonEnabled)
+      .sink { [unowned self] in screenView.loginButton.isEnabled = $0 }
+      .store(in: &cancellables)
+
+    screenView.loginButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in viewModel.didTapLogin() }
+      .store(in: &cancellables)
+  }
+}
diff --git a/Sources/RestoreFeature/Controllers/RestoreSuccessController.swift b/Sources/RestoreFeature/Controllers/RestoreSuccessController.swift
index a0624385c1d0143c5826c358f7081ada3573f8b1..6fca825a4df49a270c800ca78b3f5d7757659a66 100644
--- a/Sources/RestoreFeature/Controllers/RestoreSuccessController.swift
+++ b/Sources/RestoreFeature/Controllers/RestoreSuccessController.swift
@@ -1,44 +1,56 @@
 import UIKit
+import Shared
 import Combine
-import DependencyInjection
+import AppCore
+import Dependencies
+import AppNavigation
 
 public final class RestoreSuccessController: UIViewController {
-    @Dependency private var coordinator: RestoreCoordinating
-
-    lazy private var screenView = RestoreSuccessView()
-    private var cancellables = Set<AnyCancellable>()
-
-    public override func loadView() {
-        view = screenView
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupBindings()
-    }
-
-    public override func viewDidLayoutSubviews() {
-        super.viewDidLayoutSubviews()
-
-        let gradient = CAGradientLayer()
-        gradient.colors = [
-            UIColor(red: 122/255, green: 235/255, blue: 239/255, alpha: 1).cgColor,
-            UIColor(red: 56/255, green: 204/255, blue: 232/255, alpha: 1).cgColor,
-            UIColor(red: 63/255, green: 186/255, blue: 253/255, alpha: 1).cgColor,
-            UIColor(red: 98/255, green: 163/255, blue: 255/255, alpha: 1).cgColor
-        ]
-
-        gradient.startPoint = CGPoint(x: 0, y: 0)
-        gradient.endPoint = CGPoint(x: 1, y: 1)
-
-        gradient.frame = screenView.bounds
-        screenView.layer.insertSublayer(gradient, at: 0)
-    }
-
-    private func setupBindings() {
-        screenView.nextButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in coordinator.toChats(from: self) }
-            .store(in: &cancellables)
-    }
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+
+  private lazy var screenView = RestoreSuccessView()
+  private var cancellables = Set<AnyCancellable>()
+
+  public override func loadView() {
+    view = screenView
+  }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    statusBar.set(.darkContent)
+    navigationController?.navigationBar.customize(translucent: true)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupBindings()
+  }
+
+  public override func viewDidLayoutSubviews() {
+    super.viewDidLayoutSubviews()
+
+    let gradient = CAGradientLayer()
+    gradient.colors = [
+      UIColor(red: 122/255, green: 235/255, blue: 239/255, alpha: 1).cgColor,
+      UIColor(red: 56/255, green: 204/255, blue: 232/255, alpha: 1).cgColor,
+      UIColor(red: 63/255, green: 186/255, blue: 253/255, alpha: 1).cgColor,
+      UIColor(red: 98/255, green: 163/255, blue: 255/255, alpha: 1).cgColor
+    ]
+
+    gradient.startPoint = CGPoint(x: 0, y: 0)
+    gradient.endPoint = CGPoint(x: 1, y: 1)
+
+    gradient.frame = screenView.bounds
+    screenView.layer.insertSublayer(gradient, at: 0)
+  }
+
+  private func setupBindings() {
+    screenView
+      .nextButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        navigator.perform(PresentChatList(on: navigationController!))
+      }.store(in: &cancellables)
+  }
 }
diff --git a/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift b/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift
deleted file mode 100644
index 2183035bca4a57ee9fd4435b2656fed7123de814..0000000000000000000000000000000000000000
--- a/Sources/RestoreFeature/Coordinator/RestoreCoordinator.swift
+++ /dev/null
@@ -1,68 +0,0 @@
-import UIKit
-import Models
-import Shared
-import Presentation
-
-public protocol RestoreCoordinating {
-    func toChats(from: UIViewController)
-    func toSuccess(from: UIViewController)
-    func toDrawer(_: UIViewController, from: UIViewController)
-    func toPassphrase(from: UIViewController, _: @escaping StringClosure)
-    func toRestore(using: String, with: RestoreSettings, from: UIViewController)
-}
-
-public struct RestoreCoordinator: RestoreCoordinating {
-    var pushPresenter: Presenting = PushPresenter()
-    var bottomPresenter: Presenting = BottomPresenter()
-    var replacePresenter: Presenting = ReplacePresenter()
-
-    var successFactory: () -> UIViewController
-    var chatListFactory: () -> UIViewController
-    var restoreFactory: (String, RestoreSettings) -> UIViewController
-    var passphraseFactory: (@escaping StringClosure) -> UIViewController
-
-    public init(
-        successFactory: @escaping () -> UIViewController,
-        chatListFactory: @escaping () -> UIViewController,
-        restoreFactory: @escaping (String, RestoreSettings) -> UIViewController,
-        passphraseFactory: @escaping (@escaping StringClosure) -> UIViewController
-    ) {
-        self.successFactory = successFactory
-        self.restoreFactory = restoreFactory
-        self.chatListFactory = chatListFactory
-        self.passphraseFactory = passphraseFactory
-    }
-}
-
-public extension RestoreCoordinator {
-    func toRestore(
-        using ndf: String,
-        with settings: RestoreSettings,
-        from parent: UIViewController
-    ) {
-        let screen = restoreFactory(ndf, settings)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toChats(from parent: UIViewController) {
-        let screen = chatListFactory()
-        replacePresenter.present(screen, from: parent)
-    }
-
-    func toSuccess(from parent: UIViewController) {
-        let screen = successFactory()
-        replacePresenter.present(screen, from: parent)
-    }
-
-    func toDrawer(_ drawer: UIViewController, from parent: UIViewController) {
-        bottomPresenter.present(drawer, from: parent)
-    }
-
-    func toPassphrase(
-        from parent: UIViewController,
-        _ completion: @escaping StringClosure
-    ) {
-        let screen = passphraseFactory(completion)
-        bottomPresenter.present(screen, from: parent)
-    }
-}
diff --git a/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
index 438207bb1cc5df10104d3be19029ef01cc7d3c19..40428ff3d9c49c175afb5d48d5cf8c18af28320b 100644
--- a/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
+++ b/Sources/RestoreFeature/ViewModels/RestoreListViewModel.swift
@@ -1,154 +1,84 @@
-import HUD
 import UIKit
-import Models
 import Shared
+import AppCore
 import Combine
-import BackupFeature
-import DependencyInjection
+import CloudFiles
+import CloudFilesSFTP
+import ComposableArchitecture
 
-import SFTPFeature
-import iCloudFeature
-import DropboxFeature
-import GoogleDriveFeature
+public struct RestorationDetails {
+  var provider: CloudService
+  var metadata: Fetch.Metadata?
+}
 
 final class RestoreListViewModel {
-    @Dependency private var sftpService: SFTPService
-    @Dependency private var icloudService: iCloudInterface
-    @Dependency private var dropboxService: DropboxInterface
-    @Dependency private var googleDriveService: GoogleDriveInterface
-
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
-
-    var backupPublisher: AnyPublisher<RestoreSettings, Never> {
-        backupSubject.eraseToAnyPublisher()
-    }
-
-    private var dropboxAuthCancellable: AnyCancellable?
-    private let hudSubject = PassthroughSubject<HUDStatus, Never>()
-    private let backupSubject = PassthroughSubject<RestoreSettings, Never>()
-
-    func didTapCloud(_ cloudService: CloudService, from parent: UIViewController) {
-        switch cloudService {
-        case .drive:
-            didRequestDriveAuthorization(from: parent)
-        case .icloud:
-            didRequestICloudAuthorization()
-        case .dropbox:
-            didRequestDropboxAuthorization(from: parent)
-        case .sftp:
-            didRequestSFTPAuthorization(from: parent)
-        }
-    }
-
-    private func didRequestSFTPAuthorization(from controller: UIViewController) {
-        let params = SFTPAuthorizationParams(controller, { [weak self] in
-            guard let self = self else { return }
-            controller.navigationController?.popViewController(animated: true)
-
-            self.hudSubject.send(.on)
-
-            self.sftpService.fetchMetadata{ result in
-                switch result {
-                case .success(let settings):
-                    self.hudSubject.send(.none)
-
-                    if let settings = settings {
-                        self.backupSubject.send(settings)
-                    } else {
-                        self.backupSubject.send(.init(cloudService: .sftp))
-                    }
-                case .failure(let error):
-                    self.hudSubject.send(.error(.init(with: error)))
-                }
-            }
-        })
-
-        sftpService.authorizeFlow(params)
+  @Dependency(\.app.hudManager) var hudManager: HUDManager
+
+  var sftpPublisher: AnyPublisher<Void, Never> {
+    sftpSubject.eraseToAnyPublisher()
+  }
+
+  var detailsPublisher: AnyPublisher<RestorationDetails, Never> {
+    detailsSubject.eraseToAnyPublisher()
+  }
+
+  private let sftpSubject = PassthroughSubject<Void, Never>()
+  private let detailsSubject = PassthroughSubject<RestorationDetails, Never>()
+
+  func setupSFTP(host: String, username: String, password: String) {
+    CloudFilesManager.all[.sftp] = .sftp(
+      host: host,
+      username: username,
+      password: password,
+      fileName: "backup.xxm"
+    )
+    fetch(provider: .sftp)
+  }
+
+  func link(
+    provider: CloudService,
+    from controller: UIViewController,
+    onSuccess: @escaping () -> Void
+  ) {
+    if provider == .sftp {
+      sftpSubject.send(())
+      return
     }
-
-    private func didRequestDriveAuthorization(from controller: UIViewController) {
-        googleDriveService.authorize(presenting: controller) { authResult in
-            switch authResult {
-            case .success:
-                self.hudSubject.send(.on)
-                self.googleDriveService.downloadMetadata { downloadResult in
-                    switch downloadResult {
-                    case .success(let metadata):
-                        var backup: Backup?
-
-                        if let metadata = metadata {
-                            backup = .init(id: metadata.identifier, date: metadata.modifiedDate, size: metadata.size)
-                        }
-
-                        self.hudSubject.send(.none)
-                        self.backupSubject.send(RestoreSettings(backup: backup, cloudService: .drive))
-
-                    case .failure(let error):
-                        self.hudSubject.send(.error(.init(with: error)))
-                    }
-                }
-            case .failure(let error):
-                self.hudSubject.send(.error(.init(with: error)))
-            }
+    do {
+      try CloudFilesManager.all[provider]!.link(controller) { [weak self] in
+        guard let self else {return }
+
+        switch $0 {
+        case .success:
+          onSuccess()
+        case .failure(let error):
+          self.hudManager.show(.init(error: error))
         }
+      }
+    } catch {
+      hudManager.show(.init(error: error))
     }
-
-    private func didRequestICloudAuthorization() {
-        if icloudService.isAuthorized() {
-            self.hudSubject.send(.on)
-
-            icloudService.downloadMetadata { result in
-                switch result {
-                case .success(let metadata):
-                    var backup: Backup?
-
-                    if let metadata = metadata {
-                        backup = .init(id: metadata.path, date: metadata.modifiedDate, size: metadata.size)
-                    }
-
-                    self.hudSubject.send(.none)
-                    self.backupSubject.send(RestoreSettings(backup: backup, cloudService: .icloud))
-                case .failure(let error):
-                    self.hudSubject.send(.error(.init(with: error)))
-                }
-            }
-        } else {
-            /// This could be an alert controller asking if user wants to enable/deeplink
-            ///
-            icloudService.openSettings()
+  }
+
+  func fetch(provider: CloudService) {
+    hudManager.show()
+    do {
+      try CloudFilesManager.all[provider]!.fetch { [weak self] in
+        guard let self else { return }
+
+        switch $0 {
+        case .success(let metadata):
+          self.hudManager.hide()
+          self.detailsSubject.send(.init(
+            provider: provider,
+            metadata: metadata
+          ))
+        case .failure(let error):
+          self.hudManager.show(.init(error: error))
         }
+      }
+    } catch {
+      hudManager.show(.init(error: error))
     }
-
-    private func didRequestDropboxAuthorization(from controller: UIViewController) {
-        dropboxAuthCancellable = dropboxService.authorize(presenting: controller)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] authResult in
-                switch authResult {
-                case .success(let bool):
-                    guard bool == true else { return }
-
-                    self.hudSubject.send(.on)
-                    dropboxService.downloadMetadata { metadataResult in
-                        switch metadataResult {
-                        case .success(let metadata):
-                            var backup: Backup?
-
-                            if let metadata = metadata {
-                                backup = .init(id: metadata.path, date: metadata.modifiedDate, size: metadata.size)
-                            }
-
-                            self.hudSubject.send(.none)
-                            self.backupSubject.send(RestoreSettings(backup: backup, cloudService: .dropbox))
-
-                        case .failure(let error):
-                            self.hudSubject.send(.error(.init(with: error)))
-                        }
-                    }
-                case .failure(let error):
-                    self.hudSubject.send(.error(.init(with: error)))
-                }
-            }
-    }
+  }
 }
diff --git a/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..d18036fc37123a30f56b7126b4e595e4e9a1a89b
--- /dev/null
+++ b/Sources/RestoreFeature/ViewModels/RestoreSFTPViewModel.swift
@@ -0,0 +1,85 @@
+import UIKit
+import Shared
+import Combine
+import Foundation
+import CloudFiles
+import CloudFilesSFTP
+
+import AppCore
+import ComposableArchitecture
+
+struct SFTPViewState {
+  var host: String = ""
+  var username: String = ""
+  var password: String = ""
+  var isButtonEnabled: Bool = false
+}
+
+final class RestoreSFTPViewModel {
+  @Dependency(\.app.hudManager) var hudManager: HUDManager
+
+  var statePublisher: AnyPublisher<SFTPViewState, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
+
+  var authPublisher: AnyPublisher<(String, String, String), Never> {
+    authSubject.eraseToAnyPublisher()
+  }
+
+  private let stateSubject = CurrentValueSubject<SFTPViewState, Never>(.init())
+  private let authSubject = PassthroughSubject<(String, String, String), Never>()
+
+  func didEnterHost(_ string: String) {
+    stateSubject.value.host = string
+    validate()
+  }
+
+  func didEnterUsername(_ string: String) {
+    stateSubject.value.username = string
+    validate()
+  }
+
+  func didEnterPassword(_ string: String) {
+    stateSubject.value.password = string
+    validate()
+  }
+
+  func didTapLogin() {
+    hudManager.show()
+
+    let host = stateSubject.value.host
+    let username = stateSubject.value.username
+    let password = stateSubject.value.password
+
+    let anyController = UIViewController()
+
+    DispatchQueue.global().async { [weak self] in
+      guard let self else { return }
+      do {
+        try CloudFilesManager.sftp(
+          host: host,
+          username: username,
+          password: password,
+          fileName: "backup.xxm"
+        ).link(anyController) {
+          switch $0 {
+          case .success:
+            self.hudManager.hide()
+            self.authSubject.send((host, username, password))
+          case .failure(let error):
+            self.hudManager.show(.init(error: error))
+          }
+        }
+      } catch {
+        self.hudManager.show(.init(error: error))
+      }
+    }
+  }
+
+  private func validate() {
+    stateSubject.value.isButtonEnabled =
+    !stateSubject.value.host.isEmpty &&
+    !stateSubject.value.username.isEmpty &&
+    !stateSubject.value.password.isEmpty
+  }
+}
diff --git a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
index bea37e5113ad33a547e5680e478a0ed7717db741..1f7d88498e0f0e6ff1c04b654624a54b97bfadd3 100644
--- a/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
+++ b/Sources/RestoreFeature/ViewModels/RestoreViewModel.swift
@@ -1,172 +1,181 @@
 import UIKit
-import Models
 import Shared
 import Combine
 import Defaults
-import Foundation
-import Integration
-import BackupFeature
-import DependencyInjection
-
-import SFTPFeature
-import iCloudFeature
-import DropboxFeature
-import GoogleDriveFeature
-
-enum RestorationStep {
-    case idle(CloudService, Backup?)
-    case downloading(Float, Float)
-    case failDownload(Error)
-    case wrongPass
-    case parsingData
-    case done
+import CloudFiles
+
+import XXClient
+import XXModels
+import XXDatabase
+import XXMessengerClient
+
+import AppCore
+import ComposableArchitecture
+
+enum Step {
+  case done
+  case wrongPass
+  case parsingData
+  case failDownload(Error)
+  case downloading(Float, Float)
+  case idle(CloudService, CloudFiles.Fetch.Metadata?)
 }
 
-extension RestorationStep: Equatable {
-    static func ==(lhs: RestorationStep, rhs: RestorationStep) -> Bool {
-        switch (lhs, rhs) {
-        case (.done, .done), (.wrongPass, .wrongPass):
-            return true
-        case let (.failDownload(a), .failDownload(b)):
-            return a.localizedDescription == b.localizedDescription
-        case let (.downloading(a, b), .downloading(c, d)):
-            return a == c && b == d
-        case (.idle, _), (.downloading, _), (.parsingData, _),
-            (.done, _), (.failDownload, _), (.wrongPass, _):
-            return false
-        }
+extension Step: Equatable {
+  static func ==(lhs: Step, rhs: Step) -> Bool {
+    switch (lhs, rhs) {
+    case (.done, .done), (.wrongPass, .wrongPass):
+      return true
+    case let (.failDownload(a), .failDownload(b)):
+      return a.localizedDescription == b.localizedDescription
+    case let (.downloading(a, b), .downloading(c, d)):
+      return a == c && b == d
+    case (.idle, _), (.downloading, _), (.parsingData, _),
+      (.done, _), (.failDownload, _), (.wrongPass, _):
+      return false
     }
+  }
 }
 
 final class RestoreViewModel {
-    @Dependency private var sftpService: SFTPService
-    @Dependency private var iCloudService: iCloudInterface
-    @Dependency private var dropboxService: DropboxInterface
-    @Dependency private var googleService: GoogleDriveInterface
-
-    @KeyObject(.username, defaultValue: nil) var username: String?
-
-    var step: AnyPublisher<RestorationStep, Never> {
-        stepRelay.eraseToAnyPublisher()
-    }
-
-    // TO REFACTOR:
-    //
-    private var pendingData: Data?
-
-    private let ndf: String
-    private var passphrase: String!
-    private let settings: RestoreSettings
-    private let stepRelay: CurrentValueSubject<RestorationStep, Never>
-
-    init(ndf: String, settings: RestoreSettings) {
-        self.ndf = ndf
-        self.settings = settings
-        self.stepRelay = .init(.idle(settings.cloudService, settings.backup))
+  @Dependency(\.app.dbManager) var dbManager: DBManager
+  @Dependency(\.app.messenger) var messenger: Messenger
+
+  @KeyObject(.phone, defaultValue: nil) var phone: String?
+  @KeyObject(.email, defaultValue: nil) var email: String?
+  @KeyObject(.username, defaultValue: nil) var username: String?
+
+  var stepPublisher: AnyPublisher<Step, Never> {
+    stepSubject.eraseToAnyPublisher()
+  }
+
+  private var pendingData: Data?
+  private var passphrase: String!
+  private let details: RestorationDetails
+  private let stepSubject: CurrentValueSubject<Step, Never>
+
+  init(details: RestorationDetails) {
+    self.details = details
+    self.stepSubject = .init(.idle(
+      details.provider,
+      details.metadata
+    ))
+  }
+
+  func retryWith(passphrase: String) {
+    self.passphrase = passphrase
+    continueRestoring(data: pendingData!)
+  }
+
+  func didTapRestore(passphrase: String) {
+    self.passphrase = passphrase
+
+    guard let metadata = details.metadata else {
+      fatalError()
     }
 
-    func retryWith(passphrase: String) {
-        self.passphrase = passphrase
-        continueRestoring(data: pendingData!)
-    }
-
-    func didTapRestore(passphrase: String) {
-        self.passphrase = passphrase
-
-        guard let backup = settings.backup else { fatalError() }
+    stepSubject.send(.downloading(0.0, metadata.size))
 
-        stepRelay.send(.downloading(0.0, backup.size))
+    do {
+      try CloudFilesManager.all[details.provider]!.download { [weak self] in
+        guard let self else { return }
 
-        switch settings.cloudService {
-        case .drive:
-            downloadBackupForDrive(backup)
-        case .dropbox:
-            downloadBackupForDropbox(backup)
-        case .icloud:
-            downloadBackupForiCloud(backup)
-        case .sftp:
-            downloadBackupForSFTP(backup)
+        switch $0 {
+        case .success(let data):
+          guard let data else {
+            fatalError("There was metadata, but not data.")
+          }
+          self.continueRestoring(data: data)
+        case .failure(let error):
+          self.stepSubject.send(.failDownload(error))
         }
+      }
+    } catch {
+      stepSubject.send(.failDownload(error))
     }
-
-    private func downloadBackupForSFTP(_ backup: Backup) {
-        sftpService.downloadBackup(path: backup.id) { [weak self] in
-            guard let self = self else { return }
-            self.stepRelay.send(.downloading(backup.size, backup.size))
-
-            switch $0 {
-            case .success(let data):
-                self.continueRestoring(data: data)
-            case .failure(let error):
-                self.stepRelay.send(.failDownload(error))
-            }
+  }
+
+  private func continueRestoring(data: Data) {
+    stepSubject.send(.parsingData)
+
+    DispatchQueue.global().async { [weak self] in
+      guard let self else { return }
+
+      do {
+        print(">>> Calling messenger destroy")
+        try self.messenger.destroy()
+
+        print(">>> Calling restore backup")
+        let result = try self.messenger.restoreBackup(
+          backupData: data,
+          backupPassphrase: self.passphrase
+        )
+
+        let facts = try self.messenger.ud.tryGet().getFacts()
+        self.username = facts.get(.username)!.value
+        self.email = facts.get(.email)?.value
+        self.phone = facts.get(.phone)?.value
+
+        print(">>> Calling wait for network")
+        try self.messenger.waitForNetwork()
+
+        print(">>> Calling waitForNodes")
+        try self.messenger.waitForNodes(
+          targetRatio: 0.5,
+          sleepInterval: 3,
+          retries: 15,
+          onProgress: { print(">>> \($0)") }
+        )
+
+        try self.dbManager.getDB().saveContact(.init(
+          id: self.messenger.e2e.get()!.getContact().getId(),
+          marshaled: self.messenger.e2e.get()!.getContact().data,
+          username: self.username!,
+          email: self.email,
+          phone: self.phone,
+          nickname: nil,
+          photo: nil,
+          authStatus: .friend,
+          isRecent: false,
+          isBlocked: false,
+          isBanned: false,
+          createdAt: Date()
+        ))
+
+        print(">>> Calling multilookup")
+        let multilookup = try self.messenger.lookupContacts(ids: result.restoredContacts)
+
+        multilookup.contacts.forEach {
+          try! self.dbManager.getDB().saveContact(.init(
+            id: try $0.getId(),
+            marshaled: $0.data,
+            username: try? $0.getFact(.username)?.value,
+            email: nil,
+            phone: nil,
+            nickname: try? $0.getFact(.username)?.value,
+            photo: nil,
+            authStatus: .friend,
+            isRecent: false,
+            isBlocked: false,
+            isBanned: false,
+            createdAt: Date()
+          ))
+
+          let _ = try! self.messenger.e2e.get()!.resetAuthenticatedChannel(partner: $0)
         }
-    }
 
-    private func downloadBackupForDropbox(_ backup: Backup) {
-        dropboxService.downloadBackup(backup.id) { [weak self] in
-            guard let self = self else { return }
-            self.stepRelay.send(.downloading(backup.size, backup.size))
-
-            switch $0 {
-            case .success(let data):
-                self.continueRestoring(data: data)
-            case .failure(let error):
-                self.stepRelay.send(.failDownload(error))
-            }
-        }
-    }
+        try self.messenger.start()
 
-    private func downloadBackupForiCloud(_ backup: Backup) {
-        iCloudService.downloadBackup(backup.id) { [weak self] in
-            guard let self = self else { return }
-            self.stepRelay.send(.downloading(backup.size, backup.size))
-
-            switch $0 {
-            case .success(let data):
-                self.continueRestoring(data: data)
-            case .failure(let error):
-                self.stepRelay.send(.failDownload(error))
-            }
+        multilookup.errors.forEach {
+          print(">>> Error: \($0.localizedDescription)")
         }
-    }
 
-    private func downloadBackupForDrive(_ backup: Backup) {
-        googleService.downloadBackup(backup.id) { [weak self] in
-            if let stepRelay = self?.stepRelay {
-                stepRelay.send(.downloading($0, backup.size))
-            }
-        } _: { [weak self] in
-            guard let self = self else { return }
-
-            switch $0 {
-            case .success(let data):
-                self.continueRestoring(data: data)
-            case .failure(let error):
-                self.stepRelay.send(.failDownload(error))
-            }
-        }
-    }
-
-    private func continueRestoring(data: Data) {
-        stepRelay.send(.parsingData)
-
-        DispatchQueue.global().async { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                let session = try Session(
-                    passphrase: self.passphrase,
-                    backupFile: data,
-                    ndf: self.ndf
-                )
-
-                DependencyInjection.Container.shared.register(session as SessionType)
-                self.stepRelay.send(.done)
-            } catch {
-                self.pendingData = data
-                self.stepRelay.send(.wrongPass)
-            }
-        }
+        self.stepSubject.send(.done)
+      } catch {
+        print(">>> Error on restoration: \(error.localizedDescription)")
+        self.pendingData = data
+        self.stepSubject.send(.wrongPass)
+      }
     }
+  }
 }
diff --git a/Sources/RestoreFeature/Views/RestoreDetailsView.swift b/Sources/RestoreFeature/Views/RestoreDetailsView.swift
index 55f18c4d2befcd5dcfd9fa296fe7f6c888fd1aee..b7fad9fbd8c71b50306c9efd66fbcc51b3904e6a 100644
--- a/Sources/RestoreFeature/Views/RestoreDetailsView.swift
+++ b/Sources/RestoreFeature/Views/RestoreDetailsView.swift
@@ -1,56 +1,57 @@
 import UIKit
 import Shared
+import AppResources
 
 final class RestoreDetailsView: UIView {
-    let separatorView = UIView()
-    let imageView = UIImageView()
-    let titleLabel = UILabel()
-
-    let stackView = UIStackView()
-    let dateView = DetailRowButton()
-    let sizeView = DetailRowButton()
-
-    init() {
-        super.init(frame: .zero)
-        separatorView.backgroundColor = Asset.neutralLine.color
-
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 16.0)
-        titleLabel.textColor = Asset.neutralActive.color
-
-        stackView.axis = .vertical
-        stackView.spacing = 22
-        stackView.addArrangedSubview(dateView)
-        stackView.addArrangedSubview(sizeView)
-
-        addSubview(separatorView)
-        addSubview(imageView)
-        addSubview(titleLabel)
-        addSubview(stackView)
-
-        separatorView.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview().offset(25)
-            make.right.equalToSuperview().offset(-25)
-            make.height.equalTo(1)
-        }
-
-        imageView.snp.makeConstraints { make in
-            make.top.equalTo(separatorView.snp.bottom).offset(40)
-            make.left.equalToSuperview().offset(24)
-        }
-
-        titleLabel.snp.makeConstraints { make in
-            make.centerY.equalTo(imageView)
-            make.left.equalToSuperview().offset(92)
-        }
-
-        stackView.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(20)
-            make.left.equalTo(titleLabel)
-            make.right.equalToSuperview().offset(-40)
-            make.bottom.lessThanOrEqualToSuperview().offset(-20)
-        }
+  let separatorView = UIView()
+  let imageView = UIImageView()
+  let titleLabel = UILabel()
+
+  let stackView = UIStackView()
+  let dateView = DetailRowButton()
+  let sizeView = DetailRowButton()
+
+  init() {
+    super.init(frame: .zero)
+    separatorView.backgroundColor = Asset.neutralLine.color
+
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 16.0)
+    titleLabel.textColor = Asset.neutralActive.color
+
+    stackView.axis = .vertical
+    stackView.spacing = 22
+    stackView.addArrangedSubview(dateView)
+    stackView.addArrangedSubview(sizeView)
+
+    addSubview(separatorView)
+    addSubview(imageView)
+    addSubview(titleLabel)
+    addSubview(stackView)
+
+    separatorView.snp.makeConstraints { make in
+      make.top.equalToSuperview()
+      make.left.equalToSuperview().offset(25)
+      make.right.equalToSuperview().offset(-25)
+      make.height.equalTo(1)
     }
 
-    required init?(coder: NSCoder) { nil }
+    imageView.snp.makeConstraints { make in
+      make.top.equalTo(separatorView.snp.bottom).offset(40)
+      make.left.equalToSuperview().offset(24)
+    }
+
+    titleLabel.snp.makeConstraints { make in
+      make.centerY.equalTo(imageView)
+      make.left.equalToSuperview().offset(92)
+    }
+
+    stackView.snp.makeConstraints { make in
+      make.top.equalTo(titleLabel.snp.bottom).offset(20)
+      make.left.equalTo(titleLabel)
+      make.right.equalToSuperview().offset(-40)
+      make.bottom.lessThanOrEqualToSuperview().offset(-20)
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/RestoreFeature/Views/RestoreListView.swift b/Sources/RestoreFeature/Views/RestoreListView.swift
index a955173a742b7c0b94f95601f90634353ada327f..bb8f4e5a0767c2c6a81002e36b3ddb491ce4569f 100644
--- a/Sources/RestoreFeature/Views/RestoreListView.swift
+++ b/Sources/RestoreFeature/Views/RestoreListView.swift
@@ -1,127 +1,128 @@
 import UIKit
 import Shared
+import AppResources
 
 final class RestoreListView: UIView {
-    let titleLabel = UILabel()
-    let stackView = UIStackView()
-    let firstSubtitleLabel = UILabel()
-    let secondSubtitleLabel = UILabel()
-    let sftpButton = RowButton()
-    let driveButton = RowButton()
-    let icloudButton = RowButton()
-    let dropboxButton = RowButton()
-    let cancelButton = CapsuleButton()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        setupTitle(Localized.AccountRestore.List.title)
-        setupSubtitle(Localized.AccountRestore.List.firstSubtitle)
-
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.15
-
-        let attrString = NSMutableAttributedString(
-            string: Localized.AccountRestore.List.secondSubtitle,
-            attributes: [
-                .foregroundColor: Asset.neutralBody.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
-                .paragraphStyle: paragraph
-            ]
-        )
-
-        secondSubtitleLabel.numberOfLines = 0
-        secondSubtitleLabel.attributedText = attrString
-
-        sftpButton.setup(title: Localized.Backup.sftp, icon: Asset.restoreSFTP.image)
-        icloudButton.setup(title: Localized.Backup.iCloud, icon: Asset.restoreIcloud.image)
-        dropboxButton.setup(title: Localized.Backup.dropbox, icon: Asset.restoreDropbox.image)
-        driveButton.setup(title: Localized.Backup.googleDrive, icon: Asset.restoreDrive.image)
-
-        cancelButton.set(style: .seeThrough, title: Localized.AccountRestore.List.cancel)
-
-        stackView.axis = .vertical
-        stackView.distribution = .fillEqually
-        stackView.addArrangedSubview(driveButton)
-        stackView.addArrangedSubview(icloudButton)
-        stackView.addArrangedSubview(dropboxButton)
-        stackView.addArrangedSubview(sftpButton)
-
-        addSubview(titleLabel)
-        addSubview(firstSubtitleLabel)
-        addSubview(secondSubtitleLabel)
-        addSubview(stackView)
-        addSubview(cancelButton)
-
-        titleLabel.snp.makeConstraints {
-            $0.top.equalTo(safeAreaLayoutGuide).offset(15)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-41)
-        }
-
-        firstSubtitleLabel.snp.makeConstraints {
-            $0.top.equalTo(titleLabel.snp.bottom).offset(8)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-41)
-        }
-
-        secondSubtitleLabel.snp.makeConstraints {
-            $0.top.equalTo(firstSubtitleLabel.snp.bottom).offset(8)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-41)
-        }
-
-        stackView.snp.makeConstraints {
-            $0.top.equalTo(secondSubtitleLabel.snp.bottom).offset(28)
-            $0.left.equalToSuperview().offset(24)
-            $0.right.equalToSuperview().offset(-24)
-        }
-
-        cancelButton.snp.makeConstraints {
-            $0.top.greaterThanOrEqualTo(stackView.snp.bottom).offset(20)
-            $0.left.equalToSuperview().offset(40)
-            $0.right.equalToSuperview().offset(-40)
-            $0.bottom.equalTo(safeAreaLayoutGuide).offset(-50)
-        }
+  let titleLabel = UILabel()
+  let stackView = UIStackView()
+  let firstSubtitleLabel = UILabel()
+  let secondSubtitleLabel = UILabel()
+  let sftpButton = RowButton()
+  let driveButton = RowButton()
+  let icloudButton = RowButton()
+  let dropboxButton = RowButton()
+  let cancelButton = CapsuleButton()
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    setupTitle(Localized.AccountRestore.List.title)
+    setupSubtitle(Localized.AccountRestore.List.firstSubtitle)
+
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.15
+
+    let attrString = NSMutableAttributedString(
+      string: Localized.AccountRestore.List.secondSubtitle,
+      attributes: [
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+        .paragraphStyle: paragraph
+      ]
+    )
+
+    secondSubtitleLabel.numberOfLines = 0
+    secondSubtitleLabel.attributedText = attrString
+
+    sftpButton.setup(title: Localized.Backup.sftp, icon: Asset.restoreSFTP.image)
+    icloudButton.setup(title: Localized.Backup.iCloud, icon: Asset.restoreIcloud.image)
+    dropboxButton.setup(title: Localized.Backup.dropbox, icon: Asset.restoreDropbox.image)
+    driveButton.setup(title: Localized.Backup.googleDrive, icon: Asset.restoreDrive.image)
+
+    cancelButton.set(style: .seeThrough, title: Localized.AccountRestore.List.cancel)
+
+    stackView.axis = .vertical
+    stackView.distribution = .fillEqually
+    stackView.addArrangedSubview(driveButton)
+    stackView.addArrangedSubview(icloudButton)
+    stackView.addArrangedSubview(dropboxButton)
+    stackView.addArrangedSubview(sftpButton)
+
+    addSubview(titleLabel)
+    addSubview(firstSubtitleLabel)
+    addSubview(secondSubtitleLabel)
+    addSubview(stackView)
+    addSubview(cancelButton)
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(safeAreaLayoutGuide).offset(15)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
     }
 
-    required init?(coder: NSCoder) { nil }
-
-    private func setupTitle(_ title: String) {
-        let attString = NSMutableAttributedString(string: title)
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1
-
-        attString.addAttribute(.paragraphStyle, value: paragraph)
-        attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
-        attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
+    firstSubtitleLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
+    }
 
-        attString.addAttributes(attributes: [
-            .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
-            .foregroundColor: Asset.brandPrimary.color
-        ], betweenCharacters: "#")
+    secondSubtitleLabel.snp.makeConstraints {
+      $0.top.equalTo(firstSubtitleLabel.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
+    }
 
-        titleLabel.numberOfLines = 0
-        titleLabel.attributedText = attString
+    stackView.snp.makeConstraints {
+      $0.top.equalTo(secondSubtitleLabel.snp.bottom).offset(28)
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
     }
 
-    private func setupSubtitle(_ subtitle: String) {
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.15
-
-        let attString = NSAttributedString(
-            string: subtitle,
-            attributes: [
-                .foregroundColor: Asset.neutralBody.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
-                .paragraphStyle: paragraph
-            ])
-
-        firstSubtitleLabel.numberOfLines = 0
-        firstSubtitleLabel.attributedText = attString
+    cancelButton.snp.makeConstraints {
+      $0.top.greaterThanOrEqualTo(stackView.snp.bottom).offset(20)
+      $0.left.equalToSuperview().offset(40)
+      $0.right.equalToSuperview().offset(-40)
+      $0.bottom.equalTo(safeAreaLayoutGuide).offset(-50)
     }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  private func setupTitle(_ title: String) {
+    let attString = NSMutableAttributedString(string: title)
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1
+
+    attString.addAttribute(.paragraphStyle, value: paragraph)
+    attString.addAttribute(.foregroundColor, value: Asset.neutralActive.color)
+    attString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 34.0) as Any)
+
+    attString.addAttributes(attributes: [
+      .font: Fonts.Mulish.bold.font(size: 34.0) as Any,
+      .foregroundColor: Asset.brandPrimary.color
+    ], betweenCharacters: "#")
+
+    titleLabel.numberOfLines = 0
+    titleLabel.attributedText = attString
+  }
+
+  private func setupSubtitle(_ subtitle: String) {
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.15
+
+    let attString = NSAttributedString(
+      string: subtitle,
+      attributes: [
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+        .paragraphStyle: paragraph
+      ])
+
+    firstSubtitleLabel.numberOfLines = 0
+    firstSubtitleLabel.attributedText = attString
+  }
 }
diff --git a/Sources/RestoreFeature/Views/RestorePassphraseView.swift b/Sources/RestoreFeature/Views/RestorePassphraseView.swift
index d54fbd4a7577fe1f13d1813cf2047f3956eb8832..9eecc341c17383fd45c1a3eea48d810701e91222 100644
--- a/Sources/RestoreFeature/Views/RestorePassphraseView.swift
+++ b/Sources/RestoreFeature/Views/RestorePassphraseView.swift
@@ -1,67 +1,81 @@
 import UIKit
 import Shared
 import InputField
+import AppResources
 
 final class RestorePassphraseView: UIView {
-    let titleLabel = UILabel()
-    let subtitleLabel = UILabel()
-    let inputField = InputField()
-    let stackView = UIStackView()
-    let continueButton = CapsuleButton()
-    let cancelButton = CapsuleButton()
+  let titleLabel = UILabel()
+  let subtitleLabel = UILabel()
+  let inputField = InputField()
+  let stackView = UIStackView()
+  let continueButton = CapsuleButton()
+  let cancelButton = CapsuleButton()
 
-    init() {
-        super.init(frame: .zero)
-        setup()
-    }
-
-    required init?(coder: NSCoder) { nil }
+  init() {
+    super.init(frame: .zero)
+    layer.cornerRadius = 40
+    backgroundColor = Asset.neutralWhite.color
+    layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
 
-    private func setup() {
-        layer.cornerRadius = 40
-        backgroundColor = Asset.neutralWhite.color
-        layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
+    setupInput()
+    setupLabels()
+    setupButtons()
+    setupStackView()
+  }
 
-        subtitleLabel.numberOfLines = 0
-        titleLabel.textColor = Asset.neutralActive.color
-        subtitleLabel.textColor = Asset.neutralActive.color
+  required init?(coder: NSCoder) { nil }
 
-        inputField.setup(
-            style: .regular,
-            title: "Passphrase",
-            placeholder: "* * * * * *",
-            subtitleColor: Asset.neutralDisabled.color
-        )
+  private func setupInput() {
+    inputField.setup(
+      style: .regular,
+      title: Localized.Backup.Passphrase.Input.title,
+      placeholder: Localized.Backup.Passphrase.Input.placeholder,
+      rightView: .toggleSecureEntry,
+      subtitleColor: Asset.neutralDisabled.color,
+      allowsEmptySpace: false,
+      autocapitalization: .none,
+      contentType: .password
+    )
+  }
 
-        titleLabel.text = "Backup password"
-        titleLabel.textAlignment = .left
-        titleLabel.font = Fonts.Mulish.bold.font(size: 26.0)
+  private func setupLabels() {
+    titleLabel.textAlignment = .left
+    titleLabel.textColor = Asset.neutralActive.color
+    titleLabel.font = Fonts.Mulish.bold.font(size: 26.0)
+    titleLabel.text = Localized.Backup.Restore.Passphrase.title
 
-        subtitleLabel.text = "Please enter your backup password that you used when you did the backup setup"
-        subtitleLabel.textAlignment = .left
-        subtitleLabel.font = Fonts.Mulish.regular.font(size: 16.0)
+    subtitleLabel.numberOfLines = 0
+    subtitleLabel.textAlignment = .left
+    subtitleLabel.textColor = Asset.neutralActive.color
+    subtitleLabel.font = Fonts.Mulish.regular.font(size: 16.0)
+    subtitleLabel.text = Localized.Backup.Restore.Passphrase.subtitle
+  }
 
-        continueButton.setStyle(.brandColored)
-        continueButton.setTitle("Continue", for: .normal)
+  private func setupButtons() {
+    cancelButton.setStyle(.seeThrough)
+    cancelButton.setTitle(Localized.Backup.Passphrase.cancel, for: .normal)
 
-        cancelButton.setStyle(.seeThrough)
-        cancelButton.setTitle("Cancel", for: .normal)
+    continueButton.isEnabled = false
+    continueButton.setStyle(.brandColored)
+    continueButton.setTitle(Localized.Backup.Passphrase.continue, for: .normal)
+  }
 
-        stackView.spacing = 20
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(titleLabel)
-        stackView.addArrangedSubview(subtitleLabel)
-        stackView.addArrangedSubview(inputField)
-        stackView.addArrangedSubview(continueButton)
-        stackView.addArrangedSubview(cancelButton)
+  private func setupStackView() {
+    stackView.spacing = 20
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(titleLabel)
+    stackView.addArrangedSubview(subtitleLabel)
+    stackView.addArrangedSubview(inputField)
+    stackView.addArrangedSubview(continueButton)
+    stackView.addArrangedSubview(cancelButton)
 
-        addSubview(stackView)
+    addSubview(stackView)
 
-        stackView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(60)
-            make.left.equalToSuperview().offset(50)
-            make.right.equalToSuperview().offset(-50)
-            make.bottom.equalToSuperview().offset(-70)
-        }
+    stackView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(60)
+      $0.left.equalToSuperview().offset(50)
+      $0.right.equalToSuperview().offset(-50)
+      $0.bottom.equalToSuperview().offset(-70)
     }
+  }
 }
diff --git a/Sources/RestoreFeature/Views/RestoreProgressView.swift b/Sources/RestoreFeature/Views/RestoreProgressView.swift
index 95a471f02bf53bf936ca1cd30d98217bc3afde7c..bde3b25657b8ff55cbdc6288e37f1bed7c0e695e 100644
--- a/Sources/RestoreFeature/Views/RestoreProgressView.swift
+++ b/Sources/RestoreFeature/Views/RestoreProgressView.swift
@@ -1,87 +1,88 @@
 import UIKit
 import Shared
+import AppResources
 
 final class RestoreProgressView: UIView {
-    let progressBarFull = UIView()
-    let progressBarFiller = UIView()
-    let progressLabel = UILabel()
-    let warningLabel = UILabel()
-    let descriptiveProgressLabel = UILabel()
-
-    init() {
-        super.init(frame: .zero)
-        warningLabel.textColor = Asset.neutralDisabled.color
-        progressLabel.textColor = Asset.neutralDisabled.color
-        descriptiveProgressLabel.textColor = Asset.neutralDisabled.color
-
-        warningLabel.font = Fonts.Mulish.regular.font(size: 14.0)
-        progressLabel.font = Fonts.Mulish.regular.font(size: 14.0)
-        descriptiveProgressLabel.font = Fonts.Mulish.regular.font(size: 14.0)
-
-        descriptiveProgressLabel.textAlignment = .center
-
-        progressBarFull.backgroundColor = Asset.neutralLine.color
-        progressBarFiller.backgroundColor = Asset.brandPrimary.color
-        progressBarFull.layer.masksToBounds = true
-        progressBarFull.layer.cornerRadius = 4
-
-        warningLabel.numberOfLines = 0
-        descriptiveProgressLabel.numberOfLines = 0
-        warningLabel.text = "This may take up to 5 mins, please don’t close the app and don’t put in background and don’t close your phone screen"
-
-        addSubview(progressBarFull)
-        addSubview(progressLabel)
-        addSubview(warningLabel)
-        addSubview(descriptiveProgressLabel)
-        progressBarFull.addSubview(progressBarFiller)
-
-        descriptiveProgressLabel.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualToSuperview()
-            make.left.equalToSuperview().offset(42)
-            make.right.equalToSuperview().offset(-42)
-            make.bottom.equalTo(progressBarFull.snp.top).offset(-15)
-        }
-
-        progressBarFull.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualToSuperview()
-            make.left.equalToSuperview().offset(42)
-            make.right.equalToSuperview().offset(-42)
-            make.centerY.equalToSuperview()
-            make.height.equalTo(8)
-        }
-
-        progressBarFiller.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview()
-            make.width.equalTo(0)
-            make.bottom.equalToSuperview()
-        }
-
-        progressLabel.snp.makeConstraints { make in
-            make.top.equalTo(progressBarFull.snp.bottom).offset(15)
-            make.left.equalToSuperview().offset(42)
-            make.right.equalToSuperview().offset(-42)
-        }
-
-        warningLabel.snp.makeConstraints { make in
-            make.top.equalTo(progressLabel.snp.bottom).offset(15)
-            make.left.equalToSuperview().offset(42)
-            make.right.equalToSuperview().offset(-42)
-            make.bottom.lessThanOrEqualToSuperview()
-        }
+  let progressBarFull = UIView()
+  let progressBarFiller = UIView()
+  let progressLabel = UILabel()
+  let warningLabel = UILabel()
+  let descriptiveProgressLabel = UILabel()
+
+  init() {
+    super.init(frame: .zero)
+    warningLabel.textColor = Asset.neutralDisabled.color
+    progressLabel.textColor = Asset.neutralDisabled.color
+    descriptiveProgressLabel.textColor = Asset.neutralDisabled.color
+
+    warningLabel.font = Fonts.Mulish.regular.font(size: 14.0)
+    progressLabel.font = Fonts.Mulish.regular.font(size: 14.0)
+    descriptiveProgressLabel.font = Fonts.Mulish.regular.font(size: 14.0)
+
+    descriptiveProgressLabel.textAlignment = .center
+
+    progressBarFull.backgroundColor = Asset.neutralLine.color
+    progressBarFiller.backgroundColor = Asset.brandPrimary.color
+    progressBarFull.layer.masksToBounds = true
+    progressBarFull.layer.cornerRadius = 4
+
+    warningLabel.numberOfLines = 0
+    descriptiveProgressLabel.numberOfLines = 0
+    warningLabel.text = "This may take up to 5 mins, please don’t close the app and don’t put in background and don’t close your phone screen"
+
+    addSubview(progressBarFull)
+    addSubview(progressLabel)
+    addSubview(warningLabel)
+    addSubview(descriptiveProgressLabel)
+    progressBarFull.addSubview(progressBarFiller)
+
+    descriptiveProgressLabel.snp.makeConstraints { make in
+      make.top.greaterThanOrEqualToSuperview()
+      make.left.equalToSuperview().offset(42)
+      make.right.equalToSuperview().offset(-42)
+      make.bottom.equalTo(progressBarFull.snp.top).offset(-15)
     }
 
-    required init?(coder: NSCoder) { nil }
+    progressBarFull.snp.makeConstraints { make in
+      make.top.greaterThanOrEqualToSuperview()
+      make.left.equalToSuperview().offset(42)
+      make.right.equalToSuperview().offset(-42)
+      make.centerY.equalToSuperview()
+      make.height.equalTo(8)
+    }
+
+    progressBarFiller.snp.makeConstraints { make in
+      make.top.equalToSuperview()
+      make.left.equalToSuperview()
+      make.width.equalTo(0)
+      make.bottom.equalToSuperview()
+    }
+
+    progressLabel.snp.makeConstraints { make in
+      make.top.equalTo(progressBarFull.snp.bottom).offset(15)
+      make.left.equalToSuperview().offset(42)
+      make.right.equalToSuperview().offset(-42)
+    }
+
+    warningLabel.snp.makeConstraints { make in
+      make.top.equalTo(progressLabel.snp.bottom).offset(15)
+      make.left.equalToSuperview().offset(42)
+      make.right.equalToSuperview().offset(-42)
+      make.bottom.lessThanOrEqualToSuperview()
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
 
-    func update(downloaded: Float, total: Float) {
-        let totalkb = String(format: "%.1f kb", total/1000)
-        let downloadedKb = String(format: "%.1f kb", downloaded/1000)
-        let percent = String(format: "%.0f", downloaded/total * 100)
+  func update(downloaded: Float, total: Float) {
+    let totalkb = String(format: "%.1f kb", total/1000)
+    let downloadedKb = String(format: "%.1f kb", downloaded/1000)
+    let percent = String(format: "%.0f", downloaded/total * 100)
 
-        progressLabel.text = "Downloaded \(downloadedKb) of \(totalkb) (\(percent)%)"
+    progressLabel.text = "Downloaded \(downloadedKb) of \(totalkb) (\(percent)%)"
 
-        progressBarFiller.snp.updateConstraints { make in
-            make.width.equalTo(CGFloat(downloaded/total) * progressBarFull.frame.size.width)
-        }
+    progressBarFiller.snp.updateConstraints { make in
+      make.width.equalTo(CGFloat(downloaded/total) * progressBarFull.frame.size.width)
     }
+  }
 }
diff --git a/Sources/RestoreFeature/Views/RestoreSFTPView.swift b/Sources/RestoreFeature/Views/RestoreSFTPView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..e6595e9f9cd31fe6dcb85447049eb32f3dafdb76
--- /dev/null
+++ b/Sources/RestoreFeature/Views/RestoreSFTPView.swift
@@ -0,0 +1,84 @@
+import UIKit
+import Shared
+import InputField
+import AppResources
+
+final class RestoreSFTPView: UIView {
+  let titleLabel = UILabel()
+  let subtitleLabel = UILabel()
+  let hostField = OutlinedInputField()
+  let usernameField = OutlinedInputField()
+  let passwordField = OutlinedInputField()
+  let loginButton = CapsuleButton()
+  let stackView = UIStackView()
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    titleLabel.textColor = Asset.neutralDark.color
+    titleLabel.text = Localized.AccountRestore.Sftp.title
+    titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
+
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.15
+
+    let attString = NSMutableAttributedString(
+      string: Localized.AccountRestore.Sftp.subtitle,
+      attributes: [
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+        .paragraphStyle: paragraph
+      ])
+
+    attString.setAttributes(
+      attributes: [
+        .foregroundColor: Asset.neutralDark.color,
+        .font: Fonts.Mulish.bold.font(size: 12.0) as Any,
+        .paragraphStyle: paragraph
+      ], betweenCharacters: "*")
+
+    subtitleLabel.numberOfLines = 0
+    subtitleLabel.attributedText = attString
+
+    hostField.setup(title: Localized.AccountRestore.Sftp.host)
+    usernameField.setup(title: Localized.AccountRestore.Sftp.username)
+    passwordField.setup(title: Localized.AccountRestore.Sftp.password, sensitive: true)
+
+    loginButton.set(style: .brandColored, title: Localized.AccountRestore.Sftp.login)
+
+    stackView.spacing = 30
+    stackView.axis = .vertical
+    stackView.distribution = .fillEqually
+    stackView.addArrangedSubview(hostField)
+    stackView.addArrangedSubview(usernameField)
+    stackView.addArrangedSubview(passwordField)
+    stackView.addArrangedSubview(loginButton)
+
+    addSubview(titleLabel)
+    addSubview(subtitleLabel)
+    addSubview(stackView)
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(safeAreaLayoutGuide).offset(15)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
+    }
+
+    subtitleLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-41)
+    }
+
+    stackView.snp.makeConstraints {
+      $0.top.equalTo(subtitleLabel.snp.bottom).offset(28)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-38)
+      $0.bottom.lessThanOrEqualToSuperview()
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+}
diff --git a/Sources/RestoreFeature/Views/RestoreSuccessView.swift b/Sources/RestoreFeature/Views/RestoreSuccessView.swift
index 35bdd4c04fc356e32b54a729b642514e504f8c38..ac6b3dea1ee72ec79ccc0d39202c6ab1c25bea1c 100644
--- a/Sources/RestoreFeature/Views/RestoreSuccessView.swift
+++ b/Sources/RestoreFeature/Views/RestoreSuccessView.swift
@@ -1,78 +1,79 @@
 import UIKit
 import Shared
+import AppResources
 
 final class RestoreSuccessView: UIView {
-    let iconImageView = UIImageView()
-    let titleLabel = UILabel()
-    let subtitleLabel = UILabel()
-    let nextButton = CapsuleButton()
-
-    init() {
-        super.init(frame: .zero)
-
-        iconImageView.contentMode = .center
-        iconImageView.image = Asset.onboardingSuccess.image
-        nextButton.set(style: .white, title: Localized.Onboarding.Success.action)
-
-        subtitleLabel.numberOfLines = 0
-        subtitleLabel.textColor = Asset.neutralWhite.color
-        subtitleLabel.font = Fonts.Mulish.regular.font(size: 16.0)
-
-        addSubview(iconImageView)
-        addSubview(titleLabel)
-        addSubview(subtitleLabel)
-        addSubview(nextButton)
-
-        iconImageView.snp.makeConstraints { make in
-            make.top.equalTo(safeAreaLayoutGuide).offset(40)
-            make.left.equalToSuperview().offset(40)
-        }
-
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalTo(iconImageView.snp.bottom).offset(40)
-            make.left.equalToSuperview().offset(40)
-            make.right.equalToSuperview().offset(-90)
-        }
-
-        subtitleLabel.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(30)
-            make.left.equalToSuperview().offset(40)
-            make.right.equalToSuperview().offset(-90)
-        }
-
-        nextButton.snp.makeConstraints { make in
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-            make.bottom.equalToSuperview().offset(-60)
-        }
-
-        setTitle(Localized.AccountRestore.Success.title)
-        setSubtitle(Localized.AccountRestore.Success.subtitle)
+  let iconImageView = UIImageView()
+  let titleLabel = UILabel()
+  let subtitleLabel = UILabel()
+  let nextButton = CapsuleButton()
+
+  init() {
+    super.init(frame: .zero)
+
+    iconImageView.contentMode = .center
+    iconImageView.image = Asset.onboardingSuccess.image
+    nextButton.set(style: .white, title: Localized.Onboarding.Success.action)
+
+    subtitleLabel.numberOfLines = 0
+    subtitleLabel.textColor = Asset.neutralWhite.color
+    subtitleLabel.font = Fonts.Mulish.regular.font(size: 16.0)
+
+    addSubview(iconImageView)
+    addSubview(titleLabel)
+    addSubview(subtitleLabel)
+    addSubview(nextButton)
+
+    iconImageView.snp.makeConstraints { make in
+      make.top.equalTo(safeAreaLayoutGuide).offset(40)
+      make.left.equalToSuperview().offset(40)
     }
 
-    required init?(coder: NSCoder) { nil }
+    titleLabel.snp.makeConstraints { make in
+      make.top.equalTo(iconImageView.snp.bottom).offset(40)
+      make.left.equalToSuperview().offset(40)
+      make.right.equalToSuperview().offset(-90)
+    }
 
-    private func setTitle(_ title: String) {
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.1
+    subtitleLabel.snp.makeConstraints { make in
+      make.top.equalTo(titleLabel.snp.bottom).offset(30)
+      make.left.equalToSuperview().offset(40)
+      make.right.equalToSuperview().offset(-90)
+    }
 
-        let attrString = NSMutableAttributedString(string: title)
+    nextButton.snp.makeConstraints { make in
+      make.left.equalToSuperview().offset(24)
+      make.right.equalToSuperview().offset(-24)
+      make.bottom.equalToSuperview().offset(-60)
+    }
 
-        attrString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 39.0))
-        attrString.addAttribute(.foregroundColor, value: Asset.neutralWhite.color)
+    setTitle(Localized.AccountRestore.Success.title)
+    setSubtitle(Localized.AccountRestore.Success.subtitle)
+  }
 
-        attrString.addAttribute(
-            name: .foregroundColor,
-            value: Asset.neutralBody.color,
-            betweenCharacters: "#"
-        )
+  required init?(coder: NSCoder) { nil }
 
-        titleLabel.numberOfLines = 0
-        titleLabel.attributedText = attrString
-    }
+  private func setTitle(_ title: String) {
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .left
+    paragraph.lineHeightMultiple = 1.1
 
-    private func setSubtitle(_ subtitle: String?) {
-        subtitleLabel.text = subtitle
-    }
+    let attrString = NSMutableAttributedString(string: title)
+
+    attrString.addAttribute(.font, value: Fonts.Mulish.bold.font(size: 39.0))
+    attrString.addAttribute(.foregroundColor, value: Asset.neutralWhite.color)
+
+    attrString.addAttribute(
+      name: .foregroundColor,
+      value: Asset.neutralBody.color,
+      betweenCharacters: "#"
+    )
+
+    titleLabel.numberOfLines = 0
+    titleLabel.attributedText = attrString
+  }
+
+  private func setSubtitle(_ subtitle: String?) {
+    subtitleLabel.text = subtitle
+  }
 }
diff --git a/Sources/RestoreFeature/Views/RestoreView.swift b/Sources/RestoreFeature/Views/RestoreView.swift
index ba2e643cafc4dcca652ac7daac8ad14d22f52d15..d4bd3aeb238f38a894755497e1ff7a72bcc49c46 100644
--- a/Sources/RestoreFeature/Views/RestoreView.swift
+++ b/Sources/RestoreFeature/Views/RestoreView.swift
@@ -1,171 +1,175 @@
 import UIKit
 import Shared
-import Models
+import CloudFiles
+import AppResources
 
 final class RestoreView: UIView {
-    let titleLabel = UILabel()
-    let subtitleLabel = UILabel()
-    let detailsView = RestoreDetailsView()
-    let progressView = RestoreProgressView()
-
-    let bottomStackView = UIStackView()
-    let backButton = CapsuleButton()
-    let cancelButton = CapsuleButton()
-    let restoreButton = CapsuleButton()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        subtitleLabel.numberOfLines = 0
-        titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
-        subtitleLabel.font = Fonts.Mulish.regular.font(size: 16.0)
-        titleLabel.textColor = Asset.neutralDark.color
-        subtitleLabel.textColor = Asset.neutralDark.color
-
-        restoreButton.set(style: .brandColored, title: Localized.AccountRestore.Found.restore)
-        cancelButton.set(style: .simplestColoredBrand, title: Localized.AccountRestore.Found.cancel)
-        backButton.set(style: .seeThrough, title: Localized.AccountRestore.NotFound.back)
-
-        bottomStackView.axis = .vertical
-
-        addSubview(titleLabel)
-        addSubview(subtitleLabel)
-        addSubview(detailsView)
-        addSubview(progressView)
-        addSubview(bottomStackView)
-
-        bottomStackView.addArrangedSubview(restoreButton)
-        bottomStackView.addArrangedSubview(cancelButton)
-        bottomStackView.addArrangedSubview(backButton)
-
-        titleLabel.snp.makeConstraints {
-            $0.top.equalTo(safeAreaLayoutGuide).offset(20)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-38)
-        }
-
-        subtitleLabel.snp.makeConstraints {
-            $0.top.equalTo(titleLabel.snp.bottom).offset(20)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-38)
-        }
-
-        detailsView.snp.makeConstraints {
-            $0.top.equalTo(subtitleLabel.snp.bottom).offset(40)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-        }
-
-        progressView.snp.makeConstraints {
-            $0.top.greaterThanOrEqualTo(detailsView.snp.bottom)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.lessThanOrEqualTo(bottomStackView.snp.top)
-        }
-
-        bottomStackView.snp.makeConstraints {
-            $0.top.greaterThanOrEqualTo(detailsView.snp.bottom).offset(10)
-            $0.left.equalToSuperview().offset(40)
-            $0.right.equalToSuperview().offset(-40)
-            $0.bottom.equalTo(safeAreaLayoutGuide).offset(-20)
-        }
+  let titleLabel = UILabel()
+  let subtitleLabel = UILabel()
+  let detailsView = RestoreDetailsView()
+  let progressView = RestoreProgressView()
+
+  let bottomStackView = UIStackView()
+  let backButton = CapsuleButton()
+  let cancelButton = CapsuleButton()
+  let restoreButton = CapsuleButton()
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    subtitleLabel.numberOfLines = 0
+    titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
+    subtitleLabel.font = Fonts.Mulish.regular.font(size: 16.0)
+    titleLabel.textColor = Asset.neutralDark.color
+    subtitleLabel.textColor = Asset.neutralDark.color
+
+    restoreButton.set(style: .brandColored, title: Localized.AccountRestore.Found.restore)
+    cancelButton.set(style: .simplestColoredBrand, title: Localized.AccountRestore.Found.cancel)
+    backButton.set(style: .seeThrough, title: Localized.AccountRestore.NotFound.back)
+
+    bottomStackView.axis = .vertical
+
+    addSubview(titleLabel)
+    addSubview(subtitleLabel)
+    addSubview(detailsView)
+    addSubview(progressView)
+    addSubview(bottomStackView)
+
+    bottomStackView.addArrangedSubview(restoreButton)
+    bottomStackView.addArrangedSubview(cancelButton)
+    bottomStackView.addArrangedSubview(backButton)
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(safeAreaLayoutGuide).offset(20)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-38)
     }
 
-    required init?(coder: NSCoder) { nil }
+    subtitleLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(20)
+      $0.left.equalToSuperview().offset(38)
+      $0.right.equalToSuperview().offset(-38)
+    }
+
+    detailsView.snp.makeConstraints {
+      $0.top.equalTo(subtitleLabel.snp.bottom).offset(40)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+    }
 
-    func updateFor(step: RestorationStep) {
-        switch step {
-        case .idle(let cloudService, let backup):
-            guard let backup = backup else {
-                showNoBackupForCloud(named: cloudService.name())
-                return
-            }
+    progressView.snp.makeConstraints {
+      $0.top.greaterThanOrEqualTo(detailsView.snp.bottom)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.lessThanOrEqualTo(bottomStackView.snp.top)
+    }
 
-            showBackup(backup, fromCloud: cloudService)
+    bottomStackView.snp.makeConstraints {
+      $0.top.greaterThanOrEqualTo(detailsView.snp.bottom).offset(10)
+      $0.left.equalToSuperview().offset(40)
+      $0.right.equalToSuperview().offset(-40)
+      $0.bottom.equalTo(safeAreaLayoutGuide).offset(-20)
+    }
+  }
 
-        case .downloading(let downloaded, let total):
-            restoreButton.isHidden = true
-            cancelButton.isHidden = true
-            progressView.isHidden = false
+  required init?(coder: NSCoder) { nil }
 
-            progressView.update(downloaded: downloaded, total: total)
-        case .wrongPass:
-            progressView.descriptiveProgressLabel.text = "Incorrect password"
+  func updateFor(step: Step) {
+    switch step {
+    case .idle(let provider, let metadata):
+      guard let metadata = metadata else {
+        missingMetadataFor(provider)
+        return
+      }
 
-        case .failDownload(let error):
-            progressView.descriptiveProgressLabel.text = error.localizedDescription
+      displayDetailsFrom(provider, size: metadata.size, lastDate: metadata.lastModified)
 
-        case .parsingData:
-            progressView.descriptiveProgressLabel.text = "Parsing backup data"
+    case .downloading(let downloaded, let total):
+      restoreButton.isHidden = true
+      cancelButton.isHidden = true
+      progressView.isHidden = false
 
-        case .done:
-            progressView.descriptiveProgressLabel.text = "Done"
-        }
-    }
+      progressView.update(downloaded: downloaded, total: total)
+    case .wrongPass:
+      progressView.descriptiveProgressLabel.text = "Incorrect password"
 
-    private func showBackup(_ backup: Backup, fromCloud cloud: CloudService) {
-        titleLabel.text = Localized.AccountRestore.Found.title
-        subtitleLabel.text = Localized.AccountRestore.Found.subtitle
-
-        detailsView.titleLabel.text = cloud.name()
-        detailsView.imageView.image = cloud.asset()
-
-        detailsView.dateView.setup(
-            title: Localized.AccountRestore.Found.date,
-            value: backup.date.backupStyle(),
-            hasArrow: false
-        )
-
-        detailsView.sizeView.setup(
-            title: Localized.AccountRestore.Found.size,
-            value: String(format: "%.1f kb", backup.size/1000),
-            hasArrow: false
-        )
-
-        detailsView.isHidden = false
-        backButton.isHidden = true
-        restoreButton.isHidden = false
-        cancelButton.isHidden = false
-        progressView.isHidden = true
-    }
+    case .failDownload(let error):
+      progressView.descriptiveProgressLabel.text = error.localizedDescription
 
-    private func showNoBackupForCloud(named cloud: String) {
-        titleLabel.text = Localized.AccountRestore.NotFound.title
-        subtitleLabel.text = Localized.AccountRestore.NotFound.subtitle(cloud)
+    case .parsingData:
+      progressView.descriptiveProgressLabel.text = "Parsing backup data"
 
-        restoreButton.isHidden = true
-        cancelButton.isHidden = true
-        detailsView.isHidden = true
-        backButton.isHidden = false
-        progressView.isHidden = true
+    case .done:
+      progressView.descriptiveProgressLabel.text = "Done"
     }
+  }
+
+  private func displayDetailsFrom(
+    _ provider: CloudService,
+    size: Float,
+    lastDate: Date
+  ) {
+    titleLabel.text = Localized.AccountRestore.Found.title
+    subtitleLabel.text = Localized.AccountRestore.Found.subtitle
+    detailsView.titleLabel.text = provider.name()
+    detailsView.imageView.image = provider.asset()
+
+    detailsView.dateView.setup(
+      title: Localized.AccountRestore.Found.date,
+      value: lastDate.backupStyle(),
+      hasArrow: false
+    )
+
+    detailsView.sizeView.setup(
+      title: Localized.AccountRestore.Found.size,
+      value: String(format: "%.1f kb", size/1000),
+      hasArrow: false
+    )
+
+    detailsView.isHidden = false
+    backButton.isHidden = true
+    restoreButton.isHidden = false
+    cancelButton.isHidden = false
+    progressView.isHidden = true
+  }
+
+  private func missingMetadataFor(_ provider: CloudService) {
+    titleLabel.text = Localized.AccountRestore.NotFound.title
+    subtitleLabel.text = Localized.AccountRestore.NotFound.subtitle(provider.name())
+
+    restoreButton.isHidden = true
+    cancelButton.isHidden = true
+    detailsView.isHidden = true
+    backButton.isHidden = false
+    progressView.isHidden = true
+  }
 }
 
 private extension CloudService {
-    func name() -> String {
-        switch self {
-        case .drive:
-            return Localized.Backup.googleDrive
-        case .icloud:
-            return Localized.Backup.iCloud
-        case .dropbox:
-            return Localized.Backup.dropbox
-        case .sftp:
-            return Localized.Backup.sftp
-        }
+  func name() -> String {
+    switch self {
+    case .drive:
+      return Localized.Backup.googleDrive
+    case .icloud:
+      return Localized.Backup.iCloud
+    case .dropbox:
+      return Localized.Backup.dropbox
+    case .sftp:
+      return Localized.Backup.sftp
     }
-
-    func asset() -> UIImage {
-        switch self {
-        case .drive:
-            return Asset.restoreDrive.image
-        case .icloud:
-            return Asset.restoreIcloud.image
-        case .dropbox:
-            return Asset.restoreDropbox.image
-        case .sftp:
-            return Asset.restoreSFTP.image
-        }
+  }
+
+  func asset() -> UIImage {
+    switch self {
+    case .drive:
+      return Asset.restoreDrive.image
+    case .icloud:
+      return Asset.restoreIcloud.image
+    case .dropbox:
+      return Asset.restoreDropbox.image
+    case .sftp:
+      return Asset.restoreSFTP.image
     }
+  }
 }
diff --git a/Sources/SFTPFeature/ActionHandlers/SFTPAuthenticator.swift b/Sources/SFTPFeature/ActionHandlers/SFTPAuthenticator.swift
deleted file mode 100644
index 389cdd46fc0c0136247e09f3dbd900ec265a7858..0000000000000000000000000000000000000000
--- a/Sources/SFTPFeature/ActionHandlers/SFTPAuthenticator.swift
+++ /dev/null
@@ -1,54 +0,0 @@
-import Shout
-import Socket
-import Keychain
-import Foundation
-import DependencyInjection
-
-public struct SFTPAuthenticator {
-    public var authenticate: (String, String, String) throws -> Void
-
-    public func callAsFunction(host: String, username: String, password: String) throws {
-        try authenticate(host, username, password)
-    }
-}
-
-extension SFTPAuthenticator {
-    static let mock = SFTPAuthenticator { host, username, password in
-        print("^^^ Requested authentication on sftp service.")
-        print("^^^ Host: \(host)")
-        print("^^^ Username: \(username)")
-        print("^^^ Password: \(password)")
-    }
-
-    static let live = SFTPAuthenticator { host, username, password in
-        do {
-            try SSH.connect(
-                host: host,
-                port: 22,
-                username: username,
-                authMethod: SSHPassword(password)) { ssh in
-                    _ = try ssh.openSftp()
-
-                    let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
-                    try keychain.store(key: .host, value: host)
-                    try keychain.store(key: .pwd, value: password)
-                    try keychain.store(key: .username, value: username)
-                }
-        } catch {
-            if let error = error as? SSHError {
-                print(error.kind)
-                print(error.message)
-                print(error.description)
-            } else if let error = error as? Socket.Error {
-                print(error.errorCode)
-                print(error.description)
-                print(error.errorReason)
-                print(error.localizedDescription)
-            } else {
-                print(error.localizedDescription)
-            }
-
-            throw error
-        }
-    }
-}
diff --git a/Sources/SFTPFeature/ActionHandlers/SFTPDownloader.swift b/Sources/SFTPFeature/ActionHandlers/SFTPDownloader.swift
deleted file mode 100644
index 6a435df0051a2755f2fb73522f7be92a24c4dd77..0000000000000000000000000000000000000000
--- a/Sources/SFTPFeature/ActionHandlers/SFTPDownloader.swift
+++ /dev/null
@@ -1,56 +0,0 @@
-import Shout
-import Socket
-import Keychain
-import Foundation
-import DependencyInjection
-
-public typealias SFTPDownloadResult = (Result<Data, Error>) -> Void
-
-public struct SFTPDownloader {
-    public var download: (String, @escaping SFTPDownloadResult) -> Void
-
-    public func callAsFunction(path: String, completion: @escaping SFTPDownloadResult) {
-        download(path, completion)
-    }
-}
-
-extension SFTPDownloader {
-    static let mock = SFTPDownloader { path, _ in
-        print("^^^ Requested backup download on sftp service.")
-        print("^^^ Path: \(path)")
-    }
-
-    static let live = SFTPDownloader { path, completion in
-        DispatchQueue.global().async {
-            do {
-                let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
-                let host = try keychain.get(key: .host)
-                let password = try keychain.get(key: .pwd)
-                let username = try keychain.get(key: .username)
-
-                let ssh = try SSH(host: host!, port: 22)
-                try ssh.authenticate(username: username!, password: password!)
-                let sftp = try ssh.openSftp()
-
-                let localURL = FileManager.default
-                    .containerURL(forSecurityApplicationGroupIdentifier: "group.elixxir.messenger")!
-                    .appendingPathComponent("sftp")
-
-                try sftp.download(remotePath: path, localURL: localURL)
-
-                let data = try Data(contentsOf: localURL)
-                completion(.success(data))
-            } catch {
-                completion(.failure(error))
-
-                if var error = error as? SSHError {
-                    print(error.kind)
-                    print(error.message)
-                    print(error.description)
-                } else {
-                    print(error.localizedDescription)
-                }
-            }
-        }
-    }
-}
diff --git a/Sources/SFTPFeature/ActionHandlers/SFTPFetcher.swift b/Sources/SFTPFeature/ActionHandlers/SFTPFetcher.swift
deleted file mode 100644
index a27df80ffe8e9be6f41f09185e9962351c44cff9..0000000000000000000000000000000000000000
--- a/Sources/SFTPFeature/ActionHandlers/SFTPFetcher.swift
+++ /dev/null
@@ -1,68 +0,0 @@
-import Shout
-import Socket
-import Models
-import Keychain
-import Foundation
-import DependencyInjection
-
-public typealias SFTPFetchResult = (Result<RestoreSettings?, Error>) -> Void
-
-public struct SFTPFetcher {
-    public var fetch: (@escaping SFTPFetchResult) -> Void
-
-    public func callAsFunction(completion: @escaping SFTPFetchResult) {
-        fetch(completion)
-    }
-}
-
-extension SFTPFetcher {
-    static let mock = SFTPFetcher { _ in
-        print("^^^ Requested backup metadata on sftp service.")
-    }
-
-    static let live = SFTPFetcher { completion in
-        DispatchQueue.global().async {
-            do {
-                let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
-                let host = try keychain.get(key: .host)
-                let password = try keychain.get(key: .pwd)
-                let username = try keychain.get(key: .username)
-
-                let ssh = try SSH(host: host!, port: 22)
-                try ssh.authenticate(username: username!, password: password!)
-                let sftp = try ssh.openSftp()
-
-                if let files = try? sftp.listFiles(in: "backup"),
-                   let backup = files.filter({ file in file.0 == "backup.xxm" }).first {
-                    completion(.success(.init(
-                        backup: .init(
-                            id: "backup/backup.xxm",
-                            date: backup.value.lastModified,
-                            size: Float(backup.value.size)
-                        ),
-                        cloudService: .sftp
-                    )))
-
-                    return
-                }
-
-                completion(.success(nil))
-            } catch {
-                if let error = error as? SSHError {
-                    print(error.kind)
-                    print(error.message)
-                    print(error.description)
-                } else if let error = error as? Socket.Error {
-                    print(error.errorCode)
-                    print(error.description)
-                    print(error.errorReason)
-                    print(error.localizedDescription)
-                } else {
-                    print(error.localizedDescription)
-                }
-
-                completion(.failure(error))
-            }
-        }
-    }
-}
diff --git a/Sources/SFTPFeature/ActionHandlers/SFTPUploader.swift b/Sources/SFTPFeature/ActionHandlers/SFTPUploader.swift
deleted file mode 100644
index fee691d1b7e1669226fecfad6ecc37c97e64f9c5..0000000000000000000000000000000000000000
--- a/Sources/SFTPFeature/ActionHandlers/SFTPUploader.swift
+++ /dev/null
@@ -1,69 +0,0 @@
-import Shout
-import Socket
-import Models
-import Keychain
-import Foundation
-import DependencyInjection
-
-public typealias SFTPUploadResult = (Result<Backup, Error>) -> Void
-
-public struct SFTPUploader {
-    public var upload: (URL, @escaping SFTPUploadResult) -> Void
-
-    public func callAsFunction(url: URL, completion: @escaping SFTPUploadResult) {
-        upload(url, completion)
-    }
-}
-
-extension SFTPUploader {
-    static let mock = SFTPUploader(
-        upload: { url, _ in
-            print("^^^ Requested upload on sftp service")
-            print("^^^ URL path: \(url.path)")
-        }
-    )
-
-    static let live = SFTPUploader { url, completion in
-        DispatchQueue.global().async {
-            do {
-                let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
-                let host = try keychain.get(key: .host)
-                let password = try keychain.get(key: .pwd)
-                let username = try keychain.get(key: .username)
-
-                let ssh = try SSH(host: host!, port: 22)
-                try ssh.authenticate(username: username!, password: password!)
-                let sftp = try ssh.openSftp()
-
-                let data = try Data(contentsOf: url)
-
-                if (try? sftp.listFiles(in: "backup")) == nil {
-                    try sftp.createDirectory("backup")
-                }
-
-                try sftp.upload(data: data, remotePath: "backup/backup.xxm")
-
-                completion(.success(.init(
-                    id: "backup/backup.xxm",
-                    date: Date(),
-                    size: Float(data.count)
-                )))
-            } catch {
-                if let error = error as? SSHError {
-                    print(error.kind)
-                    print(error.message)
-                    print(error.description)
-                } else if let error = error as? Socket.Error {
-                    print(error.errorCode)
-                    print(error.description)
-                    print(error.errorReason)
-                    print(error.localizedDescription)
-                } else {
-                    print(error.localizedDescription)
-                }
-
-                completion(.failure(error))
-            }
-        }
-    }
-}
diff --git a/Sources/SFTPFeature/SFTPController.swift b/Sources/SFTPFeature/SFTPController.swift
deleted file mode 100644
index 21bf8ca1f224ad3b5439d619fbd27025a2a96295..0000000000000000000000000000000000000000
--- a/Sources/SFTPFeature/SFTPController.swift
+++ /dev/null
@@ -1,86 +0,0 @@
-import HUD
-import UIKit
-import Combine
-import DependencyInjection
-import ScrollViewController
-
-public final class SFTPController: UIViewController {
-    @Dependency private var hud: HUD
-
-    lazy private var screenView = SFTPView()
-    lazy private var scrollViewController = ScrollViewController()
-
-    private let completion: () -> Void
-    private let viewModel = SFTPViewModel()
-    private var cancellables = Set<AnyCancellable>()
-
-    public init(_ completion: @escaping () -> Void) {
-        self.completion = completion
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        navigationController?.navigationBar.customize(translucent: true)
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupScrollView()
-        setupBindings()
-    }
-
-    private func setupScrollView() {
-        scrollViewController.scrollView.backgroundColor = .white
-
-        addChild(scrollViewController)
-        view.addSubview(scrollViewController.view)
-        scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
-        scrollViewController.didMove(toParent: self)
-        scrollViewController.contentView = screenView
-    }
-
-    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] in completion() }
-            .store(in: &cancellables)
-
-        screenView.hostField
-            .textPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in viewModel.didEnterHost($0) }
-            .store(in: &cancellables)
-
-        screenView.usernameField
-            .textPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in viewModel.didEnterUsername($0) }
-            .store(in: &cancellables)
-
-        screenView.passwordField
-            .textPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in viewModel.didEnterPassword($0) }
-            .store(in: &cancellables)
-
-        viewModel.statePublisher
-            .receive(on: DispatchQueue.main)
-            .map(\.isButtonEnabled)
-            .sink { [unowned self] in screenView.loginButton.isEnabled = $0 }
-            .store(in: &cancellables)
-
-        screenView.loginButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in viewModel.didTapLogin() }
-            .store(in: &cancellables)
-    }
-}
diff --git a/Sources/SFTPFeature/SFTPService.swift b/Sources/SFTPFeature/SFTPService.swift
deleted file mode 100644
index f1a908564df810ed2aeaa1b4af358b367253f84b..0000000000000000000000000000000000000000
--- a/Sources/SFTPFeature/SFTPService.swift
+++ /dev/null
@@ -1,47 +0,0 @@
-import UIKit
-import Keychain
-import Presentation
-import DependencyInjection
-
-public typealias SFTPAuthorizationParams = (UIViewController, () -> Void)
-
-public struct SFTPService {
-    public var isAuthorized: () -> Bool
-    public var fetchMetadata: SFTPFetcher
-    public var uploadBackup: SFTPUploader
-    public var authorizeFlow: (SFTPAuthorizationParams) -> Void
-    public var authenticate: SFTPAuthenticator
-    public var downloadBackup: SFTPDownloader
-}
-
-public extension SFTPService {
-    static var mock = SFTPService(
-        isAuthorized: { true },
-        fetchMetadata: .mock,
-        uploadBackup: .mock,
-        authorizeFlow: { (_, completion) in completion() },
-        authenticate: .mock,
-        downloadBackup: .mock
-    )
-
-    static var live = SFTPService(
-        isAuthorized: {
-            if let keychain = try? DependencyInjection.Container.shared.resolve() as KeychainHandling,
-               let pwd = try? keychain.get(key: .pwd),
-               let host = try? keychain.get(key: .host),
-               let username = try? keychain.get(key: .username) {
-                return true
-            }
-
-            return false
-        },
-        fetchMetadata: .live,
-        uploadBackup: .live ,
-        authorizeFlow: { controller, completion in
-            var pushPresenter: Presenting = PushPresenter()
-            pushPresenter.present(SFTPController(completion), from: controller)
-        },
-        authenticate: .live,
-        downloadBackup: .live
-    )
-}
diff --git a/Sources/SFTPFeature/SFTPView.swift b/Sources/SFTPFeature/SFTPView.swift
deleted file mode 100644
index 5653d85bacd6c112d737217dfdb9c8312c631de4..0000000000000000000000000000000000000000
--- a/Sources/SFTPFeature/SFTPView.swift
+++ /dev/null
@@ -1,83 +0,0 @@
-import UIKit
-import Shared
-import InputField
-
-final class SFTPView: UIView {
-    let titleLabel = UILabel()
-    let subtitleLabel = UILabel()
-    let hostField = OutlinedInputField()
-    let usernameField = OutlinedInputField()
-    let passwordField = OutlinedInputField()
-    let loginButton = CapsuleButton()
-    let stackView = UIStackView()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        titleLabel.textColor = Asset.neutralDark.color
-        titleLabel.text = Localized.AccountRestore.Sftp.title
-        titleLabel.font = Fonts.Mulish.bold.font(size: 24.0)
-
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .left
-        paragraph.lineHeightMultiple = 1.15
-
-        let attString = NSMutableAttributedString(
-            string: Localized.AccountRestore.Sftp.subtitle,
-            attributes: [
-                .foregroundColor: Asset.neutralBody.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
-                .paragraphStyle: paragraph
-            ])
-
-        attString.setAttributes(
-            attributes: [
-                .foregroundColor: Asset.neutralDark.color,
-                .font: Fonts.Mulish.bold.font(size: 12.0) as Any,
-                .paragraphStyle: paragraph
-        ], betweenCharacters: "*")
-
-        subtitleLabel.numberOfLines = 0
-        subtitleLabel.attributedText = attString
-
-        hostField.setup(title: Localized.AccountRestore.Sftp.host)
-        usernameField.setup(title: Localized.AccountRestore.Sftp.username)
-        passwordField.setup(title: Localized.AccountRestore.Sftp.password, sensitive: true)
-
-        loginButton.set(style: .brandColored, title: Localized.AccountRestore.Sftp.login)
-
-        stackView.spacing = 30
-        stackView.axis = .vertical
-        stackView.distribution = .fillEqually
-        stackView.addArrangedSubview(hostField)
-        stackView.addArrangedSubview(usernameField)
-        stackView.addArrangedSubview(passwordField)
-        stackView.addArrangedSubview(loginButton)
-
-        addSubview(titleLabel)
-        addSubview(subtitleLabel)
-        addSubview(stackView)
-
-        titleLabel.snp.makeConstraints {
-            $0.top.equalTo(safeAreaLayoutGuide).offset(15)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-41)
-        }
-
-        subtitleLabel.snp.makeConstraints {
-            $0.top.equalTo(titleLabel.snp.bottom).offset(8)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-41)
-        }
-
-        stackView.snp.makeConstraints {
-            $0.top.equalTo(subtitleLabel.snp.bottom).offset(28)
-            $0.left.equalToSuperview().offset(38)
-            $0.right.equalToSuperview().offset(-38)
-            $0.bottom.lessThanOrEqualToSuperview()
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-}
diff --git a/Sources/SFTPFeature/SFTPViewModel.swift b/Sources/SFTPFeature/SFTPViewModel.swift
deleted file mode 100644
index e64536bfd96277642d25ddb13f5c81570836a22b..0000000000000000000000000000000000000000
--- a/Sources/SFTPFeature/SFTPViewModel.swift
+++ /dev/null
@@ -1,77 +0,0 @@
-import HUD
-import Combine
-import Foundation
-import DependencyInjection
-
-struct SFTPViewState {
-    var host: String = ""
-    var username: String = ""
-    var password: String = ""
-    var isButtonEnabled: Bool = false
-}
-
-final class SFTPViewModel {
-    @Dependency private var service: SFTPService
-
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
-    }
-
-    var statePublisher: AnyPublisher<SFTPViewState, Never> {
-        stateSubject.eraseToAnyPublisher()
-    }
-
-    var authPublisher: AnyPublisher<Void, Never> {
-        authSubject.eraseToAnyPublisher()
-    }
-
-    private let authSubject = PassthroughSubject<Void, Never>()
-    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
-    private let stateSubject = CurrentValueSubject<SFTPViewState, Never>(.init())
-
-    func didEnterHost(_ string: String) {
-        stateSubject.value.host = string
-        validate()
-    }
-
-    func didEnterUsername(_ string: String) {
-        stateSubject.value.username = string
-        validate()
-    }
-
-    func didEnterPassword(_ string: String) {
-        stateSubject.value.password = string
-        validate()
-    }
-
-    func didTapLogin() {
-        hudSubject.send(.on)
-
-        let host = stateSubject.value.host
-        let username = stateSubject.value.username
-        let password = stateSubject.value.password
-
-        DispatchQueue.global().async { [weak self] in
-            guard let self = self else { return }
-            do {
-                try self.service.authenticate(
-                    host: host,
-                    username: username,
-                    password: password
-                )
-
-                self.hudSubject.send(.none)
-                self.authSubject.send(())
-            } catch {
-                self.hudSubject.send(.error(.init(with: error)))
-            }
-        }
-    }
-
-    private func validate() {
-        stateSubject.value.isButtonEnabled =
-        !stateSubject.value.host.isEmpty &&
-        !stateSubject.value.username.isEmpty &&
-        !stateSubject.value.password.isEmpty
-    }
-}
diff --git a/Sources/ScanFeature/Controllers/ScanContainerController.swift b/Sources/ScanFeature/Controllers/ScanContainerController.swift
index fb1f9d440421512de2f3034c1b46b5d1fd70b2a3..435710b65d50319e33f4ab0bf1de67f538692f36 100644
--- a/Sources/ScanFeature/Controllers/ScanContainerController.swift
+++ b/Sources/ScanFeature/Controllers/ScanContainerController.swift
@@ -1,179 +1,191 @@
 import UIKit
-import Theme
 import Shared
 import Combine
+import AppCore
+import Dependencies
+import AppResources
+import AppNavigation
 import DrawerFeature
-import DependencyInjection
 
 public final class ScanContainerController: UIViewController {
-    @Dependency private var coordinator: ScanCoordinating
-    @Dependency private var statusBarController: StatusBarStyleControlling
-
-    lazy private var screenView = ScanContainerView()
-
-    private var previousPoint: CGPoint = .zero
-    private let scanController = ScanController()
-    private let displayController = ScanDisplayController()
-
-    private var cancellables = Set<AnyCancellable>()
-    private var drawerCancellables = Set<AnyCancellable>()
-
-    public override func loadView() {
-        view = screenView
-        screenView.scrollView.delegate = self
-
-        addChild(scanController)
-        addChild(displayController)
-
-        screenView.scrollView.addSubview(scanController.view)
-        screenView.scrollView.addSubview(displayController.view)
-
-        scanController.view.snp.makeConstraints {
-            $0.top.equalTo(screenView)
-            $0.width.equalTo(screenView)
-            $0.bottom.equalTo(screenView)
-            $0.left.equalToSuperview()
-            $0.right.equalTo(displayController.view.snp.left)
-        }
-
-        displayController.view.snp.makeConstraints {
-            $0.top.equalTo(screenView.segmentedControl.snp.bottom)
-            $0.width.equalTo(scanController.view)
-            $0.bottom.equalTo(scanController.view)
-        }
-
-        scanController.didMove(toParent: self)
-        displayController.didMove(toParent: self)
-
-        screenView.bringSubviewToFront(screenView.segmentedControl)
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+
+  private lazy var screenView = ScanContainerView()
+
+  private let scanController = ScanController()
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
+  private let displayController = ScanDisplayController()
+  private let pageController = UIPageViewController(
+    transitionStyle: .scroll,
+    navigationOrientation: .horizontal
+  )
+
+  public override func loadView() {
+    view = screenView
+
+    addChild(pageController)
+    screenView.addSubview(pageController.view)
+    pageController.view.snp.makeConstraints {
+      $0.top.equalTo(screenView.stackView.snp.bottom)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalTo(screenView)
     }
-
-    public override func viewDidLayoutSubviews() {
-        super.viewDidLayoutSubviews()
-        screenView.scrollView.contentOffset = previousPoint
+    pageController.delegate = self
+    pageController.dataSource = self
+    pageController.didMove(toParent: self)
+    pageController.setViewControllers([scanController], direction: .forward, animated: true)
+    screenView.bringSubviewToFront(screenView.stackView)
+  }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    statusBar.set(.lightContent)
+    navigationController?.navigationBar.customize(translucent: true)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupNavigationBar()
+    setupBindings()
+
+    displayController.didTapInfo = { [weak self] in
+      guard let self else { return }
+      self.presentInfo(
+        title: Localized.Scan.Info.title,
+        subtitle: Localized.Scan.Info.subtitle
+      )
     }
-
-    public override func viewWillDisappear(_ animated: Bool) {
-        super.viewWillDisappear(animated)
-        previousPoint = screenView.scrollView.contentOffset
-        screenView.scrollView.contentOffset = .zero
+    displayController.didTapAddEmail = { [weak self] in
+      guard let self else { return }
+      self.navigator.perform(PresentProfileEmail(on: self.navigationController!))
     }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        statusBarController.style.send(.lightContent)
-        navigationController?.navigationBar.customize(translucent: true)
-        screenView.scrollView.contentOffset = .zero
+    displayController.didTapAddPhone = { [weak self] in
+      guard let self else { return }
+      self.navigator.perform(PresentProfilePhone(on: self.navigationController!))
     }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupNavigationBar()
-        setupBindings()
-
-        displayController.didTapInfo = { [weak self] in
-            self?.presentInfo(
-                title: Localized.Scan.Info.title,
-                subtitle: Localized.Scan.Info.subtitle
-            )
+  }
+
+  private func setupNavigationBar() {
+    navigationItem.backButtonTitle = ""
+
+    let titleLabel = UILabel()
+    titleLabel.text = "QR Code"
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 18.0)
+    titleLabel.textColor = Asset.neutralWhite.color
+
+    let menuButton = UIButton()
+    menuButton.tintColor = Asset.neutralWhite.color
+    menuButton.setImage(Asset.chatListMenu.image, for: .normal)
+    menuButton.addTarget(self, action: #selector(didTapMenu), for: .touchUpInside)
+    menuButton.snp.makeConstraints { $0.width.equalTo(50) }
+
+    navigationItem.leftBarButtonItem = UIBarButtonItem(
+      customView: UIStackView(arrangedSubviews: [menuButton, titleLabel])
+    )
+  }
+
+  private func setupBindings() {
+    screenView
+      .leftButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] _ in
+        screenView.leftButton.set(selected: true)
+        screenView.rightButton.set(selected: false)
+        pageController.setViewControllers([scanController], direction: .reverse, animated: true, completion: nil)
+      }.store(in: &cancellables)
+
+    screenView
+      .rightButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] _ in
+        screenView.leftButton.set(selected: false)
+        screenView.rightButton.set(selected: true)
+        pageController.setViewControllers([displayController], direction: .forward, animated: true, completion: nil)
+      }.store(in: &cancellables)
+  }
+
+  @objc private func didTapMenu() {
+    navigator.perform(PresentMenu(currentItem: .scan, from: self))
+  }
+
+  private func presentInfo(title: String, subtitle: String) {
+    let actionButton = CapsuleButton()
+    actionButton.set(
+      style: .seeThrough,
+      title: Localized.Settings.InfoDrawer.action
+    )
+
+    actionButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
         }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: title,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 19
+      ),
+      DrawerText(
+        font: Fonts.Mulish.regular.font(size: 16.0),
+        text: subtitle,
+        color: Asset.neutralBody.color,
+        alignment: .left,
+        lineHeightMultiple: 1.1,
+        spacingAfter: 37
+      ),
+      DrawerStack(views: [
+        actionButton,
+        FlexibleSpace()
+      ])
+    ], isDismissable: true, from: self))
+  }
+}
 
-        displayController.didTapAddEmail = { [weak self] in
-            guard let self = self else { return }
-            self.coordinator.toEmail(from: self)
-        }
-
-        displayController.didTapAddPhone = { [weak self] in
-            guard let self = self else { return }
-            self.coordinator.toPhone(from: self)
-        }
-    }
-
-    private func setupNavigationBar() {
-        navigationItem.backButtonTitle = ""
-
-        let titleLabel = UILabel()
-        titleLabel.text = "QR Code"
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 18.0)
-        titleLabel.textColor = Asset.neutralWhite.color
-
-        let menuButton = UIButton()
-        menuButton.tintColor = Asset.neutralWhite.color
-        menuButton.setImage(Asset.chatListMenu.image, for: .normal)
-        menuButton.addTarget(self, action: #selector(didTapMenu), for: .touchUpInside)
-        menuButton.snp.makeConstraints { $0.width.equalTo(50) }
-
-        navigationItem.leftBarButtonItem = UIBarButtonItem(
-            customView: UIStackView(arrangedSubviews: [menuButton, titleLabel])
-        )
-    }
-
-    private func setupBindings() {
-        screenView.segmentedControl.leftButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] _ in screenView.scrollView.setContentOffset(.zero, animated: true) }
-            .store(in: &cancellables)
-
-        screenView.segmentedControl.rightButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] _ in
-                let point = CGPoint(x: screenView.frame.width, y: 0.0)
-                screenView.scrollView.setContentOffset(point, animated: true)
-            }.store(in: &cancellables)
-    }
-
-    @objc private func didTapMenu() {
-        coordinator.toSideMenu(from: self)
-    }
-
-    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
-        let percentage = scrollView.contentOffset.x / view.frame.width
+extension ScanContainerController: UIPageViewControllerDataSource {
+  public func pageViewController(
+    _ pageViewController: UIPageViewController,
+    viewControllerAfter viewController: UIViewController
+  ) -> UIViewController? {
+    guard viewController != displayController else { return nil }
+    return displayController
+  }
+
+  public func pageViewController(
+    _ pageViewController: UIPageViewController,
+    viewControllerBefore viewController: UIViewController
+  ) -> UIViewController? {
+    guard viewController != scanController else { return nil }
+    return scanController
+  }
+}
 
-        scanController.view.alpha = 1 - percentage
-        displayController.view.alpha = percentage
-        screenView.segmentedControl.updateLeftConstraint(percentage)
-    }
 
-    private func presentInfo(title: String, subtitle: String) {
-        let actionButton = CapsuleButton()
-        actionButton.set(
-            style: .seeThrough,
-            title: Localized.Settings.InfoDrawer.action
-        )
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
-            ),
-            DrawerText(
-                font: Fonts.Mulish.regular.font(size: 16.0),
-                text: subtitle,
-                color: Asset.neutralBody.color,
-                alignment: .left,
-                lineHeightMultiple: 1.1,
-                spacingAfter: 37
-            ),
-            DrawerStack(views: [
-                actionButton,
-                FlexibleSpace()
-            ])
-        ])
-
-        actionButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
+extension ScanContainerController: UIPageViewControllerDelegate {
+  public func pageViewController(
+    _ pageViewController: UIPageViewController,
+    didFinishAnimating finished: Bool,
+    previousViewControllers: [UIViewController],
+    transitionCompleted completed: Bool
+  ) {
+    guard finished, completed else { return }
+
+    if previousViewControllers.contains(scanController) {
+      screenView.leftButton.set(selected: false)
+      screenView.rightButton.set(selected: true)
+    } else {
+      screenView.leftButton.set(selected: true)
+      screenView.rightButton.set(selected: false)
     }
+  }
 }
-
-extension ScanContainerController: UIScrollViewDelegate {}
diff --git a/Sources/ScanFeature/Controllers/ScanController.swift b/Sources/ScanFeature/Controllers/ScanController.swift
index 2a1b99aa6da269007f9692b5ebb0b3b81e6ca377..e21b9d035bf0d664a06d7f2873244747614f2bf9 100644
--- a/Sources/ScanFeature/Controllers/ScanController.swift
+++ b/Sources/ScanFeature/Controllers/ScanController.swift
@@ -1,117 +1,123 @@
 import UIKit
 import Shared
 import Combine
-import Permissions
+import AppCore
+import AppNavigation
 import CombineSchedulers
-import DependencyInjection
+import PermissionsFeature
+import ComposableArchitecture
 
 final class ScanController: UIViewController {
-    @Dependency private var coordinator: ScanCoordinating
-    @Dependency private var permissions: PermissionHandling
-
-    lazy private var screenView = ScanView()
-
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler()
-
-    private var status: ScanStatus?
-    private let camera: CameraType
-    private let viewModel = ScanViewModel()
-    private var cancellables = Set<AnyCancellable>()
-
-    init(camera: CameraType = Camera()) {
-         #if DEBUG
-         self.camera = MockCamera()
-         #else
-        self.camera = camera
-         #endif
-
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    override func loadView() {
-        view = screenView
-    }
-
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        screenView.layer.insertSublayer(camera.previewLayer, at: 0)
-        setupBindings()
-    }
-
-    override func viewDidLayoutSubviews() {
-        super.viewDidLayoutSubviews()
-        camera.previewLayer.frame = screenView.bounds
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.permissions) var permissions: PermissionsManager
+  @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue>
+
+  private lazy var screenView = ScanView()
+
+  private var status: ScanStatus?
+  private let camera: CameraType
+  private let viewModel = ScanViewModel()
+  private var cancellables = Set<AnyCancellable>()
+  
+  init(camera: CameraType = Camera()) {
+#if DEBUG
+    self.camera = MockCamera()
+#else
+    self.camera = camera
+#endif
+    
+    super.init(nibName: nil, bundle: nil)
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  override func loadView() {
+    view = screenView
+  }
+  
+  override func viewDidLoad() {
+    super.viewDidLoad()
+    screenView.layer.insertSublayer(camera.previewLayer, at: 0)
+    setupBindings()
+  }
+  
+  override func viewDidLayoutSubviews() {
+    super.viewDidLayoutSubviews()
+    camera.previewLayer.frame = screenView.bounds
+  }
+  
+  override func viewDidAppear(_ animated: Bool) {
+    super.viewDidAppear(animated)
+    viewModel.resetScanner()
+    startCamera()
+  }
+  
+  override func viewWillDisappear(_ animated: Bool) {
+    super.viewWillDisappear(animated)
+    bgQueue.schedule { [weak self] in
+      guard let self else { return }
+      self.camera.stop()
     }
-
-    override func viewDidAppear(_ animated: Bool) {
-        super.viewDidAppear(animated)
-        viewModel.resetScanner()
-        startCamera()
-    }
-
-    override func viewWillDisappear(_ animated: Bool) {
-        super.viewWillDisappear(animated)
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-            self.camera.stop()
+  }
+  
+  private func startCamera() {
+    permissions.camera.request { [weak self] granted in
+      guard let self else { return }
+      
+      if granted {
+        self.bgQueue.schedule {
+          self.camera.start()
         }
-    }
-
-    private func startCamera() {
-        permissions.requestCamera { [weak self] granted in
-            guard let self = self else { return }
-
-            if granted {
-                self.backgroundScheduler.schedule {
-                    self.camera.start()
-                }
-            } else {
-                DispatchQueue.main.async {
-                    self.status = .failed(.cameraPermission)
-                    self.screenView.update(with: .failed(.cameraPermission))
-                }
-            }
+      } else {
+        DispatchQueue.main.async {
+          self.status = .failed(.cameraPermission)
+          self.screenView.update(with: .failed(.cameraPermission))
         }
+      }
     }
-
-    private func setupBindings() {
-        viewModel.contactPublisher
-            .receive(on: DispatchQueue.main)
-            .delay(for: 1, scheduler: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toContact($0, from: self) }
-            .store(in: &cancellables)
-
-        viewModel.state
-            .map(\.status)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                status = $0
-                screenView.update(with: $0)
-            }.store(in: &cancellables)
-
-        screenView.actionButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                switch status {
-                case .failed(.cameraPermission):
-                    guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
-                    UIApplication.shared.open(url, options: [:])
-                case .failed(.requestOpened):
-                    coordinator.toRequests(from: self)
-                case .failed(.alreadyFriends):
-                    coordinator.toContacts(from: self)
-                default:
-                    break
-                }
-            }.store(in: &cancellables)
-
-        camera
-            .dataPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] data in viewModel.didScanData(data) }
-            .store(in: &cancellables)
-    }
+  }
+  
+  private func setupBindings() {
+    viewModel
+      .contactPublisher
+      .receive(on: DispatchQueue.main)
+      .delay(for: 1, scheduler: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentContact(contact: $0, on: navigationController!))
+      }.store(in: &cancellables)
+    
+    viewModel
+      .statePublisher
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        status = $0
+        screenView.update(with: $0)
+      }.store(in: &cancellables)
+    
+    screenView
+      .actionButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        switch status {
+        case .failed(.cameraPermission):
+          guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
+          UIApplication.shared.open(url, options: [:])
+        case .failed(.requestOpened):
+          navigator.perform(PresentRequests(on: navigationController!))
+        case .failed(.alreadyFriends):
+          navigator.perform(PresentContactList(on: navigationController!))
+        default:
+          break
+        }
+      }.store(in: &cancellables)
+    
+    camera
+      .dataPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        viewModel.didScanData($0)
+      }.store(in: &cancellables)
+  }
 }
diff --git a/Sources/ScanFeature/Controllers/ScanDisplayController.swift b/Sources/ScanFeature/Controllers/ScanDisplayController.swift
index e373d4de40ca31bae004de3d80c85702d538b604..c439855ca678d594657c86a08896b7df2bbafa2d 100644
--- a/Sources/ScanFeature/Controllers/ScanDisplayController.swift
+++ b/Sources/ScanFeature/Controllers/ScanDisplayController.swift
@@ -2,7 +2,7 @@ import UIKit
 import Combine
 
 final class ScanDisplayController: UIViewController {
-    lazy private var screenView = ScanDisplayView()
+    private lazy var screenView = ScanDisplayView()
 
     private let viewModel = ScanDisplayViewModel()
     private var cancellables = Set<AnyCancellable>()
diff --git a/Sources/ScanFeature/Coordinator/ScanCoordinator.swift b/Sources/ScanFeature/Coordinator/ScanCoordinator.swift
deleted file mode 100644
index 98e605775ccdf54b5ffda4b0a6ad0183a5c15fe1..0000000000000000000000000000000000000000
--- a/Sources/ScanFeature/Coordinator/ScanCoordinator.swift
+++ /dev/null
@@ -1,88 +0,0 @@
-import UIKit
-import Models
-import XXModels
-import MenuFeature
-import Presentation
-import ContactFeature
-
-public protocol ScanCoordinating {
-    func toEmail(from: UIViewController)
-    func toPhone(from: UIViewController)
-    func toContacts(from: UIViewController)
-    func toRequests(from: UIViewController)
-    func toSideMenu(from: UIViewController)
-    func toContact(_: Contact, from: UIViewController)
-    func toDrawer(_: UIViewController, from: UIViewController)
-}
-
-public struct ScanCoordinator: ScanCoordinating {
-    var pushPresenter: Presenting = PushPresenter()
-    var sidePresenter: Presenting = SideMenuPresenter()
-    var bottomPresenter: Presenting = BottomPresenter()
-    var replacePresenter: Presenting = ReplacePresenter(mode: .replaceLast)
-
-    var emailFactory: () -> UIViewController
-    var phoneFactory: () -> UIViewController
-    var contactsFactory: () -> UIViewController
-    var requestsFactory: () -> UIViewController
-    var contactFactory: (Contact) -> UIViewController
-    var sideMenuFactory: (MenuItem, UIViewController) -> UIViewController
-
-    public init(
-        emailFactory: @escaping () -> UIViewController,
-        phoneFactory: @escaping () -> UIViewController,
-        contactsFactory: @escaping () -> UIViewController,
-        requestsFactory: @escaping () -> UIViewController,
-        contactFactory: @escaping (Contact) -> UIViewController,
-        sideMenuFactory: @escaping (MenuItem, UIViewController) -> UIViewController
-    ) {
-        self.emailFactory = emailFactory
-        self.phoneFactory = phoneFactory
-        self.contactFactory = contactFactory
-        self.contactsFactory = contactsFactory
-        self.requestsFactory = requestsFactory
-        self.sideMenuFactory = sideMenuFactory
-    }
-}
-
-public extension ScanCoordinator {
-    func toContact(
-        _ contact: Contact,
-        from parent: UIViewController
-    ) {
-        let screen = contactFactory(contact)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toDrawer(
-        _ drawer: UIViewController,
-        from parent: UIViewController
-    ) {
-        bottomPresenter.present(drawer, from: parent)
-    }
-
-    func toRequests(from parent: UIViewController) {
-        let screen = requestsFactory()
-        replacePresenter.present(screen, from: parent)
-    }
-
-    func toContacts(from parent: UIViewController) {
-        let screen = contactsFactory()
-        replacePresenter.present(screen, from: parent)
-    }
-
-    func toSideMenu(from parent: UIViewController) {
-        let screen = sideMenuFactory(.scan, parent)
-        sidePresenter.present(screen, from: parent)
-    }
-
-    func toEmail(from parent: UIViewController) {
-        let screen = emailFactory()
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toPhone(from parent: UIViewController) {
-        let screen = phoneFactory()
-        pushPresenter.present(screen, from: parent)
-    }
-}
diff --git a/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift b/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift
index df4560653bbf5bd8f342148a3847ddef9f383d97..75410119075162b70a45a95e8622b36083bb637e 100644
--- a/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift
+++ b/Sources/ScanFeature/ViewModels/ScanDisplayViewModel.swift
@@ -1,9 +1,11 @@
 import UIKit
+import Shared
 import Combine
 import Defaults
-import Countries
-import Integration
-import DependencyInjection
+import XXClient
+import AppCore
+import Dependencies
+import XXMessengerClient
 
 struct ScanDisplayViewState: Equatable {
     var image: CIImage?
@@ -14,12 +16,13 @@ struct ScanDisplayViewState: Equatable {
 }
 
 final class ScanDisplayViewModel {
-    @Dependency private var session: SessionType
+    @Dependency(\.app.messenger) var messenger: Messenger
 
-    @KeyObject(.email, defaultValue: nil) private var email: String?
-    @KeyObject(.phone, defaultValue: nil) private var phone: String?
-    @KeyObject(.sharingEmail, defaultValue: false) private var sharingEmail: Bool
-    @KeyObject(.sharingPhone, defaultValue: false) private var sharingPhone: Bool
+    @KeyObject(.email, defaultValue: nil) var email: String?
+    @KeyObject(.phone, defaultValue: nil) var phone: String?
+    @KeyObject(.username, defaultValue: nil) var username: String?
+    @KeyObject(.sharingEmail, defaultValue: false) var sharingEmail: Bool
+    @KeyObject(.sharingPhone, defaultValue: false) var sharingPhone: Bool
 
     var statePublisher: AnyPublisher<ScanDisplayViewState, Never> {
         stateSubject.eraseToAnyPublisher()
@@ -58,7 +61,21 @@ final class ScanDisplayViewModel {
     func generateQR() {
         guard let filter = CIFilter(name: "CIQRCodeGenerator") else { return }
 
-        filter.setValue(session.myQR, forKey: "inputMessage")
+        var facts: [Fact] = [.init(type: .username, value: username!)]
+
+        if sharingPhone {
+            facts.append(.init(type: .phone, value: phone!))
+        }
+
+        if sharingEmail {
+            facts.append(.init(type: .email, value: email!))
+        }
+
+        let e2e = messenger.e2e.get()!
+        var contact = e2e.getContact()
+        try! contact.setFacts(facts)
+
+        filter.setValue(contact.data, forKey: "inputMessage")
         let transform = CGAffineTransform(scaleX: 5, y: 5)
 
         if let output = filter.outputImage?.transformed(by: transform) {
diff --git a/Sources/ScanFeature/ViewModels/ScanViewModel.swift b/Sources/ScanFeature/ViewModels/ScanViewModel.swift
index b940226d75390dc33a79118d44ca74ca4cbe3240..d69cbd692028a4485b276e75e7209d85301b087b 100644
--- a/Sources/ScanFeature/ViewModels/ScanViewModel.swift
+++ b/Sources/ScanFeature/ViewModels/ScanViewModel.swift
@@ -1,106 +1,98 @@
 import Shared
-import Models
+import AppCore
 import Combine
 import XXModels
+import XXClient
 import Foundation
-import Integration
-import CombineSchedulers
-import DependencyInjection
+import AppResources
+import Dependencies
+import ReportingFeature
 
 enum ScanStatus: Equatable {
-    case reading
-    case processing
-    case success
-    case failed(ScanError)
+  case reading
+  case processing
+  case success
+  case failed(ScanError)
 }
 
 enum ScanError: Equatable {
-    case requestOpened
-    case unknown(String)
-    case cameraPermission
-    case alreadyFriends(String)
-}
-
-struct ScanViewState: Equatable {
-    var status: ScanStatus = .reading
+  case requestOpened
+  case unknown(String)
+  case cameraPermission
+  case alreadyFriends(String)
 }
 
 final class ScanViewModel {
-    @Dependency private var session: SessionType
+  @Dependency(\.app.dbManager) var dbManager: DBManager
+  @Dependency(\.reportingStatus) var reportingStatus: ReportingStatus
 
-    var backgroundScheduler: AnySchedulerOf<DispatchQueue>
-        = DispatchQueue.global().eraseToAnyScheduler()
+  var contactPublisher: AnyPublisher<XXModels.Contact, Never> {
+    contactSubject.eraseToAnyPublisher()
+  }
 
-    var contactPublisher: AnyPublisher<Contact, Never> { contactRelay.eraseToAnyPublisher() }
-    private let contactRelay = PassthroughSubject<Contact, Never>()
+  var statePublisher: AnyPublisher<ScanStatus, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
 
-    var state: AnyPublisher<ScanViewState, Never> { stateRelay.eraseToAnyPublisher() }
-    private let stateRelay = CurrentValueSubject<ScanViewState, Never>(.init())
+  private let contactSubject = PassthroughSubject<XXModels.Contact, Never>()
+  private let stateSubject = CurrentValueSubject<ScanStatus, Never>(.reading)
 
-    func resetScanner() {
-        stateRelay.value.status = .reading
-    }
+  func resetScanner() {
+    stateSubject.send(.reading)
+  }
 
-    func didScanData(_ data: Data) {
-        guard stateRelay.value.status == .reading else { return }
-        stateRelay.value.status = .processing
-
-        backgroundScheduler.schedule { [weak self] in
-            guard let self = self else { return }
-
-            do {
-                guard let usernameAndId = try self.verifyScanned(data) else {
-                    self.stateRelay.value.status = .failed(.unknown(Localized.Scan.Error.general))
-                    return
-                }
-
-
-
-                if let previouslyAdded = try? self.session.dbManager.fetchContacts(.init(id: [usernameAndId.1])).first {
-                    var error = ScanError.unknown(Localized.Scan.Error.general)
-
-                    switch previouslyAdded.authStatus {
-                    case .friend:
-                        error = .alreadyFriends(usernameAndId.0)
-                    case .requested, .verified:
-                        error = .requestOpened
-                    default:
-                        break
-                    }
-
-                    self.stateRelay.value.status = .failed(error)
-                    return
-                }
-
-                let contact = Contact(
-                    id: usernameAndId.1,
-                    marshaled: data,
-                    username: usernameAndId.0,
-                    email: try? self.session.extract(fact: .email, from: data),
-                    phone: try? self.session.extract(fact: .phone, from: data),
-                    nickname: nil,
-                    photo: nil,
-                    authStatus: .stranger,
-                    isRecent: false,
-                    createdAt: Date()
-                )
-
-                self.succeed(with: contact)
-            } catch {
-                self.stateRelay.value.status = .failed(.unknown(Localized.Scan.Error.invalid))
-            }
-        }
-    }
+  func didScanData(_ data: Data) {
+    guard stateSubject.value == .reading else { return }
+    stateSubject.send(.processing)
 
-    private func verifyScanned(_ data: Data) throws -> (String, Data)? {
-        guard let username = try session.extract(fact: .username, from: data),
-                let id = session.getId(from: data) else { return nil }
+    let user = XXClient.Contact.live(data)
 
-        return (username, id)
+    guard let uid = try? user.getId(),
+          let facts = try? user.getFacts(),
+          let username = facts.first(where: { $0.type == .username })?.value else {
+      let errorTitle = Localized.Scan.Error.invalid
+      stateSubject.send(.failed(.unknown(errorTitle)))
+      return
     }
 
-    private func succeed(with contact: Contact) {
-        stateRelay.value.status = .success
-        contactRelay.send(contact)
+    let email = facts.first { $0.type == .email }?.value
+    let phone = facts.first { $0.type == .phone }?.value
+
+    if let alreadyContact = try? dbManager.getDB().fetchContacts(.init(id: [uid])).first {
+      if alreadyContact.isBlocked, reportingStatus.isEnabled() {
+        stateSubject.send(.failed(.unknown("You previously blocked this user.")))
+        return
+      }
+
+      if alreadyContact.isBanned, reportingStatus.isEnabled() {
+        stateSubject.send(.failed(.unknown("This user was banned.")))
+        return
+      }
+
+      if alreadyContact.authStatus == .friend {
+        stateSubject.send(.failed(.alreadyFriends(username)))
+      } else if [.requested, .verified].contains(alreadyContact.authStatus) {
+        stateSubject.send(.failed(.requestOpened))
+      } else {
+        let generalErrorTitle = Localized.Scan.Error.general
+        stateSubject.send(.failed(.unknown(generalErrorTitle)))
+      }
+
+      return
     }
+
+    stateSubject.send(.success)
+    contactSubject.send(.init(
+      id: uid,
+      marshaled: data,
+      username: username,
+      email: email,
+      phone: phone,
+      nickname: nil,
+      photo: nil,
+      authStatus: .stranger,
+      isRecent: false,
+      createdAt: Date()
+    ))
+  }
 }
diff --git a/Sources/ScanFeature/Views/AttributeSwitcher.swift b/Sources/ScanFeature/Views/AttributeSwitcher.swift
index 4bc957c6816dfc1c36b50ed5c47e46d6cf0180f5..464979a68754855cdd8b1c1f225acd2d66c4cb7e 100644
--- a/Sources/ScanFeature/Views/AttributeSwitcher.swift
+++ b/Sources/ScanFeature/Views/AttributeSwitcher.swift
@@ -1,102 +1,103 @@
 import UIKit
 import Shared
+import AppResources
 
 final class AttributeSwitcher: UIView {
-    struct State {
-        var content: String
-        var isVisible: Bool
+  struct State {
+    var content: String
+    var isVisible: Bool
+  }
+
+  private let titleLabel = UILabel()
+  private let contentLabel = UILabel()
+  private let stackView = UIStackView()
+  private(set) var switcherView = UISwitch()
+  private let verticalStackView = UIStackView()
+
+  private(set) var addButton: UIControl = {
+    let label = UILabel()
+    let icon = UIImageView()
+    let control = UIControl()
+
+    icon.image = Asset.scanAdd.image
+    label.text = Localized.Scan.Display.Share.add
+    label.textColor = Asset.brandPrimary.color
+
+    control.addSubview(icon)
+    control.addSubview(label)
+
+    icon.snp.makeConstraints {
+      $0.left.equalToSuperview()
+      $0.top.equalToSuperview()
+      $0.bottom.equalToSuperview()
+      $0.width.equalTo(icon.snp.height)
     }
 
-    private let titleLabel = UILabel()
-    private let contentLabel = UILabel()
-    private let stackView = UIStackView()
-    private(set) var switcherView = UISwitch()
-    private let verticalStackView = UIStackView()
-
-    private(set) var addButton: UIControl = {
-        let label = UILabel()
-        let icon = UIImageView()
-        let control = UIControl()
-
-        icon.image = Asset.scanAdd.image
-        label.text = Localized.Scan.Display.Share.add
-        label.textColor = Asset.brandPrimary.color
-
-        control.addSubview(icon)
-        control.addSubview(label)
-
-        icon.snp.makeConstraints {
-            $0.left.equalToSuperview()
-            $0.top.equalToSuperview()
-            $0.bottom.equalToSuperview()
-            $0.width.equalTo(icon.snp.height)
-        }
-
-        label.snp.makeConstraints {
-            $0.left.equalTo(icon.snp.right).offset(5)
-            $0.top.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
+    label.snp.makeConstraints {
+      $0.left.equalTo(icon.snp.right).offset(5)
+      $0.top.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
 
-        return control
-    }()
+    return control
+  }()
 
-    public init() {
-        super.init(frame: .zero)
+  public init() {
+    super.init(frame: .zero)
 
-        contentLabel.textColor = Asset.neutralActive.color
-        titleLabel.textColor = Asset.neutralWeak.color
-        switcherView.onTintColor = Asset.brandPrimary.color
+    contentLabel.textColor = Asset.neutralActive.color
+    titleLabel.textColor = Asset.neutralWeak.color
+    switcherView.onTintColor = Asset.brandPrimary.color
 
-        contentLabel.numberOfLines = 0
-        contentLabel.font = Fonts.Mulish.regular.font(size: 16.0)
-        titleLabel.font = Fonts.Mulish.bold.font(size: 12.0)
+    contentLabel.numberOfLines = 0
+    contentLabel.font = Fonts.Mulish.regular.font(size: 16.0)
+    titleLabel.font = Fonts.Mulish.bold.font(size: 12.0)
 
-        addSubview(stackView)
+    addSubview(stackView)
 
-        verticalStackView.spacing = 5
-        verticalStackView.axis = .vertical
-        verticalStackView.addArrangedSubview(titleLabel)
-        verticalStackView.addArrangedSubview(contentLabel)
+    verticalStackView.spacing = 5
+    verticalStackView.axis = .vertical
+    verticalStackView.addArrangedSubview(titleLabel)
+    verticalStackView.addArrangedSubview(contentLabel)
 
-        switcherView.setContentCompressionResistancePriority(.required, for: .vertical)
-        switcherView.setContentCompressionResistancePriority(.required, for: .horizontal)
+    switcherView.setContentCompressionResistancePriority(.required, for: .vertical)
+    switcherView.setContentCompressionResistancePriority(.required, for: .horizontal)
 
-        stackView.addArrangedSubview(verticalStackView)
-        stackView.addArrangedSubview(FlexibleSpace())
+    stackView.addArrangedSubview(verticalStackView)
+    stackView.addArrangedSubview(FlexibleSpace())
 
-        let otherHStack = UIStackView()
-        otherHStack.addArrangedSubview(addButton)
-        otherHStack.addArrangedSubview(switcherView)
+    let otherHStack = UIStackView()
+    otherHStack.addArrangedSubview(addButton)
+    otherHStack.addArrangedSubview(switcherView)
 
-        let otherVStack = UIStackView()
-        otherVStack.axis = .vertical
-        otherVStack.addArrangedSubview(otherHStack)
-        otherVStack.addArrangedSubview(FlexibleSpace())
+    let otherVStack = UIStackView()
+    otherVStack.axis = .vertical
+    otherVStack.addArrangedSubview(otherHStack)
+    otherVStack.addArrangedSubview(FlexibleSpace())
 
-        stackView.addArrangedSubview(otherVStack)
+    stackView.addArrangedSubview(otherVStack)
 
-        stackView.snp.makeConstraints {
-            $0.edges.equalToSuperview()
-        }
+    stackView.snp.makeConstraints {
+      $0.edges.equalToSuperview()
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 
-    func setup(state: State?, title: String) {
-        titleLabel.text = title
+  func setup(state: State?, title: String) {
+    titleLabel.text = title
 
-        guard let state = state else {
-            addButton.isHidden = false
-            switcherView.isHidden = true
-            contentLabel.text = Localized.Scan.Display.Share.notAdded
-            return
-        }
-
-        addButton.isHidden = true
-        switcherView.isHidden = false
-        switcherView.isOn = state.isVisible
-        contentLabel.text = state.isVisible ? state.content : Localized.Scan.Display.Share.hidden
+    guard let state = state else {
+      addButton.isHidden = false
+      switcherView.isHidden = true
+      contentLabel.text = Localized.Scan.Display.Share.notAdded
+      return
     }
+
+    addButton.isHidden = true
+    switcherView.isHidden = false
+    switcherView.isOn = state.isVisible
+    contentLabel.text = state.isVisible ? state.content : Localized.Scan.Display.Share.hidden
+  }
 }
diff --git a/Sources/ScanFeature/Views/ScanContainerView.swift b/Sources/ScanFeature/Views/ScanContainerView.swift
index 7e5eeee37a1a32765a33735ec8e1c65a05473dd5..78432064b4d1b02a22629b81e462e4239a14b322 100644
--- a/Sources/ScanFeature/Views/ScanContainerView.swift
+++ b/Sources/ScanFeature/Views/ScanContainerView.swift
@@ -1,28 +1,37 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ScanContainerView: UIView {
-    let scrollView = UIScrollView()
-    let segmentedControl = ScanSegmentedControl()
-
-    init() {
-        super.init(frame: .zero)
-
-        backgroundColor = Asset.neutralDark.color
-        addSubview(segmentedControl)
-        addSubview(scrollView)
-
-        scrollView.snp.makeConstraints {
-            $0.edges.equalToSuperview()
-        }
-
-        segmentedControl.snp.makeConstraints {
-            $0.top.equalTo(safeAreaLayoutGuide).offset(10)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.height.equalTo(60)
-        }
+  let stackView = UIStackView()
+  let leftButton = ScanSegmentedControlButton()
+  let rightButton = ScanSegmentedControlButton()
+
+  init() {
+    super.init(frame: .zero)
+
+    backgroundColor = Asset.neutralDark.color
+
+    leftButton.set(selected: true)
+    rightButton.set(selected: false)
+    leftButton.imageView.image = Asset.scanScan.image
+    rightButton.imageView.image = Asset.scanQr.image
+    leftButton.titleLabel.text = Localized.Scan.SegmentedControl.left
+    rightButton.titleLabel.text = Localized.Scan.SegmentedControl.right
+
+    stackView.distribution = .fillEqually
+    stackView.addArrangedSubview(leftButton)
+    stackView.addArrangedSubview(rightButton)
+
+    addSubview(stackView)
+
+    stackView.snp.makeConstraints {
+      $0.top.equalTo(safeAreaLayoutGuide).offset(10)
+      $0.left.equalToSuperview().offset(50)
+      $0.right.equalToSuperview().offset(-50)
+      $0.height.equalTo(60)
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/ScanFeature/Views/ScanDisplayShareView.swift b/Sources/ScanFeature/Views/ScanDisplayShareView.swift
index acad98f7cab2ffd36c0b210737c17cd26dc21904..87beade8fdb98b3d6e07040d71ea072f27588234 100644
--- a/Sources/ScanFeature/Views/ScanDisplayShareView.swift
+++ b/Sources/ScanFeature/Views/ScanDisplayShareView.swift
@@ -2,197 +2,198 @@ import UIKit
 import Shared
 import SnapKit
 import Combine
+import AppResources
 
 final class ScanDisplayShareView: UIView {
-    enum Action {
-        case info
-        case addEmail
-        case addPhone
-        case toggleEmail
-        case togglePhone
+  enum Action {
+    case info
+    case addEmail
+    case addPhone
+    case toggleEmail
+    case togglePhone
+  }
+  
+  private var isExpanded = false {
+    didSet { updateBottomConstraint() }
+  }
+  
+  private let upperView = UIView()
+  private let lowerView = UIView()
+  private var bottomConstraint: Constraint?
+  
+  private let imageView = UIImageView()
+  private let titleView = TextWithInfoView()
+  private let emailView = AttributeSwitcher()
+  private let phoneView = AttributeSwitcher()
+  private var cancellables = Set<AnyCancellable>()
+  
+  private var currentConstraintConstant: CGFloat = 0.0 {
+    didSet { bottomConstraint?.update(offset: currentConstraintConstant) }
+  }
+  
+  private var bottomConstraintExpanded: CGFloat {
+    -lowerView.frame.height
+  }
+  
+  private var bottomConstraintNotExpanded: CGFloat {
+    0
+  }
+  
+  var actionPublisher: AnyPublisher<Action, Never> {
+    actionSubject.eraseToAnyPublisher()
+  }
+  
+  private let actionSubject = PassthroughSubject<Action, Never>()
+  
+  init() {
+    super.init(frame: .zero)
+    
+    upperView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(didPan(_:))))
+    lowerView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(didPan(_:))))
+    
+    layer.cornerRadius = 30
+    imageView.image = Asset.scanDropdown.image
+    backgroundColor = Asset.neutralWhite.color
+    clipsToBounds = true
+    layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
+    
+    addSubview(upperView)
+    addSubview(lowerView)
+    
+    upperView.addSubview(imageView)
+    upperView.addSubview(titleView)
+    lowerView.addSubview(emailView)
+    lowerView.addSubview(phoneView)
+    
+    let paragraphStyle = NSMutableParagraphStyle()
+    paragraphStyle.lineBreakMode = .byWordWrapping
+    
+    titleView.setup(
+      text: Localized.Scan.Display.Share.title,
+      attributes: [
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+        .paragraphStyle: paragraphStyle
+      ],
+      didTapInfo: { [weak self] in self?.actionSubject.send(.info) }
+    )
+    
+    emailView.switcherView
+      .publisher(for: .valueChanged)
+      .sink { [unowned self] in actionSubject.send(.toggleEmail) }
+      .store(in: &cancellables)
+    
+    phoneView.switcherView
+      .publisher(for: .valueChanged)
+      .sink { [unowned self] in actionSubject.send(.togglePhone) }
+      .store(in: &cancellables)
+    
+    emailView.addButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in actionSubject.send(.addEmail) }
+      .store(in: &cancellables)
+    
+    phoneView.addButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in actionSubject.send(.addPhone) }
+      .store(in: &cancellables)
+    
+    emailView.setup(state: nil, title: Localized.Scan.Display.Share.email)
+    phoneView.setup(state: nil, title: Localized.Scan.Display.Share.phone)
+    emailView.alpha = 0.0
+    phoneView.alpha = 0.0
+    
+    imageView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(15)
+      $0.centerX.equalToSuperview()
     }
-
-    private var isExpanded = false {
-        didSet { updateBottomConstraint() }
-    }
-
-    private let upperView = UIView()
-    private let lowerView = UIView()
-    private var bottomConstraint: Constraint?
-
-    private let imageView = UIImageView()
-    private let titleView = TextWithInfoView()
-    private let emailView = AttributeSwitcher()
-    private let phoneView = AttributeSwitcher()
-    private var cancellables = Set<AnyCancellable>()
-
-    private var currentConstraintConstant: CGFloat = 0.0 {
-        didSet { bottomConstraint?.update(offset: currentConstraintConstant) }
+    
+    titleView.snp.makeConstraints {
+      $0.top.equalTo(imageView.snp.bottom).offset(10)
+      $0.left.equalToSuperview().offset(40)
+      $0.right.lessThanOrEqualToSuperview().offset(-40)
+      $0.centerY.equalToSuperview()
     }
-
-    private var bottomConstraintExpanded: CGFloat {
-        -lowerView.frame.height
-    }
-
-    private var bottomConstraintNotExpanded: CGFloat {
-        0
-    }
-
-    var actionPublisher: AnyPublisher<Action, Never> {
-        actionSubject.eraseToAnyPublisher()
+    
+    emailView.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview().offset(40)
+      $0.right.equalToSuperview().offset(-40)
     }
-
-    private let actionSubject = PassthroughSubject<Action, Never>()
-
-    init() {
-        super.init(frame: .zero)
-
-        upperView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(didPan(_:))))
-        lowerView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(didPan(_:))))
-
-        layer.cornerRadius = 30
-        imageView.image = Asset.scanDropdown.image
-        backgroundColor = Asset.neutralWhite.color
-        clipsToBounds = true
-        layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
-
-        addSubview(upperView)
-        addSubview(lowerView)
-
-        upperView.addSubview(imageView)
-        upperView.addSubview(titleView)
-        lowerView.addSubview(emailView)
-        lowerView.addSubview(phoneView)
-
-        let paragraphStyle = NSMutableParagraphStyle()
-        paragraphStyle.lineBreakMode = .byWordWrapping
-
-        titleView.setup(
-            text: Localized.Scan.Display.Share.title,
-            attributes: [
-                .foregroundColor: Asset.neutralBody.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
-                .paragraphStyle: paragraphStyle
-            ],
-            didTapInfo: { [weak self] in self?.actionSubject.send(.info) }
-        )
-
-        emailView.switcherView
-            .publisher(for: .valueChanged)
-            .sink { [unowned self] in actionSubject.send(.toggleEmail) }
-            .store(in: &cancellables)
-
-        phoneView.switcherView
-            .publisher(for: .valueChanged)
-            .sink { [unowned self] in actionSubject.send(.togglePhone) }
-            .store(in: &cancellables)
-
-        emailView.addButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in actionSubject.send(.addEmail) }
-            .store(in: &cancellables)
-
-        phoneView.addButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in actionSubject.send(.addPhone) }
-            .store(in: &cancellables)
-
-        emailView.setup(state: nil, title: Localized.Scan.Display.Share.email)
-        phoneView.setup(state: nil, title: Localized.Scan.Display.Share.phone)
-        emailView.alpha = 0.0
-        phoneView.alpha = 0.0
-
-        imageView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(15)
-            $0.centerX.equalToSuperview()
-        }
-
-        titleView.snp.makeConstraints {
-            $0.top.equalTo(imageView.snp.bottom).offset(10)
-            $0.left.equalToSuperview().offset(40)
-            $0.right.lessThanOrEqualToSuperview().offset(-40)
-            $0.centerY.equalToSuperview()
-        }
-
-        emailView.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.left.equalToSuperview().offset(40)
-            $0.right.equalToSuperview().offset(-40)
-        }
-
-        phoneView.snp.makeConstraints {
-            $0.top.equalTo(emailView.snp.bottom).offset(25)
-            $0.left.equalToSuperview().offset(40)
-            $0.right.equalToSuperview().offset(-40)
-            $0.bottom.equalToSuperview().offset(-40)
-        }
-
-        upperView.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            bottomConstraint = $0.bottom
-                .equalTo(safeAreaLayoutGuide)
-                .constraint
-        }
-
-        lowerView.snp.makeConstraints {
-            $0.top.equalTo(upperView.snp.bottom).offset(-30)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-        }
+    
+    phoneView.snp.makeConstraints {
+      $0.top.equalTo(emailView.snp.bottom).offset(25)
+      $0.left.equalToSuperview().offset(40)
+      $0.right.equalToSuperview().offset(-40)
+      $0.bottom.equalToSuperview().offset(-40)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    func setup(email state: AttributeSwitcher.State?) {
-        emailView.setup(state: state, title: Localized.Scan.Display.Share.email)
+    
+    upperView.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      bottomConstraint = $0.bottom
+        .equalTo(safeAreaLayoutGuide)
+        .constraint
     }
-
-    func setup(phone state: AttributeSwitcher.State?) {
-        phoneView.setup(state: state, title: Localized.Scan.Display.Share.phone)
+    
+    lowerView.snp.makeConstraints {
+      $0.top.equalTo(upperView.snp.bottom).offset(-30)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
     }
-
-    @objc private func didPan(_ sender: UIPanGestureRecognizer) {
-        switch sender.state {
-        case .began, .changed:
-            let isUpwards = sender.translation(in: self).y < 0
-            let result = currentConstraintConstant + sender.translation(in: self).y
-
-            if isUpwards {
-                currentConstraintConstant = max(bottomConstraintExpanded, result)
-            } else {
-                currentConstraintConstant = min(bottomConstraintNotExpanded, result)
-            }
-
-            let currentMinusExpanded = currentConstraintConstant - bottomConstraintExpanded
-            let notExpandedMinusExpanded = bottomConstraintNotExpanded - bottomConstraintExpanded
-            let alpha = 1 - (currentMinusExpanded / abs(notExpandedMinusExpanded))
-            emailView.alpha = alpha
-            phoneView.alpha = alpha
-
-        case .cancelled, .ended, .failed:
-            let currentMinusExpanded = currentConstraintConstant - bottomConstraintExpanded
-            let notExpandedMinusExpanded = bottomConstraintNotExpanded - bottomConstraintExpanded
-            let percentage = currentMinusExpanded / abs(notExpandedMinusExpanded)
-            isExpanded = percentage < 0.5
-
-        case .possible:
-            break
-        @unknown default:
-            break
-        }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  func setup(email state: AttributeSwitcher.State?) {
+    emailView.setup(state: state, title: Localized.Scan.Display.Share.email)
+  }
+  
+  func setup(phone state: AttributeSwitcher.State?) {
+    phoneView.setup(state: state, title: Localized.Scan.Display.Share.phone)
+  }
+  
+  @objc private func didPan(_ sender: UIPanGestureRecognizer) {
+    switch sender.state {
+    case .began, .changed:
+      let isUpwards = sender.translation(in: self).y < 0
+      let result = currentConstraintConstant + sender.translation(in: self).y
+      
+      if isUpwards {
+        currentConstraintConstant = max(bottomConstraintExpanded, result)
+      } else {
+        currentConstraintConstant = min(bottomConstraintNotExpanded, result)
+      }
+      
+      let currentMinusExpanded = currentConstraintConstant - bottomConstraintExpanded
+      let notExpandedMinusExpanded = bottomConstraintNotExpanded - bottomConstraintExpanded
+      let alpha = 1 - (currentMinusExpanded / abs(notExpandedMinusExpanded))
+      emailView.alpha = alpha
+      phoneView.alpha = alpha
+      
+    case .cancelled, .ended, .failed:
+      let currentMinusExpanded = currentConstraintConstant - bottomConstraintExpanded
+      let notExpandedMinusExpanded = bottomConstraintNotExpanded - bottomConstraintExpanded
+      let percentage = currentMinusExpanded / abs(notExpandedMinusExpanded)
+      isExpanded = percentage < 0.5
+      
+    case .possible:
+      break
+    @unknown default:
+      break
     }
-
-    private func updateBottomConstraint() {
-        if isExpanded {
-            emailView.alpha = 1.0
-            phoneView.alpha = 1.0
-            currentConstraintConstant = bottomConstraintExpanded
-        } else {
-            emailView.alpha = 0.0
-            phoneView.alpha = 0.0
-            currentConstraintConstant = bottomConstraintNotExpanded
-        }
+  }
+  
+  private func updateBottomConstraint() {
+    if isExpanded {
+      emailView.alpha = 1.0
+      phoneView.alpha = 1.0
+      currentConstraintConstant = bottomConstraintExpanded
+    } else {
+      emailView.alpha = 0.0
+      phoneView.alpha = 0.0
+      currentConstraintConstant = bottomConstraintNotExpanded
     }
+  }
 }
diff --git a/Sources/ScanFeature/Views/ScanDisplayView.swift b/Sources/ScanFeature/Views/ScanDisplayView.swift
index fa2b02438c0d735708b9a30ffc4a6c2228926890..5c3bf28c2f7ce73826007a228cd8f9c00e7027ad 100644
--- a/Sources/ScanFeature/Views/ScanDisplayView.swift
+++ b/Sources/ScanFeature/Views/ScanDisplayView.swift
@@ -1,101 +1,102 @@
 import UIKit
 import Shared
 import Combine
+import AppResources
 
 final class ScanDisplayView: UIView {
-    var actionPublisher: AnyPublisher<ScanDisplayShareView.Action, Never> {
-        shareSheetView.actionPublisher.eraseToAnyPublisher()
+  var actionPublisher: AnyPublisher<ScanDisplayShareView.Action, Never> {
+    shareSheetView.actionPublisher.eraseToAnyPublisher()
+  }
+
+  private let copyLabel = UILabel()
+  private let codeButton = ScanQRButton()
+  private let copyImageView = UIImageView()
+  private let copyContainerButton = UIControl()
+  private var cancellables = Set<AnyCancellable>()
+  private let shareSheetView = ScanDisplayShareView()
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralDark.color
+
+    copyImageView.image = Asset.scanCopy.image
+    copyLabel.text = Localized.Scan.Display.copy
+    copyLabel.textColor = Asset.neutralDisabled.color
+    copyLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
+
+    codeButton.publisher(for: .touchUpInside)
+      .merge(with: copyContainerButton.publisher(for: .touchUpInside))
+      .sink { [unowned self] in
+        UIGraphicsBeginImageContext(codeButton.frame.size)
+        codeButton.layer.render(in: UIGraphicsGetCurrentContext()!)
+        let output = UIGraphicsGetImageFromCurrentImageContext()
+        UIGraphicsEndImageContext()
+
+        UIImageWriteToSavedPhotosAlbum(output!, nil, nil, nil)
+        codeButton.blinkCopied()
+      }.store(in: &cancellables)
+
+    addSubview(codeButton)
+    addSubview(copyContainerButton)
+    copyContainerButton.addSubview(copyLabel)
+    copyContainerButton.addSubview(copyImageView)
+
+    addSubview(shareSheetView)
+
+    codeButton.snp.makeConstraints {
+      $0.centerX.equalTo(safeAreaLayoutGuide)
+      $0.centerY.equalTo(safeAreaLayoutGuide).multipliedBy(0.6)
+      $0.width.equalTo(safeAreaLayoutGuide).multipliedBy(0.6)
+      $0.height.equalTo(codeButton.snp.width)
     }
 
-    private let copyLabel = UILabel()
-    private let codeButton = ScanQRButton()
-    private let copyImageView = UIImageView()
-    private let copyContainerButton = UIControl()
-    private var cancellables = Set<AnyCancellable>()
-    private let shareSheetView = ScanDisplayShareView()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralDark.color
-
-        copyImageView.image = Asset.scanCopy.image
-        copyLabel.text = Localized.Scan.Display.copy
-        copyLabel.textColor = Asset.neutralDisabled.color
-        copyLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
-
-        codeButton.publisher(for: .touchUpInside)
-            .merge(with: copyContainerButton.publisher(for: .touchUpInside))
-            .sink { [unowned self] in
-                UIGraphicsBeginImageContext(codeButton.frame.size)
-                codeButton.layer.render(in: UIGraphicsGetCurrentContext()!)
-                let output = UIGraphicsGetImageFromCurrentImageContext()
-                UIGraphicsEndImageContext()
-
-                UIImageWriteToSavedPhotosAlbum(output!, nil, nil, nil)
-                codeButton.blinkCopied()
-            }.store(in: &cancellables)
-
-        addSubview(codeButton)
-        addSubview(copyContainerButton)
-        copyContainerButton.addSubview(copyLabel)
-        copyContainerButton.addSubview(copyImageView)
-
-        addSubview(shareSheetView)
-
-        codeButton.snp.makeConstraints {
-            $0.centerX.equalTo(safeAreaLayoutGuide)
-            $0.centerY.equalTo(safeAreaLayoutGuide).multipliedBy(0.6)
-            $0.width.equalTo(safeAreaLayoutGuide).multipliedBy(0.6)
-            $0.height.equalTo(codeButton.snp.width)
-        }
-
-        copyContainerButton.snp.makeConstraints {
-            $0.top.equalTo(codeButton.snp.bottom).offset(33)
-            $0.centerX.equalTo(codeButton)
-        }
-
-        copyImageView.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.left.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
-
-        copyLabel.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.left.equalTo(copyImageView.snp.right).offset(5)
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
-
-        shareSheetView.snp.makeConstraints {
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
+    copyContainerButton.snp.makeConstraints {
+      $0.top.equalTo(codeButton.snp.bottom).offset(33)
+      $0.centerX.equalTo(codeButton)
     }
 
-    required init?(coder: NSCoder) { nil }
+    copyImageView.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
+
+    copyLabel.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalTo(copyImageView.snp.right).offset(5)
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
 
-    func setup(code image: CIImage) {
-        codeButton.setup(code: image)
+    shareSheetView.snp.makeConstraints {
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  func setup(code image: CIImage) {
+    codeButton.setup(code: image)
+  }
+
+  func setupAttributes(
+    email: String?,
+    phone: String?,
+    emailSharing: Bool,
+    phoneSharing: Bool
+  ) {
+    if let email = email {
+      shareSheetView.setup(email: .init(content: email, isVisible: emailSharing))
+    } else {
+      shareSheetView.setup(email: nil)
     }
 
-    func setupAttributes(
-        email: String?,
-        phone: String?,
-        emailSharing: Bool,
-        phoneSharing: Bool
-    ) {
-        if let email = email {
-            shareSheetView.setup(email: .init(content: email, isVisible: emailSharing))
-        } else {
-            shareSheetView.setup(email: nil)
-        }
-
-        if let phone = phone {
-            shareSheetView.setup(phone: .init(content: phone, isVisible: phoneSharing))
-        } else {
-            shareSheetView.setup(phone: nil)
-        }
+    if let phone = phone {
+      shareSheetView.setup(phone: .init(content: phone, isVisible: phoneSharing))
+    } else {
+      shareSheetView.setup(phone: nil)
     }
+  }
 }
diff --git a/Sources/ScanFeature/Views/ScanOverlayView.swift b/Sources/ScanFeature/Views/ScanOverlayView.swift
index 14bc4c5dc736a388a41d9c04f21a51fa49467e99..060bfbdac4476f7c2a020ee654ee1b9ee7670718 100644
--- a/Sources/ScanFeature/Views/ScanOverlayView.swift
+++ b/Sources/ScanFeature/Views/ScanOverlayView.swift
@@ -1,181 +1,182 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ScanOverlayView: UIView {
-    private let cropView = UIView()
-    private let scanViewLength = 266.0
-    private let maskLayer = CAShapeLayer()
-    private let topLeftLayer = CAShapeLayer()
-    private let topRightLayer = CAShapeLayer()
-    private let bottomLeftLayer = CAShapeLayer()
-    private let bottomRightLayer = CAShapeLayer()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralDark.color.withAlphaComponent(0.5)
-
-        addSubview(cropView)
-
-        cropView.snp.makeConstraints {
-            $0.width.equalTo(scanViewLength)
-            $0.centerY.equalToSuperview().offset(-50)
-            $0.centerX.equalToSuperview()
-            $0.height.equalTo(scanViewLength)
-        }
-
-        maskLayer.fillRule = .evenOdd
-        layer.mask = maskLayer
-        layer.masksToBounds = true
-
-        [topLeftLayer, topRightLayer, bottomLeftLayer, bottomRightLayer].forEach {
-            $0.strokeColor = Asset.brandPrimary.color.cgColor
-            $0.fillColor = UIColor.clear.cgColor
-            $0.lineWidth = 3.0
-            $0.lineCap = .round
-            layer.addSublayer($0)
-        }
+  private let cropView = UIView()
+  private let scanViewLength = 266.0
+  private let maskLayer = CAShapeLayer()
+  private let topLeftLayer = CAShapeLayer()
+  private let topRightLayer = CAShapeLayer()
+  private let bottomLeftLayer = CAShapeLayer()
+  private let bottomRightLayer = CAShapeLayer()
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralDark.color.withAlphaComponent(0.5)
+
+    addSubview(cropView)
+
+    cropView.snp.makeConstraints {
+      $0.width.equalTo(scanViewLength)
+      $0.centerY.equalToSuperview().offset(-50)
+      $0.centerX.equalToSuperview()
+      $0.height.equalTo(scanViewLength)
     }
 
-    required init?(coder: NSCoder) { nil }
+    maskLayer.fillRule = .evenOdd
+    layer.mask = maskLayer
+    layer.masksToBounds = true
 
-    override func layoutSubviews() {
-        super.layoutSubviews()
-
-        maskLayer.frame = bounds
-        let path = UIBezierPath(rect: bounds)
-        path.append(UIBezierPath(roundedRect: cropView.frame, cornerRadius: 30.0))
-        maskLayer.path = path.cgPath
-
-        topLeftLayer.frame = bounds
-        topRightLayer.frame = bounds
-        bottomRightLayer.frame = bounds
-        bottomLeftLayer.frame = bounds
-
-        topLeftLayer.path = topLeftPath()
-        topRightLayer.path = topRightPath()
-        bottomRightLayer.path = bottomRightPath()
-        bottomLeftLayer.path = bottomLeftPath()
-    }
-
-    func updateCornerColor(_ color: UIColor) {
-        [topLeftLayer, topRightLayer, bottomLeftLayer, bottomRightLayer].forEach {
-            $0.strokeColor = color.cgColor
-        }
-    }
-
-    func topLeftPath() -> CGPath {
-        let path = UIBezierPath()
-
-        let vert0X = cropView.frame.minX - 15
-        let vert0Y = cropView.frame.minY + 45
-        let vert0 = CGPoint(x: vert0X, y: vert0Y)
-        path.move(to: vert0)
-
-        let vertNX = cropView.frame.minX - 15
-        let vertNY = cropView.frame.minY + 15
-        let vertN = CGPoint(x: vertNX, y: vertNY)
-        path.addLine(to: vertN)
-
-        let arcCenterX = cropView.frame.minX + 15
-        let arcCenterY = cropView.frame.minY + 15
-        let arcCenter = CGPoint(x: arcCenterX , y: arcCenterY)
-        path.addArc(center: arcCenter, startAngle: .pi)
-
-        let horizX = cropView.frame.minX + 45
-        let horizY = cropView.frame.minY - 15
-        let horiz = CGPoint(x: horizX, y: horizY)
-        path.addLine(to: horiz)
-
-        return path.cgPath
+    [topLeftLayer, topRightLayer, bottomLeftLayer, bottomRightLayer].forEach {
+      $0.strokeColor = Asset.brandPrimary.color.cgColor
+      $0.fillColor = UIColor.clear.cgColor
+      $0.lineWidth = 3.0
+      $0.lineCap = .round
+      layer.addSublayer($0)
     }
+  }
 
-    func topRightPath() -> CGPath {
-        let path = UIBezierPath()
+  required init?(coder: NSCoder) { nil }
 
-        let horiz0X = cropView.frame.maxX - 45
-        let horiz0Y = cropView.frame.minY - 15
-        let horiz0 = CGPoint(x: horiz0X, y: horiz0Y)
-        path.move(to: horiz0)
+  override func layoutSubviews() {
+    super.layoutSubviews()
 
-        let horizNX = cropView.frame.maxX - 15
-        let horizNY = cropView.frame.minY - 15
-        let horizN = CGPoint(x: horizNX, y: horizNY)
-        path.addLine(to: horizN)
+    maskLayer.frame = bounds
+    let path = UIBezierPath(rect: bounds)
+    path.append(UIBezierPath(roundedRect: cropView.frame, cornerRadius: 30.0))
+    maskLayer.path = path.cgPath
 
-        let arcCenterX = cropView.frame.maxX - 15
-        let arcCenterY = cropView.frame.minY + 15
-        let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
-        path.addArc(center: arcCenter, startAngle: 3 * .pi/2)
+    topLeftLayer.frame = bounds
+    topRightLayer.frame = bounds
+    bottomRightLayer.frame = bounds
+    bottomLeftLayer.frame = bounds
 
-        let vertX = cropView.frame.maxX + 15
-        let vertY = cropView.frame.minY + 45
-        let vert = CGPoint(x: vertX, y: vertY)
-        path.addLine(to: vert)
+    topLeftLayer.path = topLeftPath()
+    topRightLayer.path = topRightPath()
+    bottomRightLayer.path = bottomRightPath()
+    bottomLeftLayer.path = bottomLeftPath()
+  }
 
-        return path.cgPath
-    }
-
-    func bottomRightPath() -> CGPath {
-        let path = UIBezierPath()
-
-        let vert0X = cropView.frame.maxX + 15
-        let vert0Y = cropView.frame.maxY - 45
-        let vert0 = CGPoint(x: vert0X, y: vert0Y)
-        path.move(to: vert0)
-
-        let vertNX = cropView.frame.maxX + 15
-        let vertNY = cropView.frame.maxY - 15
-        let vertN = CGPoint(x: vertNX, y: vertNY)
-        path.addLine(to: vertN)
-
-        let arcCenterX = cropView.frame.maxX - 15
-        let arcCenterY = cropView.frame.maxY - 15
-        let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
-        path.addArc(center: arcCenter, startAngle: 0)
-
-        let horizX = cropView.frame.maxX - 45
-        let horizY = cropView.frame.maxY + 15
-        let horiz = CGPoint(x: horizX, y: horizY)
-        path.addLine(to: horiz)
-
-        return path.cgPath
-    }
-
-    func bottomLeftPath() -> CGPath {
-        let path = UIBezierPath()
-
-        let horiz0X = cropView.frame.minX + 45
-        let horiz0Y = cropView.frame.maxY + 15
-        let horiz0 = CGPoint(x: horiz0X, y: horiz0Y)
-        path.move(to: horiz0)
-
-        let horizNX = cropView.frame.minX + 15
-        let horizNY = cropView.frame.maxY + 15
-        let horizN = CGPoint(x: horizNX, y: horizNY)
-        path.addLine(to: horizN)
-
-        let arcCenterX = cropView.frame.minX + 15
-        let arcCenterY = cropView.frame.maxY - 15
-        let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
-        path.addArc(center: arcCenter, startAngle: .pi/2)
-
-        let vertX = cropView.frame.minX - 15
-        let vertY = cropView.frame.maxY - 45
-        let vert = CGPoint(x: vertX, y: vertY)
-        path.addLine(to: vert)
-
-        return path.cgPath
+  func updateCornerColor(_ color: UIColor) {
+    [topLeftLayer, topRightLayer, bottomLeftLayer, bottomRightLayer].forEach {
+      $0.strokeColor = color.cgColor
     }
+  }
+
+  func topLeftPath() -> CGPath {
+    let path = UIBezierPath()
+
+    let vert0X = cropView.frame.minX - 15
+    let vert0Y = cropView.frame.minY + 45
+    let vert0 = CGPoint(x: vert0X, y: vert0Y)
+    path.move(to: vert0)
+
+    let vertNX = cropView.frame.minX - 15
+    let vertNY = cropView.frame.minY + 15
+    let vertN = CGPoint(x: vertNX, y: vertNY)
+    path.addLine(to: vertN)
+
+    let arcCenterX = cropView.frame.minX + 15
+    let arcCenterY = cropView.frame.minY + 15
+    let arcCenter = CGPoint(x: arcCenterX , y: arcCenterY)
+    path.addArc(center: arcCenter, startAngle: .pi)
+
+    let horizX = cropView.frame.minX + 45
+    let horizY = cropView.frame.minY - 15
+    let horiz = CGPoint(x: horizX, y: horizY)
+    path.addLine(to: horiz)
+
+    return path.cgPath
+  }
+
+  func topRightPath() -> CGPath {
+    let path = UIBezierPath()
+
+    let horiz0X = cropView.frame.maxX - 45
+    let horiz0Y = cropView.frame.minY - 15
+    let horiz0 = CGPoint(x: horiz0X, y: horiz0Y)
+    path.move(to: horiz0)
+
+    let horizNX = cropView.frame.maxX - 15
+    let horizNY = cropView.frame.minY - 15
+    let horizN = CGPoint(x: horizNX, y: horizNY)
+    path.addLine(to: horizN)
+
+    let arcCenterX = cropView.frame.maxX - 15
+    let arcCenterY = cropView.frame.minY + 15
+    let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
+    path.addArc(center: arcCenter, startAngle: 3 * .pi/2)
+
+    let vertX = cropView.frame.maxX + 15
+    let vertY = cropView.frame.minY + 45
+    let vert = CGPoint(x: vertX, y: vertY)
+    path.addLine(to: vert)
+
+    return path.cgPath
+  }
+
+  func bottomRightPath() -> CGPath {
+    let path = UIBezierPath()
+
+    let vert0X = cropView.frame.maxX + 15
+    let vert0Y = cropView.frame.maxY - 45
+    let vert0 = CGPoint(x: vert0X, y: vert0Y)
+    path.move(to: vert0)
+
+    let vertNX = cropView.frame.maxX + 15
+    let vertNY = cropView.frame.maxY - 15
+    let vertN = CGPoint(x: vertNX, y: vertNY)
+    path.addLine(to: vertN)
+
+    let arcCenterX = cropView.frame.maxX - 15
+    let arcCenterY = cropView.frame.maxY - 15
+    let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
+    path.addArc(center: arcCenter, startAngle: 0)
+
+    let horizX = cropView.frame.maxX - 45
+    let horizY = cropView.frame.maxY + 15
+    let horiz = CGPoint(x: horizX, y: horizY)
+    path.addLine(to: horiz)
+
+    return path.cgPath
+  }
+
+  func bottomLeftPath() -> CGPath {
+    let path = UIBezierPath()
+
+    let horiz0X = cropView.frame.minX + 45
+    let horiz0Y = cropView.frame.maxY + 15
+    let horiz0 = CGPoint(x: horiz0X, y: horiz0Y)
+    path.move(to: horiz0)
+
+    let horizNX = cropView.frame.minX + 15
+    let horizNY = cropView.frame.maxY + 15
+    let horizN = CGPoint(x: horizNX, y: horizNY)
+    path.addLine(to: horizN)
+
+    let arcCenterX = cropView.frame.minX + 15
+    let arcCenterY = cropView.frame.maxY - 15
+    let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
+    path.addArc(center: arcCenter, startAngle: .pi/2)
+
+    let vertX = cropView.frame.minX - 15
+    let vertY = cropView.frame.maxY - 45
+    let vert = CGPoint(x: vertX, y: vertY)
+    path.addLine(to: vert)
+
+    return path.cgPath
+  }
 }
 
 private extension UIBezierPath {
-    func addArc(center: CGPoint, startAngle: CGFloat) {
-        addArc(
-            withCenter: center,
-            radius: 30,
-            startAngle: startAngle,
-            endAngle: startAngle + .pi/2,
-            clockwise: true
-        )
-    }
+  func addArc(center: CGPoint, startAngle: CGFloat) {
+    addArc(
+      withCenter: center,
+      radius: 30,
+      startAngle: startAngle,
+      endAngle: startAngle + .pi/2,
+      clockwise: true
+    )
+  }
 }
diff --git a/Sources/ScanFeature/Views/ScanQRButton.swift b/Sources/ScanFeature/Views/ScanQRButton.swift
index 66d911c602a67c4ad01a02002fc4f9de2b2563ac..35c045df11c1b3eb54da0360d653695d85a79e2b 100644
--- a/Sources/ScanFeature/Views/ScanQRButton.swift
+++ b/Sources/ScanFeature/Views/ScanQRButton.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ScanQRButton: UIControl {
     private let overlayView = UIView()
diff --git a/Sources/ScanFeature/Views/ScanSegmentedControl.swift b/Sources/ScanFeature/Views/ScanSegmentedControl.swift
deleted file mode 100644
index f3fd72acf3326b05b8057e5a9fe0948aa113688c..0000000000000000000000000000000000000000
--- a/Sources/ScanFeature/Views/ScanSegmentedControl.swift
+++ /dev/null
@@ -1,80 +0,0 @@
-import UIKit
-import Shared
-import SnapKit
-
-final class ScanSegmentedControl: UIView {
-    private let trackHeight = 2.0
-    private let numberOfTabs = 2.0
-    private let trackView = UIView()
-    private let stackView = UIStackView()
-    private var leftConstraint: Constraint?
-    private let trackIndicatorView = UIView()
-    private(set) var leftButton = ScanSegmentedControlButton()
-    private(set) var rightButton = ScanSegmentedControlButton()
-
-    init() {
-        super.init(frame: .zero)
-
-        rightButton.setup(
-            title: Localized.Scan.SegmentedControl.right,
-            icon: Asset.scanQr.image
-        )
-
-        leftButton.setup(
-            title: Localized.Scan.SegmentedControl.left,
-            icon: Asset.scanScan.image
-        )
-
-        trackView.backgroundColor = Asset.neutralLine.color
-        trackIndicatorView.backgroundColor = Asset.brandPrimary.color
-
-        stackView.distribution = .fillEqually
-        stackView.addArrangedSubview(leftButton)
-        stackView.addArrangedSubview(rightButton)
-
-        addSubview(stackView)
-        addSubview(trackView)
-        trackView.addSubview(trackIndicatorView)
-
-        stackView.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalTo(trackView.snp.top)
-        }
-
-        trackView.snp.makeConstraints {
-            $0.height.equalTo(trackHeight)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
-
-        trackIndicatorView.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            leftConstraint = $0.left.equalToSuperview().constraint
-            $0.width.equalToSuperview().dividedBy(numberOfTabs)
-            $0.bottom.equalToSuperview()
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    func updateLeftConstraint(_ percentageScrolled: CGFloat) {
-        let tabWidth = bounds.width / numberOfTabs
-        let leftOffset = percentageScrolled * tabWidth
-        leftConstraint?.update(offset: leftOffset)
-
-        leftButton.update(color: .fade(
-            from: Asset.brandPrimary.color,
-            to: Asset.neutralLine.color,
-            pcent: percentageScrolled
-        ))
-
-        rightButton.update(color: .fade(
-            from: Asset.brandPrimary.color,
-            to: Asset.neutralLine.color,
-            pcent: 1 - percentageScrolled
-        ))
-    }
-}
diff --git a/Sources/ScanFeature/Views/ScanSegmentedControlButton.swift b/Sources/ScanFeature/Views/ScanSegmentedControlButton.swift
index b9e711c614b239dfb55158c530adbe121f0ca691..010997ef1d52245501f61ec9a5fb1a8cb8381980 100644
--- a/Sources/ScanFeature/Views/ScanSegmentedControlButton.swift
+++ b/Sources/ScanFeature/Views/ScanSegmentedControlButton.swift
@@ -1,41 +1,74 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ScanSegmentedControlButton: UIControl {
-    private let titleLabel = UILabel()
-    private let imageView = UIImageView()
+  let titleLabel = UILabel()
+  let separatorView = UIView()
+  let imageView = UIImageView()
 
-    init() {
-        super.init(frame: .zero)
+  init() {
+    super.init(frame: .zero)
 
-        titleLabel.textAlignment = .center
-        titleLabel.textColor = Asset.neutralWhite.color
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
+    separatorView.alpha = 0.0
+    titleLabel.textAlignment = .center
+    imageView.tintColor = Asset.neutralWeak.color
+    titleLabel.textColor = Asset.neutralWeak.color
+    separatorView.backgroundColor = Asset.neutralWhite.color
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 13)
+    imageView.transform = imageView.transform.scaledBy(x: 0.9, y: 0.9)
+    titleLabel.transform = titleLabel.transform.scaledBy(x: 0.9, y: 0.9)
 
-        addSubview(titleLabel)
-        addSubview(imageView)
+    addSubview(titleLabel)
+    addSubview(imageView)
+    addSubview(separatorView)
 
-        imageView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(7.5)
-            $0.centerX.equalToSuperview()
-        }
-
-        titleLabel.snp.makeConstraints {
-            $0.top.equalTo(imageView.snp.bottom).offset(2)
-            $0.centerX.equalToSuperview()
-            $0.bottom.equalToSuperview().offset(-7.5)
-        }
+    imageView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(7.5)
+      $0.centerX.equalToSuperview()
     }
 
-    required init?(coder: NSCoder) { nil }
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(imageView.snp.bottom).offset(2)
+      $0.centerX.equalToSuperview()
+      $0.bottom.equalToSuperview().offset(-7.5)
+    }
 
-    func setup(title: String, icon: UIImage) {
-        titleLabel.text = title
-        imageView.image = icon
+    separatorView.snp.makeConstraints {
+      $0.height.equalTo(2)
+      $0.left.equalToSuperview().offset(20)
+      $0.right.equalToSuperview().offset(-20)
+      $0.bottom.equalToSuperview()
     }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  func set(selected: Bool) {
+    switch (isSelected, selected) {
+    case (true, false):
+      UIView.animate(withDuration: 0.25) {
+        self.imageView.transform = self.imageView.transform.scaledBy(x: 0.9, y: 0.9)
+        self.titleLabel.transform = self.titleLabel.transform.scaledBy(x: 0.9, y: 0.9)
+        self.imageView.tintColor = Asset.neutralWeak.color
+        self.titleLabel.textColor = Asset.neutralWeak.color
+        self.separatorView.alpha = 0.0
+      } completion: { _ in
+        self.isSelected = false
+      }
 
-    func update(color: UIColor) {
-        imageView.tintColor = color
-        titleLabel.textColor = color
+    case (false, true):
+      UIView.animate(withDuration: 0.25) {
+        self.imageView.transform = .identity
+        self.titleLabel.transform = .identity
+        self.imageView.tintColor = Asset.neutralWhite.color
+        self.titleLabel.textColor = Asset.neutralWhite.color
+        self.separatorView.alpha = 1.0
+      } completion: { _ in
+        self.isSelected = true
+      }
+    case (true, true), (false, false):
+      break
     }
+  }
 }
diff --git a/Sources/ScanFeature/Views/ScanView.swift b/Sources/ScanFeature/Views/ScanView.swift
index 540f68f524bac7122bb0db21111c62e1aaddb454..8ae5933bf9a82d430ac0cd7cff31ec00faf5bee8 100644
--- a/Sources/ScanFeature/Views/ScanView.swift
+++ b/Sources/ScanFeature/Views/ScanView.swift
@@ -1,112 +1,113 @@
 import UIKit
 import Shared
+import AppResources
 
 final class ScanView: UIView {
-    private let statusLabel = UILabel()
-    private let imageView = UIImageView()
-    private let stackView = UIStackView()
-    private let animationView = DotAnimation()
-    private let overlayView = ScanOverlayView()
-    private(set) var actionButton = CapsuleButton()
-
-    init() {
-        super.init(frame: .zero)
-        imageView.contentMode = .center
-        actionButton.setStyle(.brandColored)
-
-        statusLabel.numberOfLines = 0
-        statusLabel.textAlignment = .center
-        statusLabel.textColor = Asset.neutralWhite.color
-        statusLabel.font = Fonts.Mulish.regular.font(size: 14.0)
-
-        stackView.spacing = 15
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(animationView)
-        stackView.addArrangedSubview(imageView)
-        stackView.addArrangedSubview(statusLabel)
-        stackView.addArrangedSubview(actionButton)
-
-        imageView.isHidden = true
-        actionButton.isHidden = true
-        animationView.isHidden = false
-
-        addSubview(overlayView)
-        addSubview(stackView)
-
-        overlayView.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
-
-        stackView.snp.makeConstraints {
-            $0.left.equalToSuperview().offset(57)
-            $0.right.equalToSuperview().offset(-57)
-            $0.bottom.equalTo(safeAreaLayoutGuide).offset(-100)
-        }
+  private let statusLabel = UILabel()
+  private let imageView = UIImageView()
+  private let stackView = UIStackView()
+  private let animationView = DotAnimation()
+  private let overlayView = ScanOverlayView()
+  private(set) var actionButton = CapsuleButton()
+
+  init() {
+    super.init(frame: .zero)
+    imageView.contentMode = .center
+    actionButton.setStyle(.brandColored)
+
+    statusLabel.numberOfLines = 0
+    statusLabel.textAlignment = .center
+    statusLabel.textColor = Asset.neutralWhite.color
+    statusLabel.font = Fonts.Mulish.regular.font(size: 14.0)
+
+    stackView.spacing = 15
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(animationView)
+    stackView.addArrangedSubview(imageView)
+    stackView.addArrangedSubview(statusLabel)
+    stackView.addArrangedSubview(actionButton)
+
+    imageView.isHidden = true
+    actionButton.isHidden = true
+    animationView.isHidden = false
+
+    addSubview(overlayView)
+    addSubview(stackView)
+
+    overlayView.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
     }
 
-    required init?(coder: NSCoder) { nil }
-
-    func update(with state: ScanStatus) {
-        var text: String
-
-        switch state {
-        case .reading, .processing:
-            imageView.isHidden = true
-            actionButton.isHidden = true
-            text = Localized.Scan.Status.reading
-            overlayView.updateCornerColor(Asset.brandPrimary.color)
-
-        case .success:
-            animationView.isHidden = true
-            actionButton.isHidden = true
-            imageView.isHidden = false
-            imageView.image = Asset.sharedSuccess.image
-            text = Localized.Scan.Status.success
-            overlayView.updateCornerColor(Asset.accentSuccess.color)
-
-        case .failed(let error):
-            animationView.isHidden = true
-            imageView.image = Asset.scanError.image
-            imageView.isHidden = false
-            overlayView.updateCornerColor(Asset.accentDanger.color)
-
-            switch error {
-            case .requestOpened:
-                text = Localized.Scan.Error.requested
-                actionButton.setTitle(Localized.Scan.requests, for: .normal)
-                actionButton.isHidden = false
-
-            case .alreadyFriends(let name):
-                text = Localized.Scan.Error.alreadyFriends(name)
-                actionButton.setTitle(Localized.Scan.contact, for: .normal)
-                actionButton.isHidden = false
-
-            case .cameraPermission:
-                text = Localized.Scan.Error.cameraPermissionNeeded
-                actionButton.setTitle(Localized.Scan.settings, for: .normal)
-                actionButton.isHidden = false
-
-            case .unknown(let content):
-                text = content
-            }
-        }
-
-        let attString = NSMutableAttributedString(string: text)
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .center
-        paragraph.lineHeightMultiple = 1.35
-
-        attString.addAttribute(.paragraphStyle, value: paragraph)
-        attString.addAttribute(.foregroundColor, value: Asset.neutralWhite.color)
-        attString.addAttribute(.font, value: Fonts.Mulish.regular.font(size: 14.0) as Any)
-
-        if text.contains("#") {
-            attString.addAttribute(name: .foregroundColor, value: Asset.brandPrimary.color, betweenCharacters: "#")
-        }
-
-        statusLabel.attributedText = attString
+    stackView.snp.makeConstraints {
+      $0.left.equalToSuperview().offset(57)
+      $0.right.equalToSuperview().offset(-57)
+      $0.bottom.equalTo(safeAreaLayoutGuide).offset(-100)
     }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  func update(with state: ScanStatus) {
+    var text: String
+
+    switch state {
+    case .reading, .processing:
+      imageView.isHidden = true
+      actionButton.isHidden = true
+      text = Localized.Scan.Status.reading
+      overlayView.updateCornerColor(Asset.brandPrimary.color)
+
+    case .success:
+      animationView.isHidden = true
+      actionButton.isHidden = true
+      imageView.isHidden = false
+      imageView.image = Asset.sharedSuccess.image
+      text = Localized.Scan.Status.success
+      overlayView.updateCornerColor(Asset.accentSuccess.color)
+
+    case .failed(let error):
+      animationView.isHidden = true
+      imageView.image = Asset.scanError.image
+      imageView.isHidden = false
+      overlayView.updateCornerColor(Asset.accentDanger.color)
+
+      switch error {
+      case .requestOpened:
+        text = Localized.Scan.Error.requested
+        actionButton.setTitle(Localized.Scan.requests, for: .normal)
+        actionButton.isHidden = false
+
+      case .alreadyFriends(let name):
+        text = Localized.Scan.Error.alreadyFriends(name)
+        actionButton.setTitle(Localized.Scan.contact, for: .normal)
+        actionButton.isHidden = false
+
+      case .cameraPermission:
+        text = Localized.Scan.Error.cameraPermissionNeeded
+        actionButton.setTitle(Localized.Scan.settings, for: .normal)
+        actionButton.isHidden = false
+
+      case .unknown(let content):
+        text = content
+      }
+    }
+
+    let attString = NSMutableAttributedString(string: text)
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .center
+    paragraph.lineHeightMultiple = 1.35
+
+    attString.addAttribute(.paragraphStyle, value: paragraph)
+    attString.addAttribute(.foregroundColor, value: Asset.neutralWhite.color)
+    attString.addAttribute(.font, value: Fonts.Mulish.regular.font(size: 14.0) as Any)
+
+    if text.contains("#") {
+      attString.addAttribute(name: .foregroundColor, value: Asset.brandPrimary.color, betweenCharacters: "#")
+    }
+
+    statusLabel.attributedText = attString
+  }
 }
diff --git a/Sources/SearchFeature/Controllers/SearchContainerController.swift b/Sources/SearchFeature/Controllers/SearchContainerController.swift
index 3a1bf1e45af9077b7bc70315f5584be93b116242..ec9bcd59eb990643390ba4d458c184d488b4d193 100644
--- a/Sources/SearchFeature/Controllers/SearchContainerController.swift
+++ b/Sources/SearchFeature/Controllers/SearchContainerController.swift
@@ -1,183 +1,187 @@
 import UIKit
-import Theme
 import Shared
 import Combine
+import AppCore
 import XXModels
+import Dependencies
+import AppResources
+import AppNavigation
 import DrawerFeature
-import DependencyInjection
 
 public final class SearchContainerController: UIViewController {
-    @Dependency var coordinator: SearchCoordinating
-    @Dependency var statusBarController: StatusBarStyleControlling
-
-    lazy private var screenView = SearchContainerView()
-
-    private var contentOffset: CGPoint?
-    private var cancellables = Set<AnyCancellable>()
-    private let leftController: SearchLeftController
-    private let viewModel = SearchContainerViewModel()
-    private let rightController = SearchRightController()
-    private var drawerCancellables = Set<AnyCancellable>()
-
-    public init(_ invitation: String? = nil) {
-        self.leftController = .init(invitation)
-        super.init(nibName: nil, bundle: nil)
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+
+  private lazy var screenView = SearchContainerView()
+
+  private var contentOffset: CGPoint?
+  private var cancellables = Set<AnyCancellable>()
+  private let leftController: SearchLeftController
+  private let viewModel = SearchContainerViewModel()
+  private let rightController = SearchRightController()
+  private var drawerCancellables = Set<AnyCancellable>()
+
+  public init(_ invitation: String? = nil) {
+    self.leftController = .init(invitation)
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func loadView() {
+    view = screenView
+    embedControllers()
+  }
+
+  public func startSearchingFor(_ string: String) {
+    leftController.viewModel.invitation = string
+    leftController.viewModel.viewDidAppear()
+  }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    statusBar.set(.darkContent)
+    navigationController?.navigationBar.customize(
+      backgroundColor: Asset.neutralWhite.color
+    )
+
+    if let contentOffset = self.contentOffset {
+      screenView.scrollView.setContentOffset(contentOffset, animated: true)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func loadView() {
-        view = screenView
-        embedControllers()
-    }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        statusBarController.style.send(.darkContent)
-        navigationController?.navigationBar.customize(
-            backgroundColor: Asset.neutralWhite.color
-        )
-
-        if let contentOffset = self.contentOffset {
-            screenView.scrollView.setContentOffset(contentOffset, animated: true)
+  }
+
+  public override func viewWillDisappear(_ animated: Bool) {
+    super.viewWillDisappear(animated)
+    contentOffset = screenView.scrollView.contentOffset
+  }
+
+  public override func viewDidAppear(_ animated: Bool) {
+    super.viewDidAppear(animated)
+    viewModel.didAppear()
+    rightController.viewModel.viewWillAppear()
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupNavigationBar()
+    setupBindings()
+  }
+
+  private func setupNavigationBar() {
+    let title = UILabel()
+    title.text = Localized.Ud.title
+    title.textColor = Asset.neutralActive.color
+    title.font = Fonts.Mulish.semiBold.font(size: 18.0)
+
+    navigationItem.leftBarButtonItem = UIBarButtonItem(customView: title)
+    navigationItem.leftItemsSupplementBackButton = true
+  }
+
+  private func setupBindings() {
+    screenView.segmentedControl
+      .actionPublisher
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        if $0 == .qr {
+          let point = CGPoint(x: screenView.frame.width, y: 0.0)
+          screenView.scrollView.setContentOffset(point, animated: true)
+          leftController.endEditing()
+        } else {
+          screenView.scrollView.setContentOffset(.zero, animated: true)
+          leftController.viewModel.didSelectItem($0)
         }
+      }.store(in: &cancellables)
+
+    viewModel.coverTrafficPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in presentCoverTrafficDrawer() }
+      .store(in: &cancellables)
+  }
+
+  private func embedControllers() {
+    addChild(leftController)
+    addChild(rightController)
+
+    screenView.scrollView.addSubview(leftController.view)
+    screenView.scrollView.addSubview(rightController.view)
+
+    leftController.view.snp.makeConstraints {
+      $0.top.equalTo(screenView.segmentedControl.snp.bottom)
+      $0.width.equalTo(screenView)
+      $0.bottom.equalTo(screenView)
+      $0.left.equalToSuperview()
+      $0.right.equalTo(rightController.view.snp.left)
     }
 
-    public override func viewWillDisappear(_ animated: Bool) {
-        super.viewWillDisappear(animated)
-        contentOffset = screenView.scrollView.contentOffset
-    }
-
-    public override func viewDidAppear(_ animated: Bool) {
-        super.viewDidAppear(animated)
-        viewModel.didAppear()
-        rightController.viewModel.viewWillAppear()
+    rightController.view.snp.makeConstraints {
+      $0.top.equalTo(screenView.segmentedControl.snp.bottom)
+      $0.width.equalTo(screenView)
+      $0.bottom.equalTo(screenView)
     }
 
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupNavigationBar()
-        setupBindings()
-    }
-
-    private func setupNavigationBar() {
-        let title = UILabel()
-        title.text = Localized.Ud.title
-        title.textColor = Asset.neutralActive.color
-        title.font = Fonts.Mulish.semiBold.font(size: 18.0)
-
-        navigationItem.leftBarButtonItem = UIBarButtonItem(customView: title)
-        navigationItem.leftItemsSupplementBackButton = true
-    }
-
-    private func setupBindings() {
-        screenView.segmentedControl
-            .actionPublisher
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                if $0 == .qr {
-                    let point = CGPoint(x: screenView.frame.width, y: 0.0)
-                    screenView.scrollView.setContentOffset(point, animated: true)
-                    leftController.endEditing()
-                } else {
-                    screenView.scrollView.setContentOffset(.zero, animated: true)
-                    leftController.viewModel.didSelectItem($0)
-                }
-            }.store(in: &cancellables)
-
-        viewModel.coverTrafficPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in presentCoverTrafficDrawer() }
-            .store(in: &cancellables)
-    }
-
-    private func embedControllers() {
-        addChild(leftController)
-        addChild(rightController)
-
-        screenView.scrollView.addSubview(leftController.view)
-        screenView.scrollView.addSubview(rightController.view)
-
-        leftController.view.snp.makeConstraints {
-            $0.top.equalTo(screenView.segmentedControl.snp.bottom)
-            $0.width.equalTo(screenView)
-            $0.bottom.equalTo(screenView)
-            $0.left.equalToSuperview()
-            $0.right.equalTo(rightController.view.snp.left)
-        }
-
-        rightController.view.snp.makeConstraints {
-            $0.top.equalTo(screenView.segmentedControl.snp.bottom)
-            $0.width.equalTo(screenView)
-            $0.bottom.equalTo(screenView)
-        }
-
-        leftController.didMove(toParent: self)
-        rightController.didMove(toParent: self)
-    }
+    leftController.didMove(toParent: self)
+    rightController.didMove(toParent: self)
+  }
 }
 
 extension SearchContainerController {
-    private func presentCoverTrafficDrawer() {
-        let enableButton = CapsuleButton()
-        enableButton.set(
-            style: .brandColored,
-            title: Localized.ChatList.Traffic.positive
-        )
-
-        let dismissButton = CapsuleButton()
-        dismissButton.set(
-            style: .seeThrough,
-            title: Localized.ChatList.Traffic.negative
-        )
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: Localized.ChatList.Traffic.title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
-            ),
-            DrawerText(
-                font: Fonts.Mulish.regular.font(size: 16.0),
-                text: Localized.ChatList.Traffic.subtitle,
-                color: Asset.neutralBody.color,
-                alignment: .left,
-                lineHeightMultiple: 1.1,
-                spacingAfter: 39
-            ),
-            DrawerStack(
-                axis: .horizontal,
-                spacing: 20,
-                distribution: .fillEqually,
-                views: [enableButton, dismissButton]
-            )
-        ])
-
-        enableButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                    self.viewModel.didEnableCoverTraffic()
-                }
-            }.store(in: &drawerCancellables)
-
-        dismissButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
+  private func presentCoverTrafficDrawer() {
+    let enableButton = CapsuleButton()
+    enableButton.set(
+      style: .brandColored,
+      title: Localized.ChatList.Traffic.positive
+    )
+    let dismissButton = CapsuleButton()
+    dismissButton.set(
+      style: .seeThrough,
+      title: Localized.ChatList.Traffic.negative
+    )
+
+    enableButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          self.viewModel.didEnableCoverTraffic()
+        }
+      }.store(in: &drawerCancellables)
+
+    dismissButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: Localized.ChatList.Traffic.title,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 19
+      ),
+      DrawerText(
+        font: Fonts.Mulish.regular.font(size: 16.0),
+        text: Localized.ChatList.Traffic.subtitle,
+        color: Asset.neutralBody.color,
+        alignment: .left,
+        lineHeightMultiple: 1.1,
+        spacingAfter: 39
+      ),
+      DrawerStack(
+        axis: .horizontal,
+        spacing: 20,
+        distribution: .fillEqually,
+        views: [enableButton, dismissButton]
+      )
+    ], isDismissable: true, from: self))
+  }
 }
diff --git a/Sources/SearchFeature/Controllers/SearchLeftController.swift b/Sources/SearchFeature/Controllers/SearchLeftController.swift
index cc46ad60cf83f8baa03aa3ac7bb34562fbccaea8..4485fa22e286efcc668220c973c8aa4ab9ea0b3a 100644
--- a/Sources/SearchFeature/Controllers/SearchLeftController.swift
+++ b/Sources/SearchFeature/Controllers/SearchLeftController.swift
@@ -1,451 +1,459 @@
-import HUD
 import UIKit
 import Shared
 import Combine
 import XXModels
 import Defaults
-import Countries
+import AppResources
+import Dependencies
+import AppNavigation
 import DrawerFeature
-import DependencyInjection
+import CountryListFeature
 
 final class SearchLeftController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: SearchCoordinating
-
-    @KeyObject(.email, defaultValue: nil) var email: String?
-    @KeyObject(.phone, defaultValue: nil) var phone: String?
-    @KeyObject(.sharingEmail, defaultValue: false) var isSharingEmail: Bool
-    @KeyObject(.sharingPhone, defaultValue: false) var isSharingPhone: Bool
-
-    lazy private var screenView = SearchLeftView()
-
-    let viewModel: SearchLeftViewModel
-    private var dataSource: SearchDiffableDataSource!
-    private var drawerCancellables = Set<AnyCancellable>()
-    private let adrpURLString = "https://links.xx.network/adrp"
-
-    private var cancellables = Set<AnyCancellable>()
-    private var hudCancellables = Set<AnyCancellable>()
-
-    init(_ invitation: String? = nil) {
-        self.viewModel = .init(invitation)
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
+  @Dependency(\.navigator) var navigator: Navigator
+  @KeyObject(.email, defaultValue: nil) var email: String?
+  @KeyObject(.phone, defaultValue: nil) var phone: String?
+  @KeyObject(.sharingEmail, defaultValue: false) var isSharingEmail: Bool
+  @KeyObject(.sharingPhone, defaultValue: false) var isSharingPhone: Bool
+
+  private lazy var screenView = SearchLeftView()
+
+  private(set) var viewModel: SearchLeftViewModel
+  private var dataSource: SearchDiffableDataSource!
+  private var drawerCancellables = Set<AnyCancellable>()
+  private let adrpURLString = "https://links.xx.network/adrp"
+
+  private var cancellables = Set<AnyCancellable>()
+  private var hudCancellables = Set<AnyCancellable>()
+
+  init(_ invitation: String? = nil) {
+    self.viewModel = .init(invitation)
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  override func loadView() {
+    view = screenView
+  }
+
+  override func viewDidLoad() {
+    super.viewDidLoad()
+    setupTableView()
+    setupBindings()
+  }
+
+  override func viewDidAppear(_ animated: Bool) {
+    super.viewDidAppear(animated)
+    viewModel.viewDidAppear()
+  }
+
+  func endEditing() {
+    screenView.inputField.endEditing(true)
+  }
+
+  private func setupTableView() {
+    screenView.tableView.separatorStyle = .none
+    screenView.tableView.tableFooterView = UIView()
+    screenView.tableView.register(AvatarCell.self)
+    screenView.tableView.dataSource = dataSource
+    screenView.tableView.delegate = self
+
+    dataSource = SearchDiffableDataSource(
+      tableView: screenView.tableView
+    ) { tableView, indexPath, item in
+      let contact: Contact
+      let cell = tableView.dequeueReusableCell(forIndexPath: indexPath, ofType: AvatarCell.self)
+
+      let h1Text: String
+      var h2Text: String?
+
+      switch item {
+      case .stranger(let stranger):
+        contact = stranger
+        h1Text = stranger.username ?? ""
+
+        if stranger.authStatus == .requested {
+          h2Text = "Request pending"
+        } else if stranger.authStatus == .requestFailed {
+          h2Text = "Request failed"
+        }
 
-    override func loadView() {
-        view = screenView
-    }
+      case .connection(let connection):
+        contact = connection
+        h1Text = (connection.nickname ?? contact.username) ?? ""
 
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        setupTableView()
-        setupBindings()
+        if connection.nickname != nil {
+          h2Text = contact.username ?? ""
+        }
+      }
+
+      cell.setup(
+        title: h1Text,
+        image: contact.photo,
+        firstSubtitle: h2Text,
+        secondSubtitle: contact.email,
+        thirdSubtitle: contact.phone,
+        showSeparator: false,
+        sent: contact.authStatus == .requested
+      )
+
+      cell.didTapStateButton = { [weak self] in
+        guard let self else { return }
+        self.viewModel.didTapResend(contact: contact)
+        cell.updateToResent()
+      }
+
+      return cell
     }
+  }
+
+  private func setupBindings() {
+    viewModel
+      .statePublisher
+      .map(\.item)
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.updateUIForItem(item: $0)
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.country)
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.countryButton.setFlag($0.flag, prefix: $0.prefix)
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.input)
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.inputField.update(content: $0)
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .compactMap(\.snapshot)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        screenView.placeholderView.isHidden = true
+        screenView.emptyView.isHidden = $0.numberOfItems != 0
+        dataSource.apply($0, animatingDifferences: false)
+      }.store(in: &cancellables)
+
+    screenView
+      .placeholderView
+      .infoPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        presentSearchDisclaimer()
+      }.store(in: &cancellables)
+
+    screenView
+      .countryButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentCountryList(completion: { [weak self] in
+          guard let self else { return }
+          self.viewModel.didPick(country: $0 as! Country)
+        }, from: self))
+      }.store(in: &cancellables)
+
+    screenView
+      .inputField
+      .textPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        viewModel.didEnterInput($0)
+      }.store(in: &cancellables)
+
+    screenView
+      .inputField
+      .returnPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] _ in
+        viewModel.didStartSearching()
+      }.store(in: &cancellables)
+
+    screenView
+      .inputField
+      .isEditingPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] isEditing in
+        UIView.animate(withDuration: 0.25) {
+          self.screenView.placeholderView.titleLabel.alpha = isEditing ? 0.1 : 1.0
+          self.screenView.placeholderView.subtitleWithInfo.alpha = isEditing ? 0.1 : 1.0
+        }
+      }.store(in: &cancellables)
+
+    viewModel
+      .successPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        presentSucessDrawerFor(contact: $0)
+      }.store(in: &cancellables)
+  }
+
+  private func presentSearchDisclaimer() {
+    let actionButton = CapsuleButton()
+    actionButton.set(
+      style: .seeThrough,
+      title: Localized.Ud.Placeholder.Drawer.action
+    )
+
+    actionButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: Localized.Ud.Placeholder.Drawer.title,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 19
+      ),
+      DrawerLinkText(
+        text: Localized.Ud.Placeholder.Drawer.subtitle,
+        urlString: adrpURLString,
+        spacingAfter: 37
+      ),
+      DrawerStack(views: [
+        actionButton,
+        FlexibleSpace()
+      ])
+    ], isDismissable: true, from: self))
+  }
+
+  private func presentSucessDrawerFor(contact: Contact) {
+    var items: [DrawerItem] = []
+
+    let drawerTitle = DrawerText(
+      font: Fonts.Mulish.extraBold.font(size: 26.0),
+      text: Localized.Ud.NicknameDrawer.title,
+      color: Asset.neutralDark.color,
+      spacingAfter: 20
+    )
+
+    let drawerSubtitle = DrawerText(
+      font: Fonts.Mulish.regular.font(size: 16.0),
+      text: Localized.Ud.NicknameDrawer.subtitle,
+      color: Asset.neutralDark.color,
+      spacingAfter: 20
+    )
+
+    items.append(contentsOf: [
+      drawerTitle,
+      drawerSubtitle
+    ])
+
+    let drawerNicknameInput = DrawerInput(
+      placeholder: contact.username!,
+      validator: .init(
+        wrongIcon: .image(Asset.sharedError.image),
+        correctIcon: .image(Asset.sharedSuccess.image),
+        shouldAcceptPlaceholder: true
+      ),
+      spacingAfter: 29
+    )
+
+    items.append(drawerNicknameInput)
+
+    let drawerSaveButton = DrawerCapsuleButton(
+      model: .init(
+        title: Localized.Ud.NicknameDrawer.save,
+        style: .brandColored
+      ), spacingAfter: 5
+    )
+
+    items.append(drawerSaveButton)
+
+    let drawer = DrawerController(items)
+    var nickname: String?
+    var allowsSave = true
+
+    drawerNicknameInput
+      .validationPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { allowsSave = $0 }
+      .store(in: &drawerCancellables)
+
+    drawerNicknameInput
+      .inputPublisher
+      .receive(on: DispatchQueue.main)
+      .sink {
+        guard !$0.isEmpty else {
+          nickname = contact.username
+          return
+        }
 
-    override func viewDidAppear(_ animated: Bool) {
-        super.viewDidAppear(animated)
-        viewModel.viewDidAppear()
-    }
+        nickname = $0
+      }
+      .store(in: &drawerCancellables)
 
-    func endEditing() {
-        screenView.inputField.endEditing(true)
-    }
+    drawerSaveButton.action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        guard allowsSave else { return }
 
-    private func setupTableView() {
-        screenView.tableView.separatorStyle = .none
-        screenView.tableView.tableFooterView = UIView()
-        screenView.tableView.register(AvatarCell.self)
-        screenView.tableView.dataSource = dataSource
-        screenView.tableView.delegate = self
-
-        dataSource = SearchDiffableDataSource(
-            tableView: screenView.tableView
-        ) { tableView, indexPath, item in
-            let contact: Contact
-            let cell = tableView.dequeueReusableCell(forIndexPath: indexPath, ofType: AvatarCell.self)
-
-            let h1Text: String
-            var h2Text: String?
-
-            switch item {
-            case .stranger(let stranger):
-                contact = stranger
-                h1Text = stranger.username ?? ""
-
-                if stranger.authStatus == .requested {
-                    h2Text = "Request pending"
-                } else if stranger.authStatus == .requestFailed {
-                    h2Text = "Request failed"
-                }
-
-            case .connection(let connection):
-                contact = connection
-                h1Text = (connection.nickname ?? contact.username) ?? ""
-
-                if connection.nickname != nil {
-                    h2Text = contact.username ?? ""
-                }
-            }
-
-            cell.setup(
-                title: h1Text,
-                image: contact.photo,
-                firstSubtitle: h2Text,
-                secondSubtitle: contact.email,
-                thirdSubtitle: contact.phone,
-                showSeparator: false,
-                sent: contact.authStatus == .requested
-            )
-
-            cell.didTapStateButton = { [weak self] in
-                guard let self = self else { return }
-                self.viewModel.didTapResend(contact: contact)
-                cell.updateToResent()
-            }
-
-            return cell
+        drawer.dismiss(animated: true) {
+          self.viewModel.didSet(nickname: nickname ?? contact.username!, for: contact)
         }
+      }
+      .store(in: &drawerCancellables)
+
+    //coordinator.toNicknameDrawer(drawer, from: self)
+  }
+
+  private func presentRequestDrawer(forContact contact: Contact) {
+    var items: [DrawerItem] = []
+
+    let drawerTitle = DrawerText(
+      font: Fonts.Mulish.extraBold.font(size: 26.0),
+      text: Localized.Ud.RequestDrawer.title,
+      color: Asset.neutralDark.color,
+      spacingAfter: 20
+    )
+
+    var subtitleFragment = "Share your information with #\(contact.username ?? "")"
+
+    if let email = contact.email {
+      subtitleFragment.append(contentsOf: " (\(email))#")
+    } else if let phone = contact.phone {
+      subtitleFragment.append(contentsOf: " (\(Country.findFrom(phone).prefix) \(phone.dropLast(2)))#")
+    } else {
+      subtitleFragment.append(contentsOf: "#")
     }
 
-    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.statePublisher
-            .map(\.item)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.updateUIForItem(item: $0) }
-            .store(in: &cancellables)
-
-        viewModel.statePublisher
-            .map(\.country)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.countryButton.setFlag($0.flag, prefix: $0.prefix) }
-            .store(in: &cancellables)
-
-        viewModel.statePublisher
-            .map(\.input)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in screenView.inputField.update(content: $0) }
-            .store(in: &cancellables)
-
-        viewModel.statePublisher
-            .compactMap(\.snapshot)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                screenView.placeholderView.isHidden = true
-                screenView.emptyView.isHidden = $0.numberOfItems != 0
-
-                dataSource.apply($0, animatingDifferences: false)
-            }.store(in: &cancellables)
-
-        screenView.placeholderView
-            .infoPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in presentSearchDisclaimer() }
-            .store(in: &cancellables)
-
-        screenView.countryButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                coordinator.toCountries(from: self) { [weak self] country in
-                    guard let self = self else { return }
-                    self.viewModel.didPick(country: country)
-                }
-            }.store(in: &cancellables)
-
-        screenView.inputField
-            .textPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in viewModel.didEnterInput($0) }
-            .store(in: &cancellables)
-
-        screenView.inputField
-            .returnPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] _ in viewModel.didStartSearching() }
-            .store(in: &cancellables)
-
-        screenView.inputField
-            .isEditingPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] isEditing in
-                UIView.animate(withDuration: 0.25) {
-                    self.screenView.placeholderView.titleLabel.alpha = isEditing ? 0.1 : 1.0
-                    self.screenView.placeholderView.subtitleWithInfo.alpha = isEditing ? 0.1 : 1.0
-                }
-            }.store(in: &cancellables)
-
-        viewModel.successPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in presentSucessDrawerFor(contact: $0) }
-            .store(in: &cancellables)
-    }
-
-    private func presentSearchDisclaimer() {
-        let actionButton = CapsuleButton()
-        actionButton.set(
-            style: .seeThrough,
-            title: Localized.Ud.Placeholder.Drawer.action
-        )
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: Localized.Ud.Placeholder.Drawer.title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
-            ),
-            DrawerLinkText(
-                text: Localized.Ud.Placeholder.Drawer.subtitle,
-                urlString: adrpURLString,
-                spacingAfter: 37
-            ),
-            DrawerStack(views: [
-                actionButton,
-                FlexibleSpace()
-            ])
-        ])
-
-        actionButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &self.drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
-
-    private func presentSucessDrawerFor(contact: Contact) {
-        var items: [DrawerItem] = []
-
-        let drawerTitle = DrawerText(
-            font: Fonts.Mulish.extraBold.font(size: 26.0),
-            text: Localized.Ud.NicknameDrawer.title,
-            color: Asset.neutralDark.color,
-            spacingAfter: 20
-        )
-
-        let drawerSubtitle = DrawerText(
-            font: Fonts.Mulish.regular.font(size: 16.0),
-            text: Localized.Ud.NicknameDrawer.subtitle,
-            color: Asset.neutralDark.color,
-            spacingAfter: 20
-        )
-
-        items.append(contentsOf: [
-            drawerTitle,
-            drawerSubtitle
-        ])
-
-        let drawerNicknameInput = DrawerInput(
-            placeholder: contact.username!,
-            validator: .init(
-                wrongIcon: .image(Asset.sharedError.image),
-                correctIcon: .image(Asset.sharedSuccess.image),
-                shouldAcceptPlaceholder: true
-            ),
-            spacingAfter: 29
-        )
-
-        items.append(drawerNicknameInput)
-
-        let drawerSaveButton = DrawerCapsuleButton(
-            model: .init(
-                title: Localized.Ud.NicknameDrawer.save,
-                style: .brandColored
-            ), spacingAfter: 5
-        )
-
-        items.append(drawerSaveButton)
-
-        let drawer = DrawerController(with: items)
-        var nickname: String?
-        var allowsSave = true
-
-        drawerNicknameInput.validationPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { allowsSave = $0 }
-            .store(in: &drawerCancellables)
-
-        drawerNicknameInput.inputPublisher
-            .receive(on: DispatchQueue.main)
-            .sink {
-                guard !$0.isEmpty else {
-                    nickname = contact.username
-                    return
-                }
-
-                nickname = $0
-            }
-            .store(in: &drawerCancellables)
-
-        drawerSaveButton.action
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                guard allowsSave else { return }
-
-                drawer.dismiss(animated: true) {
-                    self.viewModel.didSet(nickname: nickname ?? contact.username!, for: contact)
-                }
-            }
-            .store(in: &drawerCancellables)
-
-        coordinator.toNicknameDrawer(drawer, from: self)
+    subtitleFragment.append(contentsOf: " so they know its you.")
+
+    let drawerSubtitle = DrawerText(
+      font: Fonts.Mulish.regular.font(size: 16.0),
+      text: subtitleFragment,
+      color: Asset.neutralDark.color,
+      spacingAfter: 31.5,
+      customAttributes: [
+        .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
+        .foregroundColor: Asset.brandPrimary.color
+      ]
+    )
+
+    items.append(contentsOf: [
+      drawerTitle,
+      drawerSubtitle
+    ])
+
+    if let email = email {
+      let drawerEmail = DrawerSwitch(
+        title: Localized.Ud.RequestDrawer.email,
+        content: email,
+        spacingAfter: phone != nil ? 23 : 31,
+        isInitiallyOn: isSharingEmail
+      )
+
+      items.append(drawerEmail)
+
+      drawerEmail.isOnPublisher
+        .receive(on: DispatchQueue.main)
+        .sink { [weak self] in self?.isSharingEmail = $0 }
+        .store(in: &drawerCancellables)
     }
 
-    private func presentRequestDrawer(forContact contact: Contact) {
-        var items: [DrawerItem] = []
-
-        let drawerTitle = DrawerText(
-            font: Fonts.Mulish.extraBold.font(size: 26.0),
-            text: Localized.Ud.RequestDrawer.title,
-            color: Asset.neutralDark.color,
-            spacingAfter: 20
-        )
+    if let phone = phone {
+      let drawerPhone = DrawerSwitch(
+        title: Localized.Ud.RequestDrawer.phone,
+        content: "\(Country.findFrom(phone).prefix) \(phone.dropLast(2))",
+        spacingAfter: 31,
+        isInitiallyOn: isSharingPhone
+      )
 
-        var subtitleFragment = "Share your information with #\(contact.username ?? "")"
+      items.append(drawerPhone)
 
-        if let email = contact.email {
-            subtitleFragment.append(contentsOf: " (\(email))#")
-        } else if let phone = contact.phone {
-            subtitleFragment.append(contentsOf: " (\(Country.findFrom(phone).prefix) \(phone.dropLast(2)))#")
-        } else {
-            subtitleFragment.append(contentsOf: "#")
-        }
+      drawerPhone.isOnPublisher
+        .receive(on: DispatchQueue.main)
+        .sink { [weak self] in self?.isSharingPhone = $0 }
+        .store(in: &drawerCancellables)
+    }
 
-        subtitleFragment.append(contentsOf: " so they know its you.")
-
-        let drawerSubtitle = DrawerText(
-            font: Fonts.Mulish.regular.font(size: 16.0),
-            text: subtitleFragment,
-            color: Asset.neutralDark.color,
-            spacingAfter: 31.5,
-            customAttributes: [
-                .font: Fonts.Mulish.regular.font(size: 16.0) as Any,
-                .foregroundColor: Asset.brandPrimary.color
-            ]
-        )
-
-        items.append(contentsOf: [
-            drawerTitle,
-            drawerSubtitle
-        ])
-
-        if let email = email {
-            let drawerEmail = DrawerSwitch(
-                title: Localized.Ud.RequestDrawer.email,
-                content: email,
-                spacingAfter: phone != nil ? 23 : 31,
-                isInitiallyOn: isSharingEmail
-            )
-
-            items.append(drawerEmail)
-
-            drawerEmail.isOnPublisher
-                .receive(on: DispatchQueue.main)
-                .sink { [weak self] in self?.isSharingEmail = $0 }
-                .store(in: &drawerCancellables)
+    let drawerSendButton = DrawerCapsuleButton(
+      model: .init(
+        title: Localized.Ud.RequestDrawer.send,
+        style: .brandColored
+      ), spacingAfter: 5
+    )
+
+    let drawerCancelButton = DrawerCapsuleButton(
+      model: .init(
+        title: Localized.Ud.RequestDrawer.cancel,
+        style: .simplestColoredBrand
+      ), spacingAfter: 5
+    )
+
+    items.append(contentsOf: [drawerSendButton, drawerCancelButton])
+
+    drawerSendButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          self.viewModel.didTapRequest(contact: contact)
         }
-
-        if let phone = phone {
-            let drawerPhone = DrawerSwitch(
-                title: Localized.Ud.RequestDrawer.phone,
-                content: "\(Country.findFrom(phone).prefix) \(phone.dropLast(2))",
-                spacingAfter: 31,
-                isInitiallyOn: isSharingPhone
-            )
-
-            items.append(drawerPhone)
-
-            drawerPhone.isOnPublisher
-                .receive(on: DispatchQueue.main)
-                .sink { [weak self] in self?.isSharingPhone = $0 }
-                .store(in: &drawerCancellables)
+      }.store(in: &drawerCancellables)
+
+    drawerCancelButton
+      .action
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
         }
-
-        let drawerSendButton = DrawerCapsuleButton(
-            model: .init(
-                title: Localized.Ud.RequestDrawer.send,
-                style: .brandColored
-            ), spacingAfter: 5
-        )
-
-        let drawerCancelButton = DrawerCapsuleButton(
-            model: .init(
-                title: Localized.Ud.RequestDrawer.cancel,
-                style: .simplestColoredBrand
-            ), spacingAfter: 5
-        )
-
-        items.append(contentsOf: [drawerSendButton, drawerCancelButton])
-        let drawer = DrawerController(with: items)
-
-        drawerSendButton.action
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                drawer.dismiss(animated: true) {
-                    self.viewModel.didTapRequest(contact: contact)
-                }
-            }.store(in: &drawerCancellables)
-
-        drawerCancelButton.action
-            .receive(on: DispatchQueue.main)
-            .sink { drawer.dismiss(animated: true) }
-            .store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(
+      items: items,
+      isDismissable: true,
+      from: self
+    ))
+  }
 }
 
 extension SearchLeftController: UITableViewDelegate {
-    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-        if let item = dataSource.itemIdentifier(for: indexPath) {
-            switch item {
-            case .stranger(let contact):
-                didTap(contact: contact)
-            case .connection(let contact):
-                didTap(contact: contact)
-            }
-        }
+  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+    if let item = dataSource.itemIdentifier(for: indexPath) {
+      switch item {
+      case .stranger(let contact):
+        didTap(contact: contact)
+      case .connection(let contact):
+        didTap(contact: contact)
+      }
     }
+  }
 
-    func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
-        (view as! UITableViewHeaderFooterView).textLabel?.textColor = Asset.neutralWeak.color
-    }
-
-    private func didTap(contact: Contact) {
-        guard contact.authStatus == .stranger else {
-            coordinator.toContact(contact, from: self)
-            return
-        }
+  func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
+    (view as! UITableViewHeaderFooterView).textLabel?.textColor = Asset.neutralWeak.color
+  }
 
-        presentRequestDrawer(forContact: contact)
+  private func didTap(contact: Contact) {
+    guard contact.authStatus == .stranger else {
+      navigator.perform(PresentContact(contact: contact, on: navigationController!))
+      return
     }
+
+    presentRequestDrawer(forContact: contact)
+  }
 }
diff --git a/Sources/SearchFeature/Controllers/SearchRightController.swift b/Sources/SearchFeature/Controllers/SearchRightController.swift
index 35240054497992ff2b5a277618321dceffeb652a..3445f2266bc8a43dd479eb7cfe2bb9287e49e34b 100644
--- a/Sources/SearchFeature/Controllers/SearchRightController.swift
+++ b/Sources/SearchFeature/Controllers/SearchRightController.swift
@@ -1,81 +1,88 @@
 import UIKit
 import Combine
-import DependencyInjection
+import AppNavigation
+import ComposableArchitecture
 
 final class SearchRightController: UIViewController {
-    @Dependency var coordinator: SearchCoordinating
+  @Dependency(\.navigator) var navigator: Navigator
 
-    lazy private var screenView = SearchRightView()
+  private lazy var screenView = SearchRightView()
 
-    private var cancellables = Set<AnyCancellable>()
-    private let cameraController = CameraController()
-    private(set) var viewModel = SearchRightViewModel()
+  private var cancellables = Set<AnyCancellable>()
+  private let cameraController = CameraController()
+  private(set) var viewModel = SearchRightViewModel()
 
-    override func loadView() {
-        view = screenView
-    }
+  override func loadView() {
+    view = screenView
+  }
 
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        screenView.layer.insertSublayer(cameraController.previewLayer, at: 0)
-        setupBindings()
-    }
+  override func viewDidLayoutSubviews() {
+    super.viewDidLayoutSubviews()
+    cameraController.previewLayer.frame = screenView.bounds
+  }
 
-    override func viewDidLayoutSubviews() {
-        super.viewDidLayoutSubviews()
-        cameraController.previewLayer.frame = screenView.bounds
-    }
+  override func viewWillDisappear(_ animated: Bool) {
+    super.viewWillDisappear(animated)
+    viewModel.viewWillDisappear()
+  }
 
-    override func viewWillDisappear(_ animated: Bool) {
-        super.viewWillDisappear(animated)
-        viewModel.viewWillDisappear()
-    }
+  override func viewDidLoad() {
+    super.viewDidLoad()
+    screenView.layer.insertSublayer(
+      cameraController.previewLayer, at: 0
+    )
 
-    private func setupBindings() {
-        cameraController
-            .dataPublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in viewModel.didScan(data: $0) }
-            .store(in: &cancellables)
+    cameraController
+      .dataPublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        viewModel.didScan(data: $0)
+      }.store(in: &cancellables)
 
-        viewModel.cameraSemaphorePublisher
-            .removeDuplicates()
-            .receive(on: DispatchQueue.global())
-            .sink { [unowned self] setOn in
-                if setOn {
-                    cameraController.start()
-                } else {
-                    cameraController.stop()
-                }
-            }.store(in: &cancellables)
+    viewModel
+      .cameraSemaphorePublisher
+      .removeDuplicates()
+      .receive(on: DispatchQueue.global())
+      .sink { [unowned self] in
+        if $0 {
+          cameraController.start()
+        } else {
+          cameraController.stop()
+        }
+      }.store(in: &cancellables)
 
-        viewModel.foundPublisher
-            .receive(on: DispatchQueue.main)
-            .delay(for: 1, scheduler: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toContact($0, from: self) }
-            .store(in: &cancellables)
+    viewModel
+      .foundPublisher
+      .receive(on: DispatchQueue.main)
+      .delay(for: 1, scheduler: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentContact(contact: $0, on: navigationController!))
+      }.store(in: &cancellables)
 
-        viewModel.statusPublisher
-            .receive(on: DispatchQueue.main)
-            .removeDuplicates()
-            .sink { [unowned self] in screenView.update(status: $0) }
-            .store(in: &cancellables)
+    viewModel
+      .statusPublisher
+      .receive(on: DispatchQueue.main)
+      .removeDuplicates()
+      .sink { [unowned self] in
+        screenView.update(status: $0)
+      }.store(in: &cancellables)
 
-        screenView.actionButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                switch viewModel.statusSubject.value {
-                case .failed(.cameraPermission):
-                    guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
-                    UIApplication.shared.open(url, options: [:])
-                case .failed(.requestOpened):
-                    coordinator.toRequests(from: self)
-                case .failed(.alreadyFriends):
-                    coordinator.toContacts(from: self)
-                default:
-                    break
-                }
-            }.store(in: &cancellables)
-    }
+    screenView
+      .actionButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        switch viewModel.statusSubject.value {
+        case .failed(.cameraPermission):
+          guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
+          UIApplication.shared.open(url, options: [:])
+        case .failed(.requestOpened):
+          navigator.perform(PresentRequests(on: navigationController!))
+        case .failed(.alreadyFriends):
+          navigator.perform(PresentContactList(on: navigationController!))
+        default:
+          break
+        }
+      }.store(in: &cancellables)
+  }
 }
diff --git a/Sources/SearchFeature/Coordinator/SearchCoordinator.swift b/Sources/SearchFeature/Coordinator/SearchCoordinator.swift
deleted file mode 100644
index 21a9d7b3d5eb9b1cef283a410135157fb7571ef1..0000000000000000000000000000000000000000
--- a/Sources/SearchFeature/Coordinator/SearchCoordinator.swift
+++ /dev/null
@@ -1,84 +0,0 @@
-import UIKit
-import Models
-import XXModels
-import Countries
-import Presentation
-import ScrollViewController
-
-public protocol SearchCoordinating {
-    func toRequests(from: UIViewController)
-    func toContacts(from: UIViewController)
-    func toContact(_: Contact, from: UIViewController)
-    func toDrawer(_: UIViewController, from: UIViewController)
-    func toNicknameDrawer(_: UIViewController, from: UIViewController)
-    func toCountries(from: UIViewController, _: @escaping (Country) -> Void)
-}
-
-public struct SearchCoordinator {
-    var pushPresenter: Presenting = PushPresenter()
-    var bottomPresenter: Presenting = BottomPresenter()
-    var replacePresenter: Presenting = ReplacePresenter()
-    var fullscreenPresenter: Presenting = FullscreenPresenter()
-
-    var contactsFactory: () -> UIViewController
-    var requestsFactory: () -> UIViewController
-    var contactFactory: (Contact) -> UIViewController
-    var countriesFactory: (@escaping (Country) -> Void) -> UIViewController
-
-    public init(
-        contactsFactory: @escaping () -> UIViewController,
-        requestsFactory: @escaping () -> UIViewController,
-        contactFactory: @escaping (Contact) -> UIViewController,
-        countriesFactory: @escaping (@escaping (Country) -> Void) -> UIViewController
-    ) {
-        self.contactFactory = contactFactory
-        self.contactsFactory = contactsFactory
-        self.requestsFactory = requestsFactory
-        self.countriesFactory = countriesFactory
-    }
-}
-
-extension SearchCoordinator: SearchCoordinating {
-    public func toRequests(from parent: UIViewController) {
-        let screen = requestsFactory()
-        replacePresenter.present(screen, from: parent)
-    }
-
-    public func toContacts(from parent: UIViewController) {
-        let screen = contactsFactory()
-        replacePresenter.present(screen, from: parent)
-    }
-
-    public func toContact(_ contact: Contact, from parent: UIViewController) {
-        let screen = contactFactory(contact)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    public func toDrawer(_ drawer: UIViewController, from parent: UIViewController) {
-        bottomPresenter.present(drawer, from: parent)
-    }
-
-    public func toCountries(from parent: UIViewController, _ onChoose: @escaping (Country) -> Void) {
-        let screen = countriesFactory(onChoose)
-        pushPresenter.present(screen, from: parent)
-    }
-
-    public func toNicknameDrawer(_ target: UIViewController, from parent: UIViewController) {
-        let screen = ScrollViewController.embedding(target)
-        fullscreenPresenter.present(screen, from: parent)
-    }
-}
-
-extension ScrollViewController {
-    static func embedding(_ viewController: UIViewController) -> ScrollViewController {
-        let scrollViewController = ScrollViewController()
-        scrollViewController.addChild(viewController)
-        scrollViewController.contentView = viewController.view
-        scrollViewController.wrapperView.handlesTouchesOutsideContent = false
-        scrollViewController.wrapperView.alignContentToBottom = true
-        scrollViewController.scrollView.bounces = false
-
-        viewController.didMove(toParent: scrollViewController)
-        return scrollViewController
-    }
-}
diff --git a/Sources/SearchFeature/ViewModels/SearchContainerViewModel.swift b/Sources/SearchFeature/ViewModels/SearchContainerViewModel.swift
index e308f6a5b21134224ec17155b9585a9718cf1204..1571afdae49af23f77a166b50518bffe21f92582 100644
--- a/Sources/SearchFeature/ViewModels/SearchContainerViewModel.swift
+++ b/Sources/SearchFeature/ViewModels/SearchContainerViewModel.swift
@@ -1,58 +1,51 @@
 import UIKit
 import Combine
 import Defaults
-import Integration
-import PushFeature
-import DependencyInjection
+import XXClient
+import PermissionsFeature
+import ComposableArchitecture
 
 final class SearchContainerViewModel {
-    @Dependency var session: SessionType
-    @Dependency var pushHandler: PushHandling
-
-    @KeyObject(.dummyTrafficOn, defaultValue: false) var isCoverTrafficEnabled
-    @KeyObject(.pushNotifications, defaultValue: false) var pushNotifications
-    @KeyObject(.askedDummyTrafficOnce, defaultValue: false) var offeredCoverTraffic
-
-    var coverTrafficPublisher: AnyPublisher<Void, Never> {
-        coverTrafficSubject.eraseToAnyPublisher()
-    }
-
-    private let coverTrafficSubject = PassthroughSubject<Void, Never>()
-
-    func didAppear() {
-        verifyCoverTraffic()
-        verifyNotifications()
-    }
-
-    func didEnableCoverTraffic() {
-        isCoverTrafficEnabled = true
-        session.setDummyTraffic(status: true)
-    }
-
-    private func verifyCoverTraffic() {
-        guard offeredCoverTraffic == false else { return }
-        offeredCoverTraffic = true
-        coverTrafficSubject.send()
-    }
-
-    private func verifyNotifications() {
-        guard pushNotifications == false else { return }
-
-        pushHandler.requestAuthorization { [weak self] result in
-            guard let self = self else { return }
-
-            switch result {
-            case .success(let granted):
-                if granted {
-                    DispatchQueue.main.async {
-                        UIApplication.shared.registerForRemoteNotifications()
-                    }
-                }
-
-                self.pushNotifications = granted
-            case .failure:
-                self.pushNotifications = false
-            }
+  @Dependency(\.permissions) var permissions: PermissionsManager
+  //@Dependency(\.app.dummyTraffic) var dummyTraffic: DummyTraffic
+
+  @KeyObject(.dummyTrafficOn, defaultValue: false) var dummyTrafficOn
+  @KeyObject(.pushNotifications, defaultValue: false) var pushNotifications
+  @KeyObject(.askedDummyTrafficOnce, defaultValue: false) var offeredCoverTraffic
+
+  var coverTrafficPublisher: AnyPublisher<Void, Never> {
+    coverTrafficSubject.eraseToAnyPublisher()
+  }
+
+  private let coverTrafficSubject = PassthroughSubject<Void, Never>()
+
+  func didAppear() {
+    verifyCoverTraffic()
+    verifyNotifications()
+  }
+
+  func didEnableCoverTraffic() {
+//    try! dummyTraffic.setStatus(true)
+    dummyTrafficOn = true
+  }
+
+  private func verifyCoverTraffic() {
+    guard offeredCoverTraffic == false else { return }
+    offeredCoverTraffic = true
+    coverTrafficSubject.send()
+  }
+
+  private func verifyNotifications() {
+    guard pushNotifications == false else { return }
+
+    permissions.push.request { [weak self] granted in
+      guard let self else { return }
+      if granted == true {
+        DispatchQueue.main.async {
+          UIApplication.shared.registerForRemoteNotifications()
         }
+      }
+      self.pushNotifications = granted
     }
+  }
 }
diff --git a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
index 6f708401cc71d12838efcc64ca594818ecf2ae42..9a9aacb84b1b125233beb46f38601296a4f7df6d 100644
--- a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
+++ b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
@@ -1,188 +1,377 @@
-import HUD
+import Retry
 import UIKit
 import Shared
 import Combine
 import XXModels
+import XXClient
 import Defaults
-import Countries
-import Integration
-import NetworkMonitor
+import CustomDump
+import AppResources
 import ReportingFeature
-import DependencyInjection
+import CombineSchedulers
+import XXMessengerClient
+import CountryListFeature
+import Dependencies
+import AppCore
 
 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 session: SessionType
-    @Dependency var reportingStatus: ReportingStatus
-    @Dependency var networkMonitor: NetworkMonitoring
-
-    var hudPublisher: AnyPublisher<HUDStatus, Never> {
-        hudSubject.eraseToAnyPublisher()
+  @Dependency(\.app.dbManager) var dbManager
+  @Dependency(\.app.messenger) var messenger
+  @Dependency(\.app.hudManager) var hudManager
+  @Dependency(\.app.toastManager) var toastManager
+  @Dependency(\.reportingStatus) var reportingStatus
+  @Dependency(\.app.networkMonitor) var networkMonitor
+
+  @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
+      hudManager.show(.init(
+        actionTitle: Localized.Ud.Search.cancel,
+        hasDotAnimation: true,
+        onTapClosure: { [weak self] in
+          guard let self else { return }
+          self.didTapCancelSearch()
+        }
+      ))
+
+      networkCancellable.removeAll()
+
+//      networkMonitor
+//        .statusPublisher
+//        .first { $0 == .available }
+//        .eraseToAnyPublisher()
+//        .flatMap { _ in
+//          self.waitForNodes(timeout: 5)
+//        }.sink(receiveCompletion: {
+//          if case .failure(let error) = $0 {
+//            self.hudManager.show(.init(error: error))
+//          }
+//        }, receiveValue: {
+//          self.didStartSearching()
+//        }).store(in: &networkCancellable)
+    }
+  }
+
+  func didEnterInput(_ string: String) {
+    stateSubject.value.input = string
+  }
+
+  func didPick(country: Country) {
+    stateSubject.value.country = country
+  }
+
+  func didSelectItem(_ item: SearchSegmentedControl.Item) {
+    stateSubject.value.item = item
+  }
+
+  func didTapCancelSearch() {
+    searchCancellables.forEach { $0.cancel() }
+    searchCancellables.removeAll()
+    hudManager.hide()
+  }
+
+  func didStartSearching() {
+    guard stateSubject.value.input.isEmpty == false else { return }
+
+    hudManager.show(.init(
+      actionTitle: Localized.Ud.Search.cancel,
+      hasDotAnimation: true,
+      onTapClosure: { [weak self] in
+        guard let self else { return }
+        self.didTapCancelSearch()
+      }
+    ))
+
+    var content = stateSubject.value.input
+
+    if stateSubject.value.item == .phone {
+      content += stateSubject.value.country.code
     }
 
-    var successPublisher: AnyPublisher<Contact, Never> {
-        successSubject.eraseToAnyPublisher()
+    enum NodeRegistrationError: Error {
+      case unhealthyNet
+      case belowMinimum
     }
 
-    var statePublisher: AnyPublisher<SearchLeftViewState, Never> {
-        stateSubject.eraseToAnyPublisher()
+    retry(max: 5, retryStrategy: .delay(seconds: 2)) { [weak self] in
+      guard let 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 else { return }
+
+      if case .unhealthyNet = $0 as? NodeRegistrationError {
+        self.hudManager.show(.init(content: "Network is not healthy yet, try again within the next minute or so."))
+      } else if case .belowMinimum = $0 as? NodeRegistrationError {
+        self.hudManager.show(.init(content:"Node registration ratio is still below 80%, try again within the next minute or so."))
+      } else {
+        self.hudManager.show(.init(error: $0))
+      }
+
+      return
     }
 
-    private var invitation: String?
-    private var searchCancellables = Set<AnyCancellable>()
-    private let successSubject = PassthroughSubject<Contact, Never>()
-    private let hudSubject = CurrentValueSubject<HUDStatus, Never>(.none)
-    private let stateSubject = CurrentValueSubject<SearchLeftViewState, Never>(.init())
-    private var networkCancellable = Set<AnyCancellable>()
+    var factType: FactType = .username
 
-    init(_ invitation: String? = nil) {
-        self.invitation = invitation
+    if stateSubject.value.item == .phone {
+      factType = .phone
+    } else if stateSubject.value.item == .email {
+      factType = .email
     }
 
-    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.session.waitForNodes(timeout: 5) }
-                .sink {
-                    if case .failure(let error) = $0 {
-                        self.hudSubject.send(.error(.init(with: error)))
-                    }
-                } receiveValue: {
-                    self.didStartSearching()
-                }.store(in: &networkCancellable)
-        }
-    }
+    backgroundScheduler.schedule { [weak self] in
+      guard let 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.hudManager.hide()
+              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.hudManager.show(.init(error: error))
+            }
+          })
+        )
 
-    func didEnterInput(_ string: String) {
-        stateSubject.value.input = string
+        print(">>> UDSearch.Report: \(report))")
+      } catch {
+        print(">>> UDSearch.Exception: \(error.localizedDescription)")
+      }
     }
+  }
 
-    func didPick(country: Country) {
-        stateSubject.value.country = country
-    }
+  func didTapResend(contact: XXModels.Contact) {
+    hudManager.show()
 
-    func didSelectItem(_ item: SearchSegmentedControl.Item) {
-        stateSubject.value.item = item
-    }
+    var contact = contact
+    contact.authStatus = .requesting
 
-    func didTapCancelSearch() {
-        searchCancellables.forEach { $0.cancel() }
-        searchCancellables.removeAll()
-        hudSubject.send(.none)
-    }
+    backgroundScheduler.schedule { [weak self] in
+      guard let self else { return }
 
-    func didStartSearching() {
-        guard stateSubject.value.input.isEmpty == false else { return }
+      do {
+        try self.dbManager.getDB().saveContact(contact)
 
-        hudSubject.send(.onAction(Localized.Ud.Search.cancel))
+        var includedFacts: [Fact] = []
+        let myFacts = try self.messenger.ud.get()!.getFacts()
 
-        var content = stateSubject.value.input
-        let prefix = stateSubject.value.item.written.first!.uppercased()
+        if let fact = myFacts.get(.username) {
+          includedFacts.append(fact)
+        }
 
-        if stateSubject.value.item == .phone {
-            content += stateSubject.value.country.code
+        if self.sharingEmail, let fact = myFacts.get(.email) {
+          includedFacts.append(fact)
         }
 
-        session.search(fact: "\(prefix)\(content)")
-            .sink { [unowned self] in
-                if case .failure(let error) = $0 {
-                    self.appendToLocalSearch(nil)
-                    self.hudSubject.send(.error(.init(with: error)))
-                }
-            } receiveValue: { contact in
-                self.hudSubject.send(.none)
-                self.appendToLocalSearch(contact)
-            }.store(in: &searchCancellables)
-    }
+        if self.sharingPhone, let fact = myFacts.get(.phone) {
+          includedFacts.append(fact)
+        }
 
-    func didTapResend(contact: Contact) {
-        hudSubject.send(.on)
+        let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
+          partner: .live(contact.marshaled!),
+          myFacts: includedFacts
+        )
 
-        do {
-            try self.session.retryRequest(contact)
-            hudSubject.send(.none)
-        } catch {
-            hudSubject.send(.error(.init(with: error)))
-        }
-    }
+        contact.authStatus = .requested
+        contact = try self.dbManager.getDB().saveContact(contact)
 
-    func didTapRequest(contact: Contact) {
-        hudSubject.send(.on)
-        var contact = contact
-        contact.nickname = contact.username
-
-        do {
-            try self.session.add(contact)
-            hudSubject.send(.none)
-            successSubject.send(contact)
-        } catch {
-            hudSubject.send(.error(.init(with: error)))
-        }
+        self.hudManager.hide()
+        self.presentSuccessToast(for: contact, resent: true)
+      } catch {
+        contact.authStatus = .requestFailed
+        _ = try? self.dbManager.getDB().saveContact(contact)
+        self.hudManager.show(.init(error: error))
+      }
     }
+  }
 
-    func didSet(nickname: String, for contact: Contact) {
-        if var contact = try? session.dbManager.fetchContacts(.init(id: [contact.id])).first {
-            contact.nickname = nickname
-            _ = try? session.dbManager.saveContact(contact)
-        }
-    }
+  func didTapRequest(contact: XXModels.Contact) {
+    hudManager.show()
 
-    private func appendToLocalSearch(_ user: Contact?) {
-        var snapshot = SearchSnapshot()
+    var contact = contact
+    contact.nickname = contact.username
+    contact.authStatus = .requesting
 
-        if var user = user {
-            if let contact = try! session.dbManager.fetchContacts(.init(id: [user.id])).first {
-                user.isBanned = contact.isBanned
-                user.isBlocked = contact.isBlocked
-                user.authStatus = contact.authStatus
-            }
+    backgroundScheduler.schedule { [weak self] in
+      guard let self else { return }
 
-            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)
-            }
+      do {
+        try self.dbManager.getDB().saveContact(contact)
+
+        var includedFacts: [Fact] = []
+        let myFacts = try self.messenger.ud.get()!.getFacts()
+
+        if let fact = myFacts.get(.username) {
+          includedFacts.append(fact)
         }
 
-        let localsQuery = Contact.Query(
-            text: stateSubject.value.input,
-            authStatus: [.friend],
-            isBlocked: reportingStatus.isEnabled() ? false : nil,
-            isBanned: reportingStatus.isEnabled() ? false : nil
-        )
+        if self.sharingEmail, let fact = myFacts.get(.email) {
+          includedFacts.append(fact)
+        }
 
-        if let locals = try? session.dbManager.fetchContacts(localsQuery),
-           let localsWithoutMe = removeMyself(from: locals),
-           localsWithoutMe.isEmpty == false {
-            snapshot.appendSections([.connections])
-            snapshot.appendItems(
-                localsWithoutMe.map(SearchItem.connection),
-                toSection: .connections
-            )
+        if self.sharingPhone, let fact = myFacts.get(.phone) {
+          includedFacts.append(fact)
         }
 
-        stateSubject.value.snapshot = snapshot
+        let _ = try self.messenger.e2e.get()!.requestAuthenticatedChannel(
+          partner: .live(contact.marshaled!),
+          myFacts: includedFacts
+        )
+
+        contact.authStatus = .requested
+        contact = try self.dbManager.getDB().saveContact(contact)
+
+        self.hudManager.hide()
+        self.successSubject.send(contact)
+        self.presentSuccessToast(for: contact, resent: false)
+      } catch {
+        contact.authStatus = .requestFailed
+        _ = try? self.dbManager.getDB().saveContact(contact)
+        self.hudManager.show(.init(error: error))
+      }
+    }
+  }
+
+  func didSet(nickname: String, for contact: XXModels.Contact) {
+    if var contact = try? dbManager.getDB().fetchContacts(.init(id: [contact.id])).first {
+      contact.nickname = nickname
+      _ = try? dbManager.getDB().saveContact(contact)
+    }
+  }
+
+  private func appendToLocalSearch(_ user: XXModels.Contact?) {
+    var snapshot = SearchSnapshot()
+
+    if var user = user {
+      if let contact = try? dbManager.getDB().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 removeMyself(from collection: [Contact]) -> [Contact]? {
-        collection.filter { $0.id != session.myId }
+    let localsQuery = Contact.Query(
+      text: stateSubject.value.input,
+      authStatus: [.friend],
+      isBlocked: reportingStatus.isEnabled() ? false : nil,
+      isBanned: reportingStatus.isEnabled() ? false : nil
+    )
+
+    if let locals = try? dbManager.getDB().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 ?? "")
+
+    toastManager.enqueue(.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 else { return }
+          _ = try self.messenger.cMix.get()!.getNodeRegistrationStatus()
+          promise(.success(()))
+        }.finalCatch {
+          promise(.failure($0))
+        }
+      }
+    }.eraseToAnyPublisher()
+  }
 }
diff --git a/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift
index db977528e9a65491ada83947cdad167897532982..12dab510ef00415aaa67d419f2f3c3e75000540e 100644
--- a/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift
+++ b/Sources/SearchFeature/ViewModels/SearchRightViewModel.swift
@@ -1,124 +1,135 @@
 import Shared
 import Combine
+import AppCore
 import XXModels
 import Defaults
+import XXClient
 import Foundation
-import Permissions
-import Integration
+import AppResources
 import ReportingFeature
-import DependencyInjection
+import XXMessengerClient
+import PermissionsFeature
+import ComposableArchitecture
 
 enum ScanningStatus: Equatable {
-    case reading
-    case processing
-    case success
-    case failed(ScanningError)
+  case reading
+  case processing
+  case success
+  case failed(ScanningError)
 }
 
 enum ScanningError: Equatable {
-    case requestOpened
-    case unknown(String)
-    case cameraPermission
-    case alreadyFriends(String)
+  case requestOpened
+  case unknown(String)
+  case cameraPermission
+  case alreadyFriends(String)
 }
 
 final class SearchRightViewModel {
-    @Dependency var session: SessionType
-    @Dependency var permissions: PermissionHandling
-    @Dependency var reportingStatus: ReportingStatus
-
-    var foundPublisher: AnyPublisher<Contact, Never> {
-        foundSubject.eraseToAnyPublisher()
-    }
-
-    var cameraSemaphorePublisher: AnyPublisher<Bool, Never> {
-        cameraSemaphoreSubject.eraseToAnyPublisher()
+  @Dependency(\.app.dbManager) var dbManager: DBManager
+  @Dependency(\.permissions) var permissions: PermissionsManager
+  @Dependency(\.reportingStatus) var reportingStatus: ReportingStatus
+
+  var foundPublisher: AnyPublisher<XXModels.Contact, Never> {
+    foundSubject.eraseToAnyPublisher()
+  }
+
+  var cameraSemaphorePublisher: AnyPublisher<Bool, Never> {
+    cameraSemaphoreSubject.eraseToAnyPublisher()
+  }
+
+  var statusPublisher: AnyPublisher<ScanningStatus, Never> {
+    statusSubject.eraseToAnyPublisher()
+  }
+
+  private let foundSubject = PassthroughSubject<XXModels.Contact, Never>()
+  private let cameraSemaphoreSubject = PassthroughSubject<Bool, Never>()
+  private(set) var statusSubject = CurrentValueSubject<ScanningStatus, Never>(.reading)
+
+  func viewWillAppear() {
+    permissions.camera.request { [weak self] granted in
+      guard let self else { return }
+
+      if granted {
+        self.statusSubject.value = .reading
+        self.cameraSemaphoreSubject.send(true)
+      } else {
+        self.statusSubject.send(.failed(.cameraPermission))
+      }
     }
-
-    var statusPublisher: AnyPublisher<ScanningStatus, Never> {
-        statusSubject.eraseToAnyPublisher()
+  }
+
+  func viewWillDisappear() {
+    cameraSemaphoreSubject.send(false)
+  }
+
+  func didScan(data: Data) {
+    /// We need to be accepting new readings in order
+    /// to process what just got scanned.
+    ///
+    guard statusSubject.value == .reading else { return }
+    statusSubject.send(.processing)
+
+    /// Whatever got scanned, needs to have id and username
+    /// otherwise is just noise or an unknown qr code
+    ///
+    let user = XXClient.Contact.live(data)
+
+    guard
+      let uid = try? user.getId(),
+      let facts = try? user.getFacts(),
+      let username = facts.first(where: { $0.type == .username })?.value
+    else {
+      let errorTitle = Localized.Scan.Error.invalid
+      statusSubject.send(.failed(.unknown(errorTitle)))
+      return
     }
 
-    private let foundSubject = PassthroughSubject<Contact, Never>()
-    private let cameraSemaphoreSubject = PassthroughSubject<Bool, Never>()
-    private(set) var statusSubject = CurrentValueSubject<ScanningStatus, Never>(.reading)
-
-    func viewWillAppear() {
-        permissions.requestCamera { [weak self] granted in
-            guard let self = self else { return }
-
-            if granted {
-                self.statusSubject.value = .reading
-                self.cameraSemaphoreSubject.send(true)
-            } else {
-                self.statusSubject.send(.failed(.cameraPermission))
-            }
-        }
+    let email = facts.first { $0.type == .email }?.value
+    let phone = facts.first { $0.type == .phone }?.value
+
+    /// Make sure we are not processing a contact
+    /// that we already have
+    ///
+    if let alreadyContact = try? dbManager.getDB().fetchContacts(.init(id: [uid])).first {
+      if alreadyContact.isBlocked, reportingStatus.isEnabled() {
+        statusSubject.send(.failed(.unknown("You previously blocked this user.")))
+        return
+      }
+
+      if alreadyContact.isBanned, reportingStatus.isEnabled() {
+        statusSubject.send(.failed(.unknown("This user was banned.")))
+        return
+      }
+
+      /// Show error accordingly to the auth status
+      ///
+      if alreadyContact.authStatus == .friend {
+        statusSubject.send(.failed(.alreadyFriends(username)))
+      } else if [.requested, .verified].contains(alreadyContact.authStatus) {
+        statusSubject.send(.failed(.requestOpened))
+      } else {
+        let generalErrorTitle = Localized.Scan.Error.general
+        statusSubject.send(.failed(.unknown(generalErrorTitle)))
+      }
+
+      return
     }
 
-    func viewWillDisappear() {
-        cameraSemaphoreSubject.send(false)
-    }
-
-    func didScan(data: Data) {
-        /// We need to be accepting new readings in order
-        /// to process what just got scanned.
-        ///
-        guard statusSubject.value == .reading else { return }
-        statusSubject.send(.processing)
-
-        /// Whatever got scanned, needs to have id and username
-        /// otherwise is just noise or an unknown qr code
-        ///
-        guard let userId = session.getId(from: data),
-              let username = try? session.extract(fact: .username, from: data) else {
-            let errorTitle = Localized.Scan.Error.invalid
-            statusSubject.send(.failed(.unknown(errorTitle)))
-            return
-        }
-
-        /// Make sure we are not processing a contact
-        /// that we already have
-        ///
-        if let alreadyContact = try? session.dbManager.fetchContacts(.init(id: [userId])).first {
-            if alreadyContact.isBlocked, reportingStatus.isEnabled() {
-                statusSubject.send(.failed(.unknown("You previously blocked this user.")))
-                return
-            }
-
-            if alreadyContact.isBanned, reportingStatus.isEnabled() {
-                statusSubject.send(.failed(.unknown("This user was banned.")))
-                return
-            }
-
-            /// Show error accordingly to the auth status
-            ///
-            if alreadyContact.authStatus == .friend {
-                statusSubject.send(.failed(.alreadyFriends(username)))
-            } else if [.requested, .verified].contains(alreadyContact.authStatus) {
-                statusSubject.send(.failed(.requestOpened))
-            } else {
-                let generalErrorTitle = Localized.Scan.Error.general
-                statusSubject.send(.failed(.unknown(generalErrorTitle)))
-            }
-
-            return
-        }
-
-        statusSubject.send(.success)
-        cameraSemaphoreSubject.send(false)
-
-        foundSubject.send(.init(
-            id: userId,
-            marshaled: data,
-            username: username,
-            email: try? session.extract(fact: .email, from: data),
-            phone: try? session.extract(fact: .phone, from: data),
-            nickname: nil,
-            photo: nil,
-            authStatus: .stranger,
-            isRecent: false,
-            createdAt: Date()
-        ))
-    }
+    statusSubject.send(.success)
+    cameraSemaphoreSubject.send(false)
+
+    foundSubject.send(.init(
+      id: uid,
+      marshaled: data,
+      username: username,
+      email: email,
+      phone: phone,
+      nickname: nil,
+      photo: nil,
+      authStatus: .stranger,
+      isRecent: false,
+      createdAt: Date()
+    ))
+  }
 }
diff --git a/Sources/SearchFeature/Views/OverlayView.swift b/Sources/SearchFeature/Views/OverlayView.swift
index 8242857716936fe49cf21a59bad280195c726165..4e85a3ea143885bd0ed21e1cb236c126a6ba9843 100644
--- a/Sources/SearchFeature/Views/OverlayView.swift
+++ b/Sources/SearchFeature/Views/OverlayView.swift
@@ -1,181 +1,182 @@
 import UIKit
 import Shared
+import AppResources
 
 final class OverlayView: UIView {
-    private let cropView = UIView()
-    private let scanViewLength = 266.0
-    private let maskLayer = CAShapeLayer()
-    private let topLeftLayer = CAShapeLayer()
-    private let topRightLayer = CAShapeLayer()
-    private let bottomLeftLayer = CAShapeLayer()
-    private let bottomRightLayer = CAShapeLayer()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralDark.color.withAlphaComponent(0.5)
-
-        addSubview(cropView)
-
-        cropView.snp.makeConstraints {
-            $0.width.equalTo(scanViewLength)
-            $0.centerY.equalToSuperview().offset(-50)
-            $0.centerX.equalToSuperview()
-            $0.height.equalTo(scanViewLength)
-        }
-
-        maskLayer.fillRule = .evenOdd
-        layer.mask = maskLayer
-        layer.masksToBounds = true
-
-        [topLeftLayer, topRightLayer, bottomLeftLayer, bottomRightLayer].forEach {
-            $0.strokeColor = Asset.brandPrimary.color.cgColor
-            $0.fillColor = UIColor.clear.cgColor
-            $0.lineWidth = 3.0
-            $0.lineCap = .round
-            layer.addSublayer($0)
-        }
+  private let cropView = UIView()
+  private let scanViewLength = 266.0
+  private let maskLayer = CAShapeLayer()
+  private let topLeftLayer = CAShapeLayer()
+  private let topRightLayer = CAShapeLayer()
+  private let bottomLeftLayer = CAShapeLayer()
+  private let bottomRightLayer = CAShapeLayer()
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralDark.color.withAlphaComponent(0.5)
+
+    addSubview(cropView)
+
+    cropView.snp.makeConstraints {
+      $0.width.equalTo(scanViewLength)
+      $0.centerY.equalToSuperview().offset(-50)
+      $0.centerX.equalToSuperview()
+      $0.height.equalTo(scanViewLength)
     }
 
-    required init?(coder: NSCoder) { nil }
+    maskLayer.fillRule = .evenOdd
+    layer.mask = maskLayer
+    layer.masksToBounds = true
 
-    override func layoutSubviews() {
-        super.layoutSubviews()
-
-        maskLayer.frame = bounds
-        let path = UIBezierPath(rect: bounds)
-        path.append(UIBezierPath(roundedRect: cropView.frame, cornerRadius: 30.0))
-        maskLayer.path = path.cgPath
-
-        topLeftLayer.frame = bounds
-        topRightLayer.frame = bounds
-        bottomRightLayer.frame = bounds
-        bottomLeftLayer.frame = bounds
-
-        topLeftLayer.path = topLeftPath()
-        topRightLayer.path = topRightPath()
-        bottomRightLayer.path = bottomRightPath()
-        bottomLeftLayer.path = bottomLeftPath()
-    }
-
-    func updateCornerColor(_ color: UIColor) {
-        [topLeftLayer, topRightLayer, bottomLeftLayer, bottomRightLayer].forEach {
-            $0.strokeColor = color.cgColor
-        }
-    }
-
-    func topLeftPath() -> CGPath {
-        let path = UIBezierPath()
-
-        let vert0X = cropView.frame.minX - 15
-        let vert0Y = cropView.frame.minY + 45
-        let vert0 = CGPoint(x: vert0X, y: vert0Y)
-        path.move(to: vert0)
-
-        let vertNX = cropView.frame.minX - 15
-        let vertNY = cropView.frame.minY + 15
-        let vertN = CGPoint(x: vertNX, y: vertNY)
-        path.addLine(to: vertN)
-
-        let arcCenterX = cropView.frame.minX + 15
-        let arcCenterY = cropView.frame.minY + 15
-        let arcCenter = CGPoint(x: arcCenterX , y: arcCenterY)
-        path.addArc(center: arcCenter, startAngle: .pi)
-
-        let horizX = cropView.frame.minX + 45
-        let horizY = cropView.frame.minY - 15
-        let horiz = CGPoint(x: horizX, y: horizY)
-        path.addLine(to: horiz)
-
-        return path.cgPath
+    [topLeftLayer, topRightLayer, bottomLeftLayer, bottomRightLayer].forEach {
+      $0.strokeColor = Asset.brandPrimary.color.cgColor
+      $0.fillColor = UIColor.clear.cgColor
+      $0.lineWidth = 3.0
+      $0.lineCap = .round
+      layer.addSublayer($0)
     }
+  }
 
-    func topRightPath() -> CGPath {
-        let path = UIBezierPath()
+  required init?(coder: NSCoder) { nil }
 
-        let horiz0X = cropView.frame.maxX - 45
-        let horiz0Y = cropView.frame.minY - 15
-        let horiz0 = CGPoint(x: horiz0X, y: horiz0Y)
-        path.move(to: horiz0)
+  override func layoutSubviews() {
+    super.layoutSubviews()
 
-        let horizNX = cropView.frame.maxX - 15
-        let horizNY = cropView.frame.minY - 15
-        let horizN = CGPoint(x: horizNX, y: horizNY)
-        path.addLine(to: horizN)
+    maskLayer.frame = bounds
+    let path = UIBezierPath(rect: bounds)
+    path.append(UIBezierPath(roundedRect: cropView.frame, cornerRadius: 30.0))
+    maskLayer.path = path.cgPath
 
-        let arcCenterX = cropView.frame.maxX - 15
-        let arcCenterY = cropView.frame.minY + 15
-        let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
-        path.addArc(center: arcCenter, startAngle: 3 * .pi/2)
+    topLeftLayer.frame = bounds
+    topRightLayer.frame = bounds
+    bottomRightLayer.frame = bounds
+    bottomLeftLayer.frame = bounds
 
-        let vertX = cropView.frame.maxX + 15
-        let vertY = cropView.frame.minY + 45
-        let vert = CGPoint(x: vertX, y: vertY)
-        path.addLine(to: vert)
+    topLeftLayer.path = topLeftPath()
+    topRightLayer.path = topRightPath()
+    bottomRightLayer.path = bottomRightPath()
+    bottomLeftLayer.path = bottomLeftPath()
+  }
 
-        return path.cgPath
-    }
-
-    func bottomRightPath() -> CGPath {
-        let path = UIBezierPath()
-
-        let vert0X = cropView.frame.maxX + 15
-        let vert0Y = cropView.frame.maxY - 45
-        let vert0 = CGPoint(x: vert0X, y: vert0Y)
-        path.move(to: vert0)
-
-        let vertNX = cropView.frame.maxX + 15
-        let vertNY = cropView.frame.maxY - 15
-        let vertN = CGPoint(x: vertNX, y: vertNY)
-        path.addLine(to: vertN)
-
-        let arcCenterX = cropView.frame.maxX - 15
-        let arcCenterY = cropView.frame.maxY - 15
-        let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
-        path.addArc(center: arcCenter, startAngle: 0)
-
-        let horizX = cropView.frame.maxX - 45
-        let horizY = cropView.frame.maxY + 15
-        let horiz = CGPoint(x: horizX, y: horizY)
-        path.addLine(to: horiz)
-
-        return path.cgPath
-    }
-
-    func bottomLeftPath() -> CGPath {
-        let path = UIBezierPath()
-
-        let horiz0X = cropView.frame.minX + 45
-        let horiz0Y = cropView.frame.maxY + 15
-        let horiz0 = CGPoint(x: horiz0X, y: horiz0Y)
-        path.move(to: horiz0)
-
-        let horizNX = cropView.frame.minX + 15
-        let horizNY = cropView.frame.maxY + 15
-        let horizN = CGPoint(x: horizNX, y: horizNY)
-        path.addLine(to: horizN)
-
-        let arcCenterX = cropView.frame.minX + 15
-        let arcCenterY = cropView.frame.maxY - 15
-        let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
-        path.addArc(center: arcCenter, startAngle: .pi/2)
-
-        let vertX = cropView.frame.minX - 15
-        let vertY = cropView.frame.maxY - 45
-        let vert = CGPoint(x: vertX, y: vertY)
-        path.addLine(to: vert)
-
-        return path.cgPath
+  func updateCornerColor(_ color: UIColor) {
+    [topLeftLayer, topRightLayer, bottomLeftLayer, bottomRightLayer].forEach {
+      $0.strokeColor = color.cgColor
     }
+  }
+
+  func topLeftPath() -> CGPath {
+    let path = UIBezierPath()
+
+    let vert0X = cropView.frame.minX - 15
+    let vert0Y = cropView.frame.minY + 45
+    let vert0 = CGPoint(x: vert0X, y: vert0Y)
+    path.move(to: vert0)
+
+    let vertNX = cropView.frame.minX - 15
+    let vertNY = cropView.frame.minY + 15
+    let vertN = CGPoint(x: vertNX, y: vertNY)
+    path.addLine(to: vertN)
+
+    let arcCenterX = cropView.frame.minX + 15
+    let arcCenterY = cropView.frame.minY + 15
+    let arcCenter = CGPoint(x: arcCenterX , y: arcCenterY)
+    path.addArc(center: arcCenter, startAngle: .pi)
+
+    let horizX = cropView.frame.minX + 45
+    let horizY = cropView.frame.minY - 15
+    let horiz = CGPoint(x: horizX, y: horizY)
+    path.addLine(to: horiz)
+
+    return path.cgPath
+  }
+
+  func topRightPath() -> CGPath {
+    let path = UIBezierPath()
+
+    let horiz0X = cropView.frame.maxX - 45
+    let horiz0Y = cropView.frame.minY - 15
+    let horiz0 = CGPoint(x: horiz0X, y: horiz0Y)
+    path.move(to: horiz0)
+
+    let horizNX = cropView.frame.maxX - 15
+    let horizNY = cropView.frame.minY - 15
+    let horizN = CGPoint(x: horizNX, y: horizNY)
+    path.addLine(to: horizN)
+
+    let arcCenterX = cropView.frame.maxX - 15
+    let arcCenterY = cropView.frame.minY + 15
+    let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
+    path.addArc(center: arcCenter, startAngle: 3 * .pi/2)
+
+    let vertX = cropView.frame.maxX + 15
+    let vertY = cropView.frame.minY + 45
+    let vert = CGPoint(x: vertX, y: vertY)
+    path.addLine(to: vert)
+
+    return path.cgPath
+  }
+
+  func bottomRightPath() -> CGPath {
+    let path = UIBezierPath()
+
+    let vert0X = cropView.frame.maxX + 15
+    let vert0Y = cropView.frame.maxY - 45
+    let vert0 = CGPoint(x: vert0X, y: vert0Y)
+    path.move(to: vert0)
+
+    let vertNX = cropView.frame.maxX + 15
+    let vertNY = cropView.frame.maxY - 15
+    let vertN = CGPoint(x: vertNX, y: vertNY)
+    path.addLine(to: vertN)
+
+    let arcCenterX = cropView.frame.maxX - 15
+    let arcCenterY = cropView.frame.maxY - 15
+    let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
+    path.addArc(center: arcCenter, startAngle: 0)
+
+    let horizX = cropView.frame.maxX - 45
+    let horizY = cropView.frame.maxY + 15
+    let horiz = CGPoint(x: horizX, y: horizY)
+    path.addLine(to: horiz)
+
+    return path.cgPath
+  }
+
+  func bottomLeftPath() -> CGPath {
+    let path = UIBezierPath()
+
+    let horiz0X = cropView.frame.minX + 45
+    let horiz0Y = cropView.frame.maxY + 15
+    let horiz0 = CGPoint(x: horiz0X, y: horiz0Y)
+    path.move(to: horiz0)
+
+    let horizNX = cropView.frame.minX + 15
+    let horizNY = cropView.frame.maxY + 15
+    let horizN = CGPoint(x: horizNX, y: horizNY)
+    path.addLine(to: horizN)
+
+    let arcCenterX = cropView.frame.minX + 15
+    let arcCenterY = cropView.frame.maxY - 15
+    let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
+    path.addArc(center: arcCenter, startAngle: .pi/2)
+
+    let vertX = cropView.frame.minX - 15
+    let vertY = cropView.frame.maxY - 45
+    let vert = CGPoint(x: vertX, y: vertY)
+    path.addLine(to: vert)
+
+    return path.cgPath
+  }
 }
 
 private extension UIBezierPath {
-    func addArc(center: CGPoint, startAngle: CGFloat) {
-        addArc(
-            withCenter: center,
-            radius: 30,
-            startAngle: startAngle,
-            endAngle: startAngle + .pi/2,
-            clockwise: true
-        )
-    }
+  func addArc(center: CGPoint, startAngle: CGFloat) {
+    addArc(
+      withCenter: center,
+      radius: 30,
+      startAngle: startAngle,
+      endAngle: startAngle + .pi/2,
+      clockwise: true
+    )
+  }
 }
diff --git a/Sources/SearchFeature/Views/SearchContainerView.swift b/Sources/SearchFeature/Views/SearchContainerView.swift
index 3a270bc5a52891fc915951bf68f738f2e5cdcd6f..878d438a464f7c22006518517f5f534f92c17a77 100644
--- a/Sources/SearchFeature/Views/SearchContainerView.swift
+++ b/Sources/SearchFeature/Views/SearchContainerView.swift
@@ -1,35 +1,32 @@
 import UIKit
 import Shared
+import AppResources
 
 final class SearchContainerView: UIView {
-    let scrollView = UIScrollView()
-    let segmentedControl = SearchSegmentedControl()
+  let scrollView = UIScrollView()
+  let segmentedControl = SearchSegmentedControl()
 
-    init() {
-        super.init(frame: .zero)
+  init() {
+    super.init(frame: .zero)
 
-        backgroundColor = Asset.neutralWhite.color
-        addSubview(segmentedControl)
-        addSubview(scrollView)
+    backgroundColor = Asset.neutralWhite.color
+    addSubview(segmentedControl)
+    addSubview(scrollView)
 
-        setupConstraints()
+    segmentedControl.snp.makeConstraints {
+      $0.top.equalTo(safeAreaLayoutGuide).offset(10)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.height.equalTo(60)
     }
 
-    required init?(coder: NSCoder) { nil }
-
-    private func setupConstraints() {
-        segmentedControl.snp.makeConstraints {
-            $0.top.equalTo(safeAreaLayoutGuide).offset(10)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.height.equalTo(60)
-        }
-
-        scrollView.snp.makeConstraints {
-            $0.top.equalTo(segmentedControl.snp.bottom)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
+    scrollView.snp.makeConstraints {
+      $0.top.equalTo(segmentedControl.snp.bottom)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
     }
+  }
+
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/SearchFeature/Views/SearchLeftEmptyView.swift b/Sources/SearchFeature/Views/SearchLeftEmptyView.swift
index 84c64c87a6096a16b2bbf793a9eadc1c95495324..4f33b053f0a104cd83a8d9c59e5ebbefdb40fc7b 100644
--- a/Sources/SearchFeature/Views/SearchLeftEmptyView.swift
+++ b/Sources/SearchFeature/Views/SearchLeftEmptyView.swift
@@ -1,26 +1,27 @@
 import UIKit
 import Shared
+import AppResources
 
 final class SearchLeftEmptyView: UIView {
-    let titleLabel = UILabel()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        titleLabel.numberOfLines = 0
-        titleLabel.textAlignment = .center
-        titleLabel.font = Fonts.Mulish.regular.font(size: 15.0)
-        titleLabel.textColor = Asset.neutralSecondaryAlternative.color
-
-        addSubview(titleLabel)
-
-        titleLabel.snp.makeConstraints {
-            $0.center.equalToSuperview()
-            $0.left.equalToSuperview().offset(20)
-            $0.right.equalToSuperview().offset(-20)
-        }
+  let titleLabel = UILabel()
+  
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+    
+    titleLabel.numberOfLines = 0
+    titleLabel.textAlignment = .center
+    titleLabel.font = Fonts.Mulish.regular.font(size: 15.0)
+    titleLabel.textColor = Asset.neutralSecondaryAlternative.color
+    
+    addSubview(titleLabel)
+    
+    titleLabel.snp.makeConstraints {
+      $0.center.equalToSuperview()
+      $0.left.equalToSuperview().offset(20)
+      $0.right.equalToSuperview().offset(-20)
     }
-
-    required init?(coder: NSCoder) { nil }
+  }
+  
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/SearchFeature/Views/SearchLeftPlaceholderView.swift b/Sources/SearchFeature/Views/SearchLeftPlaceholderView.swift
index 7742ff1d64c56151a88ae9b5ae47e82ebb59dc1b..fa7f5c4fd37a6ade7e3105bf2f17fd3f45ab6938 100644
--- a/Sources/SearchFeature/Views/SearchLeftPlaceholderView.swift
+++ b/Sources/SearchFeature/Views/SearchLeftPlaceholderView.swift
@@ -1,74 +1,75 @@
 import UIKit
 import Shared
 import Combine
+import AppResources
 
 final class SearchLeftPlaceholderView: UIView {
-    let titleLabel = UILabel()
-    let subtitleWithInfo = TextWithInfoView()
-
-    var infoPublisher: AnyPublisher<Void, Never> {
-        infoSubject.eraseToAnyPublisher()
-    }
-
-    private let infoSubject = PassthroughSubject<Void, Never>()
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        let attrString = NSMutableAttributedString(
-            string: Localized.Ud.Search.Placeholder.title,
-            attributes: [
-                .foregroundColor: Asset.neutralDark.color,
-                .font: Fonts.Mulish.bold.font(size: 32.0)
-            ]
-        )
-
-        attrString.addAttribute(
-            name: .foregroundColor,
-            value: Asset.brandPrimary.color,
-            betweenCharacters: "#"
-        )
-
-        titleLabel.numberOfLines = 0
-        titleLabel.attributedText = attrString
-
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.lineHeightMultiple = 1.3
-
-        subtitleWithInfo.setup(
-            text: Localized.Ud.Search.Placeholder.subtitle,
-            attributes: [
-                .paragraphStyle: paragraph,
-                .foregroundColor: Asset.neutralBody.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0)
-            ],
-            didTapInfo: { [weak self] in
-                guard let self = self else { return }
-                self.infoSubject.send(())
-            }
-        )
-
-        addSubview(titleLabel)
-        addSubview(subtitleWithInfo)
-
-        setupConstraints()
+  let titleLabel = UILabel()
+  let subtitleWithInfo = TextWithInfoView()
+  
+  var infoPublisher: AnyPublisher<Void, Never> {
+    infoSubject.eraseToAnyPublisher()
+  }
+  
+  private let infoSubject = PassthroughSubject<Void, Never>()
+  
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+    
+    let attrString = NSMutableAttributedString(
+      string: Localized.Ud.Search.Placeholder.title,
+      attributes: [
+        .foregroundColor: Asset.neutralDark.color,
+        .font: Fonts.Mulish.bold.font(size: 32.0)
+      ]
+    )
+    
+    attrString.addAttribute(
+      name: .foregroundColor,
+      value: Asset.brandPrimary.color,
+      betweenCharacters: "#"
+    )
+    
+    titleLabel.numberOfLines = 0
+    titleLabel.attributedText = attrString
+    
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.lineHeightMultiple = 1.3
+    
+    subtitleWithInfo.setup(
+      text: Localized.Ud.Search.Placeholder.subtitle,
+      attributes: [
+        .paragraphStyle: paragraph,
+        .foregroundColor: Asset.neutralBody.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0)
+      ],
+      didTapInfo: { [weak self] in
+        guard let self else { return }
+        self.infoSubject.send(())
+      }
+    )
+    
+    addSubview(titleLabel)
+    addSubview(subtitleWithInfo)
+    
+    setupConstraints()
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  private func setupConstraints() {
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(50)
+      $0.left.equalToSuperview().offset(32.5)
+      $0.right.equalToSuperview().offset(-32.5)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    private func setupConstraints() {
-        titleLabel.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(50)
-            $0.left.equalToSuperview().offset(32.5)
-            $0.right.equalToSuperview().offset(-32.5)
-        }
-
-        subtitleWithInfo.snp.makeConstraints {
-            $0.top.equalTo(titleLabel.snp.bottom).offset(30)
-            $0.left.equalToSuperview().offset(32.5)
-            $0.right.equalToSuperview().offset(-32.5)
-            $0.bottom.equalToSuperview()
-        }
+    
+    subtitleWithInfo.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(30)
+      $0.left.equalToSuperview().offset(32.5)
+      $0.right.equalToSuperview().offset(-32.5)
+      $0.bottom.equalToSuperview()
     }
+  }
 }
diff --git a/Sources/SearchFeature/Views/SearchLeftView.swift b/Sources/SearchFeature/Views/SearchLeftView.swift
index bbdda3f926818439e4c28d5beb020239a623048a..4eefca9a834265e687fee288e19a9a88689529d7 100644
--- a/Sources/SearchFeature/Views/SearchLeftView.swift
+++ b/Sources/SearchFeature/Views/SearchLeftView.swift
@@ -1,71 +1,72 @@
 import UIKit
 import Shared
+import AppResources
 
 final class SearchLeftView: UIView {
-    let tableView = UITableView()
-    let inputStackView = UIStackView()
-    let inputField = SearchComponent()
-    let emptyView = SearchLeftEmptyView()
-    let countryButton = SearchCountryComponent()
-    let placeholderView = SearchLeftPlaceholderView()
+  let tableView = UITableView()
+  let inputStackView = UIStackView()
+  let inputField = SearchComponent()
+  let emptyView = SearchLeftEmptyView()
+  let countryButton = SearchCountryComponent()
+  let placeholderView = SearchLeftPlaceholderView()
 
-    init() {
-        super.init(frame: .zero)
+  init() {
+    super.init(frame: .zero)
 
-        emptyView.isHidden = true
-        backgroundColor = Asset.neutralWhite.color
-        tableView.backgroundColor = Asset.neutralWhite.color
+    emptyView.isHidden = true
+    backgroundColor = Asset.neutralWhite.color
+    tableView.backgroundColor = Asset.neutralWhite.color
 
-        inputStackView.spacing = 5
-        inputStackView.addArrangedSubview(countryButton)
-        inputStackView.addArrangedSubview(inputField)
+    inputStackView.spacing = 5
+    inputStackView.addArrangedSubview(countryButton)
+    inputStackView.addArrangedSubview(inputField)
 
-        addSubview(inputStackView)
-        addSubview(tableView)
-        addSubview(emptyView)
-        addSubview(placeholderView)
+    addSubview(inputStackView)
+    addSubview(tableView)
+    addSubview(emptyView)
+    addSubview(placeholderView)
 
-        setupConstraints()
-    }
+    setupConstraints()
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 
-    func updateUIForItem(item: SearchSegmentedControl.Item) {
-        countryButton.isHidden = item != .phone
+  func updateUIForItem(item: SearchSegmentedControl.Item) {
+    countryButton.isHidden = item != .phone
 
-        let emptyTitle = Localized.Ud.Search.empty(item.written)
-        emptyView.titleLabel.text = emptyTitle
+    let emptyTitle = Localized.Ud.Search.empty(item.written)
+    emptyView.titleLabel.text = emptyTitle
 
-        let inputFieldTitle = Localized.Ud.Search.input(item.written)
-        inputField.set(placeholder: inputFieldTitle, imageAtRight: nil)
-    }
+    let inputFieldTitle = Localized.Ud.Search.input(item.written)
+    inputField.set(placeholder: inputFieldTitle, imageAtRight: nil)
+  }
 
-    private func setupConstraints() {
-        inputStackView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(20)
-            $0.left.equalToSuperview().offset(20)
-            $0.right.equalToSuperview().offset(-20)
-        }
+  private func setupConstraints() {
+    inputStackView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(20)
+      $0.left.equalToSuperview().offset(20)
+      $0.right.equalToSuperview().offset(-20)
+    }
 
-        tableView.snp.makeConstraints {
-            $0.top.equalTo(inputField.snp.bottom).offset(20)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
+    tableView.snp.makeConstraints {
+      $0.top.equalTo(inputField.snp.bottom).offset(20)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
 
-        emptyView.snp.makeConstraints {
-            $0.top.equalTo(inputField.snp.bottom).offset(20)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
+    emptyView.snp.makeConstraints {
+      $0.top.equalTo(inputField.snp.bottom).offset(20)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
 
-        placeholderView.snp.makeConstraints {
-            $0.top.equalTo(inputField.snp.bottom)
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
+    placeholderView.snp.makeConstraints {
+      $0.top.equalTo(inputField.snp.bottom)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
     }
+  }
 }
diff --git a/Sources/SearchFeature/Views/SearchRightView.swift b/Sources/SearchFeature/Views/SearchRightView.swift
index c2fd74760c79e8799c31bb91b0f73a85d4d5cd98..a374439fb729112f781ce56848daa2ebf78cf60d 100644
--- a/Sources/SearchFeature/Views/SearchRightView.swift
+++ b/Sources/SearchFeature/Views/SearchRightView.swift
@@ -1,5 +1,6 @@
 import UIKit
 import Shared
+import AppResources
 
 final class SearchRightView: UIView {
     let statusLabel = UILabel()
diff --git a/Sources/SearchFeature/Views/SearchSegmentedButton.swift b/Sources/SearchFeature/Views/SearchSegmentedButton.swift
index 3b8e65fb1b778703a748626155d6f2b0b18ec94b..ead382a4d69a9caab5e0e48fce4e7f89a1b737e7 100644
--- a/Sources/SearchFeature/Views/SearchSegmentedButton.swift
+++ b/Sources/SearchFeature/Views/SearchSegmentedButton.swift
@@ -1,49 +1,50 @@
 import UIKit
 import Shared
+import AppResources
 
 final class SearchSegmentedButton: UIControl {
-    private let titleLabel = UILabel()
-    private let imageView = UIImageView()
-    private let highlightColor = Asset.brandPrimary.color
-    private let discreteColor = Asset.neutralDisabled.color
-
-    init() {
-        super.init(frame: .zero)
-
-        imageView.contentMode = .center
-        titleLabel.textAlignment = .center
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
-
-        addSubview(titleLabel)
-        addSubview(imageView)
-
-        setupConstraints()
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    func setup(title: String, icon: UIImage) {
-        imageView.image = icon
-        titleLabel.text = title
-        imageView.tintColor = discreteColor
-        titleLabel.textColor = discreteColor
-    }
-
-    func setSelected(_ bool: Bool) {
-        imageView.tintColor = bool ? highlightColor : discreteColor
-        titleLabel.textColor = bool ? highlightColor : discreteColor
+  private let titleLabel = UILabel()
+  private let imageView = UIImageView()
+  private let highlightColor = Asset.brandPrimary.color
+  private let discreteColor = Asset.neutralDisabled.color
+
+  init() {
+    super.init(frame: .zero)
+
+    imageView.contentMode = .center
+    titleLabel.textAlignment = .center
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
+
+    addSubview(titleLabel)
+    addSubview(imageView)
+
+    setupConstraints()
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  func setup(title: String, icon: UIImage) {
+    imageView.image = icon
+    titleLabel.text = title
+    imageView.tintColor = discreteColor
+    titleLabel.textColor = discreteColor
+  }
+
+  func setSelected(_ bool: Bool) {
+    imageView.tintColor = bool ? highlightColor : discreteColor
+    titleLabel.textColor = bool ? highlightColor : discreteColor
+  }
+
+  private func setupConstraints() {
+    imageView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(7.5)
+      $0.centerX.equalToSuperview()
     }
 
-    private func setupConstraints() {
-        imageView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(7.5)
-            $0.centerX.equalToSuperview()
-        }
-
-        titleLabel.snp.makeConstraints {
-            $0.top.equalTo(imageView.snp.bottom).offset(2)
-            $0.centerX.equalToSuperview()
-            $0.bottom.equalToSuperview().offset(-7.5)
-        }
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(imageView.snp.bottom).offset(2)
+      $0.centerX.equalToSuperview()
+      $0.bottom.equalToSuperview().offset(-7.5)
     }
+  }
 }
diff --git a/Sources/SearchFeature/Views/SearchSegmentedControl.swift b/Sources/SearchFeature/Views/SearchSegmentedControl.swift
index 6141360378cd659e89dbdf67c4180f4a0e9b4ce6..94833095934ff15522cbf2f01255bdaaf652fb91 100644
--- a/Sources/SearchFeature/Views/SearchSegmentedControl.swift
+++ b/Sources/SearchFeature/Views/SearchSegmentedControl.swift
@@ -2,6 +2,7 @@ import UIKit
 import Shared
 import SnapKit
 import Combine
+import AppResources
 
 final class SearchSegmentedControl: UIView {
     enum Item: Int {
diff --git a/Sources/SettingsFeature/Controllers/AccountDeleteController.swift b/Sources/SettingsFeature/Controllers/AccountDeleteController.swift
deleted file mode 100644
index ee5be65a72df17338ab3daca3ddcf58531057338..0000000000000000000000000000000000000000
--- a/Sources/SettingsFeature/Controllers/AccountDeleteController.swift
+++ /dev/null
@@ -1,120 +0,0 @@
-import HUD
-import UIKit
-import DrawerFeature
-import Shared
-import Combine
-import Defaults
-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
-
-    lazy private var screenView = AccountDeleteView()
-    lazy private var scrollViewController = ScrollViewController()
-
-    private let viewModel = AccountDeleteViewModel()
-    private var cancellables = Set<AnyCancellable>()
-    private var drawerCancellables = Set<AnyCancellable>()
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationController?.navigationBar
-            .customize(backgroundColor: Asset.neutralWhite.color)
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupScrollView()
-        setupBindings()
-
-        screenView.update(username: username)
-
-        screenView.setInfoClosure { [weak self] in
-            self?.presentInfo(
-                title: Localized.Settings.Delete.Info.title,
-                subtitle: Localized.Settings.Delete.Info.subtitle
-            )
-        }
-    }
-
-    private func setupScrollView() {
-        addChild(scrollViewController)
-        view.addSubview(scrollViewController.view)
-        scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
-        scrollViewController.didMove(toParent: self)
-        scrollViewController.contentView = screenView
-        scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color
-    }
-
-    private func setupBindings() {
-        viewModel.hud
-            .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) }
-            .store(in: &cancellables)
-
-        screenView.inputField.textPublisher
-            .sink { [unowned self] in screenView.update(status: $0 == username ? .valid("") : .invalid("")) }
-            .store(in: &cancellables)
-
-        screenView.confirmButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                DispatchQueue.global().async { [weak self] in
-                    self?.viewModel.didTapDelete()
-                }
-            }.store(in: &cancellables)
-
-        screenView.cancelButton.publisher(for: .touchUpInside)
-            .sink { [unowned self] in navigationController?.popViewController(animated: true) }
-            .store(in: &cancellables)
-    }
-
-    private func presentInfo(title: String, subtitle: String) {
-        let actionButton = CapsuleButton()
-        actionButton.set(
-            style: .seeThrough,
-            title: Localized.Settings.InfoDrawer.action
-        )
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
-            ),
-            DrawerText(
-                font: Fonts.Mulish.regular.font(size: 16.0),
-                text: subtitle,
-                color: Asset.neutralBody.color,
-                alignment: .left,
-                lineHeightMultiple: 1.1,
-                spacingAfter: 37
-            ),
-            DrawerStack(views: [
-                actionButton,
-                FlexibleSpace()
-            ])
-        ])
-
-        actionButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
-}
diff --git a/Sources/SettingsFeature/Controllers/SettingsAdvancedController.swift b/Sources/SettingsFeature/Controllers/SettingsAdvancedController.swift
index efe25a0030b832db3507172bf4a966d26e0ed54f..96225a7dbebff485ce4289417abe0646150fc6f1 100644
--- a/Sources/SettingsFeature/Controllers/SettingsAdvancedController.swift
+++ b/Sources/SettingsFeature/Controllers/SettingsAdvancedController.swift
@@ -1,90 +1,111 @@
 import UIKit
-import Shared
 import Combine
-import DependencyInjection
+import Dependencies
+import AppResources
+import AppNavigation
 
 public final class SettingsAdvancedController: UIViewController {
-    @Dependency private var coordinator: SettingsCoordinating
-
-    lazy private var screenView = SettingsAdvancedView()
-
-    private var cancellables = Set<AnyCancellable>()
-    private let viewModel = SettingsAdvancedViewModel()
-
-    public override func loadView() {
-        view = screenView
-    }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        navigationController?.navigationBar
-            .customize(backgroundColor: Asset.neutralWhite.color)
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-        setupNavigationBar()
-        setupBindings()
-
-        viewModel.loadCachedSettings()
-    }
-
-    private func setupNavigationBar() {
-        let title = UILabel()
-        title.text = Localized.Settings.Advanced.title
-        title.textColor = Asset.neutralActive.color
-        title.font = Fonts.Mulish.semiBold.font(size: 18.0)
-
-        navigationItem.leftBarButtonItem = UIBarButtonItem(customView: title)
-        navigationItem.leftItemsSupplementBackButton = true
-    }
-
-    private func setupBindings() {
-        screenView.downloadLogsButton
-            .publisher(for: .touchUpInside)
-            .sink { [weak viewModel] in viewModel?.didTapDownloadLogs() }
-            .store(in: &cancellables)
-
-        screenView.logRecordingSwitcher.switcherView
-            .publisher(for: .valueChanged)
-            .sink { [weak viewModel] in viewModel?.didToggleRecordLogs() }
-            .store(in: &cancellables)
-
-        screenView.showUsernamesSwitcher.switcherView
-            .publisher(for: .valueChanged)
-            .sink { [weak viewModel] in viewModel?.didToggleShowUsernames() }
-            .store(in: &cancellables)
-
-        screenView.crashReportingSwitcher.switcherView
-            .publisher(for: .valueChanged)
-            .sink { [weak viewModel] in viewModel?.didToggleCrashReporting() }
-            .store(in: &cancellables)
-
-        screenView.reportingSwitcher.switcherView
-            .publisher(for: .valueChanged)
-            .compactMap { [weak screenView] _ in screenView?.reportingSwitcher.switcherView.isOn }
-            .sink { [weak viewModel] isOn in viewModel?.didSetReporting(enabled: isOn) }
-            .store(in: &cancellables)
-
-        viewModel.sharePublisher
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toActivityController(with: [$0], from: self) }
-            .store(in: &cancellables)
-
-        viewModel.state
-            .removeDuplicates()
-            .map(\.isReportingOptional)
-            .sink { [unowned self] in screenView.reportingSwitcher.isHidden = !$0 }
-            .store(in: &cancellables)
-
-        viewModel.state
-            .removeDuplicates()
-            .sink { [unowned self] state in
-                screenView.logRecordingSwitcher.switcherView.setOn(state.isRecordingLogs, animated: true)
-                screenView.crashReportingSwitcher.switcherView.setOn(state.isCrashReporting, animated: true)
-                screenView.showUsernamesSwitcher.switcherView.setOn(state.isShowingUsernames, animated: true)
-                screenView.reportingSwitcher.switcherView.setOn(state.isReportingEnabled, animated: true)
-            }.store(in: &cancellables)
-    }
+  @Dependency(\.navigator) var navigator: Navigator
+
+  private lazy var screenView = SettingsAdvancedView()
+
+  private var cancellables = Set<AnyCancellable>()
+  private let viewModel = SettingsAdvancedViewModel()
+
+  public override func loadView() {
+    view = screenView
+  }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    navigationController?.navigationBar
+      .customize(backgroundColor: Asset.neutralWhite.color)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupNavigationBar()
+    setupBindings()
+
+    viewModel.loadCachedSettings()
+  }
+
+  private func setupNavigationBar() {
+    let title = UILabel()
+    title.text = Localized.Settings.Advanced.title
+    title.textColor = Asset.neutralActive.color
+    title.font = Fonts.Mulish.semiBold.font(size: 18.0)
+
+    navigationItem.leftBarButtonItem = UIBarButtonItem(customView: title)
+    navigationItem.leftItemsSupplementBackButton = true
+  }
+
+  private func setupBindings() {
+    screenView
+      .downloadLogsButton
+      .publisher(for: .touchUpInside)
+      .sink { [weak viewModel] in
+        viewModel?.didTapDownloadLogs()
+      }.store(in: &cancellables)
+
+    screenView
+      .logRecordingSwitcher
+      .switcherView
+      .publisher(for: .valueChanged)
+      .sink { [weak viewModel] in
+        viewModel?.didToggleRecordLogs()
+      }.store(in: &cancellables)
+
+    screenView
+      .showUsernamesSwitcher
+      .switcherView
+      .publisher(for: .valueChanged)
+      .sink { [weak viewModel] in
+        viewModel?.didToggleShowUsernames()
+      }.store(in: &cancellables)
+
+    screenView
+      .crashReportingSwitcher
+      .switcherView
+      .publisher(for: .valueChanged)
+      .sink { [weak viewModel] in
+        viewModel?.didToggleCrashReporting()
+      }.store(in: &cancellables)
+
+    screenView
+      .reportingSwitcher
+      .switcherView
+      .publisher(for: .valueChanged)
+      .compactMap { [weak screenView] _ in
+        screenView?.reportingSwitcher.switcherView.isOn
+      }.sink { [weak viewModel] isOn in
+        viewModel?.didSetReporting(enabled: isOn)
+      }.store(in: &cancellables)
+
+    viewModel
+      .sharePublisher
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentActivitySheet(items: [$0], from: self))
+      }.store(in: &cancellables)
+
+    viewModel
+      .state
+      .removeDuplicates()
+      .map(\.isReportingOptional)
+      .sink { [unowned self] in
+        screenView.reportingSwitcher.isHidden = !$0
+      }.store(in: &cancellables)
+
+    viewModel
+      .state
+      .removeDuplicates()
+      .sink { [unowned self] state in
+        screenView.logRecordingSwitcher.switcherView.setOn(state.isRecordingLogs, animated: true)
+        screenView.crashReportingSwitcher.switcherView.setOn(state.isCrashReporting, animated: true)
+        screenView.showUsernamesSwitcher.switcherView.setOn(state.isShowingUsernames, animated: true)
+        screenView.reportingSwitcher.switcherView.setOn(state.isReportingEnabled, animated: true)
+      }.store(in: &cancellables)
+  }
 }
diff --git a/Sources/SettingsFeature/Controllers/SettingsController.swift b/Sources/SettingsFeature/Controllers/SettingsController.swift
deleted file mode 100644
index e58a347096e53ddefc3014517c4f11c946a06429..0000000000000000000000000000000000000000
--- a/Sources/SettingsFeature/Controllers/SettingsController.swift
+++ /dev/null
@@ -1,300 +0,0 @@
-import HUD
-import DrawerFeature
-import UIKit
-import Theme
-import Shared
-import Combine
-import DependencyInjection
-import ScrollViewController
-
-public final class SettingsController: UIViewController {
-    @Dependency private var hud: HUD
-    @Dependency private var coordinator: SettingsCoordinating
-    @Dependency private var statusBarController: StatusBarStyleControlling
-
-    lazy private var scrollViewController = ScrollViewController()
-    lazy private var screenView = SettingsView {
-        switch $0 {
-        case .icognitoKeyboard:
-            self.presentInfo(
-                title: Localized.Settings.InfoDrawer.Icognito.title,
-                subtitle: Localized.Settings.InfoDrawer.Icognito.subtitle
-            )
-        case .biometrics:
-            self.presentInfo(
-                title: Localized.Settings.InfoDrawer.Biometrics.title,
-                subtitle: Localized.Settings.InfoDrawer.Biometrics.subtitle
-            )
-        case .notifications:
-            self.presentInfo(
-                title: Localized.Settings.InfoDrawer.Notifications.title,
-                subtitle: Localized.Settings.InfoDrawer.Notifications.subtitle,
-                urlString: "https://links.xx.network/denseids"
-            )
-
-        case .dummyTraffic:
-            self.presentInfo(
-                title: Localized.Settings.InfoDrawer.Traffic.title,
-                subtitle: Localized.Settings.InfoDrawer.Traffic.subtitle,
-                urlString: "https://links.xx.network/covertraffic"
-            )
-        }
-    }
-
-    private let viewModel = SettingsViewModel()
-    private var cancellables = Set<AnyCancellable>()
-    private var drawerCancellables = Set<AnyCancellable>()
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        statusBarController.style.send(.darkContent)
-        navigationController?.navigationBar
-            .customize(backgroundColor: Asset.neutralWhite.color)
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-
-        setupNavigationBar()
-        setupScrollView()
-        setupBindings()
-
-        viewModel.loadCachedSettings()
-    }
-
-    private func setupNavigationBar() {
-        navigationItem.backButtonTitle = ""
-
-        let titleLabel = UILabel()
-        titleLabel.text = Localized.Settings.title
-        titleLabel.textColor = Asset.neutralActive.color
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 18.0)
-
-        let menuButton = UIButton()
-        menuButton.tintColor = Asset.neutralDark.color
-        menuButton.setImage(Asset.chatListMenu.image, for: .normal)
-        menuButton.addTarget(self, action: #selector(didTapMenu), for: .touchUpInside)
-        menuButton.snp.makeConstraints { $0.width.equalTo(50) }
-
-        navigationItem.leftBarButtonItem = UIBarButtonItem(
-            customView: UIStackView(arrangedSubviews: [menuButton, titleLabel])
-        )
-    }
-
-    private func setupScrollView() {
-        scrollViewController.view.backgroundColor = Asset.neutralWhite.color
-
-        addChild(scrollViewController)
-        view.addSubview(scrollViewController.view)
-
-        scrollViewController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
-        scrollViewController.didMove(toParent: self)
-        scrollViewController.contentView = screenView
-    }
-
-    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() }
-            .store(in: &cancellables)
-
-        screenView.dummyTraffic.switcherView
-            .publisher(for: .valueChanged)
-            .sink { [weak viewModel] in viewModel?.didToggleDummyTraffic() }
-            .store(in: &cancellables)
-
-        screenView.remoteNotifications.switcherView
-            .publisher(for: .valueChanged)
-            .sink { [weak viewModel] in viewModel?.didTogglePushNotifications() }
-            .store(in: &cancellables)
-
-        screenView.hideActiveApp.switcherView
-            .publisher(for: .valueChanged)
-            .sink { [weak viewModel] in viewModel?.didToggleHideActiveApps() }
-            .store(in: &cancellables)
-
-        screenView.icognitoKeyboard.switcherView
-            .publisher(for: .valueChanged)
-            .sink { [weak viewModel] in viewModel?.didToggleIcognitoKeyboard() }
-            .store(in: &cancellables)
-
-        screenView.biometrics.switcherView
-            .publisher(for: .valueChanged)
-            .sink { [weak viewModel] in viewModel?.didToggleBiometrics() }
-            .store(in: &cancellables)
-
-        screenView.privacyPolicyButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                presentDrawer(
-                    title: Localized.Settings.Drawer.title(Localized.Settings.privacyPolicy),
-                    subtitle: Localized.Settings.Drawer.subtitle(Localized.Settings.privacyPolicy),
-                    actionTitle: Localized.ChatList.Dashboard.open) {
-                        guard let url = URL(string: "https://elixxir.io/privategrity-corporation-privacy-policy/") else { return }
-                        UIApplication.shared.open(url, options: [:])
-                    }
-            }.store(in: &cancellables)
-
-        screenView.disclosuresButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in
-                presentDrawer(
-                    title: Localized.Settings.Drawer.title(Localized.Settings.disclosures),
-                    subtitle: Localized.Settings.Drawer.subtitle(Localized.Settings.disclosures),
-                    actionTitle: Localized.ChatList.Dashboard.open) {
-                        guard let url = URL(string: "https://elixxir.io/privategrity-corporation-terms-of-use/") else { return }
-                        UIApplication.shared.open(url, options: [:])
-                    }
-            }.store(in: &cancellables)
-
-        screenView.deleteButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toDelete(from: self) }
-            .store(in: &cancellables)
-
-        screenView.accountBackupButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toBackup(from: self) }
-            .store(in: &cancellables)
-
-        screenView.advancedButton
-            .publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in coordinator.toAdvanced(from: self) }
-            .store(in: &cancellables)
-
-        viewModel.state
-            .map(\.isBiometricsPossible)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [weak screenView] in screenView?.biometrics.switcherView.isEnabled = $0 }
-            .store(in: &cancellables)
-
-        viewModel.state
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] state in
-                screenView.biometrics.switcherView.setOn(state.isBiometricsEnabled, animated: true)
-                screenView.hideActiveApp.switcherView.setOn(state.isHideActiveApps, animated: true)
-                screenView.icognitoKeyboard.switcherView.setOn(state.isIcognitoKeyboard, animated: true)
-                screenView.inAppNotifications.switcherView.setOn(state.isInAppNotification, animated: true)
-                screenView.remoteNotifications.switcherView.setOn(state.isPushNotification, animated: true)
-                screenView.dummyTraffic.switcherView.setOn(state.isDummyTrafficOn, animated: true)
-            }.store(in: &cancellables)
-    }
-
-    private func presentDrawer(
-        title: String,
-        subtitle: String,
-        actionTitle: String,
-        action: @escaping () -> Void
-    ) {
-        let actionButton = CapsuleButton()
-        actionButton.setStyle(.red)
-        actionButton.setTitle(actionTitle, for: .normal)
-
-        let cancelButton = CapsuleButton()
-        cancelButton.setStyle(.seeThrough)
-        cancelButton.setTitle(Localized.ChatList.Dashboard.cancel, for: .normal)
-
-        let drawer = DrawerController(with: [
-            DrawerImage(
-                image: Asset.drawerNegative.image
-            ),
-            DrawerText(
-                font: Fonts.Mulish.semiBold.font(size: 18.0),
-                text: title,
-                color: Asset.neutralActive.color
-            ),
-            DrawerText(
-                font: Fonts.Mulish.semiBold.font(size: 14.0),
-                text: subtitle,
-                color: Asset.neutralWeak.color,
-                lineHeightMultiple: 1.35,
-                spacingAfter: 25
-            ),
-            DrawerStack(
-                spacing: 20.0,
-                views: [actionButton, cancelButton]
-            )
-        ])
-
-        actionButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-
-                    action()
-                }
-            }.store(in: &drawerCancellables)
-
-        cancelButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    self?.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
-
-    @objc private func didTapMenu() {
-        coordinator.toSideMenu(from: self)
-    }
-}
-
-extension SettingsController {
-    private func presentInfo(
-        title: String,
-        subtitle: String,
-        urlString: String = ""
-    ) {
-        let actionButton = CapsuleButton()
-        actionButton.set(
-            style: .seeThrough,
-            title: Localized.Settings.InfoDrawer.action
-        )
-
-        let drawer = DrawerController(with: [
-            DrawerText(
-                font: Fonts.Mulish.bold.font(size: 26.0),
-                text: title,
-                color: Asset.neutralActive.color,
-                alignment: .left,
-                spacingAfter: 19
-            ),
-            DrawerLinkText(
-                text: subtitle,
-                urlString: urlString,
-                spacingAfter: 37
-            ),
-            DrawerStack(views: [
-                actionButton,
-                FlexibleSpace()
-            ])
-        ])
-
-        actionButton.publisher(for: .touchUpInside)
-            .receive(on: DispatchQueue.main)
-            .sink {
-                drawer.dismiss(animated: true) { [weak self] in
-                    guard let self = self else { return }
-                    self.drawerCancellables.removeAll()
-                }
-            }.store(in: &drawerCancellables)
-
-        coordinator.toDrawer(drawer, from: self)
-    }
-}
diff --git a/Sources/SettingsFeature/Controllers/SettingsDeleteController.swift b/Sources/SettingsFeature/Controllers/SettingsDeleteController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..0c64d28d92144c28ed4718a281b352f8ab749669
--- /dev/null
+++ b/Sources/SettingsFeature/Controllers/SettingsDeleteController.swift
@@ -0,0 +1,128 @@
+import UIKit
+import Shared
+import Combine
+import Defaults
+import Dependencies
+import AppResources
+import AppNavigation
+import DrawerFeature
+import ScrollViewController
+
+public final class SettingsDeleteController: UIViewController {
+  @Dependency(\.navigator) var navigator: Navigator
+
+  @KeyObject(.username, defaultValue: "") var username: String
+
+  private lazy var screenView = SettingsDeleteView()
+  private lazy var scrollViewController = ScrollViewController()
+
+  private let viewModel = SettingsDeleteViewModel()
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationController?.navigationBar
+      .customize(backgroundColor: Asset.neutralWhite.color)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    setupScrollView()
+    setupBindings()
+
+    screenView.update(username: username)
+
+    screenView.setInfoClosure { [weak self] in
+      guard let self else { return }
+      self.presentInfo(
+        title: Localized.Settings.Delete.Info.title,
+        subtitle: Localized.Settings.Delete.Info.subtitle
+      )
+    }
+  }
+
+  private func setupScrollView() {
+    addChild(scrollViewController)
+    view.addSubview(scrollViewController.view)
+    scrollViewController.view.snp.makeConstraints {
+      $0.edges.equalToSuperview()
+    }
+    scrollViewController.didMove(toParent: self)
+    scrollViewController.contentView = screenView
+    scrollViewController.scrollView.backgroundColor = Asset.neutralWhite.color
+  }
+
+  private func setupBindings() {
+    screenView
+      .cancelButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        dismiss(animated: true)
+      }.store(in: &cancellables)
+
+    screenView
+      .inputField
+      .textPublisher
+      .sink { [unowned self] in
+        screenView.update(
+          status: $0 == username ?
+            .valid("") : .invalid("")
+        )
+      }.store(in: &cancellables)
+
+    screenView
+      .confirmButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        viewModel.didTapDelete()
+      }.store(in: &cancellables)
+
+    screenView
+      .cancelButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        navigationController?.popViewController(animated: true)
+      }.store(in: &cancellables)
+  }
+
+  private func presentInfo(title: String, subtitle: String) {
+    let actionButton = CapsuleButton()
+    actionButton.set(
+      style: .seeThrough,
+      title: Localized.Settings.InfoDrawer.action
+    )
+    actionButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: title,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 19
+      ),
+      DrawerText(
+        font: Fonts.Mulish.regular.font(size: 16.0),
+        text: subtitle,
+        color: Asset.neutralBody.color,
+        alignment: .left,
+        lineHeightMultiple: 1.1,
+        spacingAfter: 37
+      ),
+      DrawerStack(views: [
+        actionButton,
+        FlexibleSpace()
+      ])
+    ], isDismissable: true, from: self))
+  }
+}
diff --git a/Sources/SettingsFeature/Controllers/SettingsMainController.swift b/Sources/SettingsFeature/Controllers/SettingsMainController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..574c0dca110aa011dd7643eae8e60a2b028827e5
--- /dev/null
+++ b/Sources/SettingsFeature/Controllers/SettingsMainController.swift
@@ -0,0 +1,321 @@
+import UIKit
+import Shared
+import Combine
+import AppCore
+import Dependencies
+import AppResources
+import AppNavigation
+import DrawerFeature
+import ScrollViewController
+
+public final class SettingsMainController: UIViewController {
+  @Dependency(\.navigator) var navigator: Navigator
+  @Dependency(\.app.statusBar) var statusBar: StatusBarStylist
+
+  private lazy var scrollViewController = ScrollViewController()
+  private lazy var screenView = SettingsMainView {
+    switch $0 {
+    case .icognitoKeyboard:
+      self.presentInfo(
+        title: Localized.Settings.InfoDrawer.Icognito.title,
+        subtitle: Localized.Settings.InfoDrawer.Icognito.subtitle
+      )
+    case .biometrics:
+      self.presentInfo(
+        title: Localized.Settings.InfoDrawer.Biometrics.title,
+        subtitle: Localized.Settings.InfoDrawer.Biometrics.subtitle
+      )
+    case .notifications:
+      self.presentInfo(
+        title: Localized.Settings.InfoDrawer.Notifications.title,
+        subtitle: Localized.Settings.InfoDrawer.Notifications.subtitle,
+        urlString: "https://links.xx.network/denseids"
+      )
+
+    case .dummyTraffic:
+      self.presentInfo(
+        title: Localized.Settings.InfoDrawer.Traffic.title,
+        subtitle: Localized.Settings.InfoDrawer.Traffic.subtitle,
+        urlString: "https://links.xx.network/covertraffic"
+      )
+    }
+  }
+
+  private let viewModel = SettingsMainViewModel()
+  private var cancellables = Set<AnyCancellable>()
+  private var drawerCancellables = Set<AnyCancellable>()
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    statusBar.set(.darkContent)
+    navigationController?.navigationBar
+      .customize(backgroundColor: Asset.neutralWhite.color)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+
+    setupNavigationBar()
+    setupScrollView()
+    setupBindings()
+
+    viewModel.loadCachedSettings()
+  }
+
+  private func setupNavigationBar() {
+    navigationItem.backButtonTitle = ""
+
+    let titleLabel = UILabel()
+    titleLabel.text = Localized.Settings.title
+    titleLabel.textColor = Asset.neutralActive.color
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 18.0)
+
+    let menuButton = UIButton()
+    menuButton.tintColor = Asset.neutralDark.color
+    menuButton.setImage(Asset.chatListMenu.image, for: .normal)
+    menuButton.addTarget(self, action: #selector(didTapMenu), for: .touchUpInside)
+    menuButton.snp.makeConstraints { $0.width.equalTo(50) }
+
+    navigationItem.leftBarButtonItem = UIBarButtonItem(
+      customView: UIStackView(arrangedSubviews: [menuButton, titleLabel])
+    )
+  }
+
+  private func setupScrollView() {
+    scrollViewController.view.backgroundColor = Asset.neutralWhite.color
+    addChild(scrollViewController)
+    view.addSubview(scrollViewController.view)
+    scrollViewController.view.snp.makeConstraints {
+      $0.edges.equalToSuperview()
+    }
+    scrollViewController.didMove(toParent: self)
+    scrollViewController.contentView = screenView
+  }
+
+  private func setupBindings() {
+    screenView
+      .inAppNotifications
+      .switcherView
+      .publisher(for: .valueChanged)
+      .sink { [weak viewModel] in
+        viewModel?.didToggleInAppNotifications()
+      }.store(in: &cancellables)
+
+    screenView
+      .dummyTraffic
+      .switcherView
+      .publisher(for: .valueChanged)
+      .sink { [weak viewModel] in
+        viewModel?.didToggleDummyTraffic()
+      }.store(in: &cancellables)
+
+    screenView
+      .remoteNotifications
+      .switcherView
+      .publisher(for: .valueChanged)
+      .sink { [weak viewModel] in
+        viewModel?.didTogglePushNotifications()
+      }.store(in: &cancellables)
+
+    screenView
+      .hideActiveApp
+      .switcherView
+      .publisher(for: .valueChanged)
+      .sink { [weak viewModel] in
+        viewModel?.didToggleHideActiveApps()
+      }.store(in: &cancellables)
+
+    screenView
+      .icognitoKeyboard
+      .switcherView
+      .publisher(for: .valueChanged)
+      .sink { [weak viewModel] in
+        viewModel?.didToggleIcognitoKeyboard()
+      }.store(in: &cancellables)
+
+    screenView
+      .biometrics
+      .switcherView
+      .publisher(for: .valueChanged)
+      .sink { [weak viewModel] in
+        viewModel?.didToggleBiometrics()
+      }.store(in: &cancellables)
+
+    screenView
+      .privacyPolicyButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        presentDrawer(
+          title: Localized.Settings.Drawer.title(Localized.Settings.privacyPolicy),
+          subtitle: Localized.Settings.Drawer.subtitle(Localized.Settings.privacyPolicy),
+          actionTitle: Localized.ChatList.Dashboard.open) {
+            guard let url = URL(string: "https://elixxir.io/privategrity-corporation-privacy-policy/") else { return }
+            UIApplication.shared.open(url, options: [:])
+          }
+      }.store(in: &cancellables)
+
+    screenView
+      .disclosuresButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        presentDrawer(
+          title: Localized.Settings.Drawer.title(Localized.Settings.disclosures),
+          subtitle: Localized.Settings.Drawer.subtitle(Localized.Settings.disclosures),
+          actionTitle: Localized.ChatList.Dashboard.open) {
+            guard let url = URL(string: "https://elixxir.io/privategrity-corporation-terms-of-use/") else { return }
+            UIApplication.shared.open(url, options: [:])
+          }
+      }.store(in: &cancellables)
+
+    screenView
+      .deleteButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentSettingsAccountDelete(on: navigationController!))
+      }.store(in: &cancellables)
+
+    screenView
+      .accountBackupButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentSettingsBackup(on: navigationController!))
+      }.store(in: &cancellables)
+
+    screenView
+      .advancedButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(PresentSettingsAdvanced(on: navigationController!))
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .map(\.isBiometricsPossible)
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [weak screenView] in
+        screenView?.biometrics.switcherView.isEnabled = $0
+      }.store(in: &cancellables)
+
+    viewModel
+      .statePublisher
+      .removeDuplicates()
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] state in
+        screenView.biometrics.switcherView.setOn(state.isBiometricsEnabled, animated: true)
+        screenView.hideActiveApp.switcherView.setOn(state.isHideActiveApps, animated: true)
+        screenView.icognitoKeyboard.switcherView.setOn(state.isIcognitoKeyboard, animated: true)
+        screenView.inAppNotifications.switcherView.setOn(state.isInAppNotification, animated: true)
+        screenView.remoteNotifications.switcherView.setOn(state.isPushNotification, animated: true)
+        screenView.dummyTraffic.switcherView.setOn(state.isDummyTrafficOn, animated: true)
+      }.store(in: &cancellables)
+  }
+
+  private func presentDrawer(
+    title: String,
+    subtitle: String,
+    actionTitle: String,
+    action: @escaping () -> Void
+  ) {
+    let actionButton = CapsuleButton()
+    actionButton.setStyle(.red)
+    actionButton.setTitle(actionTitle, for: .normal)
+
+    let cancelButton = CapsuleButton()
+    cancelButton.setStyle(.seeThrough)
+    cancelButton.setTitle(Localized.ChatList.Dashboard.cancel, for: .normal)
+
+    actionButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+          action()
+        }
+      }.store(in: &drawerCancellables)
+
+    cancelButton.publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerImage(
+        image: Asset.drawerNegative.image
+      ),
+      DrawerText(
+        font: Fonts.Mulish.semiBold.font(size: 18.0),
+        text: title,
+        color: Asset.neutralActive.color
+      ),
+      DrawerText(
+        font: Fonts.Mulish.semiBold.font(size: 14.0),
+        text: subtitle,
+        color: Asset.neutralWeak.color,
+        lineHeightMultiple: 1.35,
+        spacingAfter: 25
+      ),
+      DrawerStack(
+        spacing: 20.0,
+        views: [actionButton, cancelButton]
+      )
+    ], isDismissable: true, from: self))
+  }
+
+  @objc private func didTapMenu() {
+    navigator.perform(PresentMenu(currentItem: .settings, from: self))
+  }
+}
+
+extension SettingsMainController {
+  private func presentInfo(
+    title: String,
+    subtitle: String,
+    urlString: String = ""
+  ) {
+    let actionButton = CapsuleButton()
+    actionButton.set(
+      style: .seeThrough,
+      title: Localized.Settings.InfoDrawer.action
+    )
+    actionButton
+      .publisher(for: .touchUpInside)
+      .receive(on: DispatchQueue.main)
+      .sink { [unowned self] in
+        navigator.perform(DismissModal(from: self)) { [weak self] in
+          guard let self else { return }
+          self.drawerCancellables.removeAll()
+        }
+      }.store(in: &drawerCancellables)
+
+    navigator.perform(PresentDrawer(items: [
+      DrawerText(
+        font: Fonts.Mulish.bold.font(size: 26.0),
+        text: title,
+        color: Asset.neutralActive.color,
+        alignment: .left,
+        spacingAfter: 19
+      ),
+      DrawerLinkText(
+        text: subtitle,
+        urlString: urlString,
+        spacingAfter: 37
+      ),
+      DrawerStack(views: [
+        actionButton,
+        FlexibleSpace()
+      ])
+    ], isDismissable: true, from: self))
+  }
+}
diff --git a/Sources/SettingsFeature/Coordinator/SettingsCoordinator.swift b/Sources/SettingsFeature/Coordinator/SettingsCoordinator.swift
deleted file mode 100644
index 73de549152915e86ff93f911c5e28626dff74d00..0000000000000000000000000000000000000000
--- a/Sources/SettingsFeature/Coordinator/SettingsCoordinator.swift
+++ /dev/null
@@ -1,70 +0,0 @@
-import UIKit
-import Shared
-import MenuFeature
-import Presentation
-
-public protocol SettingsCoordinating {
-    func toBackup(from: UIViewController)
-    func toDelete(from: UIViewController)
-    func toAdvanced(from: UIViewController)
-    func toSideMenu(from: UIViewController)
-    func toDrawer(_: UIViewController, from: UIViewController)
-    func toActivityController(with: [Any], from: UIViewController)
-}
-
-public struct SettingsCoordinator: SettingsCoordinating {
-    var pushPresenter: Presenting = PushPresenter()
-    var modalPresenter: Presenting = ModalPresenter()
-    var sidePresenter: Presenting = SideMenuPresenter()
-    var bottomPresenter: Presenting = BottomPresenter()
-
-    var backupFactory: () -> UIViewController
-    var advancedFactory: () -> UIViewController
-    var accountDeleteFactory: () -> UIViewController
-    var sideMenuFactory: (MenuItem, UIViewController) -> UIViewController
-    var activityControllerFactory: ([Any]) -> UIViewController
-    = { UIActivityViewController(activityItems: $0, applicationActivities: nil) }
-
-    public init(
-        backupFactory: @escaping () -> UIViewController,
-        advancedFactory: @escaping () -> UIViewController,
-        accountDeleteFactory: @escaping () -> UIViewController,
-        sideMenuFactory: @escaping (MenuItem, UIViewController) -> UIViewController
-    ) {
-        self.backupFactory = backupFactory
-        self.advancedFactory = advancedFactory
-        self.sideMenuFactory = sideMenuFactory
-        self.accountDeleteFactory = accountDeleteFactory
-    }
-}
-
-public extension SettingsCoordinator {
-    func toAdvanced(from parent: UIViewController) {
-        let screen = advancedFactory()
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toDelete(from parent: UIViewController) {
-        let screen = accountDeleteFactory()
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toBackup(from parent: UIViewController) {
-        let screen = backupFactory()
-        pushPresenter.present(screen, from: parent)
-    }
-
-    func toDrawer(_ drawer: UIViewController, from parent: UIViewController) {
-        bottomPresenter.present(drawer, from: parent)
-    }
-
-    func toActivityController(with items: [Any], from parent: UIViewController) {
-        let screen = activityControllerFactory(items)
-        modalPresenter.present(screen, from: parent)
-    }
-
-    func toSideMenu(from parent: UIViewController) {
-        let screen = sideMenuFactory(.settings, parent)
-        sidePresenter.present(screen, from: parent)
-    }
-}
diff --git a/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift b/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift
deleted file mode 100644
index db7e64016a317b8fe74fa496102437c771f500db..0000000000000000000000000000000000000000
--- a/Sources/SettingsFeature/ViewModels/AccountDeleteViewModel.swift
+++ /dev/null
@@ -1,41 +0,0 @@
-import HUD
-import Combine
-import Integration
-import Foundation
-import DependencyInjection
-
-final class AccountDeleteViewModel {
-    @Dependency private var session: SessionType
-
-    var deleting = false
-
-    var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() }
-    private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none)
-
-    func didTapDelete() {
-        guard deleting == false else { return }
-        deleting = true
-
-        DispatchQueue.main.async { [weak self] in
-            self?.hudRelay.send(.on)
-        }
-
-        do {
-            try session.deleteMyself()
-            DependencyInjection.Container.shared.unregister(SessionType.self)
-
-            DispatchQueue.main.async { [weak self] in
-                self?.hudRelay.send(.error(.init(
-                    content: "Now kill the app and re-open",
-                    title: "Account deleted",
-                    dismissable: false
-                )))
-            }
-        } catch {
-
-            DispatchQueue.main.async { [weak self] in
-                self?.hudRelay.send(.error(.init(with: error)))
-            }
-        }
-    }
-}
diff --git a/Sources/SettingsFeature/ViewModels/SettingsAdvancedViewModel.swift b/Sources/SettingsFeature/ViewModels/SettingsAdvancedViewModel.swift
index 3ec9be365abd621e7c1d9d421aff530d0560acc7..585880a067a7664e55035300cf9deeb2e15c0c70 100644
--- a/Sources/SettingsFeature/ViewModels/SettingsAdvancedViewModel.swift
+++ b/Sources/SettingsFeature/ViewModels/SettingsAdvancedViewModel.swift
@@ -1,93 +1,98 @@
 import Combine
-import XXLogger
+import AppCore
 import Defaults
 import Foundation
-import CrashReporting
+import CrashReport
 import ReportingFeature
-import DependencyInjection
+import ComposableArchitecture
 
 struct AdvancedViewState: Equatable {
-    var isRecordingLogs = false
-    var isCrashReporting = false
-    var isShowingUsernames = false
-    var isReportingEnabled = false
-    var isReportingOptional = false
+  var isRecordingLogs = false
+  var isCrashReporting = false
+  var isShowingUsernames = false
+  var isReportingEnabled = false
+  var isReportingOptional = false
 }
 
 final class SettingsAdvancedViewModel {
-    @KeyObject(.recordingLogs, defaultValue: true) var isRecordingLogs: Bool
-    @KeyObject(.crashReporting, defaultValue: true) var isCrashReporting: Bool
+  @KeyObject(.recordingLogs, defaultValue: true) var isRecordingLogs: Bool
+  @KeyObject(.crashReporting, defaultValue: true) var isCrashReporting: Bool
 
-    private var cancellables = Set<AnyCancellable>()
-    private let isShowingUsernamesKey = "isShowingUsernames"
+  private var cancellables = Set<AnyCancellable>()
+  private let isShowingUsernamesKey = "isShowingUsernames"
 
-    @Dependency private var logger: XXLogger
-    @Dependency private var crashReporter: CrashReporter
-    @Dependency private var reportingStatus: ReportingStatus
+  @Dependency(\.app.log) var logger: Logger
+  @Dependency(\.crashReport) var crashReport: CrashReport
+  @Dependency(\.reportingStatus) var reportingStatus: ReportingStatus
 
-    var sharePublisher: AnyPublisher<URL, Never> { shareRelay.eraseToAnyPublisher() }
-    private let shareRelay = PassthroughSubject<URL, Never>()
+  var sharePublisher: AnyPublisher<URL, Never> {
+    shareRelay.eraseToAnyPublisher()
+  }
 
-    var state: AnyPublisher<AdvancedViewState, Never> { stateRelay.eraseToAnyPublisher() }
-    private let stateRelay = CurrentValueSubject<AdvancedViewState, Never>(.init())
+  private let shareRelay = PassthroughSubject<URL, Never>()
 
-    func loadCachedSettings() {
-        stateRelay.value.isRecordingLogs = isRecordingLogs
-        stateRelay.value.isCrashReporting = isCrashReporting
-        stateRelay.value.isReportingOptional = reportingStatus.isOptional()
+  var state: AnyPublisher<AdvancedViewState, Never> {
+    stateRelay.eraseToAnyPublisher()
+  }
+  private let stateRelay = CurrentValueSubject<AdvancedViewState, Never>(.init())
 
-        reportingStatus
-            .isEnabledPublisher()
-            .sink { [weak stateRelay] in stateRelay?.value.isReportingEnabled = $0 }
-            .store(in: &cancellables)
+  func loadCachedSettings() {
+    stateRelay.value.isRecordingLogs = isRecordingLogs
+    stateRelay.value.isCrashReporting = isCrashReporting
+    stateRelay.value.isReportingOptional = reportingStatus.isOptional()
 
-        guard let defaults = UserDefaults(suiteName: "group.elixxir.messenger") else {
-            print("^^^ Couldn't access user defaults in the app group container \(#file):\(#line)")
-            return
-        }
+    reportingStatus
+      .isEnabledPublisher()
+      .sink { [weak stateRelay] in stateRelay?.value.isReportingEnabled = $0 }
+      .store(in: &cancellables)
 
-        guard let isShowingUsernames = defaults.value(forKey: isShowingUsernamesKey) as? Bool else {
-            defaults.set(false, forKey: isShowingUsernamesKey)
-            return
-        }
+    guard let defaults = UserDefaults(suiteName: "group.elixxir.messenger") else {
+      print("^^^ Couldn't access user defaults in the app group container \(#file):\(#line)")
+      return
+    }
 
-        stateRelay.value.isShowingUsernames = isShowingUsernames
+    guard let isShowingUsernames = defaults.value(forKey: isShowingUsernamesKey) as? Bool else {
+      defaults.set(false, forKey: isShowingUsernamesKey)
+      return
     }
 
-    func didToggleShowUsernames() {
-        stateRelay.value.isShowingUsernames.toggle()
+    stateRelay.value.isShowingUsernames = isShowingUsernames
+  }
 
-        guard let defaults = UserDefaults(suiteName: "group.elixxir.messenger") else {
-            print("^^^ Couldn't access user defaults in the app group container \(#file):\(#line)")
-            return
-        }
+  func didToggleShowUsernames() {
+    stateRelay.value.isShowingUsernames.toggle()
 
-        defaults.set(stateRelay.value.isShowingUsernames, forKey: isShowingUsernamesKey)
+    guard let defaults = UserDefaults(suiteName: "group.elixxir.messenger") else {
+      print("^^^ Couldn't access user defaults in the app group container \(#file):\(#line)")
+      return
     }
 
-    func didToggleRecordLogs() {
-        if isRecordingLogs == true {
-            XXLogger.stop()
-        } else {
-            XXLogger.start()
-        }
+    defaults.set(stateRelay.value.isShowingUsernames, forKey: isShowingUsernamesKey)
+  }
 
-        isRecordingLogs.toggle()
-        stateRelay.value.isRecordingLogs.toggle()
+  func didToggleRecordLogs() {
+    if isRecordingLogs == true {
+//      XXLogger.stop()
+    } else {
+//      XXLogger.start()
     }
 
-    func didTapDownloadLogs() {
-        let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
-        shareRelay.send(url.appendingPathComponent("swiftybeaver.log"))
-    }
+    isRecordingLogs.toggle()
+    stateRelay.value.isRecordingLogs.toggle()
+  }
 
-    func didToggleCrashReporting() {
-        isCrashReporting.toggle()
-        stateRelay.value.isCrashReporting.toggle()
-        crashReporter.setEnabled(isCrashReporting)
-    }
+  func didTapDownloadLogs() {
+    let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
+    shareRelay.send(url.appendingPathComponent("swiftybeaver.log"))
+  }
 
-    func didSetReporting(enabled: Bool) {
-        reportingStatus.enable(enabled)
-    }
+  func didToggleCrashReporting() {
+    isCrashReporting.toggle()
+    stateRelay.value.isCrashReporting.toggle()
+    crashReport.setEnabled(isCrashReporting)
+  }
+
+  func didSetReporting(enabled: Bool) {
+    reportingStatus.enable(enabled)
+  }
 }
diff --git a/Sources/SettingsFeature/ViewModels/SettingsDeleteViewModel.swift b/Sources/SettingsFeature/ViewModels/SettingsDeleteViewModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..87dadb95c894fa10d06b82a53b094ccc04d00a67
--- /dev/null
+++ b/Sources/SettingsFeature/ViewModels/SettingsDeleteViewModel.swift
@@ -0,0 +1,60 @@
+import AppCore
+import Defaults
+import Keychain
+import Foundation
+import Dependencies
+import XXMessengerClient
+
+final class SettingsDeleteViewModel {
+  @Dependency(\.keychain) var keychain: KeychainManager
+  @Dependency(\.app.dbManager) var dbManager: DBManager
+  @Dependency(\.app.messenger) var messenger: Messenger
+  @Dependency(\.app.hudManager) var hudManager: HUDManager
+  @KeyObject(.username, defaultValue: nil) var username: String?
+
+  private var isCurrentlyDeleting = false
+  
+  func didTapDelete() {
+    guard isCurrentlyDeleting == false else { return }
+    isCurrentlyDeleting = true
+
+    hudManager.show()
+    
+    do {
+      try cleanUD()
+      try messenger.destroy()
+      try keychain.destroy()
+      try dbManager.removeDB()
+      try deleteDatabase()
+      
+      UserDefaults.resetStandardUserDefaults()
+      UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
+      UserDefaults.standard.synchronize()
+      
+      hudManager.show(.init(
+        title: "Account deleted",
+        content: "Now kill the app and re-open"
+      ))
+    } catch {
+      DispatchQueue.main.async { [weak self] in
+        guard let self else { return }
+        self.hudManager.show(.init(error: error))
+      }
+    }
+  }
+  
+  private func cleanUD() throws {
+    try messenger.ud.get()!.permanentDeleteAccount(
+      username: .init(type: .username, value: username!)
+    )
+  }
+  
+  private func deleteDatabase() throws {
+    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/SettingsMainViewModel.swift b/Sources/SettingsFeature/ViewModels/SettingsMainViewModel.swift
new file mode 100644
index 0000000000000000000000000000000000000000..4fd4323dd0086207e7be26d2acd82bc117779a8b
--- /dev/null
+++ b/Sources/SettingsFeature/ViewModels/SettingsMainViewModel.swift
@@ -0,0 +1,128 @@
+import UIKit
+import AppCore
+import Combine
+import XXClient
+import Defaults
+import XXMessengerClient
+import PermissionsFeature
+import ComposableArchitecture
+
+final class SettingsMainViewModel {
+  struct ViewState: 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
+  }
+
+  @Dependency(\.app.bgQueue) var bgQueue
+  @Dependency(\.permissions) var permissions
+  @Dependency(\.app.messenger) var messenger
+  @Dependency(\.dummyTraffic) var dummyTraffic
+  @Dependency(\.app.hudManager) var hudManager
+
+  @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 statePublisher: AnyPublisher<ViewState, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
+
+  private let stateSubject = CurrentValueSubject<ViewState, Never>(.init())
+
+  func loadCachedSettings() {
+    stateSubject.value.isHideActiveApps = hideAppList
+    stateSubject.value.isBiometricsEnabled = biometrics
+    stateSubject.value.isIcognitoKeyboard = icognitoKeyboard
+    stateSubject.value.isPushNotification = pushNotifications
+    stateSubject.value.isInAppNotification = inAppNotifications
+    stateSubject.value.isBiometricsPossible = permissions.biometrics.status()
+    stateSubject.value.isDummyTrafficOn = dummyTraffic.get()!.getStatus()
+  }
+  
+  func didToggleBiometrics() {
+    biometricAuthentication(enable: !biometrics)
+  }
+  
+  func didToggleInAppNotifications() {
+    inAppNotifications.toggle()
+    stateSubject.value.isInAppNotification.toggle()
+  }
+  
+  func didTogglePushNotifications() {
+    pushNotifications(enable: !pushNotifications)
+  }
+  
+  func didToggleDummyTraffic() {
+    let currently = dummyTraffic.get()!.getStatus()
+    try! dummyTraffic.get()!.setStatus(!currently)
+    stateSubject.value.isDummyTrafficOn = !currently
+    dummyTrafficOn = stateSubject.value.isDummyTrafficOn
+  }
+
+  func didToggleHideActiveApps() {
+    hideAppList.toggle()
+    stateSubject.value.isHideActiveApps.toggle()
+  }
+
+  func didToggleIcognitoKeyboard() {
+    icognitoKeyboard.toggle()
+    stateSubject.value.isIcognitoKeyboard.toggle()
+  }
+
+  private func biometricAuthentication(enable: Bool) {
+    stateSubject.value.isBiometricsEnabled = enable
+
+    guard enable == true else {
+      biometrics = false
+      stateSubject.value.isBiometricsEnabled = false
+      return
+    }
+
+    permissions.biometrics.request { [weak self] in
+      guard let self else { return }
+      self.biometrics = $0
+      self.stateSubject.value.isBiometricsEnabled = $0
+    }
+  }
+  
+  private func pushNotifications(enable: Bool) {
+    hudManager.show()
+
+    if enable == true {
+      permissions.push.request { [weak self] granted in
+        guard let self else { return }
+        self.pushNotifications = granted
+        self.stateSubject.value.isPushNotification = granted
+        if granted {
+          DispatchQueue.main.async {
+            UIApplication.shared.registerForRemoteNotifications()
+          }
+        }
+        self.hudManager.hide()
+      }
+    } else {
+      bgQueue.schedule { [weak self] in
+        guard let self else { return }
+        do {
+          try UnregisterForNotifications.live(
+            e2eId: self.messenger.e2e.get()!.getId()
+          )
+          self.hudManager.hide()
+        } catch {
+          let xxError = CreateUserFriendlyErrorMessage.live(error.localizedDescription)
+          self.hudManager.show(.init(content: xxError))
+        }
+        self.pushNotifications = false
+        self.stateSubject.value.isPushNotification = false
+      }
+    }
+  }
+}
diff --git a/Sources/SettingsFeature/ViewModels/SettingsViewModel.swift b/Sources/SettingsFeature/ViewModels/SettingsViewModel.swift
deleted file mode 100644
index 9b985922329599ac17010bdd2d16c43dec81a454..0000000000000000000000000000000000000000
--- a/Sources/SettingsFeature/ViewModels/SettingsViewModel.swift
+++ /dev/null
@@ -1,148 +0,0 @@
-import HUD
-import UIKit
-import Shared
-import Combine
-import Defaults
-import Permissions
-import Integration
-import PushFeature
-import UserNotifications
-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
-}
-
-final class SettingsViewModel {
-    @Dependency private var session: SessionType
-    @Dependency private var pushHandler: PushHandling
-    @Dependency private var permissions: PermissionHandling
-
-    @KeyObject(.dummyTrafficOn, defaultValue: false) var isDummyTrafficOn: Bool
-    @KeyObject(.biometrics, defaultValue: false) private var biometrics
-    @KeyObject(.hideAppList, defaultValue: false) private var hideAppList
-    @KeyObject(.icognitoKeyboard, defaultValue: false) private var icognitoKeyboard
-    @KeyObject(.pushNotifications, defaultValue: false) private var pushNotifications
-    @KeyObject(.inappnotifications, defaultValue: true) private 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 = isDummyTrafficOn
-    }
-
-    func didToggleBiometrics() {
-        biometricAuthentication(enable: !biometrics)
-    }
-
-    func didToggleInAppNotifications() {
-        inAppNotifications.toggle()
-        stateRelay.value.isInAppNotification.toggle()
-    }
-
-    func didTogglePushNotifications() {
-        pushNotifications(enable: !pushNotifications)
-    }
-
-    func didToggleDummyTraffic() {
-        isDummyTrafficOn.toggle()
-        stateRelay.value.isDummyTrafficOn = isDummyTrafficOn
-        session.setDummyTraffic(status: isDummyTrafficOn)
-    }
-
-    func didToggleHideActiveApps() {
-        hideAppList.toggle()
-        stateRelay.value.isHideActiveApps.toggle()
-    }
-
-    func didToggleIcognitoKeyboard() {
-        icognitoKeyboard.toggle()
-        stateRelay.value.isIcognitoKeyboard.toggle()
-    }
-
-    // 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
-            }
-        }
-    }
-
-    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 self.session.unregisterNotifications()
-                    self.hudRelay.send(.none)
-                } catch {
-                    self.hudRelay.send(.error(.init(with: error)))
-                }
-
-                self.pushNotifications = false
-                self.stateRelay.value.isPushNotification = false
-            }
-        }
-    }
-}
diff --git a/Sources/SettingsFeature/Views/AccountDeleteView.swift b/Sources/SettingsFeature/Views/AccountDeleteView.swift
deleted file mode 100644
index e4aef7f5642929216a84e9e7b889ec0d3fa095ff..0000000000000000000000000000000000000000
--- a/Sources/SettingsFeature/Views/AccountDeleteView.swift
+++ /dev/null
@@ -1,120 +0,0 @@
-import UIKit
-import Shared
-import InputField
-
-final class AccountDeleteView: UIView {
-    let titleLabel = UILabel()
-    let subtitleView = TextWithInfoView()
-    let iconImageView = UIImageView()
-    let inputField = InputField()
-
-    let stackView = UIStackView()
-    let confirmButton = CapsuleButton()
-    let cancelButton = CapsuleButton()
-
-    var didTapInfo: (() -> Void)?
-
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-        iconImageView.image = Asset.settingsDeleteLarge.image
-
-        iconImageView.contentMode = .center
-
-        inputField.setup(
-            style: .regular,
-            title: Localized.Settings.Delete.input,
-            placeholder: "",
-            leftView: .image(Asset.personGray.image),
-            subtitleColor: Asset.neutralDisabled.color,
-            allowsEmptySpace: false,
-            autocapitalization: .none
-        )
-
-        titleLabel.text = Localized.Settings.Delete.title
-        titleLabel.textAlignment = .center
-        titleLabel.textColor = Asset.neutralActive.color
-        titleLabel.font = Fonts.Mulish.bold.font(size: 32.0)
-
-        let paragraph = NSMutableParagraphStyle()
-        paragraph.alignment = .center
-        paragraph.lineHeightMultiple = 1.1
-
-        subtitleView.setup(
-            text: Localized.Settings.Delete.subtitle,
-            attributes: [
-                .foregroundColor: Asset.neutralActive.color,
-                .font: Fonts.Mulish.regular.font(size: 16.0),
-                .paragraphStyle: paragraph
-            ],
-            didTapInfo: { self.didTapInfo?() }
-        )
-
-        confirmButton.setStyle(.red)
-        confirmButton.isEnabled = false
-        confirmButton.setTitle(Localized.Settings.Delete.delete, for: .normal)
-        cancelButton.setStyle(.simplestColoredRed)
-        cancelButton.setTitle(Localized.Settings.Delete.cancel, for: .normal)
-
-        stackView.spacing = 12
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(confirmButton)
-        stackView.addArrangedSubview(cancelButton)
-
-        addSubview(iconImageView)
-        addSubview(inputField)
-        addSubview(titleLabel)
-        addSubview(subtitleView)
-        addSubview(stackView)
-
-        iconImageView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(30)
-            make.centerX.equalToSuperview()
-        }
-
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalTo(iconImageView.snp.bottom).offset(34)
-            make.centerX.equalToSuperview()
-        }
-
-        subtitleView.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(20)
-            make.left.equalToSuperview().offset(50)
-            make.right.equalToSuperview().offset(-50)
-        }
-
-        inputField.snp.makeConstraints { make in
-            make.top.equalTo(subtitleView.snp.bottom).offset(50)
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-        }
-
-        stackView.snp.makeConstraints { make in
-            make.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20)
-            make.left.equalToSuperview().offset(50)
-            make.right.equalToSuperview().offset(-50)
-            make.bottom.equalToSuperview().offset(-44)
-        }
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    func setInfoClosure(_ closure: @escaping () -> Void) {
-        didTapInfo = closure
-    }
-
-    func update(username: String) {
-        inputField.update(placeholder: username)
-    }
-
-    func update(status: InputField.ValidationStatus) {
-        inputField.update(status: status)
-
-        switch status {
-        case .valid:
-            confirmButton.isEnabled = true
-        case .invalid, .unknown:
-            confirmButton.isEnabled = false
-        }
-    }
-}
diff --git a/Sources/SettingsFeature/Views/SettingsAdvancedView.swift b/Sources/SettingsFeature/Views/SettingsAdvancedView.swift
index 0e5e5d89159fbb554f78dd781f5a9d3d4cdfe9da..3d976f2505a4e8c23090724abdbbcd1a00fe70f5 100644
--- a/Sources/SettingsFeature/Views/SettingsAdvancedView.swift
+++ b/Sources/SettingsFeature/Views/SettingsAdvancedView.swift
@@ -1,64 +1,62 @@
 import UIKit
 import Shared
+import AppResources
 
 final class SettingsAdvancedView: UIView {
-    let stackView = UIStackView()
-    let downloadLogsButton = UIButton()
-    let logRecordingSwitcher = SettingsSwitcher()
-    let crashReportingSwitcher = SettingsSwitcher()
-    let showUsernamesSwitcher = SettingsSwitcher()
-    let reportingSwitcher = SettingsSwitcher()
-
-    init() {
-        super.init(frame: .zero)
-
-        backgroundColor = Asset.neutralWhite.color
-        downloadLogsButton.setImage(Asset.settingsDownload.image, for: .normal)
-
-        showUsernamesSwitcher.set(
-            title: Localized.Settings.Advanced.ShowUsername.title,
-            text: Localized.Settings.Advanced.ShowUsername.description,
-            icon: Asset.settingsHide.image
-        )
-
-        logRecordingSwitcher.set(
-            title: Localized.Settings.Advanced.Logs.title,
-            text: Localized.Settings.Advanced.Logs.description,
-            icon: Asset.settingsLogs.image,
-            extraAction: downloadLogsButton
-        )
-
-        crashReportingSwitcher.set(
-            title: Localized.Settings.Advanced.Crashes.title,
-            text: Localized.Settings.Advanced.Crashes.description,
-            icon: Asset.settingsCrash.image
-        )
-
-        reportingSwitcher.set(
-            title: Localized.Settings.Advanced.Reporting.title,
-            text: Localized.Settings.Advanced.Reporting.description,
-            icon: Asset.settingsCrash.image
-        )
-
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(logRecordingSwitcher)
-        stackView.addArrangedSubview(crashReportingSwitcher)
-        stackView.addArrangedSubview(showUsernamesSwitcher)
-        stackView.addArrangedSubview(reportingSwitcher)
-
-        stackView.setCustomSpacing(20, after: logRecordingSwitcher)
-        stackView.setCustomSpacing(10, after: crashReportingSwitcher)
-        stackView.setCustomSpacing(10, after: showUsernamesSwitcher)
-        stackView.setCustomSpacing(10, after: reportingSwitcher)
-
-        addSubview(stackView)
-
-        stackView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(24)
-            $0.left.equalToSuperview().offset(16)
-            $0.right.equalToSuperview().offset(-16)
-        }
+  let stackView = UIStackView()
+  let downloadLogsButton = UIButton()
+  let logRecordingSwitcher = SettingsSwitcher()
+  let crashReportingSwitcher = SettingsSwitcher()
+  let showUsernamesSwitcher = SettingsSwitcher()
+  let reportingSwitcher = SettingsSwitcher()
+
+  init() {
+    super.init(frame: .zero)
+
+    backgroundColor = Asset.neutralWhite.color
+    downloadLogsButton.setImage(Asset.settingsDownload.image, for: .normal)
+
+    showUsernamesSwitcher.set(
+      title: Localized.Settings.Advanced.ShowUsername.title,
+      text: Localized.Settings.Advanced.ShowUsername.description,
+      icon: Asset.settingsHide.image
+    )
+    logRecordingSwitcher.set(
+      title: Localized.Settings.Advanced.Logs.title,
+      text: Localized.Settings.Advanced.Logs.description,
+      icon: Asset.settingsLogs.image,
+      extraAction: downloadLogsButton
+    )
+    crashReportingSwitcher.set(
+      title: Localized.Settings.Advanced.Crashes.title,
+      text: Localized.Settings.Advanced.Crashes.description,
+      icon: Asset.settingsCrash.image
+    )
+    reportingSwitcher.set(
+      title: Localized.Settings.Advanced.Reporting.title,
+      text: Localized.Settings.Advanced.Reporting.description,
+      icon: Asset.settingsCrash.image
+    )
+
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(logRecordingSwitcher)
+    stackView.addArrangedSubview(crashReportingSwitcher)
+    stackView.addArrangedSubview(showUsernamesSwitcher)
+    stackView.addArrangedSubview(reportingSwitcher)
+
+    stackView.setCustomSpacing(20, after: logRecordingSwitcher)
+    stackView.setCustomSpacing(10, after: crashReportingSwitcher)
+    stackView.setCustomSpacing(10, after: showUsernamesSwitcher)
+    stackView.setCustomSpacing(10, after: reportingSwitcher)
+
+    addSubview(stackView)
+
+    stackView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(24)
+      $0.left.equalToSuperview().offset(16)
+      $0.right.equalToSuperview().offset(-16)
     }
+  }
 
-    required init?(coder: NSCoder) { nil }
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/SettingsFeature/Views/SettingsDeleteView.swift b/Sources/SettingsFeature/Views/SettingsDeleteView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7ac68bccbbd95c0f299936ba726744fc3e26023b
--- /dev/null
+++ b/Sources/SettingsFeature/Views/SettingsDeleteView.swift
@@ -0,0 +1,117 @@
+import UIKit
+import Shared
+import InputField
+import AppResources
+
+final class SettingsDeleteView: UIView {
+  let titleLabel = UILabel()
+  let subtitleView = TextWithInfoView()
+  let iconImageView = UIImageView()
+  let inputField = InputField()
+
+  let stackView = UIStackView()
+  let confirmButton = CapsuleButton()
+  let cancelButton = CapsuleButton()
+
+  var didTapInfo: (() -> Void)?
+
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+    iconImageView.image = Asset.settingsDeleteLarge.image
+
+    iconImageView.contentMode = .center
+
+    inputField.setup(
+      style: .regular,
+      title: Localized.Settings.Delete.input,
+      placeholder: "",
+      leftView: .image(Asset.personGray.image),
+      subtitleColor: Asset.neutralDisabled.color,
+      allowsEmptySpace: false,
+      autocapitalization: .none
+    )
+
+    titleLabel.text = Localized.Settings.Delete.title
+    titleLabel.textAlignment = .center
+    titleLabel.textColor = Asset.neutralActive.color
+    titleLabel.font = Fonts.Mulish.bold.font(size: 32.0)
+
+    let paragraph = NSMutableParagraphStyle()
+    paragraph.alignment = .center
+    paragraph.lineHeightMultiple = 1.1
+
+    subtitleView.setup(
+      text: Localized.Settings.Delete.subtitle,
+      attributes: [
+        .foregroundColor: Asset.neutralActive.color,
+        .font: Fonts.Mulish.regular.font(size: 16.0),
+        .paragraphStyle: paragraph
+      ],
+      didTapInfo: { self.didTapInfo?() }
+    )
+
+    confirmButton.setStyle(.red)
+    confirmButton.isEnabled = false
+    confirmButton.setTitle(Localized.Settings.Delete.delete, for: .normal)
+    cancelButton.setStyle(.simplestColoredRed)
+    cancelButton.setTitle(Localized.Settings.Delete.cancel, for: .normal)
+
+    stackView.spacing = 12
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(confirmButton)
+    stackView.addArrangedSubview(cancelButton)
+
+    addSubview(iconImageView)
+    addSubview(inputField)
+    addSubview(titleLabel)
+    addSubview(subtitleView)
+    addSubview(stackView)
+
+    iconImageView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(30)
+      $0.centerX.equalToSuperview()
+    }
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(iconImageView.snp.bottom).offset(34)
+      $0.centerX.equalToSuperview()
+    }
+    subtitleView.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(20)
+      $0.left.equalToSuperview().offset(50)
+      $0.right.equalToSuperview().offset(-50)
+    }
+    inputField.snp.makeConstraints {
+      $0.top.equalTo(subtitleView.snp.bottom).offset(50)
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
+    }
+    stackView.snp.makeConstraints {
+      $0.top.greaterThanOrEqualTo(inputField.snp.bottom).offset(20)
+      $0.left.equalToSuperview().offset(50)
+      $0.right.equalToSuperview().offset(-50)
+      $0.bottom.equalToSuperview().offset(-44)
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  func setInfoClosure(_ closure: @escaping () -> Void) {
+    didTapInfo = closure
+  }
+
+  func update(username: String) {
+    inputField.update(placeholder: username)
+  }
+
+  func update(status: InputField.ValidationStatus) {
+    inputField.update(status: status)
+
+    switch status {
+    case .valid:
+      confirmButton.isEnabled = true
+    case .invalid, .unknown:
+      confirmButton.isEnabled = false
+    }
+  }
+}
diff --git a/Sources/SettingsFeature/Views/SettingsMainView.swift b/Sources/SettingsFeature/Views/SettingsMainView.swift
new file mode 100644
index 0000000000000000000000000000000000000000..f1f84e5438f9a25b9fd823e4a53e3f37806e508d
--- /dev/null
+++ b/Sources/SettingsFeature/Views/SettingsMainView.swift
@@ -0,0 +1,178 @@
+import UIKit
+import Shared
+import AppResources
+
+final class SettingsMainView: UIView {
+  enum InfoTapped {
+    case dummyTraffic
+    case biometrics
+    case notifications
+    case icognitoKeyboard
+  }
+
+  let generalStack = UIStackView()
+  let generalTitle = UILabel()
+  let biometrics = SettingsInfoSwitcher()
+  let remoteNotifications = SettingsInfoSwitcher()
+  let dummyTraffic = SettingsInfoSwitcher()
+  let inAppNotifications = SettingsSwitcher()
+
+  let chatStack = UIStackView()
+  let chatTitle = UILabel()
+  let hideActiveApp = SettingsSwitcher()
+  let icognitoKeyboard = SettingsInfoSwitcher()
+
+  let otherStackView = UIStackView()
+  let privacyPolicyButton = RowButton()
+  let disclosuresButton = RowButton()
+  let advancedButton = RowButton()
+  let accountBackupButton = RowButton()
+  let deleteButton = RowButton()
+
+  let didTap: (InfoTapped) -> Void
+
+  init(didTap: @escaping (InfoTapped) -> Void) {
+    self.didTap = didTap
+
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
+
+    setupGeneralStack()
+    setupChatStack()
+    setupOtherStack()
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  private func setupGeneralStack() {
+    generalTitle.text = Localized.Settings.general
+    generalTitle.textColor = Asset.neutralActive.color
+    generalTitle.font = Fonts.Mulish.semiBold.font(size: 18.0)
+
+    remoteNotifications.set(
+      title: Localized.Settings.RemoteNotifications.title,
+      text: Localized.Settings.RemoteNotifications.description,
+      icon: Asset.settingsNotifications.image
+    ) { self.didTap(.notifications) }
+
+    inAppNotifications.set(
+      title: Localized.Settings.InAppNotifications.title,
+      text: Localized.Settings.InAppNotifications.description,
+      icon: Asset.settingsNotifications.image
+    )
+
+    dummyTraffic.set(
+      title: Localized.Settings.Traffic.title,
+      text: Localized.Settings.Traffic.subtitle,
+      icon: Asset.settingsBiometrics.image,
+      separator: false,
+      didTapInfo: { self.didTap(.dummyTraffic) }
+    )
+
+    biometrics.set(
+      title: Localized.Settings.Biometrics.title,
+      text: Localized.Settings.Biometrics.description,
+      icon: Asset.settingsBiometrics.image,
+      separator: false
+    ) { self.didTap(.biometrics) }
+
+    generalStack.axis = .vertical
+    generalStack.addArrangedSubview(remoteNotifications)
+    generalStack.addArrangedSubview(dummyTraffic)
+    generalStack.addArrangedSubview(inAppNotifications)
+    generalStack.addArrangedSubview(biometrics)
+
+    addSubview(generalTitle)
+    addSubview(generalStack)
+
+    generalTitle.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(24)
+      $0.left.equalToSuperview().offset(21)
+    }
+
+    generalStack.snp.makeConstraints {
+      $0.top.equalTo(generalTitle.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(16)
+      $0.right.equalToSuperview().offset(-16)
+    }
+  }
+
+  private func setupChatStack() {
+    chatTitle.text = Localized.Settings.chat
+    chatTitle.textColor = Asset.neutralActive.color
+    chatTitle.font = Fonts.Mulish.semiBold.font(size: 18.0)
+
+    hideActiveApp.set(
+      title: Localized.Settings.HideActiveApps.title,
+      text: Localized.Settings.HideActiveApps.description,
+      icon: Asset.settingsHide.image
+    )
+
+    icognitoKeyboard.set(
+      title: Localized.Settings.IcognitoKeyboard.title,
+      text: Localized.Settings.IcognitoKeyboard.description,
+      icon: Asset.settingsKeyboard.image
+    ) { self.didTap(.icognitoKeyboard) }
+
+    chatStack.axis = .vertical
+    chatStack.addArrangedSubview(hideActiveApp)
+    chatStack.addArrangedSubview(icognitoKeyboard)
+
+    addSubview(chatTitle)
+    addSubview(chatStack)
+
+    chatTitle.snp.makeConstraints {
+      $0.top.equalTo(generalStack.snp.bottom).offset(20)
+      $0.left.equalToSuperview().offset(21)
+    }
+    chatStack.snp.makeConstraints {
+      $0.top.equalTo(chatTitle.snp.bottom).offset(8)
+      $0.left.equalToSuperview().offset(16)
+      $0.right.equalToSuperview().offset(-16)
+    }
+  }
+
+  private func setupOtherStack() {
+    privacyPolicyButton.setup(
+      title: Localized.Settings.privacyPolicy,
+      icon: Asset.settingsPrivacy.image,
+      separator: false
+    )
+    disclosuresButton.setup(
+      title: Localized.Settings.disclosures,
+      icon: Asset.settingsFolder.image
+    )
+    advancedButton.setup(
+      title: Localized.Settings.advanced,
+      icon: Asset.settingsAdvanced.image
+    )
+    accountBackupButton.setup(
+      title: Localized.Settings.Advanced.AccountBackup.title,
+      icon: Asset.settingsAdvanced.image,
+      style: .clean,
+      separator: false
+    )
+    deleteButton.setup(
+      title: Localized.Settings.delete,
+      icon: Asset.settingsDelete.image,
+      style: .delete,
+      separator: false
+    )
+
+    otherStackView.axis = .vertical
+    otherStackView.addArrangedSubview(privacyPolicyButton)
+    otherStackView.addArrangedSubview(disclosuresButton)
+    otherStackView.addArrangedSubview(accountBackupButton)
+    otherStackView.addArrangedSubview(advancedButton)
+    otherStackView.addArrangedSubview(deleteButton)
+
+    addSubview(otherStackView)
+
+    otherStackView.snp.makeConstraints {
+      $0.top.equalTo(chatStack.snp.bottom).offset(15)
+      $0.left.equalToSuperview().offset(16)
+      $0.right.equalToSuperview().offset(-16)
+      $0.bottom.lessThanOrEqualToSuperview().offset(-20)
+    }
+  }
+}
diff --git a/Sources/SettingsFeature/Views/SettingsSwitcher.swift b/Sources/SettingsFeature/Views/SettingsSwitcher.swift
index 8b928746226f12ba5ddc5873de50d66989284f8d..8656cb7861506e22d51ff3dc22d7cde46ef3ac42 100644
--- a/Sources/SettingsFeature/Views/SettingsSwitcher.swift
+++ b/Sources/SettingsFeature/Views/SettingsSwitcher.swift
@@ -1,219 +1,218 @@
 import UIKit
 import Shared
+import AppResources
 
 final class SettingsSwitcher: UIView {
-    let titleLabel = UILabel()
-    let textLabel = UILabel()
-    let iconImageView = UIImageView()
-    let separatorView = UIView()
-    let switcherView = UISwitch()
-    let stackView = UIStackView()
-    let verticalStackView = UIStackView()
-
-    init() {
-        super.init(frame: .zero)
-
-        textLabel.textColor = Asset.neutralWeak.color
-        titleLabel.textColor = Asset.neutralActive.color
-        switcherView.onTintColor = Asset.brandPrimary.color
-        separatorView.backgroundColor = Asset.neutralLine.color
-
-        iconImageView.contentMode = .center
-        iconImageView.setContentHuggingPriority(.required, for: .horizontal)
-
-        textLabel.numberOfLines = 0
-        textLabel.font = Fonts.Mulish.regular.font(size: 12.0)
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
-
-        addSubview(stackView)
-        addSubview(separatorView)
-
-        verticalStackView.spacing = 3
-        verticalStackView.axis = .vertical
-        verticalStackView.addArrangedSubview(titleLabel)
-        verticalStackView.addArrangedSubview(textLabel)
-
-        let icon = iconImageView.pinning(at: .top(0))
-
-        stackView.spacing = 8
-        stackView.addArrangedSubview(icon)
-        stackView.addArrangedSubview(verticalStackView)
-        stackView.addArrangedSubview(switcherView.pinning(at: .top(0)))
-
-        stackView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(16)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.equalToSuperview().offset(-20)
-        }
-
-        separatorView.snp.makeConstraints { make in
-            make.height.equalTo(1)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.equalToSuperview()
-        }
+  let titleLabel = UILabel()
+  let textLabel = UILabel()
+  let iconImageView = UIImageView()
+  let separatorView = UIView()
+  let switcherView = UISwitch()
+  let stackView = UIStackView()
+  let verticalStackView = UIStackView()
+
+  init() {
+    super.init(frame: .zero)
+
+    textLabel.textColor = Asset.neutralWeak.color
+    titleLabel.textColor = Asset.neutralActive.color
+    switcherView.onTintColor = Asset.brandPrimary.color
+    separatorView.backgroundColor = Asset.neutralLine.color
+
+    iconImageView.contentMode = .center
+    iconImageView.setContentHuggingPriority(.required, for: .horizontal)
+
+    textLabel.numberOfLines = 0
+    textLabel.font = Fonts.Mulish.regular.font(size: 12.0)
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
+
+    addSubview(stackView)
+    addSubview(separatorView)
+
+    verticalStackView.spacing = 3
+    verticalStackView.axis = .vertical
+    verticalStackView.addArrangedSubview(titleLabel)
+    verticalStackView.addArrangedSubview(textLabel)
+
+    let icon = iconImageView.pinning(at: .top(0))
+
+    stackView.spacing = 8
+    stackView.addArrangedSubview(icon)
+    stackView.addArrangedSubview(verticalStackView)
+    stackView.addArrangedSubview(switcherView.pinning(at: .top(0)))
+
+    stackView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(16)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview().offset(-20)
+    }
+    separatorView.snp.makeConstraints {
+      $0.height.equalTo(1)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  func set(
+    title: String,
+    text: String? = nil,
+    icon: UIImage? = nil,
+    separator: Bool = true,
+    extraAction: UIButton? = nil
+  ) {
+    titleLabel.text = title
+
+    if let content = text {
+      let paragraphStyle = NSMutableParagraphStyle()
+      paragraphStyle.lineHeightMultiple = 1.5
+
+      textLabel.attributedText = NSAttributedString(
+        string: content, attributes: [.paragraphStyle: paragraphStyle]
+      )
+    } else {
+      verticalStackView.removeArrangedSubview(textLabel)
+    }
+
+    if let icon = icon {
+      iconImageView.image = icon
+    } else {
+      stackView.removeArrangedSubview(iconImageView)
+    }
+
+    if let button = extraAction {
+      stackView.insertArrangedSubview(button.pinning(at: .top(0)), at: 2)
     }
 
-    required init?(coder: NSCoder) { nil }
-
-    func set(
-        title: String,
-        text: String? = nil,
-        icon: UIImage? = nil,
-        separator: Bool = true,
-        extraAction: UIButton? = nil
-    ) {
-        titleLabel.text = title
-
-        if let content = text {
-            let paragraphStyle = NSMutableParagraphStyle()
-            paragraphStyle.lineHeightMultiple = 1.5
-
-            textLabel.attributedText = NSAttributedString(
-                string: content, attributes: [.paragraphStyle: paragraphStyle]
-            )
-        } else {
-            verticalStackView.removeArrangedSubview(textLabel)
-        }
-
-        if let icon = icon {
-            iconImageView.image = icon
-        } else {
-            stackView.removeArrangedSubview(iconImageView)
-        }
-
-        if let button = extraAction {
-            stackView.insertArrangedSubview(button.pinning(at: .top(0)), at: 2)
-        }
-
-        guard separator == true else {
-            separatorView.removeFromSuperview()
-            return
-        }
+    guard separator == true else {
+      separatorView.removeFromSuperview()
+      return
     }
+  }
 }
 
 final class SettingsInfoSwitcher: UIView {
-    let titleView = TextWithInfoView()
-    let textLabel = UILabel()
-    let iconImageView = UIImageView()
-    let separatorView = UIView()
-    let switcherView = UISwitch()
-    let stackView = UIStackView()
-    let verticalStackView = UIStackView()
-
-    var didTapInfo: (() -> Void)?
-
-    init() {
-        super.init(frame: .zero)
-
-        textLabel.textColor = Asset.neutralWeak.color
-        switcherView.onTintColor = Asset.brandPrimary.color
-        separatorView.backgroundColor = Asset.neutralLine.color
-
-        iconImageView.contentMode = .center
-        iconImageView.setContentHuggingPriority(.required, for: .horizontal)
-
-        textLabel.numberOfLines = 0
-        textLabel.font = Fonts.Mulish.regular.font(size: 12.0)
-        textLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
-
-        addSubview(stackView)
-        addSubview(separatorView)
-
-        let titleContainer = UIView()
-        titleContainer.addSubview(titleView)
-        titleView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(-10)
-            make.left.equalToSuperview().offset(-4)
-            make.right.equalToSuperview()
-            make.bottom.equalToSuperview().offset(10)
-        }
-
-        verticalStackView.spacing = 3
-        verticalStackView.axis = .vertical
-        verticalStackView.addArrangedSubview(titleContainer.pinning(at: .left(0)))
-        verticalStackView.addArrangedSubview(textLabel)
-
-        let icon = iconImageView.pinning(at: .top(0))
-
-        let switcherContainer = UIView()
-        switcherContainer.addSubview(switcherView)
-        switcherView.setContentCompressionResistancePriority(.required, for: .horizontal)
-        switcherContainer.setContentCompressionResistancePriority(.required, for: .horizontal)
-
-        switcherView.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.lessThanOrEqualToSuperview()
-        }
-
-        stackView.spacing = 8
-        stackView.addArrangedSubview(icon)
-        stackView.addArrangedSubview(verticalStackView)
-        stackView.addArrangedSubview(switcherContainer)
-
-        stackView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(16)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.equalToSuperview().offset(-20)
-        }
-
-        separatorView.snp.makeConstraints { make in
-            make.height.equalTo(1)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.equalToSuperview()
-        }
+  let titleView = TextWithInfoView()
+  let textLabel = UILabel()
+  let iconImageView = UIImageView()
+  let separatorView = UIView()
+  let switcherView = UISwitch()
+  let stackView = UIStackView()
+  let verticalStackView = UIStackView()
+
+  var didTapInfo: (() -> Void)?
+
+  init() {
+    super.init(frame: .zero)
+
+    textLabel.textColor = Asset.neutralWeak.color
+    switcherView.onTintColor = Asset.brandPrimary.color
+    separatorView.backgroundColor = Asset.neutralLine.color
+
+    iconImageView.contentMode = .center
+    iconImageView.setContentHuggingPriority(.required, for: .horizontal)
+
+    textLabel.numberOfLines = 0
+    textLabel.font = Fonts.Mulish.regular.font(size: 12.0)
+    textLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
+
+    addSubview(stackView)
+    addSubview(separatorView)
+
+    let titleContainer = UIView()
+    titleContainer.addSubview(titleView)
+    titleView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(-10)
+      $0.left.equalToSuperview().offset(-4)
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview().offset(10)
+    }
+
+    verticalStackView.spacing = 3
+    verticalStackView.axis = .vertical
+    verticalStackView.addArrangedSubview(titleContainer.pinning(at: .left(0)))
+    verticalStackView.addArrangedSubview(textLabel)
+
+    let icon = iconImageView.pinning(at: .top(0))
+
+    let switcherContainer = UIView()
+    switcherContainer.addSubview(switcherView)
+    switcherView.setContentCompressionResistancePriority(.required, for: .horizontal)
+    switcherContainer.setContentCompressionResistancePriority(.required, for: .horizontal)
+
+    switcherView.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.lessThanOrEqualToSuperview()
+    }
+
+    stackView.spacing = 8
+    stackView.addArrangedSubview(icon)
+    stackView.addArrangedSubview(verticalStackView)
+    stackView.addArrangedSubview(switcherContainer)
+
+    stackView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(16)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview().offset(-20)
+    }
+    separatorView.snp.makeConstraints {
+      $0.height.equalTo(1)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  func set(
+    title: String,
+    text: String? = nil,
+    icon: UIImage? = nil,
+    separator: Bool = true,
+    extraAction: UIButton? = nil,
+    didTapInfo: (() -> Void)? = nil
+  ) {
+    self.didTapInfo = didTapInfo
+
+    titleView.setup(
+      text: title,
+      attributes: [
+        .foregroundColor: Asset.neutralActive.color,
+        .font: Fonts.Mulish.semiBold.font(size: 14.0) as Any
+      ], didTapInfo: { self.didTapInfo?() }
+    )
+
+    if let content = text {
+      let paragraphStyle = NSMutableParagraphStyle()
+      paragraphStyle.lineHeightMultiple = 1.5
+
+      textLabel.attributedText = NSAttributedString(
+        string: content, attributes: [.paragraphStyle: paragraphStyle]
+      )
+    } else {
+      verticalStackView.removeArrangedSubview(textLabel)
+    }
+
+    if let icon = icon {
+      iconImageView.image = icon
+    } else {
+      stackView.removeArrangedSubview(iconImageView)
+    }
+
+    if let button = extraAction {
+      stackView.insertArrangedSubview(button.pinning(at: .top(0)), at: 2)
     }
 
-    required init?(coder: NSCoder) { nil }
-
-    func set(
-        title: String,
-        text: String? = nil,
-        icon: UIImage? = nil,
-        separator: Bool = true,
-        extraAction: UIButton? = nil,
-        didTapInfo: (() -> Void)? = nil
-    ) {
-        self.didTapInfo = didTapInfo
-
-        titleView.setup(
-            text: title,
-            attributes: [
-                .foregroundColor: Asset.neutralActive.color,
-                .font: Fonts.Mulish.semiBold.font(size: 14.0) as Any
-            ], didTapInfo: { self.didTapInfo?() }
-        )
-
-        if let content = text {
-            let paragraphStyle = NSMutableParagraphStyle()
-            paragraphStyle.lineHeightMultiple = 1.5
-
-            textLabel.attributedText = NSAttributedString(
-                string: content, attributes: [.paragraphStyle: paragraphStyle]
-            )
-        } else {
-            verticalStackView.removeArrangedSubview(textLabel)
-        }
-
-        if let icon = icon {
-            iconImageView.image = icon
-        } else {
-            stackView.removeArrangedSubview(iconImageView)
-        }
-
-        if let button = extraAction {
-            stackView.insertArrangedSubview(button.pinning(at: .top(0)), at: 2)
-        }
-
-        guard separator == true else {
-            separatorView.removeFromSuperview()
-            return
-        }
+    guard separator == true else {
+      separatorView.removeFromSuperview()
+      return
     }
+  }
 }
diff --git a/Sources/SettingsFeature/Views/SettingsView.swift b/Sources/SettingsFeature/Views/SettingsView.swift
deleted file mode 100644
index 8f17926c9bc885d82d5625d83eb7cfdf1eae6807..0000000000000000000000000000000000000000
--- a/Sources/SettingsFeature/Views/SettingsView.swift
+++ /dev/null
@@ -1,182 +0,0 @@
-import UIKit
-import Shared
-
-final class SettingsView: UIView {
-    enum InfoTapped {
-        case dummyTraffic
-        case biometrics
-        case notifications
-        case icognitoKeyboard
-    }
-
-    let generalStack = UIStackView()
-    let generalTitle = UILabel()
-    let biometrics = SettingsInfoSwitcher()
-    let remoteNotifications = SettingsInfoSwitcher()
-    let dummyTraffic = SettingsInfoSwitcher()
-    let inAppNotifications = SettingsSwitcher()
-
-    let chatStack = UIStackView()
-    let chatTitle = UILabel()
-    let hideActiveApp = SettingsSwitcher()
-    let icognitoKeyboard = SettingsInfoSwitcher()
-
-    let otherStackView = UIStackView()
-    let privacyPolicyButton = RowButton()
-    let disclosuresButton = RowButton()
-    let advancedButton = RowButton()
-    let accountBackupButton = RowButton()
-    let deleteButton = RowButton()
-
-    let didTap: (InfoTapped) -> Void
-
-    init(didTap: @escaping (InfoTapped) -> Void) {
-        self.didTap = didTap
-
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
-
-        setupGeneralStack()
-        setupChatStack()
-        setupOtherStack()
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    private func setupGeneralStack() {
-        generalTitle.text = Localized.Settings.general
-        generalTitle.textColor = Asset.neutralActive.color
-        generalTitle.font = Fonts.Mulish.semiBold.font(size: 18.0)
-
-        remoteNotifications.set(
-            title: Localized.Settings.RemoteNotifications.title,
-            text: Localized.Settings.RemoteNotifications.description,
-            icon: Asset.settingsNotifications.image
-        ) { self.didTap(.notifications) }
-
-        inAppNotifications.set(
-            title: Localized.Settings.InAppNotifications.title,
-            text: Localized.Settings.InAppNotifications.description,
-            icon: Asset.settingsNotifications.image
-        )
-
-        dummyTraffic.set(
-            title: Localized.Settings.Traffic.title,
-            text: Localized.Settings.Traffic.subtitle,
-            icon: Asset.settingsBiometrics.image,
-            separator: false,
-            didTapInfo: { self.didTap(.dummyTraffic) }
-        )
-
-        biometrics.set(
-            title: Localized.Settings.Biometrics.title,
-            text: Localized.Settings.Biometrics.description,
-            icon: Asset.settingsBiometrics.image,
-            separator: false
-        ) { self.didTap(.biometrics) }
-
-        generalStack.axis = .vertical
-        generalStack.addArrangedSubview(remoteNotifications)
-        generalStack.addArrangedSubview(dummyTraffic)
-        generalStack.addArrangedSubview(inAppNotifications)
-        generalStack.addArrangedSubview(biometrics)
-
-        addSubview(generalTitle)
-        addSubview(generalStack)
-
-        generalTitle.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(24)
-            make.left.equalToSuperview().offset(21)
-        }
-
-        generalStack.snp.makeConstraints { make in
-            make.top.equalTo(generalTitle.snp.bottom).offset(8)
-            make.left.equalToSuperview().offset(16)
-            make.right.equalToSuperview().offset(-16)
-        }
-    }
-
-    private func setupChatStack() {
-        chatTitle.text = Localized.Settings.chat
-        chatTitle.textColor = Asset.neutralActive.color
-        chatTitle.font = Fonts.Mulish.semiBold.font(size: 18.0)
-
-        hideActiveApp.set(
-            title: Localized.Settings.HideActiveApps.title,
-            text: Localized.Settings.HideActiveApps.description,
-            icon: Asset.settingsHide.image
-        )
-
-        icognitoKeyboard.set(
-            title: Localized.Settings.IcognitoKeyboard.title,
-            text: Localized.Settings.IcognitoKeyboard.description,
-            icon: Asset.settingsKeyboard.image
-        ) { self.didTap(.icognitoKeyboard) }
-
-        chatStack.axis = .vertical
-        chatStack.addArrangedSubview(hideActiveApp)
-        chatStack.addArrangedSubview(icognitoKeyboard)
-
-        addSubview(chatTitle)
-        addSubview(chatStack)
-
-        chatTitle.snp.makeConstraints { make in
-            make.top.equalTo(generalStack.snp.bottom).offset(20)
-            make.left.equalToSuperview().offset(21)
-        }
-
-        chatStack.snp.makeConstraints { make in
-            make.top.equalTo(chatTitle.snp.bottom).offset(8)
-            make.left.equalToSuperview().offset(16)
-            make.right.equalToSuperview().offset(-16)
-        }
-    }
-
-    private func setupOtherStack() {
-        privacyPolicyButton.setup(
-            title: Localized.Settings.privacyPolicy,
-            icon: Asset.settingsPrivacy.image,
-            separator: false
-        )
-
-        disclosuresButton.setup(
-            title: Localized.Settings.disclosures,
-            icon: Asset.settingsFolder.image
-        )
-
-        advancedButton.setup(
-            title: Localized.Settings.advanced,
-            icon: Asset.settingsAdvanced.image
-        )
-
-        accountBackupButton.setup(
-            title: Localized.Settings.Advanced.AccountBackup.title,
-            icon: Asset.settingsAdvanced.image,
-            style: .clean,
-            separator: false
-        )
-
-        deleteButton.setup(
-            title: Localized.Settings.delete,
-            icon: Asset.settingsDelete.image,
-            style: .delete,
-            separator: false
-        )
-
-        otherStackView.axis = .vertical
-        otherStackView.addArrangedSubview(privacyPolicyButton)
-        otherStackView.addArrangedSubview(disclosuresButton)
-        otherStackView.addArrangedSubview(accountBackupButton)
-        otherStackView.addArrangedSubview(advancedButton)
-        otherStackView.addArrangedSubview(deleteButton)
-
-        addSubview(otherStackView)
-
-        otherStackView.snp.makeConstraints { make in
-            make.top.equalTo(chatStack.snp.bottom).offset(15)
-            make.left.equalToSuperview().offset(16)
-            make.right.equalToSuperview().offset(-16)
-            make.bottom.lessThanOrEqualToSuperview().offset(-20)
-        }
-    }
-}
diff --git a/Sources/Shared/Aliases.swift b/Sources/Shared/Aliases.swift
deleted file mode 100644
index 6b3859ff2b6f5bdbc00a47fb1e352a7378868e04..0000000000000000000000000000000000000000
--- a/Sources/Shared/Aliases.swift
+++ /dev/null
@@ -1,4 +0,0 @@
-import UIKit
-
-public typealias EmptyClosure = () -> Void
-public typealias StringClosure = (String) -> Void
diff --git a/Sources/Shared/Controllers/DiffEditableDataSource.swift b/Sources/Shared/Controllers/DiffEditableDataSource.swift
index 9103733b120a2f7ff7a3a25be34c9ce6c2f3a6eb..b964b3492790b3c1aee7608aaa3c4b08d039c0ef 100644
--- a/Sources/Shared/Controllers/DiffEditableDataSource.swift
+++ b/Sources/Shared/Controllers/DiffEditableDataSource.swift
@@ -1,13 +1,13 @@
 import UIKit
 
 public struct SectionId: Hashable {
-    public init() {}
+  public init() {}
 }
 
 public final class DiffEditableDataSource<SectionIdentifierType, ItemIdentifierType>
 : UITableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType>
 where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable {
-
-    public override func tableView(_ tableView: UITableView,
-                                   canEditRowAt indexPath: IndexPath) -> Bool { true }
+  
+  public override func tableView(_ tableView: UITableView,
+                                 canEditRowAt indexPath: IndexPath) -> Bool { true }
 }
diff --git a/Sources/Shared/EditStateHandler.swift b/Sources/Shared/EditStateHandler.swift
index 1a8d4c7e89edb3449118679c03e87fc4c1afcc08..5b2407d8233a4b17797f43e8a285743e6bbe136e 100644
--- a/Sources/Shared/EditStateHandler.swift
+++ b/Sources/Shared/EditStateHandler.swift
@@ -1,18 +1,15 @@
 import Combine
 
 public final class EditStateHandler {
-    // MARK: Properties
+  public var isEditing: AnyPublisher<Bool, Never> {
+    stateSubject.eraseToAnyPublisher()
+  }
 
-    public var isEditing: AnyPublisher<Bool, Never> { stateRelay.eraseToAnyPublisher() }
-    private let stateRelay = CurrentValueSubject<Bool, Never>(false)
+  private let stateSubject = CurrentValueSubject<Bool, Never>(false)
 
-    // MARK: Lifecycle
+  public init() {}
 
-    public init() {}
-
-    // MARK: Public
-
-    public func didSwitchEditing() {
-        stateRelay.value.toggle()
-    }
+  public func didSwitchEditing() {
+    stateSubject.value.toggle()
+  }
 }
diff --git a/Sources/Shared/Extensions/BezierPath.swift b/Sources/Shared/Extensions/BezierPath.swift
index 5238360a7ef06eb0ecf089d30f8a3ab869aa445c..7994776acf2e3894587f5e6cceffc7e286297a2f 100644
--- a/Sources/Shared/Extensions/BezierPath.swift
+++ b/Sources/Shared/Extensions/BezierPath.swift
@@ -1,7 +1,7 @@
 import UIKit
 
 public extension UIBezierPath {
-    convenience init(_ size: CGSize, rad: CGFloat) {
-        self.init(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: rad)
-    }
+  convenience init(_ size: CGSize, rad: CGFloat) {
+    self.init(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: rad)
+  }
 }
diff --git a/Sources/Shared/Extensions/Button.swift b/Sources/Shared/Extensions/Button.swift
index 3148a0cdee30fa987dc5e1d3d0e0c08db0f90fa2..5c3c3e86833defd4d357eb2da52d6e0b983959ad 100644
--- a/Sources/Shared/Extensions/Button.swift
+++ b/Sources/Shared/Extensions/Button.swift
@@ -1,12 +1,13 @@
 import UIKit
+import AppResources
 
 public extension UIButton {
-    static func back(color: UIColor = Asset.neutralActive.color) -> UIButton {
-        let back = UIButton()
-        back.setImage(Asset.navigationBarBack.image, for: .normal)
-        back.tintColor = color
-        back.imageView?.contentMode = .center
-        back.snp.makeConstraints { $0.width.equalTo(50) }
-        return back
-    }
+  static func back(color: UIColor = Asset.neutralActive.color) -> UIButton {
+    let back = UIButton()
+    back.setImage(Asset.navigationBarBack.image, for: .normal)
+    back.tintColor = color
+    back.imageView?.contentMode = .center
+    back.snp.makeConstraints { $0.width.equalTo(50) }
+    return back
+  }
 }
diff --git a/Sources/Shared/Extensions/CollectionView.swift b/Sources/Shared/Extensions/CollectionView.swift
index 27b041e093c060e28b17b8fc81af0e7fb9355cd4..e2fcafa0a3e18368b6c8ce12cf92232ae51335d7 100644
--- a/Sources/Shared/Extensions/CollectionView.swift
+++ b/Sources/Shared/Extensions/CollectionView.swift
@@ -1,151 +1,152 @@
 import UIKit
 import ChatLayout
+import AppResources
 import DifferenceKit
 
 extension UICollectionReusableView: ReusableView {}
 
 public extension UICollectionView {
-    func register<T: UICollectionViewCell>(_: T.Type) {
-        register(T.self, forCellWithReuseIdentifier: T.reuseIdentifier)
+  func register<T: UICollectionViewCell>(_: T.Type) {
+    register(T.self, forCellWithReuseIdentifier: T.reuseIdentifier)
+  }
+  
+  func registerSectionHeader<T: UICollectionReusableView>(_: T.Type) {
+    register(
+      T.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
+      withReuseIdentifier: T.reuseIdentifier
+    )
+  }
+  
+  func dequeueReusableCell<T: UICollectionViewCell>(forIndexPath indexPath: IndexPath) -> T {
+    guard let cell = dequeueReusableCell(withReuseIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
+      fatalError("Could not dequeue cell with identifier: \(T.reuseIdentifier)")
     }
-
-    func registerSectionHeader<T: UICollectionReusableView>(_: T.Type) {
-        register(
-            T.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
-            withReuseIdentifier: T.reuseIdentifier
-        )
-    }
-
-    func dequeueReusableCell<T: UICollectionViewCell>(forIndexPath indexPath: IndexPath) -> T {
-        guard let cell = dequeueReusableCell(withReuseIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
-            fatalError("Could not dequeue cell with identifier: \(T.reuseIdentifier)")
-        }
-
-        return cell
+    
+    return cell
+  }
+  
+  func dequeueSupplementaryView<T: UICollectionReusableView>(forIndexPath indexPath: IndexPath) -> T {
+    dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader,
+                                     withReuseIdentifier: T.reuseIdentifier, for: indexPath) as! T
+  }
+  
+  convenience init(on view: UIView, with layout: CollectionViewChatLayout) {
+    self.init(frame: view.frame, collectionViewLayout: layout)
+    view.addSubview(self)
+    
+    frame = view.bounds
+    translatesAutoresizingMaskIntoConstraints = false
+    topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
+    bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
+    leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
+    trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
+    
+    alwaysBounceVertical = true
+    isPrefetchingEnabled = false
+    keyboardDismissMode = .interactive
+    showsHorizontalScrollIndicator = false
+    contentInsetAdjustmentBehavior = .always
+    backgroundColor = Asset.neutralSecondary.color
+    automaticallyAdjustsScrollIndicatorInsets = true
+  }
+  
+  func reload<C>(
+    using stagedChangeset: StagedChangeset<C>,
+    interrupt: ((Changeset<C>) -> Bool)? = nil,
+    onInterruptedReload: (() -> Void)? = nil,
+    completion: ((Bool) -> Void)? = nil,
+    setData: (C) -> Void
+  ) {
+    if case .none = window, let data = stagedChangeset.last?.data {
+      setData(data)
+      if let onInterruptedReload = onInterruptedReload {
+        onInterruptedReload()
+      } else {
+        reloadData()
+      }
+      completion?(false)
+      return
     }
-
-    func dequeueSupplementaryView<T: UICollectionReusableView>(forIndexPath indexPath: IndexPath) -> T {
-        dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader,
-                                         withReuseIdentifier: T.reuseIdentifier, for: indexPath) as! T
+    
+    let dispatchGroup: DispatchGroup? = completion != nil
+    ? DispatchGroup()
+    : nil
+    let completionHandler: ((Bool) -> Void)? = completion != nil
+    ? { _ in
+      dispatchGroup!.leave()
     }
-
-    convenience init(on view: UIView, with layout: ChatLayout) {
-        self.init(frame: view.frame, collectionViewLayout: layout)
-        view.addSubview(self)
-
-        frame = view.bounds
-        translatesAutoresizingMaskIntoConstraints = false
-        topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
-        bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
-        leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
-        trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
-
-        alwaysBounceVertical = true
-        isPrefetchingEnabled = false
-        keyboardDismissMode = .interactive
-        showsHorizontalScrollIndicator = false
-        contentInsetAdjustmentBehavior = .always
-        backgroundColor = Asset.neutralSecondary.color
-        automaticallyAdjustsScrollIndicatorInsets = true
-    }
-
-    func reload<C>(
-        using stagedChangeset: StagedChangeset<C>,
-        interrupt: ((Changeset<C>) -> Bool)? = nil,
-        onInterruptedReload: (() -> Void)? = nil,
-        completion: ((Bool) -> Void)? = nil,
-        setData: (C) -> Void
-    ) {
-        if case .none = window, let data = stagedChangeset.last?.data {
-            setData(data)
-            if let onInterruptedReload = onInterruptedReload {
-                onInterruptedReload()
-            } else {
-                reloadData()
-            }
-            completion?(false)
-            return
+    : nil
+    
+    for changeset in stagedChangeset {
+      if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data {
+        setData(data)
+        if let onInterruptedReload = onInterruptedReload {
+          onInterruptedReload()
+        } else {
+          reloadData()
         }
-
-        let dispatchGroup: DispatchGroup? = completion != nil
-            ? DispatchGroup()
-            : nil
-        let completionHandler: ((Bool) -> Void)? = completion != nil
-            ? { _ in
-                dispatchGroup!.leave()
-            }
-            : nil
-
-        for changeset in stagedChangeset {
-            if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data {
-                setData(data)
-                if let onInterruptedReload = onInterruptedReload {
-                    onInterruptedReload()
-                } else {
-                    reloadData()
-                }
-                completion?(false)
-                return
-            }
-
-            performBatchUpdates({
-                setData(changeset.data)
-                dispatchGroup?.enter()
-
-                if !changeset.sectionDeleted.isEmpty {
-                    deleteSections(IndexSet(changeset.sectionDeleted))
-                }
-
-                if !changeset.sectionInserted.isEmpty {
-                    insertSections(IndexSet(changeset.sectionInserted))
-                }
-
-                if !changeset.sectionUpdated.isEmpty {
-                    reloadSections(IndexSet(changeset.sectionUpdated))
-                }
-
-                for (source, target) in changeset.sectionMoved {
-                    moveSection(source, toSection: target)
-                }
-
-                if !changeset.elementDeleted.isEmpty {
-                    deleteItems(at: changeset.elementDeleted.map {
-                        IndexPath(item: $0.element, section: $0.section)
-                    })
-                }
-
-                if !changeset.elementInserted.isEmpty {
-                    insertItems(at: changeset.elementInserted.map {
-                        IndexPath(item: $0.element, section: $0.section)
-                    })
-                }
-
-                if !changeset.elementUpdated.isEmpty {
-                    reloadItems(at: changeset.elementUpdated.map {
-                        IndexPath(item: $0.element, section: $0.section)
-                    })
-                }
-
-                for (source, target) in changeset.elementMoved {
-                    moveItem(at: IndexPath(item: source.element, section: source.section), to: IndexPath(item: target.element, section: target.section))
-                }
-            }, completion: completionHandler)
+        completion?(false)
+        return
+      }
+      
+      performBatchUpdates({
+        setData(changeset.data)
+        dispatchGroup?.enter()
+        
+        if !changeset.sectionDeleted.isEmpty {
+          deleteSections(IndexSet(changeset.sectionDeleted))
+        }
+        
+        if !changeset.sectionInserted.isEmpty {
+          insertSections(IndexSet(changeset.sectionInserted))
         }
-        dispatchGroup?.notify(queue: .main) {
-            completion!(true)
+        
+        if !changeset.sectionUpdated.isEmpty {
+          reloadSections(IndexSet(changeset.sectionUpdated))
         }
+        
+        for (source, target) in changeset.sectionMoved {
+          moveSection(source, toSection: target)
+        }
+        
+        if !changeset.elementDeleted.isEmpty {
+          deleteItems(at: changeset.elementDeleted.map {
+            IndexPath(item: $0.element, section: $0.section)
+          })
+        }
+        
+        if !changeset.elementInserted.isEmpty {
+          insertItems(at: changeset.elementInserted.map {
+            IndexPath(item: $0.element, section: $0.section)
+          })
+        }
+        
+        if !changeset.elementUpdated.isEmpty {
+          reloadItems(at: changeset.elementUpdated.map {
+            IndexPath(item: $0.element, section: $0.section)
+          })
+        }
+        
+        for (source, target) in changeset.elementMoved {
+          moveItem(at: IndexPath(item: source.element, section: source.section), to: IndexPath(item: target.element, section: target.section))
+        }
+      }, completion: completionHandler)
+    }
+    dispatchGroup?.notify(queue: .main) {
+      completion!(true)
     }
+  }
 }
 
 public extension StagedChangeset {
-    func flattenIfPossible() -> StagedChangeset {
-        if count == 2,
-           self[0].sectionChangeCount == 0,
-           self[1].sectionChangeCount == 0,
-           self[0].elementDeleted.count == self[0].elementChangeCount,
-           self[1].elementInserted.count == self[1].elementChangeCount {
-            return StagedChangeset(arrayLiteral: Changeset(data: self[1].data, elementDeleted: self[0].elementDeleted, elementInserted: self[1].elementInserted))
-        }
-        return self
+  func flattenIfPossible() -> StagedChangeset {
+    if count == 2,
+       self[0].sectionChangeCount == 0,
+       self[1].sectionChangeCount == 0,
+       self[0].elementDeleted.count == self[0].elementChangeCount,
+       self[1].elementInserted.count == self[1].elementChangeCount {
+      return StagedChangeset(arrayLiteral: Changeset(data: self[1].data, elementDeleted: self[0].elementDeleted, elementInserted: self[1].elementInserted))
     }
+    return self
+  }
 }
diff --git a/Sources/Shared/Extensions/Colors.swift b/Sources/Shared/Extensions/Colors.swift
index 568f515724619f20a78636309adef24dcd506a51..28a21c17e94c28e043335ea6714c75af5289e0f5 100644
--- a/Sources/Shared/Extensions/Colors.swift
+++ b/Sources/Shared/Extensions/Colors.swift
@@ -1,18 +1,18 @@
 import UIKit
 
 public extension UIColor {
-    static func fade(from color: UIColor, to: UIColor, pcent: CGFloat) -> UIColor {
-        var fromRed: CGFloat = 0, fromGreen: CGFloat = 0, fromBlue: CGFloat = 0, fromAlpha: CGFloat = 0
-        color.getRed(&fromRed, green: &fromGreen, blue: &fromBlue, alpha: &fromAlpha)
-
-        var toRed: CGFloat = 0, toGreen: CGFloat = 0, toBlue: CGFloat = 0, toAlpha: CGFloat = 0
-        to.getRed(&toRed, green: &toGreen, blue: &toBlue, alpha: &toAlpha)
-
-        let red = (toRed - fromRed) * pcent + fromRed
-        let green = (toGreen - fromGreen) * pcent + fromGreen
-        let blue = (toBlue - fromBlue) * pcent + fromBlue
-        let alpha = (toAlpha - fromAlpha) * pcent + fromAlpha
-
-        return UIColor(red: red, green: green, blue: blue, alpha: alpha)
-    }
+  static func fade(from color: UIColor, to: UIColor, pcent: CGFloat) -> UIColor {
+    var fromRed: CGFloat = 0, fromGreen: CGFloat = 0, fromBlue: CGFloat = 0, fromAlpha: CGFloat = 0
+    color.getRed(&fromRed, green: &fromGreen, blue: &fromBlue, alpha: &fromAlpha)
+    
+    var toRed: CGFloat = 0, toGreen: CGFloat = 0, toBlue: CGFloat = 0, toAlpha: CGFloat = 0
+    to.getRed(&toRed, green: &toGreen, blue: &toBlue, alpha: &toAlpha)
+    
+    let red = (toRed - fromRed) * pcent + fromRed
+    let green = (toGreen - fromGreen) * pcent + fromGreen
+    let blue = (toBlue - fromBlue) * pcent + fromBlue
+    let alpha = (toAlpha - fromAlpha) * pcent + fromAlpha
+    
+    return UIColor(red: red, green: green, blue: blue, alpha: alpha)
+  }
 }
diff --git a/Sources/Shared/Extensions/Date.swift b/Sources/Shared/Extensions/Date.swift
index dd0fc91aaa80cfb18e028721d1ae617fd311354b..93d9058f05357fc02caae4ee5a0889bc5bcba43b 100644
--- a/Sources/Shared/Extensions/Date.swift
+++ b/Sources/Shared/Extensions/Date.swift
@@ -1,56 +1,60 @@
 import Foundation
 
 public extension Date {
-    func asDayOfMonth() -> String {
-        let formatter = DateFormatter()
-        formatter.dateFormat = DateFormatter.dateFormat(
-            fromTemplate: "d MMMM",
-            options: 0,
-            locale: Locale(identifier: "en_US")
-        )
-
-        return formatter.string(from: self)
-    }
-
-    func asHoursAndMinutes() -> String {
-        let formatter = DateFormatter()
-        formatter.dateStyle = .none
-        formatter.timeStyle = .short
-        return formatter.string(from: self)
-    }
-
-    func asRelativeFromNow() -> String {
-        let formatter = RelativeDateTimeFormatter()
-        formatter.dateTimeStyle = .named
-        return formatter.string(for: self) ?? ""
-    }
-
-    func backupStyle() -> String {
-        let formatter = DateFormatter()
-        formatter.dateFormat = DateFormatter.dateFormat(
-            fromTemplate: "MMM d, YYYY - h:mm",
-            options: 0,
-            locale: Locale(identifier: "en_US")
-        )
-
-        return formatter.string(from: self)
-    }
-
-    static var asTimestamp: Int {
-        Int(Date().timeIntervalSince1970).toNano()
-    }
-
-    static func fromTimestamp(_ timestamp: Int) -> Date {
-        Date(timeIntervalSince1970: TimeInterval(timestamp.nanoToSeconds()))
-    }
+  func asDayOfMonth() -> String {
+    let formatter = DateFormatter()
+    formatter.dateFormat = DateFormatter.dateFormat(
+      fromTemplate: "d MMMM",
+      options: 0,
+      locale: Locale(identifier: "en_US")
+    )
+    
+    return formatter.string(from: self)
+  }
+  
+  func asHoursAndMinutes() -> String {
+    let formatter = DateFormatter()
+    formatter.dateStyle = .none
+    formatter.timeStyle = .short
+    return formatter.string(from: self)
+  }
+  
+  func asRelativeFromNow() -> String {
+    let formatter = RelativeDateTimeFormatter()
+    formatter.dateTimeStyle = .named
+    return formatter.string(for: self) ?? ""
+  }
+  
+  func backupStyle() -> String {
+    let formatter = DateFormatter()
+    formatter.dateFormat = DateFormatter.dateFormat(
+      fromTemplate: "MMM d, YYYY - h:mm",
+      options: 0,
+      locale: Locale(identifier: "en_US")
+    )
+    
+    return formatter.string(from: self)
+  }
+  
+  static var asTimestamp: Int {
+    Int(Date().timeIntervalSince1970).toNano()
+  }
+  
+  static func fromTimestamp(_ timestamp: Int) -> Date {
+    Date(timeIntervalSince1970: TimeInterval(timestamp.nanoToSeconds()))
+  }
+  
+  static func fromMSTimestamp(_ timestampMS: Int64) -> Date {
+    Date(timeIntervalSince1970: TimeInterval(timestampMS) / 1000)
+  }
 }
 
 private extension Int {
-    func nanoToSeconds() -> Int {
-        self / 1000000000
-    }
-
-    func toNano() -> Int {
-        self * 1000000000
-    }
+  func nanoToSeconds() -> Int {
+    self / 1000000000
+  }
+  
+  func toNano() -> Int {
+    self * 1000000000
+  }
 }
diff --git a/Sources/Shared/Extensions/Error.swift b/Sources/Shared/Extensions/Error.swift
index 8ed7cb7677d80c088cdb3329c1d0d5f1274eb843..fc728460f381ad54294a2a211664b341c1b73c66 100644
--- a/Sources/Shared/Extensions/Error.swift
+++ b/Sources/Shared/Extensions/Error.swift
@@ -1,11 +1,11 @@
 import Foundation
 
 public extension NSError {
-    static func create(_ string: String) -> NSError {
-        NSError(
-            domain: "Internal error",
-            code: 0,
-            userInfo: [NSLocalizedDescriptionKey: NSLocalizedString(string, comment: "")]
-        )
-    }
+  static func create(_ string: String) -> NSError {
+    NSError(
+      domain: "Internal error",
+      code: 0,
+      userInfo: [NSLocalizedDescriptionKey: NSLocalizedString(string, comment: "")]
+    )
+  }
 }
diff --git a/Sources/Shared/Extensions/FileManager.swift b/Sources/Shared/Extensions/FileManager.swift
index c8639b5f82820335b584fc77d9d4887d07b7cda4..3834f2451c77c94270bc20311dc303af2a836c7a 100644
--- a/Sources/Shared/Extensions/FileManager.swift
+++ b/Sources/Shared/Extensions/FileManager.swift
@@ -2,71 +2,71 @@ import UIKit
 import Foundation
 
 public extension FileManager {
-    static var root: URL {
-        self.default.urls(for: .documentDirectory, in: .userDomainMask)
-            .first!.appendingPathComponent("xxm/")
+  static var root: URL {
+    self.default.urls(for: .documentDirectory, in: .userDomainMask)
+      .first!.appendingPathComponent("xxm/")
+  }
+  
+  static var xxContents: [String]? {
+    try? self.default.contentsOfDirectory(atPath: root.path)
+  }
+  
+  static var xxPath: String {
+    if xxContents == nil {
+      do {
+        try self.default.createDirectory(
+          at: root,
+          withIntermediateDirectories: false,
+          attributes: nil
+        )
+      } catch {
+        fatalError(error.localizedDescription)
+      }
     }
-
-    static var xxContents: [String]? {
-        try? self.default.contentsOfDirectory(atPath: root.path)
-    }
-
-    static var xxPath: String {
-        if xxContents == nil {
-            do {
-                try self.default.createDirectory(
-                    at: root,
-                    withIntermediateDirectories: false,
-                    attributes: nil
-                )
-            } catch {
-                fatalError(error.localizedDescription)
-            }
-        }
-
-        return root.path
-    }
-
-    static func xxCleanup() {
-        guard let files = xxContents else { return }
-        files.forEach { try? FileManager.default.removeItem(at: root.appendingPathComponent($0)) }
-    }
-
-    static func url(for fileName: String) -> URL? {
-        root.appendingPathComponent("\(fileName)")
-    }
-
-    static func store(data: Data, name: String, type: String) throws -> URL {
-        guard let url = Self.url(for: "\(name).\(type)") else {
-            throw NSError.create("The file path could not be retrieved")
-        }
-
-        try data.write(to: url)
-        return url
+    
+    return root.path
+  }
+  
+  static func xxCleanup() {
+    guard let files = xxContents else { return }
+    files.forEach { try? FileManager.default.removeItem(at: root.appendingPathComponent($0)) }
+  }
+  
+  static func url(for fileName: String) -> URL? {
+    root.appendingPathComponent("\(fileName)")
+  }
+  
+  static func store(data: Data, name: String, type: String) throws -> URL {
+    guard let url = Self.url(for: "\(name).\(type)") else {
+      throw NSError.create("The file path could not be retrieved")
     }
-
-    static func delete(name: String, type: String) {
-        if let url = Self.url(for: "\(name).\(type)") {
-            do {
-                try FileManager.default.removeItem(at: url)
-            } catch {
-                print(error.localizedDescription)
-            }
-        }
-    }
-
-    static func dummyAudio() -> Data {
-        let url = Bundle.module.url(forResource: "dummy_audio", withExtension: "m4a")
-        return try! Data(contentsOf: url!)
-    }
-
-    static func retrieve(name: String, type: String) -> Data? {
-        guard let url = Self.url(for: "\(name).\(type)") else { return nil }
-        return try? Data(contentsOf: url)
-    }
-
-    static func retrieve(imageNamed name: String) -> UIImage? {
-        guard let url = Self.url(for: name) else { return nil }
-        return UIImage(contentsOfFile: url.path)
+    
+    try data.write(to: url)
+    return url
+  }
+  
+  static func delete(name: String, type: String) {
+    if let url = Self.url(for: "\(name).\(type)") {
+      do {
+        try FileManager.default.removeItem(at: url)
+      } catch {
+        print(error.localizedDescription)
+      }
     }
+  }
+  
+  static func dummyAudio() -> Data {
+    let url = Bundle.module.url(forResource: "dummy_audio", withExtension: "m4a")
+    return try! Data(contentsOf: url!)
+  }
+  
+  static func retrieve(name: String, type: String) -> Data? {
+    guard let url = Self.url(for: "\(name).\(type)") else { return nil }
+    return try? Data(contentsOf: url)
+  }
+  
+  static func retrieve(imageNamed name: String) -> UIImage? {
+    guard let url = Self.url(for: name) else { return nil }
+    return UIImage(contentsOfFile: url.path)
+  }
 }
diff --git a/Sources/Shared/Extensions/Image.swift b/Sources/Shared/Extensions/Image.swift
index 442f8ff735f6fe3ef9bcb5b640e97a82080d9d8d..d69873ca9375e4677e8aff70831fc58026fd6a08 100644
--- a/Sources/Shared/Extensions/Image.swift
+++ b/Sources/Shared/Extensions/Image.swift
@@ -1,56 +1,56 @@
 import UIKit
 
 public extension UIImage {
-    static func fromBase64(_ base64String: String?) -> UIImage? {
-        guard let base64 = base64String,
-              let imageData = Data(base64Encoded: base64, options: .ignoreUnknownCharacters) else { return nil }
-        
-        return UIImage(data: imageData)
-    }
+  static func fromBase64(_ base64String: String?) -> UIImage? {
+    guard let base64 = base64String,
+          let imageData = Data(base64Encoded: base64, options: .ignoreUnknownCharacters) else { return nil }
     
-    static func color(_ color: UIColor, size: CGSize = .init(width: 1, height: 1)) -> UIImage {
-        UIGraphicsImageRenderer(size: size).image { context in
-            color.setFill()
-            context.fill(CGRect(origin: .zero, size: size))
-        }
+    return UIImage(data: imageData)
+  }
+  
+  static func color(_ color: UIColor, size: CGSize = .init(width: 1, height: 1)) -> UIImage {
+    UIGraphicsImageRenderer(size: size).image { context in
+      color.setFill()
+      context.fill(CGRect(origin: .zero, size: size))
     }
-
-    func orientedUp() -> UIImage {
-        if imageOrientation == .up { return self }
-        let format = imageRendererFormat
-        return UIGraphicsImageRenderer(size: size, format: format).image { _ in draw(at: .zero) }
+  }
+  
+  func orientedUp() -> UIImage {
+    if imageOrientation == .up { return self }
+    let format = imageRendererFormat
+    return UIGraphicsImageRenderer(size: size, format: format).image { _ in draw(at: .zero) }
+  }
+  
+  func resized(withPercentage percentage: CGFloat, isOpaque: Bool = true) -> UIImage? {
+    let canvas = CGSize(width: size.width * percentage, height: size.height * percentage)
+    let format = imageRendererFormat
+    format.opaque = isOpaque
+    return UIGraphicsImageRenderer(size: canvas, format: format).image {
+      _ in draw(in: CGRect(origin: .zero, size: canvas))
     }
-
-    func resized(withPercentage percentage: CGFloat, isOpaque: Bool = true) -> UIImage? {
-        let canvas = CGSize(width: size.width * percentage, height: size.height * percentage)
-        let format = imageRendererFormat
-        format.opaque = isOpaque
-        return UIGraphicsImageRenderer(size: canvas, format: format).image {
-            _ in draw(in: CGRect(origin: .zero, size: canvas))
-        }
-    }
-
-    func compress(to kb: Int) -> Data {
-        let bytes = kb * 1024
-        var compression: CGFloat = 1.0
-        let step: CGFloat = 0.05
-        var holderImage = self
-        var complete = false
-
-        while(!complete) {
-            if let data = holderImage.jpegData(compressionQuality: 1.0) {
-                let ratio = data.count / bytes
-                if data.count < bytes {
-                    complete = true
-                    return data
-                } else {
-                    let multiplier: CGFloat = CGFloat((ratio / 5) + 1)
-                    compression -= (step * multiplier)
-                }
-            }
-            guard let newImage = holderImage.resized(withPercentage: compression) else { break }
-            holderImage = newImage
+  }
+  
+  func compress(to kb: Int) -> Data {
+    let bytes = kb * 1024
+    var compression: CGFloat = 1.0
+    let step: CGFloat = 0.05
+    var holderImage = self
+    var complete = false
+    
+    while(!complete) {
+      if let data = holderImage.jpegData(compressionQuality: 1.0) {
+        let ratio = data.count / bytes
+        if data.count < bytes {
+          complete = true
+          return data
+        } else {
+          let multiplier: CGFloat = CGFloat((ratio / 5) + 1)
+          compression -= (step * multiplier)
         }
-        return Data()
+      }
+      guard let newImage = holderImage.resized(withPercentage: compression) else { break }
+      holderImage = newImage
     }
+    return Data()
+  }
 }
diff --git a/Sources/Shared/Extensions/ItemProvider.swift b/Sources/Shared/Extensions/ItemProvider.swift
index b0c6e48a2584ae174f86691bd28d9431577bc0db..1bad223d6127e41b0a96750309389d5e5fc8c828 100644
--- a/Sources/Shared/Extensions/ItemProvider.swift
+++ b/Sources/Shared/Extensions/ItemProvider.swift
@@ -2,27 +2,27 @@ import UIKit
 import Combine
 
 public extension NSItemProvider {
-    func loadImageObjectPublisher() -> AnyPublisher<UIImage, Error> {
-        Deferred {
-            Future { promise in
-                self.loadObject(ofClass: UIImage.self) { image, error in
-                    if let error = error {
-                        promise(.failure(error))
-                        return
-                    }
-
-                    guard let safeImage = image as? UIImage else {
-                        struct InvalidImageError: Error {
-                            let image: NSItemProviderReading?
-                        }
-
-                        promise(.failure(InvalidImageError(image: image)))
-                        return
-                    }
-
-                    promise(.success(safeImage))
-                }
+  func loadImageObjectPublisher() -> AnyPublisher<UIImage, Error> {
+    Deferred {
+      Future { promise in
+        self.loadObject(ofClass: UIImage.self) { image, error in
+          if let error = error {
+            promise(.failure(error))
+            return
+          }
+          
+          guard let safeImage = image as? UIImage else {
+            struct InvalidImageError: Error {
+              let image: NSItemProviderReading?
             }
-        }.eraseToAnyPublisher()
-    }
+            
+            promise(.failure(InvalidImageError(image: image)))
+            return
+          }
+          
+          promise(.success(safeImage))
+        }
+      }
+    }.eraseToAnyPublisher()
+  }
 }
diff --git a/Sources/Shared/Extensions/MutableAttributedString.swift b/Sources/Shared/Extensions/MutableAttributedString.swift
index f963a1d5e53d3eb058dfdccfcd169c66d0bf90c6..badcd31c78aada923438ebc5189058c081bc72fc 100644
--- a/Sources/Shared/Extensions/MutableAttributedString.swift
+++ b/Sources/Shared/Extensions/MutableAttributedString.swift
@@ -1,75 +1,75 @@
 import Foundation
 
 public extension NSMutableAttributedString {
-    func addAttribute(_ name: NSAttributedString.Key, value: Any) {
-        addAttribute(name, value: value, range: NSRange(string.startIndex..., in: string))
+  func addAttribute(_ name: NSAttributedString.Key, value: Any) {
+    addAttribute(name, value: value, range: NSRange(string.startIndex..., in: string))
+  }
+  
+  func addAttributes(_ attrs: [NSAttributedString.Key: Any]) {
+    addAttributes(attrs, range: NSRange(string.startIndex..., in: string))
+  }
+  
+  func setAttributes(attributes: [NSAttributedString.Key: Any], betweenCharacters: String) {
+    let ranges: Array = findRangesWithCharaters(charactersToFind: betweenCharacters)
+    for obj in ranges {
+      let thisValue: NSValue = obj
+      let range: NSRange = thisValue.rangeValue
+      setAttributes(attributes, range: range)
     }
-
-    func addAttributes(_ attrs: [NSAttributedString.Key: Any]) {
-        addAttributes(attrs, range: NSRange(string.startIndex..., in: string))
-    }
-
-    func setAttributes(attributes: [NSAttributedString.Key: Any], betweenCharacters: String) {
-        let ranges: Array = findRangesWithCharaters(charactersToFind: betweenCharacters)
-        for obj in ranges {
-            let thisValue: NSValue = obj
-            let range: NSRange = thisValue.rangeValue
-            setAttributes(attributes, range: range)
-        }
+  }
+  
+  func addAttribute(name: NSAttributedString.Key, value: Any, betweenCharacters: String) {
+    let ranges: Array = findRangesWithCharaters(charactersToFind: betweenCharacters)
+    for obj in ranges {
+      let thisValue: NSValue = obj
+      let range: NSRange = thisValue.rangeValue
+      addAttribute(name, value: value, range: range)
     }
-
-    func addAttribute(name: NSAttributedString.Key, value: Any, betweenCharacters: String) {
-        let ranges: Array = findRangesWithCharaters(charactersToFind: betweenCharacters)
-        for obj in ranges {
-            let thisValue: NSValue = obj
-            let range: NSRange = thisValue.rangeValue
-            addAttribute(name, value: value, range: range)
-        }
+  }
+  
+  func addAttributes(attributes: [NSAttributedString.Key: Any], betweenCharacters: String) {
+    let ranges: Array = findRangesWithCharaters(charactersToFind: betweenCharacters)
+    for obj in ranges {
+      let thisValue: NSValue = obj
+      let range: NSRange = thisValue.rangeValue
+      addAttributes(attributes, range: range)
     }
-
-    func addAttributes(attributes: [NSAttributedString.Key: Any], betweenCharacters: String) {
-        let ranges: Array = findRangesWithCharaters(charactersToFind: betweenCharacters)
-        for obj in ranges {
-            let thisValue: NSValue = obj
-            let range: NSRange = thisValue.rangeValue
-            addAttributes(attributes, range: range)
-        }
-    }
-
-    func removeAttribute(name: NSAttributedString.Key, betweenCharacters: String) {
-        let ranges: Array = findRangesWithCharaters(charactersToFind: betweenCharacters)
-        for obj in ranges {
-            let thisValue: NSValue = obj
-            let range: NSRange = thisValue.rangeValue
-            removeAttribute(name, range: range)
-        }
+  }
+  
+  func removeAttribute(name: NSAttributedString.Key, betweenCharacters: String) {
+    let ranges: Array = findRangesWithCharaters(charactersToFind: betweenCharacters)
+    for obj in ranges {
+      let thisValue: NSValue = obj
+      let range: NSRange = thisValue.rangeValue
+      removeAttribute(name, range: range)
     }
-
-    func findRangesWithCharaters(charactersToFind: String) -> [NSValue] {
-        let resultArray = NSMutableArray()
-        var insideTheRange = false
-        var startingRangeLocation: Int = 0
-
-        while self.mutableString.range(of: charactersToFind).location != NSNotFound {
-            let charactersLocation: NSRange = self.mutableString.range(of: charactersToFind)
-
-            if !insideTheRange {
-                startingRangeLocation = charactersLocation.location
-                insideTheRange = true
-
-                self.mutableString.deleteCharacters(in: charactersLocation)
-            } else {
-                let range: NSRange = NSRange(location: startingRangeLocation,
-                                             length: charactersLocation.location - startingRangeLocation)
-                insideTheRange = false
-
-                resultArray.add(NSValue(range: range))
-                self.mutableString.deleteCharacters(in: charactersLocation)
-            }
-        }
-
-        guard let result = resultArray.copy() as? [NSValue] else { return [] }
-
-        return result
+  }
+  
+  func findRangesWithCharaters(charactersToFind: String) -> [NSValue] {
+    let resultArray = NSMutableArray()
+    var insideTheRange = false
+    var startingRangeLocation: Int = 0
+    
+    while self.mutableString.range(of: charactersToFind).location != NSNotFound {
+      let charactersLocation: NSRange = self.mutableString.range(of: charactersToFind)
+      
+      if !insideTheRange {
+        startingRangeLocation = charactersLocation.location
+        insideTheRange = true
+        
+        self.mutableString.deleteCharacters(in: charactersLocation)
+      } else {
+        let range: NSRange = NSRange(location: startingRangeLocation,
+                                     length: charactersLocation.location - startingRangeLocation)
+        insideTheRange = false
+        
+        resultArray.add(NSValue(range: range))
+        self.mutableString.deleteCharacters(in: charactersLocation)
+      }
     }
+    
+    guard let result = resultArray.copy() as? [NSValue] else { return [] }
+    
+    return result
+  }
 }
diff --git a/Sources/Shared/Extensions/NavigationBar.swift b/Sources/Shared/Extensions/NavigationBar.swift
index b7efcb698321a974752e5c07cab1542ca0d26d80..db3d7e5c32a0c9040a5ef05c7cf6569f7251e5b1 100644
--- a/Sources/Shared/Extensions/NavigationBar.swift
+++ b/Sources/Shared/Extensions/NavigationBar.swift
@@ -1,21 +1,22 @@
 import UIKit
+import AppResources
 
 public extension UINavigationBar {
-    func customize(
-        translucent: Bool = false,
-        backgroundColor: UIColor = .clear,
-        shadowColor: UIColor? = nil,
-        tint: UIColor = Asset.neutralActive.color
-    ) {
-        isTranslucent = translucent
-        let barAppearance = UINavigationBarAppearance()
-        barAppearance.backgroundColor = backgroundColor
-        barAppearance.backgroundEffect = .none
-        barAppearance.shadowColor = shadowColor
-
-        tintColor = tint
-        compactAppearance = barAppearance
-        standardAppearance = barAppearance
-        scrollEdgeAppearance = barAppearance
-    }
+  func customize(
+    translucent: Bool = false,
+    backgroundColor: UIColor = .clear,
+    shadowColor: UIColor? = nil,
+    tint: UIColor = Asset.neutralActive.color
+  ) {
+    isTranslucent = translucent
+    let barAppearance = UINavigationBarAppearance()
+    barAppearance.backgroundColor = backgroundColor
+    barAppearance.backgroundEffect = .none
+    barAppearance.shadowColor = shadowColor
+    
+    tintColor = tint
+    compactAppearance = barAppearance
+    standardAppearance = barAppearance
+    scrollEdgeAppearance = barAppearance
+  }
 }
diff --git a/Sources/Shared/Extensions/Publishers.swift b/Sources/Shared/Extensions/Publishers.swift
deleted file mode 100644
index f7394821dd2b3d8d5ddefc707e204caa76d70199..0000000000000000000000000000000000000000
--- a/Sources/Shared/Extensions/Publishers.swift
+++ /dev/null
@@ -1,113 +0,0 @@
-import UIKit
-import Combine
-
-public extension UIControl {
-    func publisher(for event: Event) -> EventPublisher {
-        EventPublisher(
-            control: self,
-            event: event
-        )
-    }
-
-    struct EventPublisher: Publisher {
-        public typealias Output = Void
-        public typealias Failure = Never
-
-        fileprivate var control: UIControl
-        fileprivate var event: Event
-
-        public func receive<S: Subscriber>(
-            subscriber: S
-        ) where S.Input == Output, S.Failure == Failure {
-            let subscription = EventSubscription<S>()
-            subscription.target = subscriber
-            subscriber.receive(subscription: subscription)
-
-            control.addTarget(subscription,
-                action: #selector(subscription.trigger),
-                for: event
-            )
-        }
-    }
-}
-
-private extension UIControl {
-    class EventSubscription<Target: Subscriber>: Subscription
-        where Target.Input == Void {
-
-        var target: Target?
-
-        func request(_ demand: Subscribers.Demand) {}
-
-        func cancel() {
-            target = nil
-        }
-
-        @objc func trigger() {
-            _ = target?.receive(())
-        }
-    }
-}
-
-public extension UITextField {
-    var textPublisher: AnyPublisher<String, Never> {
-        publisher(for: .editingChanged)
-            .map { self.text ?? "" }
-            .eraseToAnyPublisher()
-    }
-
-    var returnPublisher: AnyPublisher<Void, Never> {
-        publisher(for: .editingDidEndOnExit)
-            .eraseToAnyPublisher()
-    }
-}
-
-public extension UITextView {
-    var textPublisher: Publishers.TextFieldPublisher {
-        Publishers.TextFieldPublisher(textField: self)
-    }
-}
-
-public extension Publishers {
-    struct TextFieldPublisher: Publisher {
-        public typealias Output = String
-        public typealias Failure = Never
-
-        private let textField: UITextView
-
-        init(textField: UITextView) { self.textField = textField }
-
-        public func receive<S>(subscriber: S) where S : Subscriber, Publishers.TextFieldPublisher.Failure == S.Failure, Publishers.TextFieldPublisher.Output == S.Input {
-            let subscription = TextFieldSubscription(subscriber: subscriber, textField: textField)
-            subscriber.receive(subscription: subscription)
-        }
-    }
-
-    class TextFieldSubscription<S: Subscriber>: NSObject, Subscription, UITextViewDelegate where S.Input == String, S.Failure == Never  {
-
-        private var subscriber: S?
-        private weak var textField: UITextView?
-
-        init(subscriber: S, textField: UITextView) {
-            super.init()
-            self.subscriber = subscriber
-            self.textField = textField
-            subscribe()
-        }
-
-        public func request(_ demand: Subscribers.Demand) { }
-
-        public func cancel() {
-            subscriber = nil
-            textField = nil
-        }
-
-        private func subscribe() {
-            textField?.delegate = self
-        }
-
-        public func textViewDidChange(_ textView: UITextView) {
-            _ = subscriber?.receive(textView.text)
-        }
-    }
-}
diff --git a/Sources/Shared/Extensions/StackView.swift b/Sources/Shared/Extensions/StackView.swift
index 93cf61339a5d119c455fe82e43daff240d5f621d..4997ad49716673064d898cf144e3ecff0d6bf830 100644
--- a/Sources/Shared/Extensions/StackView.swift
+++ b/Sources/Shared/Extensions/StackView.swift
@@ -1,7 +1,7 @@
 import UIKit
 
 public extension UIStackView {
-    func addArrangedSubviews(_ subviews: [UIView]) {
-        subviews.forEach(addArrangedSubview(_:))
-    }
+  func addArrangedSubviews(_ subviews: [UIView]) {
+    subviews.forEach(addArrangedSubview(_:))
+  }
 }
diff --git a/Sources/Shared/Extensions/TableView.swift b/Sources/Shared/Extensions/TableView.swift
index c05ccd74d2193cfdd01cc8c0fdd442e8d979a66a..973feeff2cf30d2f915d5f0fb76d9b654c1e26ba 100644
--- a/Sources/Shared/Extensions/TableView.swift
+++ b/Sources/Shared/Extensions/TableView.swift
@@ -4,34 +4,34 @@ extension UITableViewCell: ReusableView {}
 extension UITableViewHeaderFooterView: ReusableView {}
 
 public extension UITableView {
-    func register(cells: [AnyClass]) {
-        cells.forEach { cell in
-            register(cell, forCellReuseIdentifier: String(describing: cell))
-        }
+  func register(cells: [AnyClass]) {
+    cells.forEach { cell in
+      register(cell, forCellReuseIdentifier: String(describing: cell))
     }
-
-    func registerHeaderFooter<T: UITableViewHeaderFooterView>(type: T.Type) {
-        register(T.self, forHeaderFooterViewReuseIdentifier: T.reuseIdentifier)
-    }
-
-    func register<T: UITableViewCell>(_: T.Type) {
-        register(T.self, forCellReuseIdentifier: T.reuseIdentifier)
-    }
-
-    func dequeueReusableCell<T: UITableViewCell>(forIndexPath indexPath: IndexPath,
-                                                 ofType type: T.Type? = nil) -> T {
-        guard let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
-            fatalError("Could not dequeue cell with identifier: \(T.reuseIdentifier)")
-        }
-
-        return cell
+  }
+  
+  func registerHeaderFooter<T: UITableViewHeaderFooterView>(type: T.Type) {
+    register(T.self, forHeaderFooterViewReuseIdentifier: T.reuseIdentifier)
+  }
+  
+  func register<T: UITableViewCell>(_: T.Type) {
+    register(T.self, forCellReuseIdentifier: T.reuseIdentifier)
+  }
+  
+  func dequeueReusableCell<T: UITableViewCell>(forIndexPath indexPath: IndexPath,
+                                               ofType type: T.Type? = nil) -> T {
+    guard let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
+      fatalError("Could not dequeue cell with identifier: \(T.reuseIdentifier)")
     }
-
-    func dequeueReusableHeaderFooter<T: UITableViewHeaderFooterView>(ofType type: T.Type? = nil) -> T {
-        guard let view = dequeueReusableHeaderFooterView(withIdentifier: T.reuseIdentifier) as? T else {
-            fatalError("Could not dequeue header footer with identifier: \(T.reuseIdentifier)")
-        }
-
-        return view
+    
+    return cell
+  }
+  
+  func dequeueReusableHeaderFooter<T: UITableViewHeaderFooterView>(ofType type: T.Type? = nil) -> T {
+    guard let view = dequeueReusableHeaderFooterView(withIdentifier: T.reuseIdentifier) as? T else {
+      fatalError("Could not dequeue header footer with identifier: \(T.reuseIdentifier)")
     }
+    
+    return view
+  }
 }
diff --git a/Sources/Shared/Extensions/View.swift b/Sources/Shared/Extensions/View.swift
index cdbc4f591dd6bbe26c08c47a2ec655f91528e954..0636e0d300e590d0082d847bfb161d83d5b908d2 100644
--- a/Sources/Shared/Extensions/View.swift
+++ b/Sources/Shared/Extensions/View.swift
@@ -4,78 +4,78 @@ import SnapKit
 protocol ReusableView {}
 
 extension ReusableView where Self: UIView {
-    static var reuseIdentifier: String {
-        return String(describing: self)
-    }
+  static var reuseIdentifier: String {
+    return String(describing: self)
+  }
 }
 
 public extension UIView {
-    enum PinningPosition {
-        case hCenter
-        case top(CGFloat)
-        case left(CGFloat)
-        case right(CGFloat)
-        case bottom(CGFloat)
-        case center(CGFloat)
-    }
-
-    func pinning(at position: PinningPosition) -> UIView {
-        let container = UIView()
-        container.addSubview(self)
-
-        self.snp.makeConstraints { make in
-            switch position {
-            case let .top(padding):
-                let flex = FlexibleSpace()
-                container.addSubview(flex)
-                flex.snp.makeConstraints { $0.bottom.equalToSuperview() }
-
-                make.top.equalToSuperview().offset(padding)
-                make.left.right.equalToSuperview()
-                make.bottom.lessThanOrEqualTo(flex.snp.top)
-
-            case let .left(padding):
-                let flex = FlexibleSpace()
-                container.addSubview(flex)
-                flex.snp.makeConstraints { $0.right.equalToSuperview() }
-
-                make.top.bottom.equalToSuperview()
-                make.left.equalToSuperview().offset(padding)
-                make.right.lessThanOrEqualTo(flex.snp.left)
-
-            case let .right(padding):
-                let flex = FlexibleSpace()
-                container.addSubview(flex)
-                flex.snp.makeConstraints { $0.bottom.equalToSuperview() }
-
-                make.top.bottom.equalToSuperview()
-                make.right.equalToSuperview().offset(padding)
-                make.left.greaterThanOrEqualTo(flex.snp.right)
-
-            case let .bottom(padding):
-                let flex = FlexibleSpace()
-                container.addSubview(flex)
-                flex.snp.makeConstraints { $0.top.equalToSuperview() }
-
-                make.bottom.equalToSuperview().offset(padding)
-                make.left.right.equalToSuperview()
-                make.top.greaterThanOrEqualTo(flex.snp.bottom)
-
-            case let .center(inset):
-                make.top.greaterThanOrEqualToSuperview().offset(inset)
-                make.left.greaterThanOrEqualToSuperview().offset(inset)
-                make.center.equalToSuperview()
-                make.right.lessThanOrEqualToSuperview().offset(-inset)
-                make.bottom.lessThanOrEqualToSuperview().offset(-inset)
-            case .hCenter:
-                make.top.equalToSuperview()
-                make.centerX.equalToSuperview()
-                make.left.greaterThanOrEqualToSuperview()
-                make.right.lessThanOrEqualToSuperview()
-                make.bottom.equalToSuperview()
-            }
-        }
-
-        return container
+  enum PinningPosition {
+    case hCenter
+    case top(CGFloat)
+    case left(CGFloat)
+    case right(CGFloat)
+    case bottom(CGFloat)
+    case center(CGFloat)
+  }
+  
+  func pinning(at position: PinningPosition) -> UIView {
+    let container = UIView()
+    container.addSubview(self)
+    
+    self.snp.makeConstraints { make in
+      switch position {
+      case let .top(padding):
+        let flex = FlexibleSpace()
+        container.addSubview(flex)
+        flex.snp.makeConstraints { $0.bottom.equalToSuperview() }
+        
+        make.top.equalToSuperview().offset(padding)
+        make.left.right.equalToSuperview()
+        make.bottom.lessThanOrEqualTo(flex.snp.top)
+        
+      case let .left(padding):
+        let flex = FlexibleSpace()
+        container.addSubview(flex)
+        flex.snp.makeConstraints { $0.right.equalToSuperview() }
+        
+        make.top.bottom.equalToSuperview()
+        make.left.equalToSuperview().offset(padding)
+        make.right.lessThanOrEqualTo(flex.snp.left)
+        
+      case let .right(padding):
+        let flex = FlexibleSpace()
+        container.addSubview(flex)
+        flex.snp.makeConstraints { $0.bottom.equalToSuperview() }
+        
+        make.top.bottom.equalToSuperview()
+        make.right.equalToSuperview().offset(padding)
+        make.left.greaterThanOrEqualTo(flex.snp.right)
+        
+      case let .bottom(padding):
+        let flex = FlexibleSpace()
+        container.addSubview(flex)
+        flex.snp.makeConstraints { $0.top.equalToSuperview() }
+        
+        make.bottom.equalToSuperview().offset(padding)
+        make.left.right.equalToSuperview()
+        make.top.greaterThanOrEqualTo(flex.snp.bottom)
+        
+      case let .center(inset):
+        make.top.greaterThanOrEqualToSuperview().offset(inset)
+        make.left.greaterThanOrEqualToSuperview().offset(inset)
+        make.center.equalToSuperview()
+        make.right.lessThanOrEqualToSuperview().offset(-inset)
+        make.bottom.lessThanOrEqualToSuperview().offset(-inset)
+      case .hCenter:
+        make.top.equalToSuperview()
+        make.centerX.equalToSuperview()
+        make.left.greaterThanOrEqualToSuperview()
+        make.right.lessThanOrEqualToSuperview()
+        make.bottom.equalToSuperview()
+      }
     }
+    
+    return container
+  }
 }
diff --git a/Sources/Shared/FeedbackPlayer.swift b/Sources/Shared/FeedbackPlayer.swift
new file mode 100644
index 0000000000000000000000000000000000000000..6c2bf25ef593f3a54ced36b4b9aea53cdb602a0f
--- /dev/null
+++ b/Sources/Shared/FeedbackPlayer.swift
@@ -0,0 +1,34 @@
+import AVFoundation
+import AudioToolbox
+
+struct DeviceFeedback {
+  enum Haptic: UInt32 {
+    case impact = 1520
+    case notification = 1521
+    case selection = 1519
+  }
+
+  enum Alert: UInt32 {
+    case smsSent = 1004
+    case smsReceived = 1003
+    case contactAdded = 1117
+  }
+
+  private init() {}
+
+  static func sound(_ alert: Alert) {
+    try? AVAudioSession
+      .sharedInstance()
+      .setCategory(.ambient, mode: .default, options: .mixWithOthers)
+
+    AudioServicesPlaySystemSound(alert.rawValue)
+  }
+
+  static func shake(_ haptic: Haptic) {
+    try? AVAudioSession
+      .sharedInstance()
+      .setCategory(.ambient, mode: .default, options: .mixWithOthers)
+
+    AudioServicesPlaySystemSound(haptic.rawValue)
+  }
+}
diff --git a/Sources/Shared/Models/Country.swift b/Sources/Shared/Models/Country.swift
new file mode 100644
index 0000000000000000000000000000000000000000..ba2ba1f76c889cd9d35a7a8b012b07ca6125b410
--- /dev/null
+++ b/Sources/Shared/Models/Country.swift
@@ -0,0 +1,44 @@
+import Foundation
+
+public struct Country {
+  public var name: String
+  public var code: String
+  public var flag: String
+  public var regex: String
+  public var prefix: String
+  public var example: String
+  public var prefixWithFlag: String { "\(flag) \(prefix)" }
+  
+  public static func fromMyPhone() -> Self {
+    let all = all()
+    
+    guard let country = all.filter({ $0.code == Locale.current.regionCode }).first else {
+      return all.filter { $0.code == "US" }.first!
+    }
+    
+    return country
+  }
+  
+  public static func all() -> [Self] {
+    guard let url = Bundle.module.url(forResource: "country_codes", withExtension: "json"),
+          let data = try? Data(contentsOf: url),
+          let countries = try? JSONDecoder().decode([Country].self, from: data) else {
+      fatalError("Can't handle country codes json")
+    }
+    
+    return countries
+  }
+  
+  public static func findFrom(_ number: String) -> Self {
+    all().first { country in
+      let start = number.index(number.startIndex, offsetBy: number.count - 2)
+      let end = number.index(start, offsetBy: number.count - (number.count - 2))
+      
+      return country.code == String(number[start ..< end])
+    }!
+  }
+}
+
+extension Country: Hashable {}
+extension Country: Equatable {}
+extension Country: Decodable {}
diff --git a/Sources/Shared/Models/Reply.swift b/Sources/Shared/Models/Reply.swift
new file mode 100644
index 0000000000000000000000000000000000000000..21ffda352cf1631cd638e6a66ef6248a6d55bd87
--- /dev/null
+++ b/Sources/Shared/Models/Reply.swift
@@ -0,0 +1,19 @@
+import Foundation
+
+public struct Reply: Codable, Equatable, Hashable {
+  public let messageId: Data
+  public let senderId: Data
+
+  public init(messageId: Data, senderId: Data) {
+    self.messageId = messageId
+    self.senderId = senderId
+  }
+
+  func asTextReply() -> TextReply {
+    var reply = TextReply()
+    reply.messageID = messageId
+    reply.senderID = senderId
+
+    return reply
+  }
+}
diff --git a/Sources/Models/pbpayload.pb.swift b/Sources/Shared/Models/pbpayload.pb.swift
similarity index 100%
rename from Sources/Models/pbpayload.pb.swift
rename to Sources/Shared/Models/pbpayload.pb.swift
diff --git a/Sources/Shared/Publishers.swift b/Sources/Shared/Publishers.swift
new file mode 100644
index 0000000000000000000000000000000000000000..cc837b449f284df069edc8e28c0c76860b6b7ee0
--- /dev/null
+++ b/Sources/Shared/Publishers.swift
@@ -0,0 +1,113 @@
+import UIKit
+import Combine
+
+public extension UIControl {
+  func publisher(for event: Event) -> EventPublisher {
+    EventPublisher(
+      control: self,
+      event: event
+    )
+  }
+
+  struct EventPublisher: Publisher {
+    public typealias Output = Void
+    public typealias Failure = Never
+
+    fileprivate var control: UIControl
+    fileprivate var event: Event
+
+    public func receive<S: Subscriber>(
+      subscriber: S
+    ) where S.Input == Output, S.Failure == Failure {
+      let subscription = EventSubscription<S>()
+      subscription.target = subscriber
+      subscriber.receive(subscription: subscription)
+
+      control.addTarget(subscription,
+                        action: #selector(subscription.trigger),
+                        for: event
+      )
+    }
+  }
+}
+
+private extension UIControl {
+  class EventSubscription<Target: Subscriber>: Subscription
+  where Target.Input == Void {
+
+    var target: Target?
+
+    func request(_ demand: Subscribers.Demand) {}
+
+    func cancel() {
+      target = nil
+    }
+
+    @objc func trigger() {
+      _ = target?.receive(())
+    }
+  }
+}
+
+public extension UITextField {
+  var textPublisher: AnyPublisher<String, Never> {
+    publisher(for: .editingChanged)
+      .map { self.text ?? "" }
+      .eraseToAnyPublisher()
+  }
+
+  var returnPublisher: AnyPublisher<Void, Never> {
+    publisher(for: .editingDidEndOnExit)
+      .eraseToAnyPublisher()
+  }
+}
+
+public extension UITextView {
+  var textPublisher: Publishers.TextFieldPublisher {
+    Publishers.TextFieldPublisher(textField: self)
+  }
+}
+
+public extension Publishers {
+  struct TextFieldPublisher: Publisher {
+    public typealias Output = String
+    public typealias Failure = Never
+
+    private let textField: UITextView
+
+    init(textField: UITextView) { self.textField = textField }
+
+    public func receive<S>(subscriber: S) where S : Subscriber, Publishers.TextFieldPublisher.Failure == S.Failure, Publishers.TextFieldPublisher.Output == S.Input {
+      let subscription = TextFieldSubscription(subscriber: subscriber, textField: textField)
+      subscriber.receive(subscription: subscription)
+    }
+  }
+
+  class TextFieldSubscription<S: Subscriber>: NSObject, Subscription, UITextViewDelegate where S.Input == String, S.Failure == Never  {
+
+    private var subscriber: S?
+    private weak var textField: UITextView?
+
+    init(subscriber: S, textField: UITextView) {
+      super.init()
+      self.subscriber = subscriber
+      self.textField = textField
+      subscribe()
+    }
+
+    public func request(_ demand: Subscribers.Demand) { }
+
+    public func cancel() {
+      subscriber = nil
+      textField = nil
+    }
+
+    private func subscribe() {
+      textField?.delegate = self
+    }
+
+    public func textViewDidChange(_ textView: UITextView) {
+      _ = subscriber?.receive(textView.text)
+    }
+  }
+}
diff --git a/Sources/Countries/Resources/country_codes.json b/Sources/Shared/Resources/country_codes.json
similarity index 100%
rename from Sources/Countries/Resources/country_codes.json
rename to Sources/Shared/Resources/country_codes.json
diff --git a/Sources/Shared/Views/AttributeComponent.swift b/Sources/Shared/Views/AttributeComponent.swift
index eefa73c5c6f6ab3f0b18a21fe099bf163cb534a7..b7ed4ede8177df01963b36e44f54baa51d1ce5eb 100644
--- a/Sources/Shared/Views/AttributeComponent.swift
+++ b/Sources/Shared/Views/AttributeComponent.swift
@@ -1,81 +1,82 @@
 import UIKit
+import AppResources
 
 public final class AttributeComponent: UIView {
-    public enum Style {
-        case steady
-        case interactive
-        case requiredEditable
+  public enum Style {
+    case steady
+    case interactive
+    case requiredEditable
+  }
+  
+  public let titleLabel = UILabel()
+  public let actionButton = UIButton()
+  public let contentLabel = UILabel()
+  
+  let placeholder = "None provided"
+  var buttonStyle: Style = .steady
+  
+  public private(set) var currentValue: String? {
+    didSet { contentLabel.text = currentValue ?? placeholder }
+  }
+  
+  public init() {
+    super.init(frame: .zero)
+    
+    titleLabel.textColor = Asset.neutralWeak.color
+    contentLabel.textColor = Asset.neutralActive.color
+    titleLabel.font =  Fonts.Mulish.bold.font(size: 12.0)
+    contentLabel.font = Fonts.Mulish.regular.font(size: 16.0)
+    
+    addSubview(titleLabel)
+    addSubview(actionButton)
+    addSubview(contentLabel)
+    
+    titleLabel.snp.makeConstraints { make in
+      make.top.left.equalToSuperview()
+      make.bottom.equalToSuperview().offset(-25)
     }
-
-    public let titleLabel = UILabel()
-    public let actionButton = UIButton()
-    public let contentLabel = UILabel()
-
-    let placeholder = "None provided"
-    var buttonStyle: Style = .steady
-
-    public private(set) var currentValue: String? {
-        didSet { contentLabel.text = currentValue ?? placeholder }
+    
+    contentLabel.snp.makeConstraints { make in
+      make.top.equalTo(titleLabel.snp.bottom).offset(6)
+      make.left.equalToSuperview()
     }
-
-    public init() {
-        super.init(frame: .zero)
-
-        titleLabel.textColor = Asset.neutralWeak.color
-        contentLabel.textColor = Asset.neutralActive.color
-        titleLabel.font =  Fonts.Mulish.bold.font(size: 12.0)
-        contentLabel.font = Fonts.Mulish.regular.font(size: 16.0)
-
-        addSubview(titleLabel)
-        addSubview(actionButton)
-        addSubview(contentLabel)
-
-        titleLabel.snp.makeConstraints { make in
-            make.top.left.equalToSuperview()
-            make.bottom.equalToSuperview().offset(-25)
-        }
-
-        contentLabel.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(6)
-            make.left.equalToSuperview()
-        }
-
-        actionButton.snp.makeConstraints { $0.right.centerY.equalToSuperview() }
+    
+    actionButton.snp.makeConstraints { $0.right.centerY.equalToSuperview() }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  public func set(
+    title: String,
+    value: String? = nil,
+    icon: UIImage? = nil,
+    style: Style = .steady
+  ) {
+    titleLabel.text = title.uppercased()
+    actionButton.setImage(icon, for: .normal)
+    buttonStyle = style
+    
+    set(value: value)
+  }
+  
+  public func set(value: String?) {
+    currentValue = value
+    
+    if buttonStyle == .requiredEditable {
+      actionButton.setImage(Asset.contactNicknameEdit.image, for: .normal)
+      return
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    public func set(
-        title: String,
-        value: String? = nil,
-        icon: UIImage? = nil,
-        style: Style = .steady
-    ) {
-        titleLabel.text = title.uppercased()
-        actionButton.setImage(icon, for: .normal)
-        buttonStyle = style
-
-        set(value: value)
+    
+    guard let _ = value else {
+      if buttonStyle == .interactive {
+        actionButton.setImage(Asset.profileAdd.image, for: .normal)
+      }
+      
+      return
     }
-
-    public func set(value: String?) {
-        currentValue = value
-
-        if buttonStyle == .requiredEditable {
-            actionButton.setImage(Asset.contactNicknameEdit.image, for: .normal)
-            return
-        }
-
-        guard let _ = value else {
-            if buttonStyle == .interactive {
-                actionButton.setImage(Asset.profileAdd.image, for: .normal)
-            }
-
-            return
-        }
-
-        if buttonStyle == .interactive {
-            actionButton.setImage(Asset.profileDelete.image, for: .normal)
-        }
+    
+    if buttonStyle == .interactive {
+      actionButton.setImage(Asset.profileDelete.image, for: .normal)
     }
+  }
 }
diff --git a/Sources/Shared/Views/AvatarCardComponent.swift b/Sources/Shared/Views/AvatarCardComponent.swift
index c34a8650696b991a8677d10b1a99deaec7c44fa8..fcbe72787d87f9a13579a6b51b0f5d88c4b682b4 100644
--- a/Sources/Shared/Views/AvatarCardComponent.swift
+++ b/Sources/Shared/Views/AvatarCardComponent.swift
@@ -1,164 +1,165 @@
 import UIKit
+import AppResources
 
 public final class AvatarCardComponent: UIView {
-    public let nameLabel = UILabel()
-    public let stackView = UIStackView()
-    public let avatarView = EditableAvatarView()
-    public var nameContainer: UIView?
-    private let sendMessageView = AvatarSendMessageView()
-
-    public var image: UIImage? {
-        didSet {
-            avatarView.imageView.image = nil
-            avatarView.imageView.image = image
-            avatarView.imageView.setNeedsDisplay()
-            avatarView.placeholderImageView.image = nil
-        }
+  public let nameLabel = UILabel()
+  public let stackView = UIStackView()
+  public let avatarView = EditableAvatarView()
+  public var nameContainer: UIView?
+  private let sendMessageView = AvatarSendMessageView()
+  
+  public var image: UIImage? {
+    didSet {
+      avatarView.imageView.image = nil
+      avatarView.imageView.image = image
+      avatarView.imageView.setNeedsDisplay()
+      avatarView.placeholderImageView.image = nil
     }
-
-    public init() {
-        super.init(frame: .zero)
-
-        backgroundColor = Asset.neutralBody.color
-
-        nameLabel.textColor = Asset.neutralWhite.color
-        nameLabel.numberOfLines = 2
-        nameLabel.textAlignment = .center
-        nameLabel.font = Fonts.Mulish.bold.font(size: 24.0)
-
-        nameContainer = nameLabel.pinning(at: .center(0))
-        let imageContainer = avatarView.pinning(at: .hCenter)
-
-        stackView.axis = .vertical
-        stackView.addArrangedSubview(imageContainer)
-        stackView.addArrangedSubview(nameContainer ?? UIView())
-        stackView.setCustomSpacing(24, after: imageContainer)
-
-        addSubview(stackView)
-
-        nameLabel.snp.makeConstraints { make in
-            make.top.bottom.centerX.equalToSuperview()
-            make.left.greaterThanOrEqualToSuperview().offset(10)
-            make.right.lessThanOrEqualToSuperview().offset(-10)
-        }
-
-        stackView.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(40)
-            make.left.equalToSuperview().offset(16)
-            make.right.equalToSuperview().offset(-16)
-            make.bottom.equalToSuperview().offset(-30)
-        }
+  }
+  
+  public init() {
+    super.init(frame: .zero)
+    
+    backgroundColor = Asset.neutralBody.color
+    
+    nameLabel.textColor = Asset.neutralWhite.color
+    nameLabel.numberOfLines = 2
+    nameLabel.textAlignment = .center
+    nameLabel.font = Fonts.Mulish.bold.font(size: 24.0)
+    
+    nameContainer = nameLabel.pinning(at: .center(0))
+    let imageContainer = avatarView.pinning(at: .hCenter)
+    
+    stackView.axis = .vertical
+    stackView.addArrangedSubview(imageContainer)
+    stackView.addArrangedSubview(nameContainer ?? UIView())
+    stackView.setCustomSpacing(24, after: imageContainer)
+    
+    addSubview(stackView)
+    
+    nameLabel.snp.makeConstraints { make in
+      make.top.bottom.centerX.equalToSuperview()
+      make.left.greaterThanOrEqualToSuperview().offset(10)
+      make.right.lessThanOrEqualToSuperview().offset(-10)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    public func setupButtons(
-        info: @escaping () -> Void,
-        send: @escaping () -> Void
-    ) {
-        let container = UIView()
-        container.addSubview(sendMessageView)
-
-        sendMessageView.didTapInfo = info
-        sendMessageView.didTapSend = send
-
-        sendMessageView.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.greaterThanOrEqualToSuperview()
-            make.centerX.equalToSuperview()
-            make.right.lessThanOrEqualToSuperview()
-            make.bottom.equalToSuperview()
-        }
-
-        if let nameContainer = nameContainer {
-            stackView.addArrangedSubview(container)
-            stackView.setCustomSpacing(48, after: nameContainer)
-        }
+    
+    stackView.snp.makeConstraints { make in
+      make.top.equalToSuperview().offset(40)
+      make.left.equalToSuperview().offset(16)
+      make.right.equalToSuperview().offset(-16)
+      make.bottom.equalToSuperview().offset(-30)
     }
-}
-
-private final class AvatarSendMessageView: UIView {
-    let stackView = UIStackView()
-    let iconImageView = UIImageView()
-    let sendButton = UIButton()
-    let infoButton = UIButton()
-
-    var didTapInfo: (() -> Void)?
-    var didTapSend: (() -> Void)?
-
-    init() {
-        super.init(frame: .zero)
-
-        iconImageView.contentMode = .center
-        iconImageView.image = Asset.contactSendMessage.image
-
-        sendButton.setTitle("Send Message", for: .normal)
-        sendButton.setTitleColor(Asset.brandPrimary.color, for: .normal)
-        sendButton.titleLabel?.font = Fonts.Mulish.regular.font(size: 13.0)
-
-        infoButton.setImage(Asset.infoIconGrey.image, for: .normal)
-
-        sendButton.addTarget(self, action: #selector(didTapSendButton), for: .touchUpInside)
-        infoButton.addTarget(self, action: #selector(didTapInfoButton), for: .touchUpInside)
-
-        stackView.spacing = 8
-        stackView.distribution = .equalSpacing
-        stackView.addArrangedSubview(iconImageView)
-        stackView.addArrangedSubview(sendButton)
-        stackView.addArrangedSubview(infoButton)
-
-        addSubview(stackView)
-        stackView.snp.makeConstraints { $0.edges.equalToSuperview() }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  public func setupButtons(
+    info: @escaping () -> Void,
+    send: @escaping () -> Void
+  ) {
+    let container = UIView()
+    container.addSubview(sendMessageView)
+    
+    sendMessageView.didTapInfo = info
+    sendMessageView.didTapSend = send
+    
+    sendMessageView.snp.makeConstraints { make in
+      make.top.equalToSuperview()
+      make.left.greaterThanOrEqualToSuperview()
+      make.centerX.equalToSuperview()
+      make.right.lessThanOrEqualToSuperview()
+      make.bottom.equalToSuperview()
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    @objc private func didTapSendButton() {
-        didTapSend?()
+    
+    if let nameContainer = nameContainer {
+      stackView.addArrangedSubview(container)
+      stackView.setCustomSpacing(48, after: nameContainer)
     }
+  }
+}
 
-    @objc private func didTapInfoButton() {
-        didTapInfo?()
-    }
+private final class AvatarSendMessageView: UIView {
+  let stackView = UIStackView()
+  let iconImageView = UIImageView()
+  let sendButton = UIButton()
+  let infoButton = UIButton()
+  
+  var didTapInfo: (() -> Void)?
+  var didTapSend: (() -> Void)?
+  
+  init() {
+    super.init(frame: .zero)
+    
+    iconImageView.contentMode = .center
+    iconImageView.image = Asset.contactSendMessage.image
+    
+    sendButton.setTitle("Send Message", for: .normal)
+    sendButton.setTitleColor(Asset.brandPrimary.color, for: .normal)
+    sendButton.titleLabel?.font = Fonts.Mulish.regular.font(size: 13.0)
+    
+    infoButton.setImage(Asset.infoIconGrey.image, for: .normal)
+    
+    sendButton.addTarget(self, action: #selector(didTapSendButton), for: .touchUpInside)
+    infoButton.addTarget(self, action: #selector(didTapInfoButton), for: .touchUpInside)
+    
+    stackView.spacing = 8
+    stackView.distribution = .equalSpacing
+    stackView.addArrangedSubview(iconImageView)
+    stackView.addArrangedSubview(sendButton)
+    stackView.addArrangedSubview(infoButton)
+    
+    addSubview(stackView)
+    stackView.snp.makeConstraints { $0.edges.equalToSuperview() }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  @objc private func didTapSendButton() {
+    didTapSend?()
+  }
+  
+  @objc private func didTapInfoButton() {
+    didTapInfo?()
+  }
 }
 
 public final class EditableAvatarView: UIView {
-    public let editButton = UIButton()
-    public let imageView = UIImageView()
-    public let placeholderImageView = UIImageView()
-
-    init() {
-        super.init(frame: .zero)
-
-        imageView.layer.cornerRadius = 38
-        imageView.layer.masksToBounds = true
-        imageView.contentMode = .scaleAspectFill
-        imageView.backgroundColor = Asset.brandPrimary.color
-
-        placeholderImageView.contentMode = .center
-        placeholderImageView.image = Asset.profileImagePlaceholder.image
-
-        editButton.setImage(Asset.profileImageButton.image, for: .normal)
-
-        addSubview(imageView)
-        addSubview(editButton)
-        imageView.addSubview(placeholderImageView)
-
-        editButton.snp.makeConstraints { make in
-            make.bottom.equalTo(imageView)
-            make.right.equalTo(imageView).offset(9)
-        }
-
-        imageView.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.equalToSuperview()
-            make.width.height.equalTo(100)
-        }
-
-        placeholderImageView.snp.makeConstraints { $0.center.equalToSuperview() }
+  public let editButton = UIButton()
+  public let imageView = UIImageView()
+  public let placeholderImageView = UIImageView()
+  
+  init() {
+    super.init(frame: .zero)
+    
+    imageView.layer.cornerRadius = 38
+    imageView.layer.masksToBounds = true
+    imageView.contentMode = .scaleAspectFill
+    imageView.backgroundColor = Asset.brandPrimary.color
+    
+    placeholderImageView.contentMode = .center
+    placeholderImageView.image = Asset.profileImagePlaceholder.image
+    
+    editButton.setImage(Asset.profileImageButton.image, for: .normal)
+    
+    addSubview(imageView)
+    addSubview(editButton)
+    imageView.addSubview(placeholderImageView)
+    
+    editButton.snp.makeConstraints { make in
+      make.bottom.equalTo(imageView)
+      make.right.equalTo(imageView).offset(9)
     }
-
-    required init?(coder: NSCoder) { nil }
+    
+    imageView.snp.makeConstraints { make in
+      make.top.equalToSuperview()
+      make.left.equalToSuperview()
+      make.right.equalToSuperview()
+      make.bottom.equalToSuperview()
+      make.width.height.equalTo(100)
+    }
+    
+    placeholderImageView.snp.makeConstraints { $0.center.equalToSuperview() }
+  }
+  
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/Shared/Views/AvatarCell.swift b/Sources/Shared/Views/AvatarCell.swift
index 43f0e3fd6686464b6d66c31770627134e9c075c0..d37bbb35cb2ef214140498d77b16800e512793b0 100644
--- a/Sources/Shared/Views/AvatarCell.swift
+++ b/Sources/Shared/Views/AvatarCell.swift
@@ -1,187 +1,188 @@
 import UIKit
 import Combine
+import AppResources
 
 final class AvatarCellButton: UIControl {
-    let titleLabel = UILabel()
-    let imageView = UIImageView()
-
-    init() {
-        super.init(frame: .zero)
-        titleLabel.numberOfLines = 0
-        titleLabel.textAlignment = .right
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
-
-        addSubview(imageView)
-        addSubview(titleLabel)
-
-        imageView.snp.makeConstraints {
-            $0.top.greaterThanOrEqualToSuperview()
-            $0.left.equalToSuperview()
-            $0.centerY.equalToSuperview()
-            $0.bottom.lessThanOrEqualToSuperview()
-        }
-
-        titleLabel.snp.makeConstraints {
-            $0.top.greaterThanOrEqualToSuperview()
-            $0.left.equalTo(imageView.snp.right).offset(5)
-            $0.centerY.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.width.equalTo(60)
-            $0.bottom.lessThanOrEqualToSuperview()
-        }
+  let titleLabel = UILabel()
+  let imageView = UIImageView()
+  
+  init() {
+    super.init(frame: .zero)
+    titleLabel.numberOfLines = 0
+    titleLabel.textAlignment = .right
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
+    
+    addSubview(imageView)
+    addSubview(titleLabel)
+    
+    imageView.snp.makeConstraints {
+      $0.top.greaterThanOrEqualToSuperview()
+      $0.left.equalToSuperview()
+      $0.centerY.equalToSuperview()
+      $0.bottom.lessThanOrEqualToSuperview()
     }
-
-    required init?(coder: NSCoder) { nil }
+    
+    titleLabel.snp.makeConstraints {
+      $0.top.greaterThanOrEqualToSuperview()
+      $0.left.equalTo(imageView.snp.right).offset(5)
+      $0.centerY.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.width.equalTo(60)
+      $0.bottom.lessThanOrEqualToSuperview()
+    }
+  }
+  
+  required init?(coder: NSCoder) { nil }
 }
 
 public final class AvatarCell: UITableViewCell {
-    let h1Label = UILabel()
-    let h2Label = UILabel()
-    let h3Label = UILabel()
-    let h4Label = UILabel()
-    let separatorView = UIView()
-    let avatarView = AvatarView()
-    let stackView = UIStackView()
-    let stateButton = AvatarCellButton()
-
-    var cancellables = Set<AnyCancellable>()
-    public var didTapStateButton: (() -> Void)!
-
-    public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        super.init(style: style, reuseIdentifier: reuseIdentifier)
-
-        selectedBackgroundView = UIView()
-        multipleSelectionBackgroundView = UIView()
-        backgroundColor = Asset.neutralWhite.color
-
-        h1Label.textColor = Asset.neutralActive.color
-        h2Label.textColor = Asset.neutralSecondaryAlternative.color
-        h3Label.textColor = Asset.neutralSecondaryAlternative.color
-        h4Label.textColor = Asset.neutralSecondaryAlternative.color
-
-        h1Label.font = Fonts.Mulish.semiBold.font(size: 14.0)
-        h2Label.font = Fonts.Mulish.regular.font(size: 14.0)
-        h3Label.font = Fonts.Mulish.regular.font(size: 14.0)
-        h4Label.font = Fonts.Mulish.regular.font(size: 14.0)
-
-        stackView.spacing = 4
-        stackView.axis = .vertical
-        
-        stackView.addArrangedSubview(h1Label)
-        stackView.addArrangedSubview(h2Label)
-        stackView.addArrangedSubview(h3Label)
-        stackView.addArrangedSubview(h4Label)
-
-        separatorView.backgroundColor = Asset.neutralLine.color
-
-        contentView.addSubview(stackView)
-        contentView.addSubview(avatarView)
-        contentView.addSubview(stateButton)
-        contentView.addSubview(separatorView)
-
-        setupConstraints()
+  let h1Label = UILabel()
+  let h2Label = UILabel()
+  let h3Label = UILabel()
+  let h4Label = UILabel()
+  let separatorView = UIView()
+  let avatarView = AvatarView()
+  let stackView = UIStackView()
+  let stateButton = AvatarCellButton()
+  
+  var cancellables = Set<AnyCancellable>()
+  public var didTapStateButton: (() -> Void)!
+  
+  public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+    super.init(style: style, reuseIdentifier: reuseIdentifier)
+    
+    selectedBackgroundView = UIView()
+    multipleSelectionBackgroundView = UIView()
+    backgroundColor = Asset.neutralWhite.color
+    
+    h1Label.textColor = Asset.neutralActive.color
+    h2Label.textColor = Asset.neutralSecondaryAlternative.color
+    h3Label.textColor = Asset.neutralSecondaryAlternative.color
+    h4Label.textColor = Asset.neutralSecondaryAlternative.color
+    
+    h1Label.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    h2Label.font = Fonts.Mulish.regular.font(size: 14.0)
+    h3Label.font = Fonts.Mulish.regular.font(size: 14.0)
+    h4Label.font = Fonts.Mulish.regular.font(size: 14.0)
+    
+    stackView.spacing = 4
+    stackView.axis = .vertical
+    
+    stackView.addArrangedSubview(h1Label)
+    stackView.addArrangedSubview(h2Label)
+    stackView.addArrangedSubview(h3Label)
+    stackView.addArrangedSubview(h4Label)
+    
+    separatorView.backgroundColor = Asset.neutralLine.color
+    
+    contentView.addSubview(stackView)
+    contentView.addSubview(avatarView)
+    contentView.addSubview(stateButton)
+    contentView.addSubview(separatorView)
+    
+    setupConstraints()
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  public override func prepareForReuse() {
+    super.prepareForReuse()
+    h1Label.text = nil
+    h2Label.text = nil
+    h3Label.text = nil
+    h4Label.text = nil
+    
+    stateButton.imageView.image = nil
+    stateButton.titleLabel.text = nil
+    
+    avatarView.prepareForReuse()
+    cancellables.removeAll()
+  }
+  
+  public func setup(
+    title: String,
+    image: Data?,
+    firstSubtitle: String? = nil,
+    secondSubtitle: String? = nil,
+    thirdSubtitle: String? = nil,
+    showSeparator: Bool = true,
+    sent: Bool = false
+  ) {
+    h1Label.text = title
+    
+    if let firstSubtitle = firstSubtitle {
+      h2Label.isHidden = false
+      h2Label.text = firstSubtitle
+    } else {
+      h2Label.isHidden = true
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func prepareForReuse() {
-        super.prepareForReuse()
-        h1Label.text = nil
-        h2Label.text = nil
-        h3Label.text = nil
-        h4Label.text = nil
-
-        stateButton.imageView.image = nil
-        stateButton.titleLabel.text = nil
-
-        avatarView.prepareForReuse()
-        cancellables.removeAll()
+    
+    if let secondSubtitle = secondSubtitle {
+      h3Label.isHidden = false
+      h3Label.text = secondSubtitle
+    } else {
+      h3Label.isHidden = true
     }
-
-    public func setup(
-        title: String,
-        image: Data?,
-        firstSubtitle: String? = nil,
-        secondSubtitle: String? = nil,
-        thirdSubtitle: String? = nil,
-        showSeparator: Bool = true,
-        sent: Bool = false
-    ) {
-        h1Label.text = title
-
-        if let firstSubtitle = firstSubtitle {
-            h2Label.isHidden = false
-            h2Label.text = firstSubtitle
-        } else {
-            h2Label.isHidden = true
-        }
-
-        if let secondSubtitle = secondSubtitle {
-            h3Label.isHidden = false
-            h3Label.text = secondSubtitle
-        } else {
-            h3Label.isHidden = true
-        }
-
-        if let thirdSubtitle = thirdSubtitle {
-            h4Label.isHidden = false
-            h4Label.text = thirdSubtitle
-        } else {
-            h4Label.isHidden = true
-        }
-
-        avatarView.setupProfile(title: title, image: image, size: .medium)
-        separatorView.alpha = showSeparator ? 1.0 : 0.0
-
-        cancellables.removeAll()
-
-        if sent {
-            stateButton.imageView.image = Asset.requestsResend.image
-            stateButton.titleLabel.text = Localized.Requests.Cell.requested
-            stateButton.titleLabel.textColor = Asset.brandPrimary.color
-
-            stateButton
-                .publisher(for: .touchUpInside)
-                .sink { [unowned self] in didTapStateButton() }
-                .store(in: &cancellables)
-        }
+    
+    if let thirdSubtitle = thirdSubtitle {
+      h4Label.isHidden = false
+      h4Label.text = thirdSubtitle
+    } else {
+      h4Label.isHidden = true
     }
-
-    public func updateToResent() {
-        stateButton.imageView.image = Asset.requestsResent.image
-        stateButton.titleLabel.text = Localized.Requests.Cell.resent
-        stateButton.titleLabel.textColor = Asset.neutralWeak.color
-
-        cancellables.forEach { $0.cancel() }
-        cancellables.removeAll()
+    
+    avatarView.setupProfile(title: title, image: image, size: .medium)
+    separatorView.alpha = showSeparator ? 1.0 : 0.0
+    
+    cancellables.removeAll()
+    
+    if sent {
+      stateButton.imageView.image = Asset.requestsResend.image
+      stateButton.titleLabel.text = Localized.Requests.Cell.requested
+      stateButton.titleLabel.textColor = Asset.brandPrimary.color
+      
+      stateButton
+        .publisher(for: .touchUpInside)
+        .sink { [unowned self] in didTapStateButton() }
+        .store(in: &cancellables)
     }
-
-    private func setupConstraints() {
-        avatarView.snp.makeConstraints {
-            $0.width.height.equalTo(36)
-            $0.left.equalToSuperview().offset(27)
-            $0.centerY.equalToSuperview()
-        }
-
-        stackView.snp.makeConstraints {
-            $0.top.equalTo(avatarView)
-            $0.left.equalTo(avatarView.snp.right).offset(14)
-            $0.right.lessThanOrEqualToSuperview().offset(-10)
-            $0.bottom.greaterThanOrEqualTo(avatarView)
-            $0.bottom.lessThanOrEqualToSuperview()
-        }
-
-        separatorView.snp.makeConstraints {
-            $0.height.equalTo(1)
-            $0.top.greaterThanOrEqualTo(stackView.snp.bottom).offset(10)
-            $0.left.equalToSuperview().offset(25)
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-        }
-
-        stateButton.snp.makeConstraints {
-            $0.centerY.equalTo(stackView)
-            $0.right.equalToSuperview().offset(-24)
-        }
+  }
+  
+  public func updateToResent() {
+    stateButton.imageView.image = Asset.requestsResent.image
+    stateButton.titleLabel.text = Localized.Requests.Cell.resent
+    stateButton.titleLabel.textColor = Asset.neutralWeak.color
+    
+    cancellables.forEach { $0.cancel() }
+    cancellables.removeAll()
+  }
+  
+  private func setupConstraints() {
+    avatarView.snp.makeConstraints {
+      $0.width.height.equalTo(36)
+      $0.left.equalToSuperview().offset(27)
+      $0.centerY.equalToSuperview()
+    }
+    
+    stackView.snp.makeConstraints {
+      $0.top.equalTo(avatarView)
+      $0.left.equalTo(avatarView.snp.right).offset(14)
+      $0.right.lessThanOrEqualToSuperview().offset(-10)
+      $0.bottom.greaterThanOrEqualTo(avatarView)
+      $0.bottom.lessThanOrEqualToSuperview()
+    }
+    
+    separatorView.snp.makeConstraints {
+      $0.height.equalTo(1)
+      $0.top.greaterThanOrEqualTo(stackView.snp.bottom).offset(10)
+      $0.left.equalToSuperview().offset(25)
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
+    
+    stateButton.snp.makeConstraints {
+      $0.centerY.equalTo(stackView)
+      $0.right.equalToSuperview().offset(-24)
     }
+  }
 }
diff --git a/Sources/Shared/Views/AvatarView.swift b/Sources/Shared/Views/AvatarView.swift
index a1104edb680c869092a0017ef5e8c87310ae7bd5..cacce36f254322f1bcf58141d62852ec3ca6e57b 100644
--- a/Sources/Shared/Views/AvatarView.swift
+++ b/Sources/Shared/Views/AvatarView.swift
@@ -1,95 +1,96 @@
 import UIKit
+import AppResources
 
 public final class AvatarView: UIView {
-    public enum Size {
-        case small
-        case medium
-        case large
-    }
-
-    let imageView = UIImageView()
-    let monogramLabel = UILabel()
-    let iconImageView = UIImageView()
-
-    public init() {
-        super.init(frame: .zero)
+  public enum Size {
+    case small
+    case medium
+    case large
+  }
 
-        layer.masksToBounds = true
-        backgroundColor = Asset.brandPrimary.color
+  let imageView = UIImageView()
+  let monogramLabel = UILabel()
+  let iconImageView = UIImageView()
 
-        iconImageView.contentMode = .center
-        imageView.contentMode = .scaleAspectFill
-        monogramLabel.textColor = Asset.neutralWhite.color
+  public init() {
+    super.init(frame: .zero)
 
-        addSubview(monogramLabel)
-        addSubview(iconImageView)
-        addSubview(imageView)
+    layer.masksToBounds = true
+    backgroundColor = Asset.brandPrimary.color
 
-        imageView.snp.makeConstraints {
-            $0.edges.equalToSuperview()
-        }
+    iconImageView.contentMode = .center
+    imageView.contentMode = .scaleAspectFill
+    monogramLabel.textColor = Asset.neutralWhite.color
 
-        monogramLabel.snp.makeConstraints {
-            $0.center.equalToSuperview()
-        }
+    addSubview(monogramLabel)
+    addSubview(iconImageView)
+    addSubview(imageView)
 
-        iconImageView.snp.makeConstraints {
-            $0.center.equalToSuperview()
-        }
+    imageView.snp.makeConstraints {
+      $0.edges.equalToSuperview()
     }
 
-    required init?(coder: NSCoder) { nil }
+    monogramLabel.snp.makeConstraints {
+      $0.center.equalToSuperview()
+    }
 
-    public func prepareForReuse() {
-        imageView.image = nil
-        monogramLabel.text = nil
-        iconImageView.image = nil
+    iconImageView.snp.makeConstraints {
+      $0.center.equalToSuperview()
+    }
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public func prepareForReuse() {
+    imageView.image = nil
+    monogramLabel.text = nil
+    iconImageView.image = nil
+  }
+
+  public func setupProfile(title: String, image: Data?, size: AvatarView.Size) {
+    iconImageView.image = nil
+    monogramLabel.text = title
+      .trimmingCharacters(in: .whitespacesAndNewlines)
+      .replacingOccurrences(of: " ", with: "")
+      .prefix(2)
+      .uppercased()
+
+    monogramLabel.text = "\(title.prefix(2))".uppercased()
+
+    // TODO: What are the font sizes and corner radius for small/medium avatars?
+
+    switch size {
+    case .small:
+      layer.cornerRadius = 13.0
+      monogramLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    case .medium:
+      layer.cornerRadius = 13.0
+      monogramLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    case .large:
+      layer.cornerRadius = 18.0
+      monogramLabel.font = Fonts.Mulish.semiBold.font(size: 16.0)
     }
 
-    public func setupProfile(title: String, image: Data?, size: AvatarView.Size) {
-        iconImageView.image = nil
-        monogramLabel.text = title
-            .trimmingCharacters(in: .whitespacesAndNewlines)
-            .replacingOccurrences(of: " ", with: "")
-            .prefix(2)
-            .uppercased()
-
-        monogramLabel.text = "\(title.prefix(2))".uppercased()
-
-        // TODO: What are the font sizes and corner radius for small/medium avatars?
-
-        switch size {
-        case .small:
-            layer.cornerRadius = 13.0
-            monogramLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
-        case .medium:
-            layer.cornerRadius = 13.0
-            monogramLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
-        case .large:
-            layer.cornerRadius = 18.0
-            monogramLabel.font = Fonts.Mulish.semiBold.font(size: 16.0)
-        }
-
-        guard let image = image else {
-            imageView.image = nil
-            return
-        }
-
-        imageView.image = UIImage(data: image)
+    guard let image = image else {
+      imageView.image = nil
+      return
     }
 
-    public func setupGroup(size: AvatarView.Size) {
-        switch size {
-        case .small:
-            layer.cornerRadius = 13.0
-        case .medium:
-            layer.cornerRadius = 13.0
-        case .large:
-            layer.cornerRadius = 18.0
-        }
-
-        imageView.image = nil
-        monogramLabel.text = nil
-        iconImageView.image = Asset.sharedGroup.image
+    imageView.image = UIImage(data: image)
+  }
+
+  public func setupGroup(size: AvatarView.Size) {
+    switch size {
+    case .small:
+      layer.cornerRadius = 13.0
+    case .medium:
+      layer.cornerRadius = 13.0
+    case .large:
+      layer.cornerRadius = 18.0
     }
+
+    imageView.image = nil
+    monogramLabel.text = nil
+    iconImageView.image = Asset.sharedGroup.image
+  }
 }
diff --git a/Sources/Shared/Views/BottomFeedbackComponent.swift b/Sources/Shared/Views/BottomFeedbackComponent.swift
index aedff2e8003a07590bb41c9e7acbed0830986a41..dd5e583c7e0fe8e1563cae05c68379cb225ef981 100644
--- a/Sources/Shared/Views/BottomFeedbackComponent.swift
+++ b/Sources/Shared/Views/BottomFeedbackComponent.swift
@@ -1,83 +1,76 @@
 import UIKit
+import AppResources
 
 public struct BottomFeedbackStyle {
-    var color: UIColor
-    var iconColor: UIColor
-    var titleColor: UIColor
-    var actionColor: UIColor?
+  var color: UIColor
+  var iconColor: UIColor
+  var titleColor: UIColor
+  var actionColor: UIColor?
 }
 
 public extension BottomFeedbackStyle {
-    static let danger = BottomFeedbackStyle(
-        color: Asset.accentDanger.color,
-        iconColor: Asset.neutralWhite.color,
-        titleColor: Asset.neutralWhite.color
-    )
-
-    static let chill = BottomFeedbackStyle(
-        color: Asset.neutralSecondary.color,
-        iconColor: Asset.neutralDisabled.color,
-        titleColor: Asset.neutralBody.color
-    )
+  static let danger = BottomFeedbackStyle(
+    color: Asset.accentDanger.color,
+    iconColor: Asset.neutralWhite.color,
+    titleColor: Asset.neutralWhite.color
+  )
+  
+  static let chill = BottomFeedbackStyle(
+    color: Asset.neutralSecondary.color,
+    iconColor: Asset.neutralDisabled.color,
+    titleColor: Asset.neutralBody.color
+  )
 }
 
 public final class BottomFeedbackComponent: UIView {
-    // MARK: UI
-
-    public let title = UILabel()
-    public let icon = UIImageView()
-    public let stack = UIStackView()
-    public let button = CapsuleButton(height: 50.0, minimumWidth: 100.0)
-
-    // MARK: Lifecycle
-
-    public init() {
-        super.init(frame: .zero)
-        setup()
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    // MARK: Public
-
-    public func set(
-        icon: UIImage,
-        title: String,
-        style: BottomFeedbackStyle,
-        actionTitle: String? = nil,
-        actionStyle: CapsuleButtonStyle = .seeThroughWhite
-    ) {
-        backgroundColor = style.color
-        self.icon.tintColor = style.iconColor
-        self.title.textColor = style.titleColor
-
-        self.title.text = title
-        self.icon.image = icon.withRenderingMode(.alwaysTemplate)
-
-        guard let actionTitle = actionTitle else { return }
-
-        button.setStyle(actionStyle)
-        button.setTitle(actionTitle, for: .normal)
-        stack.addArrangedSubview(button.pinning(at: .center(0)))
-    }
-
-    // MARK: Private
-
-    private func setup() {
-        layer.cornerRadius = 15
-        icon.contentMode = .center
-        title.font = Fonts.Mulish.semiBold.font(size: 14.0)
-
-        stack.spacing = 10
-        stack.addArrangedSubview(icon)
-        stack.addArrangedSubview(title.pinning(at: .left(0)))
-        addSubview(stack)
-
-        stack.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(20)
-            make.left.equalToSuperview().offset(20)
-            make.right.equalToSuperview().offset(-20)
-            make.bottom.equalToSuperview().offset(-40)
-        }
+  public let title = UILabel()
+  public let icon = UIImageView()
+  public let stack = UIStackView()
+  public let button = CapsuleButton(height: 50.0, minimumWidth: 100.0)
+  
+  public init() {
+    super.init(frame: .zero)
+    setup()
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  public func set(
+    icon: UIImage,
+    title: String,
+    style: BottomFeedbackStyle,
+    actionTitle: String? = nil,
+    actionStyle: CapsuleButtonStyle = .seeThroughWhite
+  ) {
+    backgroundColor = style.color
+    self.icon.tintColor = style.iconColor
+    self.title.textColor = style.titleColor
+    
+    self.title.text = title
+    self.icon.image = icon.withRenderingMode(.alwaysTemplate)
+    
+    guard let actionTitle = actionTitle else { return }
+    
+    button.setStyle(actionStyle)
+    button.setTitle(actionTitle, for: .normal)
+    stack.addArrangedSubview(button.pinning(at: .center(0)))
+  }
+
+  private func setup() {
+    layer.cornerRadius = 15
+    icon.contentMode = .center
+    title.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    
+    stack.spacing = 10
+    stack.addArrangedSubview(icon)
+    stack.addArrangedSubview(title.pinning(at: .left(0)))
+    addSubview(stack)
+    
+    stack.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(20)
+      $0.left.equalToSuperview().offset(20)
+      $0.right.equalToSuperview().offset(-20)
+      $0.bottom.equalToSuperview().offset(-40)
     }
+  }
 }
diff --git a/Sources/Shared/Views/CapsuleButton.swift b/Sources/Shared/Views/CapsuleButton.swift
index 4d681dc36b0f5c8ca9d214ac2ce026c940eadf01..e3dfc57373b2b7562c4a1297ab78d8d543327605 100644
--- a/Sources/Shared/Views/CapsuleButton.swift
+++ b/Sources/Shared/Views/CapsuleButton.swift
@@ -1,153 +1,142 @@
 import UIKit
+import AppResources
 
 public struct CapsuleButtonModel {
-    public var title: String
-    public var accessibility: String?
-    public var style: CapsuleButtonStyle
-
-    public init(
-        title: String,
-        style: CapsuleButtonStyle,
-        accessibility: String? = nil
-    ) {
-        self.title = title
-        self.style = style
-        self.accessibility = accessibility
-    }
+  public var title: String
+  public var accessibility: String?
+  public var style: CapsuleButtonStyle
+  
+  public init(
+    title: String,
+    style: CapsuleButtonStyle,
+    accessibility: String? = nil
+  ) {
+    self.title = title
+    self.style = style
+    self.accessibility = accessibility
+  }
 }
 
 public struct CapsuleButtonStyle {
-    var fill: UIImage
-    var borderWidth: CGFloat
-    var borderColor: UIColor?
-    var titleColor: UIColor
-    var disabledTitleColor: UIColor
+  var fill: UIImage
+  var borderWidth: CGFloat
+  var borderColor: UIColor?
+  var titleColor: UIColor
+  var disabledTitleColor: UIColor
 }
 
 public extension CapsuleButtonStyle {
-    static let white = CapsuleButtonStyle(
-        fill: .color(Asset.neutralWhite.color),
-        borderWidth: 0,
-        borderColor: nil,
-        titleColor: Asset.brandPrimary.color,
-        disabledTitleColor: Asset.neutralWhite.color.withAlphaComponent(0.5)
-    )
-
-    static let brandColored = CapsuleButtonStyle(
-        fill: .color(Asset.brandPrimary.color),
-        borderWidth: 0,
-        borderColor: nil,
-        titleColor: Asset.neutralWhite.color,
-        disabledTitleColor: Asset.neutralWhite.color
-    )
-
-    static let red = CapsuleButtonStyle(
-        fill: .color(Asset.accentDanger.color),
-        borderWidth: 0,
-        borderColor: nil,
-        titleColor: Asset.neutralWhite.color,
-        disabledTitleColor: Asset.neutralWhite.color
-    )
-
-    static let seeThroughWhite = CapsuleButtonStyle(
-        fill: .color(UIColor.clear),
-        borderWidth: 2,
-        borderColor: Asset.neutralWhite.color,
-        titleColor: Asset.neutralWhite.color,
-        disabledTitleColor: Asset.neutralWhite.color.withAlphaComponent(0.5)
-    )
-
-    static let seeThrough = CapsuleButtonStyle(
-        fill: .color(UIColor.clear),
-        borderWidth: 2,
-        borderColor: Asset.brandPrimary.color,
-        titleColor: Asset.brandPrimary.color,
-        disabledTitleColor: Asset.brandPrimary.color.withAlphaComponent(0.5)
-    )
-
-    static let simplestColoredRed = CapsuleButtonStyle(
-        fill: .color(UIColor.clear),
-        borderWidth: 0,
-        borderColor: nil,
-        titleColor: Asset.accentDanger.color,
-        disabledTitleColor: Asset.brandDefault.color.withAlphaComponent(0.5)
-    )
-
-    static let simplestColoredBrand = CapsuleButtonStyle(
-        fill: .color(UIColor.clear),
-        borderWidth: 0,
-        borderColor: nil,
-        titleColor: Asset.brandPrimary.color,
-        disabledTitleColor: Asset.brandDefault.color.withAlphaComponent(0.5)
-    )
+  static let white = CapsuleButtonStyle(
+    fill: .color(Asset.neutralWhite.color),
+    borderWidth: 0,
+    borderColor: nil,
+    titleColor: Asset.brandPrimary.color,
+    disabledTitleColor: Asset.neutralWhite.color.withAlphaComponent(0.5)
+  )
+  
+  static let brandColored = CapsuleButtonStyle(
+    fill: .color(Asset.brandPrimary.color),
+    borderWidth: 0,
+    borderColor: nil,
+    titleColor: Asset.neutralWhite.color,
+    disabledTitleColor: Asset.neutralWhite.color
+  )
+  
+  static let red = CapsuleButtonStyle(
+    fill: .color(Asset.accentDanger.color),
+    borderWidth: 0,
+    borderColor: nil,
+    titleColor: Asset.neutralWhite.color,
+    disabledTitleColor: Asset.neutralWhite.color
+  )
+  
+  static let seeThroughWhite = CapsuleButtonStyle(
+    fill: .color(UIColor.clear),
+    borderWidth: 2,
+    borderColor: Asset.neutralWhite.color,
+    titleColor: Asset.neutralWhite.color,
+    disabledTitleColor: Asset.neutralWhite.color.withAlphaComponent(0.5)
+  )
+  
+  static let seeThrough = CapsuleButtonStyle(
+    fill: .color(UIColor.clear),
+    borderWidth: 2,
+    borderColor: Asset.brandPrimary.color,
+    titleColor: Asset.brandPrimary.color,
+    disabledTitleColor: Asset.brandPrimary.color.withAlphaComponent(0.5)
+  )
+  
+  static let simplestColoredRed = CapsuleButtonStyle(
+    fill: .color(UIColor.clear),
+    borderWidth: 0,
+    borderColor: nil,
+    titleColor: Asset.accentDanger.color,
+    disabledTitleColor: Asset.brandDefault.color.withAlphaComponent(0.5)
+  )
+  
+  static let simplestColoredBrand = CapsuleButtonStyle(
+    fill: .color(UIColor.clear),
+    borderWidth: 0,
+    borderColor: nil,
+    titleColor: Asset.brandPrimary.color,
+    disabledTitleColor: Asset.brandDefault.color.withAlphaComponent(0.5)
+  )
 }
 
 public final class CapsuleButton: UIButton {
-    // MARK: Properties
-
-    private let height: CGFloat
-    private let minimumWidth: CGFloat
-
-    // MARK: Lifecycle
-
-    public init(
-        height: CGFloat = 55.0,
-        minimumWidth: CGFloat = 200
-    ) {
-        self.height = height
-        self.minimumWidth = minimumWidth
-
-        super.init(frame: .zero)
-        setup()
+  private let height: CGFloat
+  private let minimumWidth: CGFloat
+
+  public init(
+    height: CGFloat = 55.0,
+    minimumWidth: CGFloat = 200
+  ) {
+    self.height = height
+    self.minimumWidth = minimumWidth
+    super.init(frame: .zero)
+
+    layer.cornerRadius = 55/2
+    layer.masksToBounds = true
+    titleLabel!.font = Fonts.Mulish.semiBold.font(size: 16.0)
+    adjustsImageWhenHighlighted = false
+
+    setBackgroundImage(.color(Asset.neutralDisabled.color), for: .disabled)
+
+    snp.makeConstraints {
+      $0.height.equalTo(height)
+      $0.width.greaterThanOrEqualTo(minimumWidth)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    // MARK: Public
-
-    public func set(
-        style: CapsuleButtonStyle,
-        title: String,
-        accessibility: String? = nil
-    ) {
-        setTitle(title, for: .normal)
-        accessibilityIdentifier = accessibility
-        layer.borderWidth = style.borderWidth
-
-        if let color = style.borderColor {
-            layer.borderColor = color.cgColor
-        }
-
-        setBackgroundImage(style.fill, for: .normal)
-        setTitleColor(style.titleColor, for: .normal)
-        setTitleColor(style.disabledTitleColor, for: .disabled)
-    }
-
-    public func setStyle(_ style: CapsuleButtonStyle) {
-        layer.borderWidth = style.borderWidth
-
-        if let color = style.borderColor {
-            layer.borderColor = color.cgColor
-        }
-
-        setBackgroundImage(style.fill, for: .normal)
-        setTitleColor(style.titleColor, for: .normal)
-        setTitleColor(style.disabledTitleColor, for: .disabled)
+  }
+  
+  required init?(coder: NSCoder) { nil }
+
+  public func set(
+    style: CapsuleButtonStyle,
+    title: String,
+    accessibility: String? = nil
+  ) {
+    setTitle(title, for: .normal)
+    accessibilityIdentifier = accessibility
+    layer.borderWidth = style.borderWidth
+    
+    if let color = style.borderColor {
+      layer.borderColor = color.cgColor
     }
-
-    // MARK: Private
-
-    private func setup() {
-        layer.cornerRadius = 55/2
-        layer.masksToBounds = true
-        titleLabel!.font = Fonts.Mulish.semiBold.font(size: 16.0)
-        adjustsImageWhenHighlighted = false
-
-        setBackgroundImage(.color(Asset.neutralDisabled.color), for: .disabled)
-
-        snp.makeConstraints { make in
-            make.height.equalTo(height)
-            make.width.greaterThanOrEqualTo(minimumWidth)
-        }
+    
+    setBackgroundImage(style.fill, for: .normal)
+    setTitleColor(style.titleColor, for: .normal)
+    setTitleColor(style.disabledTitleColor, for: .disabled)
+  }
+  
+  public func setStyle(_ style: CapsuleButtonStyle) {
+    layer.borderWidth = style.borderWidth
+    
+    if let color = style.borderColor {
+      layer.borderColor = color.cgColor
     }
+    
+    setBackgroundImage(style.fill, for: .normal)
+    setTitleColor(style.titleColor, for: .normal)
+    setTitleColor(style.disabledTitleColor, for: .disabled)
+  }
 }
diff --git a/Sources/Shared/Views/DetailRowButton.swift b/Sources/Shared/Views/DetailRowButton.swift
index 132d499ac0e0223be1f9333cbd6e1643cfff7157..fe0eea6ce263a6cffcc11e0872d3f2ed538c04ee 100644
--- a/Sources/Shared/Views/DetailRowButton.swift
+++ b/Sources/Shared/Views/DetailRowButton.swift
@@ -1,48 +1,47 @@
 import UIKit
+import AppResources
 
 public final class DetailRowButton: UIControl {
-    let titleLabel = UILabel()
-    let valueLabel = UILabel()
-    let rowIndicator = UIImageView()
-
-    public init() {
-        super.init(frame: .zero)
-
-        rowIndicator.contentMode = .center
-        rowIndicator.image = Asset.settingsDisclosure.image
-
-        titleLabel.font = Fonts.Mulish.bold.font(size: 12.0)
-        valueLabel.font = Fonts.Mulish.regular.font(size: 16.0)
-
-        titleLabel.textColor = Asset.neutralWeak.color
-        valueLabel.textColor = Asset.neutralActive.color
-
-        addSubview(titleLabel)
-        addSubview(valueLabel)
-        addSubview(rowIndicator)
-
-        titleLabel.snp.makeConstraints { make in
-            make.top.equalToSuperview()
-            make.left.equalToSuperview()
-        }
-
-        valueLabel.snp.makeConstraints { make in
-            make.top.equalTo(titleLabel.snp.bottom).offset(4)
-            make.left.equalToSuperview()
-            make.bottom.equalToSuperview()
-        }
-
-        rowIndicator.snp.makeConstraints { make in
-            make.centerY.equalToSuperview()
-            make.right.equalToSuperview()
-        }
+  let titleLabel = UILabel()
+  let valueLabel = UILabel()
+  let rowIndicator = UIImageView()
+  
+  public init() {
+    super.init(frame: .zero)
+    
+    rowIndicator.contentMode = .center
+    rowIndicator.image = Asset.settingsDisclosure.image
+    
+    titleLabel.font = Fonts.Mulish.bold.font(size: 12.0)
+    valueLabel.font = Fonts.Mulish.regular.font(size: 16.0)
+    
+    titleLabel.textColor = Asset.neutralWeak.color
+    valueLabel.textColor = Asset.neutralActive.color
+    
+    addSubview(titleLabel)
+    addSubview(valueLabel)
+    addSubview(rowIndicator)
+    
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview()
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    public func setup(title: String, value: String, hasArrow: Bool = true) {
-        titleLabel.text = title
-        valueLabel.text = value
-        rowIndicator.isHidden = !hasArrow
+    valueLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(4)
+      $0.left.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
+    rowIndicator.snp.makeConstraints {
+      $0.centerY.equalToSuperview()
+      $0.right.equalToSuperview()
     }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  public func setup(title: String, value: String, hasArrow: Bool = true) {
+    titleLabel.text = title
+    valueLabel.text = value
+    rowIndicator.isHidden = !hasArrow
+  }
 }
diff --git a/Sources/Shared/Views/DotAnimation.swift b/Sources/Shared/Views/DotAnimation.swift
index bfefe3c41d1e3ffdef01e6446e52b2ab20b175f9..9cda32e30765b2220e40aa1aa41d7c68a7e9a044 100644
--- a/Sources/Shared/Views/DotAnimation.swift
+++ b/Sources/Shared/Views/DotAnimation.swift
@@ -1,100 +1,73 @@
 import UIKit
-import SnapKit
+import AppResources
 
 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)
-        }
+  public 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/FlexibleSpace.swift b/Sources/Shared/Views/FlexibleSpace.swift
index c3b323ea8d352421b5e7247402047d982cf7d4c5..082a2cf95c9e5ef3f6f8766cfd5b8471f13a6309 100644
--- a/Sources/Shared/Views/FlexibleSpace.swift
+++ b/Sources/Shared/Views/FlexibleSpace.swift
@@ -1,17 +1,17 @@
 import UIKit
 
 public final class FlexibleSpace: UIView {
-    public override init(frame: CGRect) {
-        super.init(frame: frame)
-        setContentHuggingPriority(.defaultLow, for: .horizontal)
-        setContentHuggingPriority(.defaultLow, for: .vertical)
-        setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
-        setContentCompressionResistancePriority(.defaultLow, for: .vertical)
-    }
-
-    public convenience init() {
-        self.init(frame: .zero)
-    }
-
-    required init?(coder: NSCoder) { nil }
+  public override init(frame: CGRect) {
+    super.init(frame: frame)
+    setContentHuggingPriority(.defaultLow, for: .horizontal)
+    setContentHuggingPriority(.defaultLow, for: .vertical)
+    setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
+    setContentCompressionResistancePriority(.defaultLow, for: .vertical)
+  }
+  
+  public convenience init() {
+    self.init(frame: .zero)
+  }
+  
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/Shared/Views/RowButton.swift b/Sources/Shared/Views/RowButton.swift
index 689a705af98ce2364251f7d1d654de6eea3bd799..574b4b0908b56ba13d6c4c55f7f20c26195d97e6 100644
--- a/Sources/Shared/Views/RowButton.swift
+++ b/Sources/Shared/Views/RowButton.swift
@@ -1,78 +1,79 @@
 import UIKit
+import AppResources
 
 public struct RowButtonStyle {
-    var color: UIColor
-    var accessory: UIImage?
+  var color: UIColor
+  var accessory: UIImage?
 }
 
 public extension RowButtonStyle {
-    static let clean = RowButtonStyle(
-        color: Asset.neutralActive.color,
-        accessory: Asset.settingsDisclosure.image
-    )
-
-    static let delete = RowButtonStyle(
-        color: Asset.accentDanger.color,
-        accessory: nil
-    )
+  static let clean = RowButtonStyle(
+    color: Asset.neutralActive.color,
+    accessory: Asset.settingsDisclosure.image
+  )
+  
+  static let delete = RowButtonStyle(
+    color: Asset.accentDanger.color,
+    accessory: nil
+  )
 }
 
 public final class RowButton: UIControl {
-    public let title = UILabel()
-    public let icon = UIImageView()
-    public let separator = UIView()
-    public let stack = UIStackView()
-    public let accessory = UIImageView()
-
-    public init() {
-        super.init(frame: .zero)
-
-        icon.contentMode = .center
-        title.font = Fonts.Mulish.semiBold.font(size: 14.0)
-        separator.backgroundColor = Asset.neutralLine.color
-        icon.setContentHuggingPriority(.required, for: .horizontal)
-
-        stack.spacing = 10
-        stack.addArrangedSubview(icon)
-        stack.addArrangedSubview(title.pinning(at: .left(0)))
-        stack.addArrangedSubview(accessory.pinning(at: .top(10)))
-
-        addSubview(stack)
-        addSubview(separator)
-
-        stack.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(16)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.equalToSuperview().offset(-20)
-        }
-
-        separator.snp.makeConstraints { make in
-            make.height.equalTo(1)
-            make.left.equalToSuperview()
-            make.right.equalToSuperview()
-            make.bottom.equalToSuperview()
-        }
-
-        subviews.forEach { $0.isUserInteractionEnabled = false }
+  public let title = UILabel()
+  public let icon = UIImageView()
+  public let separator = UIView()
+  public let stack = UIStackView()
+  public let accessory = UIImageView()
+  
+  public init() {
+    super.init(frame: .zero)
+    
+    icon.contentMode = .center
+    title.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    separator.backgroundColor = Asset.neutralLine.color
+    icon.setContentHuggingPriority(.required, for: .horizontal)
+    
+    stack.spacing = 10
+    stack.addArrangedSubview(icon)
+    stack.addArrangedSubview(title.pinning(at: .left(0)))
+    stack.addArrangedSubview(accessory.pinning(at: .top(10)))
+    
+    addSubview(stack)
+    addSubview(separator)
+    
+    stack.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(16)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview().offset(-20)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    public func setup(
-        title: String,
-        icon: UIImage,
-        style: RowButtonStyle = .clean,
-        separator: Bool = true
-    ) {
-        self.icon.image = icon
-        self.title.text = title
-        self.title.textColor = style.color
-        self.accessory.image = style.accessory
-
-        guard separator == true else {
-            self.separator.removeFromSuperview()
-            return
-        }
+    
+    separator.snp.makeConstraints {
+      $0.height.equalTo(1)
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+    }
+    
+    subviews.forEach { $0.isUserInteractionEnabled = false }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  public func setup(
+    title: String,
+    icon: UIImage,
+    style: RowButtonStyle = .clean,
+    separator: Bool = true
+  ) {
+    self.icon.image = icon
+    self.title.text = title
+    self.title.textColor = style.color
+    self.accessory.image = style.accessory
+    
+    guard separator == true else {
+      self.separator.removeFromSuperview()
+      return
     }
+  }
 }
diff --git a/Sources/Shared/Views/RowSwitchableButton.swift b/Sources/Shared/Views/RowSwitchableButton.swift
index 38136c3ef7f656e8df8907ef6b5a23d461b1adee..175d93878e7b372c907267764180879177768c39 100644
--- a/Sources/Shared/Views/RowSwitchableButton.swift
+++ b/Sources/Shared/Views/RowSwitchableButton.swift
@@ -1,88 +1,89 @@
 import UIKit
+import AppResources
 
 public enum RowSwitchableButtonState {
-    case disclosure
-    case switcher(Bool)
+  case disclosure
+  case switcher(Bool)
 }
 
 public final class RowSwitchableButton: UIControl {
-    public let title = UILabel()
-    public let icon = UIImageView()
-    public let separator = UIView()
-
-    public let switcher = UISwitch()
-    public let disclosureIcon = UIImageView()
-
-    public init() {
-        super.init(frame: .zero)
-
-        icon.contentMode = .center
-        title.font = Fonts.Mulish.semiBold.font(size: 14.0)
-        separator.backgroundColor = Asset.neutralLine.color
-        title.textColor = Asset.neutralActive.color
-        disclosureIcon.image = Asset.settingsDisclosure.image
-        switcher.onTintColor = Asset.brandLight.color
-
-        addSubview(icon)
-        addSubview(title)
-        addSubview(disclosureIcon)
-        addSubview(switcher)
-        addSubview(separator)
-
-        icon.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(20)
-            make.left.equalToSuperview().offset(36)
-            make.bottom.equalToSuperview().offset(-20)
-        }
-
-        title.snp.makeConstraints { make in
-            make.left.equalTo(icon.snp.right).offset(25)
-            make.centerY.equalTo(icon)
-        }
-
-        disclosureIcon.snp.makeConstraints { make in
-            make.centerY.equalTo(icon)
-            make.right.equalToSuperview().offset(-48)
-        }
-
-        switcher.snp.makeConstraints { make in
-            make.right.equalToSuperview().offset(-25)
-            make.centerY.equalTo(icon)
-        }
-
-        separator.snp.makeConstraints { make in
-            make.height.equalTo(1)
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-            make.bottom.equalToSuperview()
-        }
+  public let title = UILabel()
+  public let icon = UIImageView()
+  public let separator = UIView()
+  
+  public let switcher = UISwitch()
+  public let disclosureIcon = UIImageView()
+  
+  public init() {
+    super.init(frame: .zero)
+    
+    icon.contentMode = .center
+    title.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    separator.backgroundColor = Asset.neutralLine.color
+    title.textColor = Asset.neutralActive.color
+    disclosureIcon.image = Asset.settingsDisclosure.image
+    switcher.onTintColor = Asset.brandLight.color
+    
+    addSubview(icon)
+    addSubview(title)
+    addSubview(disclosureIcon)
+    addSubview(switcher)
+    addSubview(separator)
+    
+    icon.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(20)
+      $0.left.equalToSuperview().offset(36)
+      $0.bottom.equalToSuperview().offset(-20)
     }
-
-    public required init?(coder: NSCoder) { nil }
-
-    public func setup(
-        title: String,
-        icon: UIImage,
-        state: RowSwitchableButtonState = .disclosure,
-        separator: Bool = true
-    ) {
-        self.icon.image = icon
-        self.title.text = title
-
-        switch state {
-        case .disclosure:
-            switcher.isHidden = true
-            disclosureIcon.isHidden = false
-
-        case .switcher(let bool):
-            switcher.isOn = bool
-            switcher.isHidden = false
-            disclosureIcon.isHidden = true
-        }
-
-        guard separator == true else {
-            self.separator.removeFromSuperview()
-            return
-        }
+    
+    title.snp.makeConstraints {
+      $0.left.equalTo(icon.snp.right).offset(25)
+      $0.centerY.equalTo(icon)
+    }
+    
+    disclosureIcon.snp.makeConstraints {
+      $0.centerY.equalTo(icon)
+      $0.right.equalToSuperview().offset(-48)
+    }
+    
+    switcher.snp.makeConstraints {
+      $0.right.equalToSuperview().offset(-25)
+      $0.centerY.equalTo(icon)
+    }
+    
+    separator.snp.makeConstraints {
+      $0.height.equalTo(1)
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
+      $0.bottom.equalToSuperview()
+    }
+  }
+  
+  public required init?(coder: NSCoder) { nil }
+  
+  public func setup(
+    title: String,
+    icon: UIImage,
+    state: RowSwitchableButtonState = .disclosure,
+    separator: Bool = true
+  ) {
+    self.icon.image = icon
+    self.title.text = title
+    
+    switch state {
+    case .disclosure:
+      switcher.isHidden = true
+      disclosureIcon.isHidden = false
+      
+    case .switcher(let bool):
+      switcher.isOn = bool
+      switcher.isHidden = false
+      disclosureIcon.isHidden = true
+    }
+    
+    guard separator == true else {
+      self.separator.removeFromSuperview()
+      return
     }
+  }
 }
diff --git a/Sources/Shared/Views/SearchComponent.swift b/Sources/Shared/Views/SearchComponent.swift
index 2edd280f23bb8ee82fb79b0003eb9eebcbee7359..5fb6fe2ad99153b117a9115fc5ad38fc9858eeb1 100644
--- a/Sources/Shared/Views/SearchComponent.swift
+++ b/Sources/Shared/Views/SearchComponent.swift
@@ -1,185 +1,186 @@
 import UIKit
 import Combine
+import AppResources
 
 public final class SearchComponent: UIView {
-    let rightButton = UIButton()
-    let leftImageView = UIImageView()
-    let containerView = UIView()
-    let inputField = UITextField()
-
-    public var rightPublisher: AnyPublisher<Void, Never> {
-        rightSubject.eraseToAnyPublisher()
-    }
-
-    public var textPublisher: AnyPublisher<String, Never> {
-        textSubject.eraseToAnyPublisher()
-    }
-
-    public var returnPublisher: AnyPublisher<Void, Never> {
-        returnSubject.eraseToAnyPublisher()
-    }
-
-    private var rightImage = Asset.sharedScan.image {
-        didSet {
-            rightButton.setImage(rightImage, for: .normal)
-        }
-    }
-
-    public var isEditingPublisher: AnyPublisher<Bool, Never> {
-        isEditingSubject.eraseToAnyPublisher()
-    }
-
-    private var cancellables = Set<AnyCancellable>()
-    private let rightSubject = PassthroughSubject<Void, Never>()
-    private let textSubject = PassthroughSubject<String, Never>()
-    private let returnSubject = PassthroughSubject<Void, Never>()
-    private let isEditingSubject = CurrentValueSubject<Bool, Never>(false)
-
-    public init() {
-        super.init(frame: .zero)
-
-        containerView.layer.cornerRadius = 25
-        containerView.backgroundColor = Asset.neutralSecondary.color
-
-        leftImageView.image = Asset.lens.image
-        leftImageView.contentMode = .center
-        leftImageView.tintColor = Asset.neutralDisabled.color
-
-        rightButton.tintColor = Asset.neutralBody.color
-        rightButton.setImage(rightImage, for: .normal)
-        rightButton.setContentHuggingPriority(.required, for: .horizontal)
-        rightButton.setContentCompressionResistancePriority(.required, for: .horizontal)
-
-        inputField.delegate = self
-        inputField.autocapitalizationType = .none
-        inputField.textColor = Asset.neutralActive.color
-        inputField.font = Fonts.Mulish.regular.font(size: 16.0)
-
-        let attrPlaceholder
-        = NSAttributedString(
-            string: Localized.Shared.Search.placeholder,
-            attributes: [
-                .font: Fonts.Mulish.regular.font(size: 14.0) as Any,
-                .foregroundColor: Asset.neutralWeak.color
-            ])
-
-        inputField.attributedPlaceholder = attrPlaceholder
-
-        inputField.textPublisher
-            .sink { [weak textSubject] in textSubject?.send($0) }
-            .store(in: &cancellables)
-
-        rightButton.publisher(for: .touchUpInside)
-            .sink { [weak rightSubject, self] in
-                if isEditingSubject.value == true {
-                    abortEditing()
-                } else {
-                    rightSubject?.send()
-                }
-            }.store(in: &cancellables)
-
-        addSubview(containerView)
-        containerView.addSubview(inputField)
-        containerView.addSubview(leftImageView)
-        containerView.addSubview(rightButton)
-
-        setupConstraints()
+  let rightButton = UIButton()
+  let leftImageView = UIImageView()
+  let containerView = UIView()
+  let inputField = UITextField()
+  
+  public var rightPublisher: AnyPublisher<Void, Never> {
+    rightSubject.eraseToAnyPublisher()
+  }
+  
+  public var textPublisher: AnyPublisher<String, Never> {
+    textSubject.eraseToAnyPublisher()
+  }
+  
+  public var returnPublisher: AnyPublisher<Void, Never> {
+    returnSubject.eraseToAnyPublisher()
+  }
+  
+  private var rightImage = Asset.sharedScan.image {
+    didSet {
+      rightButton.setImage(rightImage, for: .normal)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    public func set(
-        placeholder: String? = nil,
-        imageAtRight: UIImage? = nil,
-        inputAccessibility: String? = nil,
-        rightAccessibility: String? = nil
-    ) {
-        inputField.accessibilityIdentifier = inputAccessibility
-        rightButton.accessibilityIdentifier = rightAccessibility
-
-        if let placeholder = placeholder {
-            let attrPlaceholder
-                = NSAttributedString(
-                    string: placeholder,
-                    attributes: [
-                        .font: Fonts.Mulish.regular.font(size: 14.0) as Any,
-                        .foregroundColor: Asset.neutralWeak.color
-                    ])
-
-            inputField.attributedPlaceholder = attrPlaceholder
-        }
-
-        if let image = imageAtRight {
-            self.rightImage = image
+  }
+  
+  public var isEditingPublisher: AnyPublisher<Bool, Never> {
+    isEditingSubject.eraseToAnyPublisher()
+  }
+  
+  private var cancellables = Set<AnyCancellable>()
+  private let rightSubject = PassthroughSubject<Void, Never>()
+  private let textSubject = PassthroughSubject<String, Never>()
+  private let returnSubject = PassthroughSubject<Void, Never>()
+  private let isEditingSubject = CurrentValueSubject<Bool, Never>(false)
+  
+  public init() {
+    super.init(frame: .zero)
+    
+    containerView.layer.cornerRadius = 25
+    containerView.backgroundColor = Asset.neutralSecondary.color
+    
+    leftImageView.image = Asset.lens.image
+    leftImageView.contentMode = .center
+    leftImageView.tintColor = Asset.neutralDisabled.color
+    
+    rightButton.tintColor = Asset.neutralBody.color
+    rightButton.setImage(rightImage, for: .normal)
+    rightButton.setContentHuggingPriority(.required, for: .horizontal)
+    rightButton.setContentCompressionResistancePriority(.required, for: .horizontal)
+    
+    inputField.delegate = self
+    inputField.autocapitalizationType = .none
+    inputField.textColor = Asset.neutralActive.color
+    inputField.font = Fonts.Mulish.regular.font(size: 16.0)
+    
+    let attrPlaceholder
+    = NSAttributedString(
+      string: Localized.Shared.Search.placeholder,
+      attributes: [
+        .font: Fonts.Mulish.regular.font(size: 14.0) as Any,
+        .foregroundColor: Asset.neutralWeak.color
+      ])
+    
+    inputField.attributedPlaceholder = attrPlaceholder
+    
+    inputField.textPublisher
+      .sink { [weak textSubject] in textSubject?.send($0) }
+      .store(in: &cancellables)
+    
+    rightButton.publisher(for: .touchUpInside)
+      .sink { [weak rightSubject, self] in
+        if isEditingSubject.value == true {
+          abortEditing()
         } else {
-            rightButton.isHidden = true
+          rightSubject?.send()
         }
-    }
-
-    public func update(content: String) {
-        inputField.text = content
-    }
-
-    public func update(placeholder: String) {
-        inputField.attributedPlaceholder = NSAttributedString(
-            string: placeholder,
-            attributes: [
-                .font: Fonts.Mulish.regular.font(size: 14.0) as Any,
-                .foregroundColor: Asset.neutralWeak.color
+      }.store(in: &cancellables)
+    
+    addSubview(containerView)
+    containerView.addSubview(inputField)
+    containerView.addSubview(leftImageView)
+    containerView.addSubview(rightButton)
+    
+    setupConstraints()
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  public func set(
+    placeholder: String? = nil,
+    imageAtRight: UIImage? = nil,
+    inputAccessibility: String? = nil,
+    rightAccessibility: String? = nil
+  ) {
+    inputField.accessibilityIdentifier = inputAccessibility
+    rightButton.accessibilityIdentifier = rightAccessibility
+    
+    if let placeholder = placeholder {
+      let attrPlaceholder
+      = NSAttributedString(
+        string: placeholder,
+        attributes: [
+          .font: Fonts.Mulish.regular.font(size: 14.0) as Any,
+          .foregroundColor: Asset.neutralWeak.color
         ])
+      
+      inputField.attributedPlaceholder = attrPlaceholder
     }
-
-    public func abortEditing() {
-        inputField.text = nil
-        textSubject.send("")
-        inputField.endEditing(true)
-        isEditingSubject.send(false)
+    
+    if let image = imageAtRight {
+      self.rightImage = image
+    } else {
+      rightButton.isHidden = true
     }
-
-    private func setupConstraints() {
-        containerView.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-            $0.height.equalTo(50)
-        }
-
-        leftImageView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(10)
-            $0.left.equalToSuperview().offset(13)
-            $0.bottom.equalToSuperview().offset(-10)
-            $0.height.equalTo(leftImageView.snp.width)
-        }
-
-        inputField.snp.makeConstraints {
-            $0.top.bottom.equalToSuperview()
-            $0.left.equalTo(leftImageView.snp.right).offset(20)
-            $0.right.equalTo(rightButton.snp.left).offset(-32)
-        }
-
-        rightButton.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.right.equalToSuperview().offset(-13)
-            $0.bottom.equalToSuperview()
-        }
+  }
+  
+  public func update(content: String) {
+    inputField.text = content
+  }
+  
+  public func update(placeholder: String) {
+    inputField.attributedPlaceholder = NSAttributedString(
+      string: placeholder,
+      attributes: [
+        .font: Fonts.Mulish.regular.font(size: 14.0) as Any,
+        .foregroundColor: Asset.neutralWeak.color
+      ])
+  }
+  
+  public func abortEditing() {
+    inputField.text = nil
+    textSubject.send("")
+    inputField.endEditing(true)
+    isEditingSubject.send(false)
+  }
+  
+  private func setupConstraints() {
+    containerView.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+      $0.height.equalTo(50)
     }
-
-    public func textFieldDidBeginEditing(_ textField: UITextField) {
-        rightButton.setImage(Asset.sharedCross.image, for: .normal)
-        isEditingSubject.send(true)
+    
+    leftImageView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(10)
+      $0.left.equalToSuperview().offset(13)
+      $0.bottom.equalToSuperview().offset(-10)
+      $0.height.equalTo(leftImageView.snp.width)
     }
-
-    public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
-        inputField.resignFirstResponder()
-        returnSubject.send(())
-        return true
+    
+    inputField.snp.makeConstraints {
+      $0.top.bottom.equalToSuperview()
+      $0.left.equalTo(leftImageView.snp.right).offset(20)
+      $0.right.equalTo(rightButton.snp.left).offset(-32)
     }
-
-    public func textFieldDidEndEditing(_ textField: UITextField) {
-        rightButton.setImage(rightImage, for: .normal)
-        isEditingSubject.send(false)
+    
+    rightButton.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.right.equalToSuperview().offset(-13)
+      $0.bottom.equalToSuperview()
     }
+  }
+  
+  public func textFieldDidBeginEditing(_ textField: UITextField) {
+    rightButton.setImage(Asset.sharedCross.image, for: .normal)
+    isEditingSubject.send(true)
+  }
+  
+  public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+    inputField.resignFirstResponder()
+    returnSubject.send(())
+    return true
+  }
+  
+  public func textFieldDidEndEditing(_ textField: UITextField) {
+    rightButton.setImage(rightImage, for: .normal)
+    isEditingSubject.send(false)
+  }
 }
 
 extension SearchComponent: UITextFieldDelegate {}
diff --git a/Sources/Shared/Views/SearchCountryComponent.swift b/Sources/Shared/Views/SearchCountryComponent.swift
index 186c58b95981ce45a799c93e27c8a53530b16cf4..5975925f63c1f8e25661b1bab138895d4eb139af 100644
--- a/Sources/Shared/Views/SearchCountryComponent.swift
+++ b/Sources/Shared/Views/SearchCountryComponent.swift
@@ -1,57 +1,58 @@
 import UIKit
+import AppResources
 
 public final class SearchCountryComponent: UIControl {
-    let flagLabel = UILabel()
-    let prefixLabel = UILabel()
-    let containerView = UIView()
-
-    public init() {
-        super.init(frame: .zero)
-
-        containerView.layer.cornerRadius = 25
-        containerView.backgroundColor = Asset.neutralSecondary.color
-
-        flagLabel.text = "🇺🇸"
-        prefixLabel.text = "+1"
-        prefixLabel.textColor = Asset.neutralDisabled.color
-        prefixLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
-
-        addSubview(containerView)
-        containerView.addSubview(flagLabel)
-        containerView.addSubview(prefixLabel)
-
-        containerView.isUserInteractionEnabled = false
-
-        setupConstraints()
-        flagLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
-        prefixLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
+  let flagLabel = UILabel()
+  let prefixLabel = UILabel()
+  let containerView = UIView()
+  
+  public init() {
+    super.init(frame: .zero)
+    
+    containerView.layer.cornerRadius = 25
+    containerView.backgroundColor = Asset.neutralSecondary.color
+    
+    flagLabel.text = "🇺🇸"
+    prefixLabel.text = "+1"
+    prefixLabel.textColor = Asset.neutralDisabled.color
+    prefixLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
+    
+    addSubview(containerView)
+    containerView.addSubview(flagLabel)
+    containerView.addSubview(prefixLabel)
+    
+    containerView.isUserInteractionEnabled = false
+    
+    setupConstraints()
+    flagLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
+    prefixLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  public func setFlag(_ flag: String, prefix: String) {
+    flagLabel.text = flag
+    prefixLabel.text = prefix
+  }
+  
+  private func setupConstraints() {
+    containerView.snp.makeConstraints {
+      $0.top.equalToSuperview()
+      $0.left.equalToSuperview()
+      $0.right.equalToSuperview()
+      $0.bottom.equalToSuperview()
+      $0.height.equalTo(50)
     }
-
-    required init?(coder: NSCoder) { nil }
-
-    public func setFlag(_ flag: String, prefix: String) {
-        flagLabel.text = flag
-        prefixLabel.text = prefix
+    
+    flagLabel.snp.makeConstraints {
+      $0.left.equalToSuperview().offset(13)
+      $0.centerY.equalToSuperview()
     }
-
-    private func setupConstraints() {
-        containerView.snp.makeConstraints {
-            $0.top.equalToSuperview()
-            $0.left.equalToSuperview()
-            $0.right.equalToSuperview()
-            $0.bottom.equalToSuperview()
-            $0.height.equalTo(50)
-        }
-
-        flagLabel.snp.makeConstraints {
-            $0.left.equalToSuperview().offset(13)
-            $0.centerY.equalToSuperview()
-        }
-
-        prefixLabel.snp.makeConstraints {
-            $0.left.equalTo(flagLabel.snp.right).offset(10)
-            $0.right.equalToSuperview().offset(-13)
-            $0.centerY.equalToSuperview()
-        }
+    
+    prefixLabel.snp.makeConstraints {
+      $0.left.equalTo(flagLabel.snp.right).offset(10)
+      $0.right.equalToSuperview().offset(-13)
+      $0.centerY.equalToSuperview()
     }
+  }
 }
diff --git a/Sources/Shared/Views/SheetCardComponent.swift b/Sources/Shared/Views/SheetCardComponent.swift
index c5aa0b219e2fef53fc062cc777e7f58f3e45ccfc..6f60bb1688304323b59d439915c7ac87739b71a9 100644
--- a/Sources/Shared/Views/SheetCardComponent.swift
+++ b/Sources/Shared/Views/SheetCardComponent.swift
@@ -1,40 +1,30 @@
 import UIKit
+import AppResources
 
 public final class SheetCardComponent: UIView {
-    // MARK: UI
-
-    public let stack = UIStackView()
-
-    // MARK: Lifecycle
-
-    public init() {
-        super.init(frame: .zero)
-        setup()
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    // MARK: Public
-
-    public func set(buttons: [CapsuleButton]) {
-        buttons.forEach { stack.addArrangedSubview($0) }
-    }
-
-    // MARK: Private
-
-    private func setup() {
-        layer.cornerRadius = 24
-        backgroundColor = Asset.neutralSecondary.color
-
-        stack.spacing = 20
-        stack.axis = .vertical
-        addSubview(stack)
-
-        stack.snp.makeConstraints { make in
-            make.top.equalToSuperview().offset(24)
-            make.left.equalToSuperview().offset(24)
-            make.right.equalToSuperview().offset(-24)
-            make.bottom.equalToSuperview().offset(-24)
-        }
+  public let stackView = UIStackView()
+  
+  public init() {
+    super.init(frame: .zero)
+    
+    layer.cornerRadius = 24
+    backgroundColor = Asset.neutralSecondary.color
+    
+    stackView.spacing = 20
+    stackView.axis = .vertical
+    addSubview(stackView)
+    
+    stackView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(24)
+      $0.left.equalToSuperview().offset(24)
+      $0.right.equalToSuperview().offset(-24)
+      $0.bottom.equalToSuperview().offset(-24)
     }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  public func set(buttons: [CapsuleButton]) {
+    buttons.forEach { stackView.addArrangedSubview($0) }
+  }
 }
diff --git a/Sources/Shared/Views/SnackBar.swift b/Sources/Shared/Views/SnackBar.swift
index 9240ccf9537d12cf8377bcfb4e0d222442325f4c..4ca6961d64080ebc48b300cd73e4cc104d8e1f72 100644
--- a/Sources/Shared/Views/SnackBar.swift
+++ b/Sources/Shared/Views/SnackBar.swift
@@ -1,35 +1,36 @@
 import UIKit
+import AppResources
 
 public final class SnackBar: UIView {
-    private let titleLabel = UILabel()
-    private let imageView = UIImageView()
-    private let stackView = UIStackView()
-
-    public init() {
-        super.init(frame: .zero)
-
-        //alpha = 0.0
-        backgroundColor = Asset.brandPrimary.color
-
-        imageView.contentMode = .center
-        titleLabel.text = Localized.Shared.SnackBar.title
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
-        titleLabel.textColor = Asset.neutralWhite.color
-        imageView.image = Asset.sharedWhiteExclamation.image
-
-        stackView.spacing = 14
-        stackView.addArrangedSubview(imageView)
-        stackView.addArrangedSubview(titleLabel)
-
-        addSubview(stackView)
-
-        stackView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(16)
-            $0.left.equalToSuperview().offset(20)
-            $0.right.equalToSuperview().offset(-20)
-            $0.bottom.equalToSuperview().offset(-16)
-        }
+  private let titleLabel = UILabel()
+  private let imageView = UIImageView()
+  private let stackView = UIStackView()
+  
+  public init() {
+    super.init(frame: .zero)
+    
+    //alpha = 0.0
+    backgroundColor = Asset.brandPrimary.color
+    
+    imageView.contentMode = .center
+    titleLabel.text = Localized.Shared.SnackBar.title
+    titleLabel.font = Fonts.Mulish.semiBold.font(size: 13.0)
+    titleLabel.textColor = Asset.neutralWhite.color
+    imageView.image = Asset.sharedWhiteExclamation.image
+    
+    stackView.spacing = 14
+    stackView.addArrangedSubview(imageView)
+    stackView.addArrangedSubview(titleLabel)
+    
+    addSubview(stackView)
+    
+    stackView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(16)
+      $0.left.equalToSuperview().offset(20)
+      $0.right.equalToSuperview().offset(-20)
+      $0.bottom.equalToSuperview().offset(-16)
     }
-
-    required init?(coder: NSCoder) { nil }
+  }
+  
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/Shared/Views/TextWithInfoView.swift b/Sources/Shared/Views/TextWithInfoView.swift
index 62133c1d6136e300762dcb718a16ca57a02009a0..0fbe08bed56850680bc3c1dcc72c7cd50ffbe0b1 100644
--- a/Sources/Shared/Views/TextWithInfoView.swift
+++ b/Sources/Shared/Views/TextWithInfoView.swift
@@ -1,63 +1,64 @@
 import UIKit
+import AppResources
 
 public final class TextWithInfoView: UIView {
-    private let textView = UITextView()
-    public private(set) var didTapInfo: (() -> Void)?
-
-    public init() {
-        super.init(frame: .zero)
-        textView.backgroundColor = .clear
-
-        textView.isEditable = false
-        textView.isScrollEnabled = false
-        textView.isSelectable = false
-
-        addSubview(textView)
-        textView.snp.makeConstraints { $0.edges.equalToSuperview() }
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public func setup(
-        text: String,
-        attributes: [NSAttributedString.Key: Any],
-        didTapInfo: @escaping () -> Void
-    ) {
-        let mutable = NSMutableAttributedString(string: "\(text)  ", attributes: attributes)
-
-        let imageAttachment = NSTextAttachment()
-        imageAttachment.image = Asset.infoIcon.image
-
-        let imageString = NSAttributedString(attachment: imageAttachment)
-        mutable.append(imageString)
-        textView.attributedText = mutable
-
-        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tappedTextView(_:)))
-        textView.addGestureRecognizer(tapGesture)
-
-        self.didTapInfo = didTapInfo
-    }
-
-    @objc private func tappedTextView(_ sender: UITapGestureRecognizer) {
-        let textView = sender.view as! UITextView
-        let layoutManager = textView.layoutManager
-
-        var location = sender.location(in: textView)
-        location.x -= textView.textContainerInset.left;
-        location.y -= textView.textContainerInset.top;
-
-        let characterIndex = layoutManager.characterIndex(
-            for: location, in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil
-        )
-
-        if characterIndex < textView.textStorage.length {
-            let attributeValue = textView.attributedText.attribute(
-                NSAttributedString.Key.attachment, at: characterIndex, effectiveRange: nil
-            ) as? NSTextAttachment
-
-            if let _ = attributeValue {
-                didTapInfo?()
-            }
-        }
+  private let textView = UITextView()
+  public private(set) var didTapInfo: (() -> Void)?
+  
+  public init() {
+    super.init(frame: .zero)
+    textView.backgroundColor = .clear
+    
+    textView.isEditable = false
+    textView.isScrollEnabled = false
+    textView.isSelectable = false
+    
+    addSubview(textView)
+    textView.snp.makeConstraints { $0.edges.equalToSuperview() }
+  }
+  
+  required init?(coder: NSCoder) { nil }
+  
+  public func setup(
+    text: String,
+    attributes: [NSAttributedString.Key: Any],
+    didTapInfo: @escaping () -> Void
+  ) {
+    let mutable = NSMutableAttributedString(string: "\(text)  ", attributes: attributes)
+    
+    let imageAttachment = NSTextAttachment()
+    imageAttachment.image = Asset.infoIcon.image
+    
+    let imageString = NSAttributedString(attachment: imageAttachment)
+    mutable.append(imageString)
+    textView.attributedText = mutable
+    
+    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tappedTextView(_:)))
+    textView.addGestureRecognizer(tapGesture)
+    
+    self.didTapInfo = didTapInfo
+  }
+  
+  @objc private func tappedTextView(_ sender: UITapGestureRecognizer) {
+    let textView = sender.view as! UITextView
+    let layoutManager = textView.layoutManager
+    
+    var location = sender.location(in: textView)
+    location.x -= textView.textContainerInset.left;
+    location.y -= textView.textContainerInset.top;
+    
+    let characterIndex = layoutManager.characterIndex(
+      for: location, in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil
+    )
+    
+    if characterIndex < textView.textStorage.length {
+      let attributeValue = textView.attributedText.attribute(
+        NSAttributedString.Key.attachment, at: characterIndex, effectiveRange: nil
+      ) as? NSTextAttachment
+      
+      if let _ = attributeValue {
+        didTapInfo?()
+      }
     }
+  }
 }
diff --git a/Sources/Shared/Views/UnselectableTextView.swift b/Sources/Shared/Views/UnselectableTextView.swift
index c664c022bb340d50ccf007940a9d97ca3ba740cc..5da1855737f7a36acde4c09ad8c51018b962a862 100644
--- a/Sources/Shared/Views/UnselectableTextView.swift
+++ b/Sources/Shared/Views/UnselectableTextView.swift
@@ -1,19 +1,19 @@
 import UIKit
 
 public final class UnselectableTextView: UITextView {
-    public override var selectedTextRange: UITextRange? {
-        get { return nil }
-        set {}
-    }
-
-    public override func point(
-        inside point: CGPoint,
-        with event: UIEvent?
-    ) -> Bool {
-        guard let pos = closestPosition(to: point) else { return false }
-        guard let range = tokenizer.rangeEnclosingPosition(pos, with: .character, inDirection: .layout(.left)) else { return false }
-
-        let startIndex = offset(from: beginningOfDocument, to: range.start)
-        return attributedText.attribute(.link, at: startIndex, effectiveRange: nil) != nil
-    }
+  public override var selectedTextRange: UITextRange? {
+    get { return nil }
+    set {}
+  }
+  
+  public override func point(
+    inside point: CGPoint,
+    with event: UIEvent?
+  ) -> Bool {
+    guard let pos = closestPosition(to: point) else { return false }
+    guard let range = tokenizer.rangeEnclosingPosition(pos, with: .character, inDirection: .layout(.left)) else { return false }
+    
+    let startIndex = offset(from: beginningOfDocument, to: range.start)
+    return attributedText.attribute(.link, at: startIndex, effectiveRange: nil) != nil
+  }
 }
diff --git a/Sources/TermsFeature/RadioButton.swift b/Sources/TermsFeature/RadioButton.swift
index 201aa3b9c19b2325a06168c4e9abfba4cecf0cd5..77b2a084b2a6726527416844cb2dd55e0486240b 100644
--- a/Sources/TermsFeature/RadioButton.swift
+++ b/Sources/TermsFeature/RadioButton.swift
@@ -1,53 +1,54 @@
 import UIKit
 import Shared
+import AppResources
 
 final class RadioButton: UIControl {
-    private let filledView = UIView()
-    private let containerView = UIView()
+  private let filledView = UIView()
+  private let containerView = UIView()
 
-    init() {
-        super.init(frame: .zero)
+  init() {
+    super.init(frame: .zero)
 
-        containerView.layer.borderWidth = 1
-        containerView.layer.cornerRadius = 15
-        containerView.layer.masksToBounds = true
-        containerView.layer.borderColor = Asset.neutralWhite.color.cgColor
+    containerView.layer.borderWidth = 1
+    containerView.layer.cornerRadius = 15
+    containerView.layer.masksToBounds = true
+    containerView.layer.borderColor = Asset.neutralWhite.color.cgColor
 
-        filledView.isHidden = true
-        filledView.layer.cornerRadius = 10
-        filledView.layer.masksToBounds = true
-        filledView.backgroundColor = Asset.neutralWhite.color
+    filledView.isHidden = true
+    filledView.layer.cornerRadius = 10
+    filledView.layer.masksToBounds = true
+    filledView.backgroundColor = Asset.neutralWhite.color
 
-        containerView.isUserInteractionEnabled = false
-        filledView.isUserInteractionEnabled = false
+    containerView.isUserInteractionEnabled = false
+    filledView.isUserInteractionEnabled = false
 
-        addSubview(containerView)
-        containerView.addSubview(filledView)
+    addSubview(containerView)
+    containerView.addSubview(filledView)
 
-        setupConstraints()
-    }
+    setupConstraints()
+  }
+
+  required init?(coder: NSCoder) { nil }
 
-    required init?(coder: NSCoder) { nil }
+  func set(enabled: Bool) {
+    filledView.isHidden = !enabled
+  }
 
-    func set(enabled: Bool) {
-        filledView.isHidden = !enabled
+  private func setupConstraints() {
+    containerView.snp.makeConstraints {
+      $0.width.equalTo(30)
+      $0.height.equalTo(30)
+      $0.top.equalToSuperview().offset(5)
+      $0.left.equalToSuperview().offset(5)
+      $0.right.equalToSuperview().offset(-5)
+      $0.bottom.equalToSuperview().offset(-5)
     }
 
-    private func setupConstraints() {
-        containerView.snp.makeConstraints {
-            $0.width.equalTo(30)
-            $0.height.equalTo(30)
-            $0.top.equalToSuperview().offset(5)
-            $0.left.equalToSuperview().offset(5)
-            $0.right.equalToSuperview().offset(-5)
-            $0.bottom.equalToSuperview().offset(-5)
-        }
-
-        filledView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(5)
-            $0.left.equalToSuperview().offset(5)
-            $0.right.equalToSuperview().offset(-5)
-            $0.bottom.equalToSuperview().offset(-5)
-        }
+    filledView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(5)
+      $0.left.equalToSuperview().offset(5)
+      $0.right.equalToSuperview().offset(-5)
+      $0.bottom.equalToSuperview().offset(-5)
     }
+  }
 }
diff --git a/Sources/TermsFeature/RadioTextComponent.swift b/Sources/TermsFeature/RadioTextComponent.swift
index 8f6509f21562ebc8d3a0941cff1a5db53b997908..0d7906aea227f922387d4642c893e93d0ee9abf1 100644
--- a/Sources/TermsFeature/RadioTextComponent.swift
+++ b/Sources/TermsFeature/RadioTextComponent.swift
@@ -1,40 +1,37 @@
 import UIKit
 import Shared
+import AppResources
 
 final class RadioTextComponent: UIView {
-    let titleLabel = UILabel()
-    let radioButton = RadioButton()
+  let titleLabel = UILabel()
+  let radioButton = RadioButton()
 
-    var isEnabled: Bool = false {
-        didSet { radioButton.set(enabled: isEnabled) }
-    }
+  var isEnabled: Bool = false {
+    didSet { radioButton.set(enabled: isEnabled) }
+  }
 
-    init() {
-        super.init(frame: .zero)
+  init() {
+    super.init(frame: .zero)
 
-        titleLabel.numberOfLines = 0
-        titleLabel.textColor = Asset.neutralWhite.color
-        titleLabel.font = Fonts.Mulish.regular.font(size: 13.0)
+    titleLabel.numberOfLines = 0
+    titleLabel.textColor = Asset.neutralWhite.color
+    titleLabel.font = Fonts.Mulish.regular.font(size: 13.0)
 
-        addSubview(titleLabel)
-        addSubview(radioButton)
+    addSubview(titleLabel)
+    addSubview(radioButton)
 
-        setupConstraints()
+    titleLabel.snp.makeConstraints {
+      $0.left.equalTo(radioButton.snp.right).offset(7)
+      $0.centerY.equalTo(radioButton)
+      $0.right.equalToSuperview()
     }
 
-    required init?(coder: NSCoder) { nil }
-
-    private func setupConstraints() {
-        titleLabel.snp.makeConstraints {
-            $0.left.equalTo(radioButton.snp.right).offset(7)
-            $0.centerY.equalTo(radioButton)
-            $0.right.equalToSuperview()
-        }
-
-        radioButton.snp.makeConstraints {
-            $0.left.equalToSuperview()
-            $0.top.greaterThanOrEqualToSuperview()
-            $0.bottom.equalToSuperview()
-        }
+    radioButton.snp.makeConstraints {
+      $0.left.equalToSuperview()
+      $0.top.greaterThanOrEqualToSuperview()
+      $0.bottom.equalToSuperview()
     }
+  }
+
+  required init?(coder: NSCoder) { nil }
 }
diff --git a/Sources/TermsFeature/TermsConditionsController.swift b/Sources/TermsFeature/TermsConditionsController.swift
index b11ef15215945e388c8043ed98e3bc6b5001e142..ce67d0e4e30f1de7a45b7013ab32615319dc799f 100644
--- a/Sources/TermsFeature/TermsConditionsController.swift
+++ b/Sources/TermsFeature/TermsConditionsController.swift
@@ -1,93 +1,78 @@
 import UIKit
-import Theme
 import WebKit
 import Shared
 import Combine
 import Defaults
-import DependencyInjection
+import AppResources
+import AppNavigation
+import ComposableArchitecture
 
 public final class TermsConditionsController: UIViewController {
-    @Dependency var coordinator: TermsCoordinator
-    @Dependency var statusBarController: StatusBarStyleControlling
-
-    @KeyObject(.acceptedTerms, defaultValue: false) var didAcceptTerms: Bool
-
-    lazy private var screenView = TermsConditionsView()
-
-    private let ndf: String?
-    private var cancellables = Set<AnyCancellable>()
-
-    public init(_ ndf: String?) {
-        self.ndf = ndf
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func loadView() {
-        view = screenView
-    }
-
-    public override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        navigationItem.backButtonTitle = ""
-        navigationController?.navigationBar.customize(
-            translucent: true,
-            tint: Asset.neutralWhite.color
-        )
-    }
-
-    public override func viewDidLayoutSubviews() {
-        super.viewDidLayoutSubviews()
-
-        let gradient = CAGradientLayer()
-        gradient.colors = [
-            UIColor(red: 122/255, green: 235/255, blue: 239/255, alpha: 1).cgColor,
-            UIColor(red: 56/255, green: 204/255, blue: 232/255, alpha: 1).cgColor,
-            UIColor(red: 63/255, green: 186/255, blue: 253/255, alpha: 1).cgColor,
-            UIColor(red: 98/255, green: 163/255, blue: 255/255, alpha: 1).cgColor
-        ]
-
-        gradient.startPoint = CGPoint(x: 0, y: 0)
-        gradient.endPoint = CGPoint(x: 1, y: 1)
-
-        gradient.frame = screenView.bounds
-        screenView.layer.insertSublayer(gradient, at: 0)
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-
-        screenView.radioComponent
-            .radioButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                screenView.radioComponent.isEnabled.toggle()
-                screenView.nextButton.isEnabled = screenView.radioComponent.isEnabled
-                UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
-            }.store(in: &cancellables)
-
-        screenView.nextButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] in
-                didAcceptTerms = true
-
-                if let ndf = ndf {
-                    coordinator.presentUsername(ndf, self)
-                } else {
-                    coordinator.presentChatList(self)
-                }
-            }.store(in: &cancellables)
-
-        screenView.showTermsButton
-            .publisher(for: .touchUpInside)
-            .sink { [unowned self] _ in
-                let webView = WKWebView()
-                let webController = UIViewController()
-                webController.view.addSubview(webView)
-                webView.snp.makeConstraints { $0.edges.equalToSuperview() }
-                webView.load(URLRequest(url: URL(string: "https://elixxir.io/eula")!))
-                present(webController, animated: true)
-            }.store(in: &cancellables)
-    }
+  @Dependency(\.navigator) var navigator: Navigator
+
+  @KeyObject(.username, defaultValue: nil) var username: String?
+  @KeyObject(.acceptedTerms, defaultValue: false) var didAcceptTerms: Bool
+
+  private var cancellables = Set<AnyCancellable>()
+  private lazy var screenView = TermsConditionsView()
+
+  public override func loadView() {
+    view = screenView
+  }
+
+  public override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    navigationItem.backButtonTitle = ""
+    navigationController?.navigationBar.customize(
+      translucent: true,
+      tint: Asset.neutralWhite.color
+    )
+  }
+
+  public override func viewDidLayoutSubviews() {
+    super.viewDidLayoutSubviews()
+    let gradient = CAGradientLayer()
+    gradient.colors = [
+      UIColor(red: 122/255, green: 235/255, blue: 239/255, alpha: 1).cgColor,
+      UIColor(red: 56/255, green: 204/255, blue: 232/255, alpha: 1).cgColor,
+      UIColor(red: 63/255, green: 186/255, blue: 253/255, alpha: 1).cgColor,
+      UIColor(red: 98/255, green: 163/255, blue: 255/255, alpha: 1).cgColor
+    ]
+    gradient.startPoint = CGPoint(x: 0, y: 0)
+    gradient.endPoint = CGPoint(x: 1, y: 1)
+    gradient.frame = screenView.bounds
+    screenView.layer.insertSublayer(gradient, at: 0)
+  }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+    screenView
+      .radioComponent
+      .radioButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        screenView.radioComponent.isEnabled.toggle()
+        screenView.nextButton.isEnabled = screenView.radioComponent.isEnabled
+        UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
+      }.store(in: &cancellables)
+
+    screenView
+      .nextButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] in
+        didAcceptTerms = true
+        if username != nil {
+          navigator.perform(PresentChatList(on: navigationController!))
+        } else {
+          navigator.perform(PresentOnboardingUsername(on: navigationController!))
+        }
+      }.store(in: &cancellables)
+
+    screenView
+      .showTermsButton
+      .publisher(for: .touchUpInside)
+      .sink { [unowned self] _ in
+        navigator.perform(PresentWebsite(urlString: "https://elixxir.io/eula", from: self))
+      }.store(in: &cancellables)
+  }
 }
diff --git a/Sources/TermsFeature/TermsConditionsView.swift b/Sources/TermsFeature/TermsConditionsView.swift
index 2f3ff8fa327956951be5299f32b6537d7ad4824d..e3dd19a9427f823ebd4d54700176a4deeebcf1f3 100644
--- a/Sources/TermsFeature/TermsConditionsView.swift
+++ b/Sources/TermsFeature/TermsConditionsView.swift
@@ -1,56 +1,57 @@
 import UIKit
 import Shared
+import AppResources
 
 final class TermsConditionsView: UIView {
-    let nextButton = CapsuleButton()
-    let logoImageView = UIImageView()
-    let showTermsButton = CapsuleButton()
-    let radioComponent = RadioTextComponent()
+  let nextButton = CapsuleButton()
+  let logoImageView = UIImageView()
+  let showTermsButton = CapsuleButton()
+  let radioComponent = RadioTextComponent()
 
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = Asset.neutralWhite.color
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = Asset.neutralWhite.color
 
-        logoImageView.contentMode = .center
-        logoImageView.image = Asset.onboardingLogoStart.image
-        radioComponent.titleLabel.text = Localized.Terms.radio
+    logoImageView.contentMode = .center
+    logoImageView.image = Asset.onboardingLogoStart.image
+    radioComponent.titleLabel.text = Localized.Terms.radio
 
-        nextButton.isEnabled = false
-        nextButton.set(style: .white, title: Localized.Terms.accept)
-        showTermsButton.set(style: .seeThroughWhite, title: Localized.Terms.show)
+    nextButton.isEnabled = false
+    nextButton.set(style: .white, title: Localized.Terms.accept)
+    showTermsButton.set(style: .seeThroughWhite, title: Localized.Terms.show)
 
-        addSubview(logoImageView)
-        addSubview(nextButton)
-        addSubview(radioComponent)
-        addSubview(showTermsButton)
+    addSubview(logoImageView)
+    addSubview(nextButton)
+    addSubview(radioComponent)
+    addSubview(showTermsButton)
 
-        setupConstraints()
+    setupConstraints()
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  private func setupConstraints() {
+    logoImageView.snp.makeConstraints {
+      $0.top.equalTo(safeAreaLayoutGuide).offset(30)
+      $0.centerX.equalToSuperview()
+    }
+
+    radioComponent.snp.makeConstraints {
+      $0.left.equalToSuperview().offset(40)
+      $0.right.equalToSuperview().offset(-40)
+      $0.bottom.equalTo(nextButton.snp.top).offset(-20)
+    }
+
+    nextButton.snp.makeConstraints {
+      $0.left.equalToSuperview().offset(40)
+      $0.right.equalToSuperview().offset(-40)
+      $0.bottom.equalTo(showTermsButton.snp.top).offset(-10)
     }
 
-    required init?(coder: NSCoder) { nil }
-
-    private func setupConstraints() {
-        logoImageView.snp.makeConstraints {
-            $0.top.equalTo(safeAreaLayoutGuide).offset(30)
-            $0.centerX.equalToSuperview()
-        }
-
-        radioComponent.snp.makeConstraints {
-            $0.left.equalToSuperview().offset(40)
-            $0.right.equalToSuperview().offset(-40)
-            $0.bottom.equalTo(nextButton.snp.top).offset(-20)
-        }
-
-        nextButton.snp.makeConstraints {
-            $0.left.equalToSuperview().offset(40)
-            $0.right.equalToSuperview().offset(-40)
-            $0.bottom.equalTo(showTermsButton.snp.top).offset(-10)
-        }
-
-        showTermsButton.snp.makeConstraints {
-            $0.left.equalToSuperview().offset(40)
-            $0.right.equalToSuperview().offset(-40)
-            $0.bottom.equalTo(safeAreaLayoutGuide).offset(-40)
-        }
+    showTermsButton.snp.makeConstraints {
+      $0.left.equalToSuperview().offset(40)
+      $0.right.equalToSuperview().offset(-40)
+      $0.bottom.equalTo(safeAreaLayoutGuide).offset(-40)
     }
+  }
 }
diff --git a/Sources/TermsFeature/TermsCoordinator.swift b/Sources/TermsFeature/TermsCoordinator.swift
deleted file mode 100644
index daff90e4b1b81b18e75e3a6e34557a3478e26f57..0000000000000000000000000000000000000000
--- a/Sources/TermsFeature/TermsCoordinator.swift
+++ /dev/null
@@ -1,25 +0,0 @@
-import UIKit
-import Presentation
-
-public struct TermsCoordinator {
-    var presentChatList: (UIViewController) -> Void
-    var presentUsername: (String, UIViewController) -> Void
-}
-
-public extension TermsCoordinator {
-    static func live(
-        usernameFactory: @escaping (String) -> UIViewController,
-        chatListFactory: @escaping () -> UIViewController
-    ) -> Self {
-        .init(
-            presentChatList: { parent in
-                let presenter = ReplacePresenter()
-                presenter.present(chatListFactory(), from: parent)
-            },
-            presentUsername: { ndf, parent in
-                let presenter = PushPresenter()
-                presenter.present(usernameFactory(ndf), from: parent)
-            }
-        )
-    }
-}
diff --git a/Sources/TestHelpers/Dummies.swift b/Sources/TestHelpers/Dummies.swift
deleted file mode 100644
index e29064cdd83754306c24dc214ddf3b88d0dd1ab1..0000000000000000000000000000000000000000
--- a/Sources/TestHelpers/Dummies.swift
+++ /dev/null
@@ -1,37 +0,0 @@
-import Models
-import Foundation
-
-public extension Contact {
-    static let dummy = Contact(
-        photo: nil,
-        userId: Data(),
-        email: nil,
-        phone: nil,
-        status: .friend,
-        marshaled: Data(),
-        username: "username",
-        nickname: nil,
-        createdAt: Date()
-    )
-}
-
-public extension GroupChatInfo {
-    static let dummy = GroupChatInfo(
-        group: .dummy,
-        members: []
-    )
-}
-
-public extension Group {
-    static let dummy = Group(
-        leader: Data(),
-        name: "name",
-        groupId: Data(),
-        accepted: true,
-        serialize: Data()
-    )
-}
-
-public extension SingleChatInfo {
-    static let dummy = SingleChatInfo(contact: .dummy, lastMessage: nil)
-}
diff --git a/Sources/TestHelpers/PresenterDouble.swift b/Sources/TestHelpers/PresenterDouble.swift
deleted file mode 100644
index 603c6a46fc337df3c060451cbb2e83b708e1d065..0000000000000000000000000000000000000000
--- a/Sources/TestHelpers/PresenterDouble.swift
+++ /dev/null
@@ -1,17 +0,0 @@
-import UIKit
-import Presentation
-
-public final class PresenterDouble: Presenting {
-    public var didPresentFrom: UIViewController?
-    public var didPresentTarget: UIViewController?
-
-    public init() {}
-
-    public func present(
-        _ target: UIViewController,
-        from parent: UIViewController
-    ) {
-        didPresentFrom = parent
-        didPresentTarget = target
-    }
-}
diff --git a/Sources/Theme/StatusBarViewController.swift b/Sources/Theme/StatusBarViewController.swift
deleted file mode 100644
index d5dd7b000aa81a0180a517e69dc347016fa7a74c..0000000000000000000000000000000000000000
--- a/Sources/Theme/StatusBarViewController.swift
+++ /dev/null
@@ -1,59 +0,0 @@
-import UIKit
-import Combine
-import DependencyInjection
-
-public protocol StatusBarStyleControlling {
-    var style: CurrentValueSubject<UIStatusBarStyle, Never> { get }
-}
-
-public struct StatusBarController: StatusBarStyleControlling {
-    public init() {}
-
-    public let style = CurrentValueSubject<UIStatusBarStyle, Never>(.lightContent)
-}
-
-public final class StatusBarViewController: UIViewController {
-    private let content: UIViewController?
-    private var cancellables = Set<AnyCancellable>()
-
-    @Dependency private var statusBarController: StatusBarStyleControlling
-
-    public init(_ content: UIViewController?) {
-        self.content = content
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override var preferredStatusBarStyle: UIStatusBarStyle  {
-        statusBarController.style.value
-    }
-
-    public override func loadView() {
-        let view = UIView()
-        view.backgroundColor = .clear
-        self.view = view
-    }
-
-    public override func viewDidLoad() {
-        super.viewDidLoad()
-
-        if let content = content {
-            addChild(content)
-            view.addSubview(content.view)
-            content.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
-            content.view.frame = view.bounds
-            content.didMove(toParent: self)
-        } else {
-            view.isUserInteractionEnabled = false
-        }
-
-        statusBarController.style
-            .receive(on: DispatchQueue.main)
-            .sink { [weak self] style in
-                UIView.animate(withDuration: 0.2) {
-                    self?.setNeedsStatusBarAppearanceUpdate()
-                }
-            }.store(in: &cancellables)
-    }
-}
diff --git a/Sources/Theme/ThemeController.swift b/Sources/Theme/ThemeController.swift
deleted file mode 100644
index 255f1063f39a94670b4a9e4e7bbbe81e54956a73..0000000000000000000000000000000000000000
--- a/Sources/Theme/ThemeController.swift
+++ /dev/null
@@ -1,41 +0,0 @@
-import UIKit
-import Combine
-import Defaults
-
-public enum Theme: Int {
-    case system = 0
-    case dark
-
-    public var userInterfaceStyle: UIUserInterfaceStyle {
-        switch self {
-        case .system:
-            return .unspecified
-        case .dark:
-            return .dark
-        }
-    }
-}
-
-public protocol ThemeControlling {
-    var theme: CurrentValueSubject<Theme, Never> { get }
-}
-
-public final class ThemeController: ThemeControlling {
-    // MARK: Stored
-
-    @KeyObject(.theme, defaultValue: 0) var storedTheme: Int
-
-    // MARK: Properties
-
-    private var cancellables = Set<AnyCancellable>()
-    public let theme = CurrentValueSubject<Theme, Never>(.system)
-
-    // MARK: Lifecycle
-
-    public init() {
-        theme.send(Theme(rawValue: storedTheme) ?? .system)
-
-        theme.sink { [unowned self] in storedTheme = $0.rawValue }
-            .store(in: &cancellables)
-    }
-}
diff --git a/Sources/Theme/Window.swift b/Sources/Theme/Window.swift
deleted file mode 100644
index 8afb304005d85a8657b2f6bb2b1b5dba7a039546..0000000000000000000000000000000000000000
--- a/Sources/Theme/Window.swift
+++ /dev/null
@@ -1,19 +0,0 @@
-import UIKit
-import Combine
-import DependencyInjection
-
-public final class Window: UIWindow {
-    @Dependency private var themeController: ThemeControlling
-
-    private var cancellables = Set<AnyCancellable>()
-
-    public init() {
-        super.init(frame: UIScreen.main.bounds)
-
-        themeController.theme
-            .sink { [unowned self] in overrideUserInterfaceStyle = $0.userInterfaceStyle }
-            .store(in: &cancellables)
-    }
-
-    required init?(coder: NSCoder) { nil }
-}
diff --git a/Sources/ToastFeature/ToastController.swift b/Sources/ToastFeature/ToastController.swift
deleted file mode 100644
index 1b0fd60e66297ace82beb3a6c1e877185d5ad066..0000000000000000000000000000000000000000
--- a/Sources/ToastFeature/ToastController.swift
+++ /dev/null
@@ -1,22 +0,0 @@
-import Combine
-
-public final class ToastController {
-    private let queue = CurrentValueSubject<[ToastModel], Never>([])
-
-    var currentToast: AnyPublisher<ToastModel, Never> {
-        queue.compactMap(\.first)
-            .removeDuplicates(by: { $0.id == $1.id })
-            .eraseToAnyPublisher()
-    }
-
-    public init() {}
-
-    public func enqueueToast(model: ToastModel) {
-        queue.value.append(model)
-    }
-
-    public func dismissCurrentToast() {
-        guard queue.value.isEmpty == false else { return }
-        _ = queue.value.removeFirst()
-    }
-}
diff --git a/Sources/ToastFeature/ToastModel.swift b/Sources/ToastFeature/ToastModel.swift
deleted file mode 100644
index 06dd2a03fde9743309246e6438cdb9ca37383efc..0000000000000000000000000000000000000000
--- a/Sources/ToastFeature/ToastModel.swift
+++ /dev/null
@@ -1,36 +0,0 @@
-import UIKit
-import Shared
-
-public struct ToastModel {
-    let id: UUID
-    let title: String
-    let color: UIColor
-    let subtitle: String?
-    let leftImage: UIImage
-    let timeToLive: Int
-    let buttonTitle: String?
-    let autodismissable: Bool
-    let onTapClosure: (() -> Void)?
-
-    public init(
-        id: UUID = UUID(),
-        title: String,
-        color: UIColor = Asset.neutralOverlay.color,
-        subtitle: String? = nil,
-        leftImage: UIImage,
-        timeToLive: Int = 4,
-        buttonTitle: String? = nil,
-        onTapClosure: (() -> Void)? = nil,
-        autodismissable: Bool = true
-    ) {
-        self.id = id
-        self.title = title
-        self.color = color
-        self.subtitle = subtitle
-        self.leftImage = leftImage
-        self.timeToLive = timeToLive
-        self.buttonTitle = buttonTitle
-        self.onTapClosure = onTapClosure
-        self.autodismissable = autodismissable
-    }
-}
diff --git a/Sources/ToastFeature/ToastView.swift b/Sources/ToastFeature/ToastView.swift
deleted file mode 100644
index c5c96561df06b942bd640a30cdf308bed014a80f..0000000000000000000000000000000000000000
--- a/Sources/ToastFeature/ToastView.swift
+++ /dev/null
@@ -1,78 +0,0 @@
-import UIKit
-import Shared
-import Combine
-
-final class ToastView: UIView {
-    private let titleLabel = UILabel()
-    private let subtitleLabel = UILabel()
-    private let leftImageView = UIImageView()
-    private let rightButton = UIButton()
-    private let verticalStackView = UIStackView()
-    private let horizontalStackView = UIStackView()
-    private var cancellables = Set<AnyCancellable>()
-
-    init(model: ToastModel) {
-        super.init(frame: .zero)
-        backgroundColor = model.color
-        layer.cornerRadius = 18.0
-
-        titleLabel.textColor = .white
-        subtitleLabel.textColor = .white
-        leftImageView.contentMode = .center
-
-        titleLabel.numberOfLines = 0
-        subtitleLabel.numberOfLines = 0
-        titleLabel.font = Fonts.Mulish.semiBold.font(size: 16.0)
-        subtitleLabel.font = Fonts.Mulish.semiBold.font(size: 14.0)
-
-        leftImageView.image = Asset.sharedSuccess.image
-        leftImageView.setContentHuggingPriority(.required, for: .horizontal)
-
-        rightButton.titleLabel?.numberOfLines = 0
-        rightButton.titleLabel?.textAlignment = .center
-        rightButton.titleLabel?.font = Fonts.Mulish.bold.font(size: 12.0)
-
-        verticalStackView.axis = .vertical
-        verticalStackView.distribution = .fill
-        verticalStackView.addArrangedSubview(titleLabel)
-        verticalStackView.addArrangedSubview(subtitleLabel)
-
-        horizontalStackView.spacing = 12
-        horizontalStackView.addArrangedSubview(leftImageView)
-        horizontalStackView.addArrangedSubview(verticalStackView)
-        horizontalStackView.addArrangedSubview(rightButton)
-
-        addSubview(horizontalStackView)
-
-        horizontalStackView.snp.makeConstraints {
-            $0.top.equalToSuperview().offset(17)
-            $0.left.equalToSuperview().offset(20)
-            $0.right.equalToSuperview().offset(-20)
-            $0.bottom.equalToSuperview().offset(-17)
-        }
-
-        titleLabel.text = model.title
-        leftImageView.image = model.leftImage
-
-        if let subtitle = model.subtitle {
-            subtitleLabel.text = subtitle
-            subtitleLabel.numberOfLines = 0
-        } else {
-            subtitleLabel.isHidden = true
-        }
-
-        if let buttonTitle = model.buttonTitle {
-            rightButton.setTitle(buttonTitle, for: .normal)
-            rightButton.setContentHuggingPriority(.required, for: .horizontal)
-        } else {
-            rightButton.isHidden = true
-        }
-
-        rightButton
-            .publisher(for: .touchUpInside)
-            .sink { model.onTapClosure?() }
-            .store(in: &cancellables)
-    }
-
-    required init?(coder: NSCoder) { nil }
-}
diff --git a/Sources/ToastFeature/ToastViewController.swift b/Sources/ToastFeature/ToastViewController.swift
deleted file mode 100644
index 35c68a531da1d7197d3ed709472c07b452d463c6..0000000000000000000000000000000000000000
--- a/Sources/ToastFeature/ToastViewController.swift
+++ /dev/null
@@ -1,134 +0,0 @@
-import UIKit
-import Combine
-import DependencyInjection
-
-public final class ToastViewController: UIViewController {
-    @Dependency private var controller: ToastController
-
-    private var timer: Timer?
-    private let content: UIViewController
-    private let toastTopPadding: CGFloat = 10
-    private var cancellables = Set<AnyCancellable>()
-    private var topToastConstraint: NSLayoutConstraint?
-
-    public init(_ content: UIViewController) {
-        self.content = content
-        super.init(nibName: nil, bundle: nil)
-    }
-
-    required init?(coder: NSCoder) { nil }
-
-    public override func loadView() {
-        let view = UIView()
-        view.backgroundColor = .clear
-        self.view = view
-    }
-
-    override public func viewDidLoad() {
-        super.viewDidLoad()
-
-        addChild(content)
-        view.addSubview(content.view)
-        content.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
-        content.view.frame = view.bounds
-        content.didMove(toParent: self)
-
-        controller.currentToast
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] model in
-                let toastView = ToastView(model: model)
-                add(toastView: toastView)
-                present(toastView: toastView)
-            }.store(in: &cancellables)
-    }
-
-    @objc private func didPanToast(_ sender: UIPanGestureRecognizer) {
-        guard let toastView = sender.view else { return }
-
-        switch sender.state {
-        case .began, .changed:
-            timer?.invalidate()
-            let padding = toastTopPadding + min(0, sender.translation(in: view).y)
-            topToastConstraint?.constant = padding
-
-        case .cancelled, .ended, .failed:
-            let halfFrameHeight = -0.5 * toastView.frame.height
-            let verticalTranslation = sender.translation(in: toastView).y
-            let didSwipeAboveHalf = verticalTranslation < halfFrameHeight
-
-            if didSwipeAboveHalf {
-                dismiss(toastView: toastView)
-            } else {
-                present(toastView: toastView)
-            }
-
-        case .possible:
-            break
-        @unknown default:
-            break
-        }
-    }
-
-    private func dismiss(toastView: UIView) {
-        toastView.isUserInteractionEnabled = false
-        topToastConstraint?.constant = -(toastView.frame.height + view.safeAreaLayoutGuide.layoutFrame.minY)
-
-        topToastConstraint = nil
-        UIView.animate(withDuration: 0.25) {
-            self.view.setNeedsLayout()
-            self.view.layoutIfNeeded()
-        } completion: { _ in
-            toastView.isUserInteractionEnabled = true
-            toastView.removeFromSuperview()
-            self.controller.dismissCurrentToast()
-        }
-    }
-
-    private func add(toastView: UIView) {
-        let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPanToast(_:)))
-        toastView.addGestureRecognizer(gestureRecognizer)
-
-        toastView.translatesAutoresizingMaskIntoConstraints = false
-        view.addSubview(toastView)
-
-        NSLayoutConstraint.activate([
-            toastView.heightAnchor.constraint(equalToConstant: 78),
-            toastView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 20),
-            toastView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: -20)
-        ])
-
-        topToastConstraint = toastView.topAnchor.constraint(
-            equalTo: view.safeAreaLayoutGuide.topAnchor,
-            constant: -(toastView.frame.height + view.safeAreaLayoutGuide.layoutFrame.height)
-        )
-
-        topToastConstraint?.isActive = true
-
-        view.setNeedsLayout()
-        view.layoutIfNeeded()
-    }
-
-    private func present(toastView: UIView) {
-        toastView.isUserInteractionEnabled = false
-        topToastConstraint?.constant = toastTopPadding
-
-        UIView.animate(
-            withDuration: 0.5,
-            delay: 0,
-            usingSpringWithDamping: 1,
-            initialSpringVelocity: 0.5,
-            options: .curveEaseInOut
-        ) {
-            self.view.setNeedsLayout()
-            self.view.layoutIfNeeded()
-        } completion: { _ in
-            toastView.isUserInteractionEnabled = true
-
-            self.timer?.invalidate()
-            self.timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { [weak self] _ in
-                guard let self = self else { return }
-                self.dismiss(toastView: toastView)
-            }
-        }
-    }
-}
diff --git a/Sources/UpdateErrors/Dependency.swift b/Sources/UpdateErrors/Dependency.swift
new file mode 100644
index 0000000000000000000000000000000000000000..7294a48614e8353a88079224bc6cf82f551517ee
--- /dev/null
+++ b/Sources/UpdateErrors/Dependency.swift
@@ -0,0 +1,13 @@
+import Dependencies
+
+private enum UpdateErrorsDependencyKey: DependencyKey {
+  static let liveValue: UpdateErrors = .live
+  static let testValue: UpdateErrors = .unimplemented
+}
+
+extension DependencyValues {
+  public var updateErrors: UpdateErrors {
+    get { self[UpdateErrorsDependencyKey.self] }
+    set { self[UpdateErrorsDependencyKey.self] = newValue }
+  }
+}
diff --git a/Sources/UpdateErrors/UpdateErrors.swift b/Sources/UpdateErrors/UpdateErrors.swift
new file mode 100644
index 0000000000000000000000000000000000000000..6240d77c68672223e2ed8831fa85a876945bb1cf
--- /dev/null
+++ b/Sources/UpdateErrors/UpdateErrors.swift
@@ -0,0 +1,52 @@
+import XXClient
+import Foundation
+import XCTestDynamicOverlay
+
+public struct UpdateErrors {
+  public enum Error: Swift.Error {
+    case noData
+    case decodeFailure
+    case network(URLError)
+    case bindingsException
+  }
+
+  public typealias Completion = (Result<Void, Error>) -> Void
+
+  public var run: (@escaping Completion) -> Void
+
+  public func callAsFunction(_ completion: @escaping Completion) -> Void {
+    run(completion)
+  }
+}
+
+extension UpdateErrors {
+  public static let live = UpdateErrors { completion in
+    let url = URL(string: "https://git.xx.network/elixxir/client-error-database/-/raw/main/clientErrors.json")
+    URLSession.shared.dataTask(with: url!) { data, _, error in
+      if let error {
+        completion(.failure(.network(error as! URLError)))
+        return
+      }
+      guard let data else {
+        completion(.failure(.noData))
+        return
+      }
+      guard let string = String(data: data, encoding: .utf8) else {
+        completion(.failure(.decodeFailure))
+        return
+      }
+      do {
+        try UpdateCommonErrors.live(jsonFile: string)
+        completion(.success(()))
+      } catch {
+        completion(.failure(.bindingsException))
+      }
+    }.resume()
+  }
+}
+
+extension UpdateErrors {
+  public static let unimplemented = UpdateErrors(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Sources/VersionChecking/VersionChecking.swift b/Sources/VersionChecking/VersionChecking.swift
deleted file mode 100644
index cc5a719cdc5bd28f08ca8aea34dd04a7bcf233de..0000000000000000000000000000000000000000
--- a/Sources/VersionChecking/VersionChecking.swift
+++ /dev/null
@@ -1,111 +0,0 @@
-import Combine
-import Foundation
-
-#warning("TODO: Unit test this feature")
-
-public enum VersionInfo {
-    case upToDate
-    case failure(Error)
-    case updateRequired(DappVersionInformation)
-    case updateRecommended(DappVersionInformation)
-}
-
-public struct VersionDataFetcher {
-    var run: () -> AnyPublisher<DappVersionInformation, Error>
-
-    public init(run: @escaping () -> AnyPublisher<DappVersionInformation, Error>) {
-        self.run = run
-    }
-
-    public func callAsFunction() -> AnyPublisher<DappVersionInformation, Error> { run() }
-}
-
-public struct VersionChecker {
-    var run: () -> AnyPublisher<VersionInfo, Never>
-
-    public init(run: @escaping () -> AnyPublisher<VersionInfo, Never>) {
-        self.run = run
-    }
-
-    public func callAsFunction() -> AnyPublisher<VersionInfo, Never> { run() }
-}
-
-public extension VersionChecker {
-
-    static let mock: Self = .init { Just(.upToDate).eraseToAnyPublisher() }
-
-    static func live(
-        fetchVersion: VersionDataFetcher = .live(),
-        bundleVersion: @escaping () -> String = { Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String }
-    ) -> Self {
-        .init {
-            fetchVersion()
-                .map { dappInfo -> VersionInfo in
-                    let version = bundleVersion()
-                    if version >= dappInfo.recommended {
-                        return .upToDate
-                    } else if version >= dappInfo.minimum {
-                        return .updateRecommended(dappInfo)
-                    } else {
-                        return .updateRequired(dappInfo)
-                    }
-                }
-                .catch { Just(VersionInfo.failure($0)) }
-                .eraseToAnyPublisher()
-        }
-    }
-}
-
-public extension VersionDataFetcher {
-    static func mock() -> Self {
-        .init {
-            Just(DappVersionInformation(
-                appUrl: "https://testflight.apple.com/join/L1Rj0so3",
-                minimum: "1.0",
-                recommended: "1.0",
-                minimumMessage: "This app version is not supported anymore, please update to the latest version to keep enjoying our app"
-            ))
-                .setFailureType(to: Error.self)
-                .eraseToAnyPublisher()
-        }
-    }
-
-    static func live() -> Self {
-        .init {
-            let request = URLRequest(
-                url: URL(string: "https://elixxir-bins.s3-us-west-1.amazonaws.com/client/dapps/appdb.json")!,
-                cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
-                timeoutInterval: 5
-            )
-
-            return URLSession.shared
-                .dataTaskPublisher(for: request)
-                .map(\.data)
-                .decode(type: BackendVersionInformation.self, decoder: JSONDecoder())
-                .map(\.info)
-                .eraseToAnyPublisher()
-        }
-    }
-}
-
-public struct DappVersionInformation: Codable {
-    public let appUrl: String
-    public let minimum: String
-    public let recommended: String
-    public let minimumMessage: String
-
-    private enum CodingKeys: String, CodingKey {
-        case appUrl = "new_ios_app_url"
-        case minimum = "new_ios_min_version"
-        case recommended = "new_ios_recommended_version"
-        case minimumMessage = "new_minimum_popup_msg"
-    }
-}
-
-private struct BackendVersionInformation: Codable {
-    let info: DappVersionInformation
-
-    private enum CodingKeys: String, CodingKey {
-        case info = "dapp-id"
-    }
-}
diff --git a/Sources/Voxophone/Voxophone.swift b/Sources/Voxophone/Voxophone.swift
index 578a46a85df147350013fc526a5d8cd63c0718dd..61141ffd36ff068e3eb0a8385dd38d76699c9aa1 100644
--- a/Sources/Voxophone/Voxophone.swift
+++ b/Sources/Voxophone/Voxophone.swift
@@ -1,229 +1,227 @@
-import AVFoundation
-import Combine
 import Shared
+import Combine
+import AVFoundation
 
 public final class Voxophone: NSObject, AVAudioRecorderDelegate, AVAudioPlayerDelegate {
-    public enum State: Equatable {
-        case empty(isLoudspeaker: Bool)
-        case idle(URL, duration: TimeInterval, isLoudspeaker: Bool)
-        case recording(URL, time: TimeInterval, isLoudspeaker: Bool)
-        case playing(URL, duration: TimeInterval, time: TimeInterval, isLoudspeaker: Bool)
-    }
-
-    public override init() {
-        super.init()
-    }
-
-    deinit {
-        destroyPlayer()
-        destroyRecorder()
-        stopTimer()
-    }
-
-    @Published public private(set) var state: State = .empty(isLoudspeaker: false)
-
-    private let session: AVAudioSession = .sharedInstance()
-    private var recorder: AVAudioRecorder?
-    private var player: AVAudioPlayer?
-    private var timer: Timer?
-
-    public func reset() {
-        destroyPlayer()
-        destroyRecorder()
-        state = .empty(isLoudspeaker: false)
-    }
-
-    public func toggleLoudspeaker() {
-        state.isLoudspeaker.toggle()
-        setupSessionCategory()
-    }
-
-    public func load(_ url: URL) {
-        destroyPlayer()
-        destroyRecorder()
-        let player = setupPlayer(url: url)
-        state = .idle(url, duration: player.duration, isLoudspeaker: state.isLoudspeaker)
-    }
-
-    public func play() {
-        guard let player = player, let url = player.url else { return }
-        destroyRecorder()
-        state = .playing(url, duration: player.duration, time: player.currentTime, isLoudspeaker: state.isLoudspeaker)
-        startPlayback()
-    }
-
-    public func record() {
-        let url = URL(fileURLWithPath: FileManager.xxPath + "/recording_\(Date.asTimestamp).m4a")
-
-        destroyPlayer()
-        destroyRecorder()
-        let recorder = setupRecorder(url: url)
-        state = .recording(url, time: recorder.currentTime, isLoudspeaker: state.isLoudspeaker)
-        startRecording()
-    }
-
-    public func stop() {
-        switch state {
-        case .empty, .idle:
-            return
-
-        case .recording:
-            finishRecording()
-
-        case .playing(let url, let duration, _, let isLoudspeaker):
-            stopPlayback()
-            state = .idle(url, duration: duration, isLoudspeaker: isLoudspeaker)
-        }
-    }
-
-    // MARK: - Player
-
-    private func setupPlayer(url: URL) -> AVAudioPlayer {
-        let player = try! AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.m4a.rawValue)
-        self.player = player
-        return player
-    }
-
-    private func setupSessionCategory() {
-        switch state {
-        case .playing(_, _, _, let isLoud):
-            if isLoud, session.category != .playback {
-                try! session.setCategory(.playback, options: .duckOthers)
-            }
-
-            if !isLoud, session.category != .playAndRecord {
-                try! session.setCategory(.playAndRecord, options: .duckOthers)
-            }
-        case .recording(_, _, _):
-            if session.category != .playAndRecord {
-                try! session.setCategory(.playAndRecord, options: .duckOthers)
-            }
-        default:
-            break
-        }
-    }
-
-    private func startPlayback() {
-        guard let player = player else { return }
-        try! session.setActive(true)
-        setupSessionCategory()
-        player.delegate = self
-        player.prepareToPlay()
-        player.play()
-        startTimer()
-    }
-
-    private func stopPlayback() {
-        guard let player = player else { return }
-        player.stop()
-    }
-
-    private func destroyPlayer() {
-        player?.delegate = nil
-        player?.stop()
-        player = nil
-    }
-
-    // MARK: - Recorder
-
-    private func setupRecorder(url: URL) -> AVAudioRecorder {
-        let recorder = try! AVAudioRecorder(url: url, settings: [
-            AVFormatIDKey: kAudioFormatMPEG4AAC,
-            AVSampleRateKey: 12000,
-            AVNumberOfChannelsKey: 1
-        ])
-        self.recorder = recorder
-        return recorder
-    }
-
-    private func startRecording() {
-        guard let recorder = recorder else { return }
-        try! session.setActive(true)
-        setupSessionCategory()
-        recorder.delegate = self
-        recorder.record()
-        startTimer()
-    }
-
-    private func finishRecording() {
-        guard let recorder = recorder else { return }
-        recorder.stop()
-    }
-
-    private func destroyRecorder() {
-        recorder?.delegate = nil
-        recorder?.stop()
-        recorder = nil
-    }
-
-    // MARK: - Timer
-
-    private func startTimer() {
-        stopTimer()
-        timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
-            self.timerTick()
-        }
-    }
-
-    private func timerTick() {
-        switch state {
-        case .empty, .idle:
-            stopTimer()
-
-        case .recording(_, _, let isLoud):
-            guard let recorder = recorder else { return }
-            state = .recording(recorder.url, time: recorder.currentTime, isLoudspeaker: isLoud)
-
-        case .playing(_, _, _, let isLoud):
-            guard let player = player, let url = player.url else { return }
-            state = .playing(url, duration: player.duration, time: player.currentTime, isLoudspeaker: isLoud)
-        }
-    }
-
-    private func stopTimer() {
-        timer?.invalidate()
-        timer = nil
-    }
-
-    // MARK: - AVAudioRecorderDelegate
-
-    public func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
-        guard flag else {
-            state = .empty(isLoudspeaker: state.isLoudspeaker)
-            return
-        }
-        load(recorder.url)
-    }
-
-    // MARK: - AVAudioPlayerDelegate
-
-    public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
-        guard flag, let url = player.url else {
-            state = .empty(isLoudspeaker: state.isLoudspeaker)
-            return
-        }
-        load(url)
-    }
+  public enum State: Equatable {
+    case empty(isLoudspeaker: Bool)
+    case idle(URL, duration: TimeInterval, isLoudspeaker: Bool)
+    case recording(URL, time: TimeInterval, isLoudspeaker: Bool)
+    case playing(URL, duration: TimeInterval, time: TimeInterval, isLoudspeaker: Bool)
+  }
+
+  public override init() {
+    super.init()
+  }
+
+  deinit {
+    destroyPlayer()
+    destroyRecorder()
+    stopTimer()
+  }
+
+  @Published public private(set) var state: State = .empty(isLoudspeaker: false)
+
+  private let session: AVAudioSession = .sharedInstance()
+  private var recorder: AVAudioRecorder?
+  private var player: AVAudioPlayer?
+  private var timer: Timer?
+
+  public func reset() {
+    destroyPlayer()
+    destroyRecorder()
+    state = .empty(isLoudspeaker: false)
+  }
+
+  public func toggleLoudspeaker() {
+    state.isLoudspeaker.toggle()
+    setupSessionCategory()
+  }
+
+  public func load(_ url: URL) {
+    destroyPlayer()
+    destroyRecorder()
+    let player = setupPlayer(url: url)
+    state = .idle(url, duration: player.duration, isLoudspeaker: state.isLoudspeaker)
+  }
+
+  public func play() {
+    guard let player = player, let url = player.url else { return }
+    destroyRecorder()
+    state = .playing(url, duration: player.duration, time: player.currentTime, isLoudspeaker: state.isLoudspeaker)
+    startPlayback()
+  }
+
+  public func record() {
+    let url = URL(fileURLWithPath: FileManager.xxPath + "/recording_\(Date.asTimestamp).m4a")
+
+    destroyPlayer()
+    destroyRecorder()
+    let recorder = setupRecorder(url: url)
+    state = .recording(url, time: recorder.currentTime, isLoudspeaker: state.isLoudspeaker)
+    startRecording()
+  }
+
+  public func stop() {
+    switch state {
+    case .empty, .idle:
+      return
+
+    case .recording:
+      finishRecording()
+
+    case .playing(let url, let duration, _, let isLoudspeaker):
+      stopPlayback()
+      state = .idle(url, duration: duration, isLoudspeaker: isLoudspeaker)
+    }
+  }
+
+  private func setupPlayer(url: URL) -> AVAudioPlayer {
+    let player = try! AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.m4a.rawValue)
+    self.player = player
+    return player
+  }
+
+  private func setupSessionCategory() {
+    switch state {
+    case .playing(_, _, _, let isLoud):
+      if isLoud, session.category != .playback {
+        try! session.setCategory(.playback, options: .duckOthers)
+      }
+
+      if !isLoud, session.category != .playAndRecord {
+        try! session.setCategory(.playAndRecord, options: .duckOthers)
+      }
+    case .recording(_, _, _):
+      if session.category != .playAndRecord {
+        try! session.setCategory(.playAndRecord, options: .duckOthers)
+      }
+    default:
+      break
+    }
+  }
+
+  private func startPlayback() {
+    guard let player = player else { return }
+    try! session.setActive(true)
+    setupSessionCategory()
+    player.delegate = self
+    player.prepareToPlay()
+    player.play()
+    startTimer()
+  }
+
+  private func stopPlayback() {
+    guard let player = player else { return }
+    player.stop()
+  }
+
+  private func destroyPlayer() {
+    player?.delegate = nil
+    player?.stop()
+    player = nil
+  }
+
+  // MARK: - Recorder
+
+  private func setupRecorder(url: URL) -> AVAudioRecorder {
+    let recorder = try! AVAudioRecorder(url: url, settings: [
+      AVFormatIDKey: kAudioFormatMPEG4AAC,
+      AVSampleRateKey: 12000,
+      AVNumberOfChannelsKey: 1
+    ])
+    self.recorder = recorder
+    return recorder
+  }
+
+  private func startRecording() {
+    guard let recorder = recorder else { return }
+    try! session.setActive(true)
+    setupSessionCategory()
+    recorder.delegate = self
+    recorder.record()
+    startTimer()
+  }
+
+  private func finishRecording() {
+    guard let recorder = recorder else { return }
+    recorder.stop()
+  }
+
+  private func destroyRecorder() {
+    recorder?.delegate = nil
+    recorder?.stop()
+    recorder = nil
+  }
+
+  // MARK: - Timer
+
+  private func startTimer() {
+    stopTimer()
+    timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
+      self.timerTick()
+    }
+  }
+
+  private func timerTick() {
+    switch state {
+    case .empty, .idle:
+      stopTimer()
+
+    case .recording(_, _, let isLoud):
+      guard let recorder = recorder else { return }
+      state = .recording(recorder.url, time: recorder.currentTime, isLoudspeaker: isLoud)
+
+    case .playing(_, _, _, let isLoud):
+      guard let player = player, let url = player.url else { return }
+      state = .playing(url, duration: player.duration, time: player.currentTime, isLoudspeaker: isLoud)
+    }
+  }
+
+  private func stopTimer() {
+    timer?.invalidate()
+    timer = nil
+  }
+
+  // MARK: - AVAudioRecorderDelegate
+
+  public func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
+    guard flag else {
+      state = .empty(isLoudspeaker: state.isLoudspeaker)
+      return
+    }
+    load(recorder.url)
+  }
+
+  // MARK: - AVAudioPlayerDelegate
+
+  public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
+    guard flag, let url = player.url else {
+      state = .empty(isLoudspeaker: state.isLoudspeaker)
+      return
+    }
+    load(url)
+  }
 }
 
 public extension Voxophone.State {
-    var isLoudspeaker: Bool {
-        get {
-            switch self {
-            case .playing(_, _, _, let isLoud), .idle(_, _, let isLoud), .empty(let isLoud), .recording(_, _, let isLoud):
-                return isLoud
-            }
-        } set {
-            switch self {
-            case .empty(_):
-                self = .empty(isLoudspeaker: newValue)
-            case let .idle(url, duration, _):
-                self = .idle(url, duration: duration, isLoudspeaker: newValue)
-            case let .playing(url, duration, time, _):
-                self = .playing(url, duration: duration, time: time, isLoudspeaker: newValue)
-            case let .recording(url, time, _):
-                self = .recording(url, time: time, isLoudspeaker: newValue)
-            }
-        }
-    }
+  var isLoudspeaker: Bool {
+    get {
+      switch self {
+      case .playing(_, _, _, let isLoud), .idle(_, _, let isLoud), .empty(let isLoud), .recording(_, _, let isLoud):
+        return isLoud
+      }
+    } set {
+      switch self {
+      case .empty(_):
+        self = .empty(isLoudspeaker: newValue)
+      case let .idle(url, duration, _):
+        self = .idle(url, duration: duration, isLoudspeaker: newValue)
+      case let .playing(url, duration, time, _):
+        self = .playing(url, duration: duration, time: time, isLoudspeaker: newValue)
+      case let .recording(url, time, _):
+        self = .recording(url, time: time, isLoudspeaker: newValue)
+      }
+    }
+  }
 }
diff --git a/Sources/WebsiteFeature/WebsiteController.swift b/Sources/WebsiteFeature/WebsiteController.swift
new file mode 100644
index 0000000000000000000000000000000000000000..dacb0998608f3f43972f01458dcca6e6357c2c8c
--- /dev/null
+++ b/Sources/WebsiteFeature/WebsiteController.swift
@@ -0,0 +1,28 @@
+import UIKit
+import WebKit
+
+public final class WebsiteController: UIViewController {
+  private lazy var webView = WKWebView()
+
+  private let url: URL
+
+  public init(_ string: String) {
+    self.url = .init(string: string)!
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  public override func loadView() {
+    view = webView
+  }
+
+  required init?(coder: NSCoder) { nil }
+
+  public override func viewDidLoad() {
+    super.viewDidLoad()
+
+    DispatchQueue.main.async { [weak self] in
+      guard let self else { return }
+      self.webView.load(URLRequest(url: self.url))
+    }
+  }
+}
diff --git a/Sources/XXLogger/Logger.swift b/Sources/XXLogger/Logger.swift
deleted file mode 100644
index 805887aabfde4b6a27c3fdb58fc14114a4a5bbb3..0000000000000000000000000000000000000000
--- a/Sources/XXLogger/Logger.swift
+++ /dev/null
@@ -1,103 +0,0 @@
-import Foundation
-import SwiftyBeaver
-
-public typealias LogClosure = (Any, String, String, Int) -> Void
-
-public struct XXLogger {
-    var logInfo: LogClosure
-    var logDebug: LogClosure
-    var logError: LogClosure
-    var logWarning: LogClosure
-    var logVerbose: LogClosure
-
-    public init(
-        info: @escaping LogClosure,
-        debug: @escaping LogClosure,
-        error: @escaping LogClosure,
-        warning: @escaping LogClosure,
-        verbose: @escaping LogClosure
-    ) {
-        self.logInfo = info
-        self.logDebug = debug
-        self.logError = error
-        self.logWarning = warning
-        self.logVerbose = verbose
-    }
-
-    public func info(_ contents: Any, file: String = #file, function: String = #function, line: Int = #line) {
-        logInfo(contents, file, function, line)
-    }
-
-    public func debug(_ contents: Any, file: String = #file, function: String = #function, line: Int = #line) {
-        logDebug(contents, file, function, line)
-    }
-
-    public func error(_ contents: Any, file: String = #file, function: String = #function, line: Int = #line) {
-        logError(contents, file, function, line)
-    }
-
-    public func warning(_ contents: Any, file: String = #file, function: String = #function, line: Int = #line) {
-        logWarning(contents, file, function, line)
-    }
-
-    public func verbose(_ contents: Any, file: String = #file, function: String = #function, line: Int = #line) {
-        logVerbose(contents, file, function, line)
-    }
-}
-
-public extension XXLogger {
-    static func stop() {
-        let log = SwiftyBeaver.self
-        log.removeAllDestinations()
-
-        let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
-            .appendingPathComponent("swiftybeaver.log")
-
-        try? "".write(to: url, atomically: false, encoding: .utf8)
-    }
-
-    static func start() {
-        let log = SwiftyBeaver.self
-
-        let console = ConsoleDestination()
-        console.levelString.error = "🟥"
-        console.levelString.info = "✅"
-        console.levelString.warning = "[BACKEND]"
-        console.levelString.verbose = "[VERBOSE]"
-        console.format = "$DHH:mm:ss$d $L $N.$F:$l $M"
-
-        let file = FileDestination()
-        file.levelString.error = "🟥"
-        file.levelString.info = "✅"
-        file.levelString.warning = "[BACKEND]"
-        file.minLevel = .debug
-        file.format = "$DHH:mm:ss$d $L $N.$F:$l $M"
-
-        log.addDestination(console)
-        log.addDestination(file)
-    }
-
-    static func live() -> Self {
-        let log = SwiftyBeaver.self
-
-        return .init {
-            log.info($0, $1, $2, line: $3)
-        } debug: {
-            log.debug($0, $1, $2, line: $3)
-        } error: {
-            log.error($0, $1, $2, line: $3)
-        } warning: {
-            log.warning($0, $1, $2, line: $3)
-        } verbose: {
-            log.verbose($0, $1, $2, line: $3)
-        }
-    }
-
-    static let noop: Self = .init(
-        info: { _,_,_,_ in },
-        debug: { _,_,_,_ in },
-        error: { _,_,_,_ in },
-        warning: { _,_,_,_ in },
-        verbose: { _,_,_,_ in }
-    )
-}
diff --git a/Sources/iCloudFeature/iCloudInterface.swift b/Sources/iCloudFeature/iCloudInterface.swift
deleted file mode 100644
index 41b3153667013967f53fab9eb01d95a99293297c..0000000000000000000000000000000000000000
--- a/Sources/iCloudFeature/iCloudInterface.swift
+++ /dev/null
@@ -1,13 +0,0 @@
-import Foundation
-
-public protocol iCloudInterface {
-    func openSettings()
-
-    func isAuthorized() -> Bool
-
-    func downloadMetadata(_: @escaping (Result<iCloudMetadata?, Error>) -> Void)
-
-    func uploadBackup(_: URL, _: @escaping (Result<iCloudMetadata, Error>) -> Void)
-
-    func downloadBackup(_: String, _: @escaping (Result<Data, Error>) -> Void)
-}
diff --git a/Sources/iCloudFeature/iCloudMetadata.swift b/Sources/iCloudFeature/iCloudMetadata.swift
deleted file mode 100644
index 9aa70514badbef94033ec24bc965f49b23567e21..0000000000000000000000000000000000000000
--- a/Sources/iCloudFeature/iCloudMetadata.swift
+++ /dev/null
@@ -1,17 +0,0 @@
-import Foundation
-
-public struct iCloudMetadata: Equatable {
-    public var size: Float
-    public var path: String
-    public var modifiedDate: Date
-
-    public init(
-        path: String,
-        size: Float,
-        modifiedDate: Date
-    ) {
-        self.path = path
-        self.size = size
-        self.modifiedDate = modifiedDate
-    }
-}
diff --git a/Sources/iCloudFeature/iCloudService.swift b/Sources/iCloudFeature/iCloudService.swift
deleted file mode 100644
index 2ccf20ba26357c9e3966b88fe17f9941fd7525d4..0000000000000000000000000000000000000000
--- a/Sources/iCloudFeature/iCloudService.swift
+++ /dev/null
@@ -1,87 +0,0 @@
-import UIKit
-import FilesProvider
-
-public struct iCloudService: iCloudInterface {
-    private let documentsProvider = CloudFileProvider(containerId: "iCloud.xxm-cloud", scope: .data)
-
-    public init() {}
-
-    public func isAuthorized() -> Bool {
-        FileManager.default.ubiquityIdentityToken != nil
-    }
-
-    public func openSettings() {
-        if let url = URL(string: "App-Prefs:root=CASTLE"), UIApplication.shared.canOpenURL(url) {
-            UIApplication.shared.open(url, options: [:], completionHandler: nil)
-        }
-    }
-
-    public func downloadMetadata(_ completion: @escaping (Result<iCloudMetadata?, Error>) -> Void) {
-        guard let documentsProvider = documentsProvider else { fatalError() }
-
-        documentsProvider.contentsOfDirectory(path: "/", completionHandler: { contents, error in
-            guard error == nil else {
-                print(">>> [iCloud] downloadMetadata got error: \(error!.localizedDescription)")
-                completion(.failure(error!))
-                return
-            }
-
-            print(contents)
-
-            if let file = contents.first(where: { $0.name == "backup.xxm" }) {
-                completion(.success(.init(
-                    path: file.path,
-                    size: Float(file.size),
-                    modifiedDate: file.modifiedDate!
-                )))
-            } else {
-                completion(.success(nil))
-            }
-        })
-    }
-
-    public func uploadBackup(_ url: URL, _ completion: @escaping (Result<iCloudMetadata, Error>) -> Void) {
-        guard let documentsProvider = documentsProvider else { fatalError() }
-
-        do {
-            let data = try Data(contentsOf: url)
-
-            documentsProvider.writeContents(path: "backup.xxm", contents: data, overwrite: true) { error in
-                guard error == nil else {
-                    print(">>> [iCloud] uploadBackup got error: \(error!.localizedDescription)")
-                    completion(.failure(error!))
-                    return
-                }
-
-                completion(.success(.init(
-                    path: "backup.xxm",
-                    size: Float(data.count),
-                    modifiedDate: Date()
-                )))
-            }
-        } catch {
-            completion(.failure(error))
-        }
-    }
-
-    public func downloadBackup(
-        _ path: String,
-        _ completion: @escaping (Result<Data, Error>) -> Void
-    ) {
-        guard let documentsProvider = documentsProvider else { fatalError() }
-
-        documentsProvider.contents(path: path, completionHandler: { contents, error in
-            guard error == nil else {
-                print(">>> [iCloud] downloadBackup got error: \(error!.localizedDescription)")
-                completion(.failure(error!))
-                return
-            }
-
-            if let contents = contents {
-                completion(.success(contents))
-            } else {
-                completion(.failure(NSError(domain: "Backup file is invalid", code: 0)))
-            }
-        })
-    }
-}
diff --git a/Sources/iCloudFeature/iCloudServiceMock.swift b/Sources/iCloudFeature/iCloudServiceMock.swift
deleted file mode 100644
index f0bcfca66108e297f5d04de4fb3e5dc0705ae097..0000000000000000000000000000000000000000
--- a/Sources/iCloudFeature/iCloudServiceMock.swift
+++ /dev/null
@@ -1,39 +0,0 @@
-import Foundation
-
-public struct iCloudServiceMock: iCloudInterface {
-    public init() {
-        // TODO
-    }
-
-    public func openSettings() {
-        // TODO
-    }
-
-    public func isAuthorized() -> Bool {
-        true
-    }
-
-    public func downloadBackup(
-        _: String,
-        _: @escaping (Result<Data, Error>) -> Void
-    ) {
-        // TODO
-    }
-
-    public func uploadBackup(
-        _: URL,
-        _: @escaping (Result<iCloudMetadata, Error>) -> Void
-    ) {
-        // TODO
-    }
-
-    public func downloadMetadata(
-        _ completion: @escaping (Result<iCloudMetadata?, Error>) -> Void
-    ) {
-        completion(.success(.init(
-            path: "/",
-            size: 1230000000.0,
-            modifiedDate: Date()
-        )))
-    }
-}
diff --git a/Tests/AppTests/General/DateTests.swift b/Tests/AppFeatureTests/General/DateTests.swift
similarity index 100%
rename from Tests/AppTests/General/DateTests.swift
rename to Tests/AppFeatureTests/General/DateTests.swift
diff --git a/Tests/AppTests/General/InvitationTests.swift b/Tests/AppFeatureTests/General/InvitationTests.swift
similarity index 100%
rename from Tests/AppTests/General/InvitationTests.swift
rename to Tests/AppFeatureTests/General/InvitationTests.swift
diff --git a/Tests/ChatFeatureTests/Coordinator/ChatCoordinatorSpec.swift b/Tests/ChatFeatureTests/Coordinator/ChatCoordinatorSpec.swift
deleted file mode 100644
index afc03f67c8065a3a0e76079b02d9aa161af9d73c..0000000000000000000000000000000000000000
--- a/Tests/ChatFeatureTests/Coordinator/ChatCoordinatorSpec.swift
+++ /dev/null
@@ -1,118 +0,0 @@
-import UIKit
-import Quick
-import Nimble
-import TestHelpers
-
-@testable import ChatFeature
-
-final class ChatCoordinatorSpec: QuickSpec {
-    override func spec() {
-        context("init") {
-            var sut: ChatCoordinator!
-            var pusher: PresenterDouble!
-            var bottomPresenter: PresenterDouble!
-
-            var retryController: UIViewController!
-            var contactController: UIViewController!
-
-            beforeEach {
-                pusher = PresenterDouble()
-                bottomPresenter = PresenterDouble()
-                retryController = UIViewController()
-                contactController = UIViewController()
-
-                sut = ChatCoordinator(
-                    retryFactory: { retryController },
-                    contactFactory: { _ in contactController }
-                )
-
-                sut.pusher = pusher
-                sut.bottomPresenter = bottomPresenter
-            }
-
-            afterEach {
-                sut = nil
-                pusher = nil
-                bottomPresenter = nil
-                retryController = nil
-                contactController = nil
-            }
-
-            context("when presenting retry sheet") {
-                var parent: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    sut.toRetrySheet(from: parent)
-                }
-
-                it("should present RetrySheetController") {
-                    expect(bottomPresenter.didPresentFrom).to(be(parent))
-                    expect(bottomPresenter.didPresentTarget).to(be(retryController))
-                }
-            }
-
-            context("when presenting members list") {
-                var target: UIViewController!
-                var parent: UIViewController!
-
-                beforeEach {
-                    target = UIViewController()
-                    parent = UIViewController()
-                    sut.toMembersList(target, from: parent)
-                }
-
-                it("should present MembersController") {
-                    expect(bottomPresenter.didPresentFrom).to(be(parent))
-                    expect(bottomPresenter.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting Popup") {
-                var target: UIViewController!
-                var parent: UIViewController!
-
-                beforeEach {
-                    target = UIViewController()
-                    parent = UIViewController()
-                    sut.toPopup(target, from: parent)
-                }
-
-                it("should present Popup") {
-                    expect(bottomPresenter.didPresentFrom).to(be(parent))
-                    expect(bottomPresenter.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting Contact") {
-                var parent: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    sut.toContact(.dummy, from: parent)
-                }
-
-                it("should present ContactController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(contactController))
-                }
-            }
-
-            context("when presenting menu sheet") {
-                var target: UIViewController!
-                var parent: UIViewController!
-
-                beforeEach {
-                    target = UIViewController()
-                    parent = UIViewController()
-                    sut.toMenuSheet(target, from: parent)
-                }
-
-                it("should present SheetController") {
-                    expect(bottomPresenter.didPresentFrom).to(be(parent))
-                    expect(bottomPresenter.didPresentTarget).to(be(target))
-                }
-            }
-        }
-    }
-}
diff --git a/Tests/ChatListFeatureTests/Coordinator/ChatListCoordinatorSpec.swift b/Tests/ChatListFeatureTests/Coordinator/ChatListCoordinatorSpec.swift
deleted file mode 100644
index dc4a069cb02759b7d3eda601d20408e7c075a74b..0000000000000000000000000000000000000000
--- a/Tests/ChatListFeatureTests/Coordinator/ChatListCoordinatorSpec.swift
+++ /dev/null
@@ -1,243 +0,0 @@
-import UIKit
-import Quick
-import Nimble
-import MenuFeature
-import TestHelpers
-
-@testable import ChatListFeature
-
-final class ChatListCoordinatorSpec: QuickSpec {
-    override func spec() {
-        context("init") {
-            var sut: ChatListCoordinator!
-            var sider: PresenterDouble!
-            var pusher: PresenterDouble!
-            var bottomPresenter: PresenterDouble!
-
-            var scanScreen: UIViewController!
-            var searchScreen: UIViewController!
-            var profileScreen: UIViewController!
-            var settingsScreen: UIViewController!
-            var contactsScreen: UIViewController!
-            var requestsScreen: UIViewController!
-
-            beforeEach {
-                sider = PresenterDouble()
-                pusher = PresenterDouble()
-                bottomPresenter = PresenterDouble()
-
-                scanScreen = UIViewController()
-                searchScreen = UIViewController()
-                profileScreen = UIViewController()
-                settingsScreen = UIViewController()
-                contactsScreen = UIViewController()
-                requestsScreen = UIViewController()
-
-                sut = ChatListCoordinator(
-                    scanFactory: { scanScreen },
-                    searchFactory: { searchScreen },
-                    profileFactory: { profileScreen },
-                    settingsFactory: { settingsScreen },
-                    contactsFactory: { contactsScreen },
-                    requestsFactory: { requestsScreen }
-                )
-
-                sut.sider = sider
-                sut.pusher = pusher
-                sut.bottomPresenter = bottomPresenter
-            }
-
-            afterEach {
-                sut = nil
-                pusher = nil
-                sider = nil
-                scanScreen = nil
-                searchScreen = nil
-                profileScreen = nil
-                settingsScreen = nil
-                contactsScreen = nil
-                requestsScreen = nil
-                bottomPresenter = nil
-            }
-
-            context("when presenting chat") {
-                var target: UIViewController!
-                var parent: UIViewController!
-
-                beforeEach {
-                    target = UIViewController()
-                    parent = UIViewController()
-                    sut.singleChatFactory = { _ in target }
-                    sut.toSingleChat(with: .dummy, from: parent)
-                }
-
-                it("should present ChatController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting side menu") {
-                var parent: Delegate!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = Delegate()
-                    target = UIViewController()
-                    sut.sideMenuFactory = { _ in target }
-                    sut.toSideMenu(from: parent)
-                }
-
-                it("should present side menu") {
-                    expect(sider.didPresentFrom).to(be(parent))
-                    expect(sider.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting search") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.searchFactory = { target }
-                    sut.toSearch(from: parent)
-                }
-
-                it("should present SearchController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting scan") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.scanFactory = { target }
-                    sut.toScan(from: parent)
-                }
-
-                it("should present ScanController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting profile") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.profileFactory = { target }
-                    sut.toProfile(from: parent)
-                }
-
-                it("should present ProfileController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting contacts") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.contactsFactory = { target }
-                    sut.toContacts(from: parent)
-                }
-
-                it("should present ContactListController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting settings") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.settingsFactory = { target }
-                    sut.toSettings(from: parent)
-                }
-
-                it("should present SettingsController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting group chat") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.groupChatFactory = { _ in target }
-                    sut.toGroupChat(with: .init(
-                        group: .dummy,
-                        members: [],
-                        lastMessage: nil
-                    ), from: parent)
-                }
-
-                it("should present GroupChatController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting popup") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.toPopup(target, from: parent)
-                }
-
-                it("should present Popup") {
-                    expect(bottomPresenter.didPresentFrom).to(be(parent))
-                    expect(bottomPresenter.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting requests") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.requestsFactory = { target }
-                    sut.toRequests(from: parent)
-                }
-
-                it("should present RequestsContainer") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-        }
-    }
-}
-
-// MARK: - Delegate
-
-private final class Delegate: UIViewController, MenuDelegate {
-    func didSelect(item: MenuItem) {}
-}
diff --git a/Tests/ContactFeatureTests/Coordinator/ContactCoordinatorSpec.swift b/Tests/ContactFeatureTests/Coordinator/ContactCoordinatorSpec.swift
deleted file mode 100644
index 1a4026f8fb294636e838d257a33ef40fbf06aca1..0000000000000000000000000000000000000000
--- a/Tests/ContactFeatureTests/Coordinator/ContactCoordinatorSpec.swift
+++ /dev/null
@@ -1,129 +0,0 @@
-import UIKit
-import Quick
-import Nimble
-import ChatFeature
-import TestHelpers
-
-@testable import ContactFeature
-
-final class ContactCoordinatorSpec: QuickSpec {
-    override func spec() {
-        context("init") {
-            var sut: ContactCoordinator!
-            var pusher: PresenterDouble!
-            var replacer: PresenterDouble!
-            var presenter: PresenterDouble!
-            var bottomPresenter: PresenterDouble!
-
-            var requestsScreen: UIViewController!
-
-            beforeEach {
-                pusher = PresenterDouble()
-                replacer = PresenterDouble()
-                presenter = PresenterDouble()
-                requestsScreen = UIViewController()
-                bottomPresenter = PresenterDouble()
-
-                sut = ContactCoordinator(requestsFactory: { requestsScreen })
-
-                sut.pusher = pusher
-                sut.replacer = replacer
-                sut.presenter = presenter
-                sut.bottomPresenter = bottomPresenter
-            }
-
-            afterEach {
-                sut = nil
-                pusher = nil
-                replacer = nil
-                presenter = nil
-                requestsScreen = nil
-                bottomPresenter = nil
-            }
-
-            context("when presenting image picker") {
-                var parent: UIViewController!
-                var target: UIImagePickerController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIImagePickerController()
-                    sut.imagePickerFactory = { target }
-                    sut.toPhotos(from: parent)
-                }
-
-                it("should present UIImagePickerController") {
-                    expect(presenter.didPresentFrom).to(be(parent))
-                    expect(presenter.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting single chat") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.singleChatFactory = { _ in target }
-                    sut.toSingleChat(with: .dummy, from: parent)
-                }
-
-                it("should present ChatController") {
-                    expect(replacer.didPresentFrom).to(be(parent))
-                    expect(replacer.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting nickname") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.nicknameFactory = { _,_ in target }
-                    sut.toNickname(from: parent, prefilled: "", { _ in })
-                }
-
-                it("should present NickameController") {
-                    expect(bottomPresenter.didPresentFrom).to(be(parent))
-                    expect(bottomPresenter.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting requests") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.requestsFactory = { target }
-                    sut.toRequests(from: parent)
-                }
-
-                it("should present RequestsController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting popup") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.toPopup(target, from: parent)
-                }
-
-                it("should present Popup") {
-                    expect(bottomPresenter.didPresentFrom).to(be(parent))
-                    expect(bottomPresenter.didPresentTarget).to(be(target))
-                }
-            }
-        }
-    }
-}
diff --git a/Tests/ContactListFeatureTests/Coordinator/ContactListCoordinatorSpec.swift b/Tests/ContactListFeatureTests/Coordinator/ContactListCoordinatorSpec.swift
deleted file mode 100644
index ce494c1f73b2b8765832d99341f51b37f2fd7676..0000000000000000000000000000000000000000
--- a/Tests/ContactListFeatureTests/Coordinator/ContactListCoordinatorSpec.swift
+++ /dev/null
@@ -1,162 +0,0 @@
-import UIKit
-import Quick
-import Nimble
-import TestHelpers
-
-@testable import ContactListFeature
-
-final class ContactListCoordinatorSpec: QuickSpec {
-    override func spec() {
-        context("init") {
-            var sut: ContactListCoordinator!
-            var pusher: PresenterDouble!
-            var replacer: PresenterDouble!
-            var bottomPresenter: PresenterDouble!
-            var fullscreenPresenter: PresenterDouble!
-
-            var scanScreen: UIViewController!
-            var groupScreen: UIViewController!
-            var searchScreen: UIViewController!
-            var requestsScreen: UIViewController!
-
-            beforeEach {
-                scanScreen = UIViewController()
-                searchScreen = UIViewController()
-
-                sut = ContactListCoordinator(
-                    scanFactory: { scanScreen },
-                    searchFactory: { searchScreen },
-                    newGroupFactory: { groupScreen },
-                    requestsFactory: { requestsScreen }
-                )
-
-                pusher = PresenterDouble()
-                replacer = PresenterDouble()
-                bottomPresenter = PresenterDouble()
-                fullscreenPresenter = PresenterDouble()
-
-                sut.pusher = pusher
-                sut.replacer = replacer
-                sut.bottomPresenter = bottomPresenter
-                sut.fullscreenPresenter = fullscreenPresenter
-            }
-
-            afterEach {
-                sut = nil
-                pusher = nil
-                replacer = nil
-                scanScreen = nil
-                groupScreen = nil
-                searchScreen = nil
-                requestsScreen = nil
-                bottomPresenter = nil
-                fullscreenPresenter = nil
-            }
-
-            context("when presenting contact details") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.contactFactory = { _ in target }
-                    sut.toContact(.dummy, from: parent)
-                }
-
-                it("should present contact details screen") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting SearchScreen") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.searchFactory = { target }
-                    sut.toSearch(from: parent)
-                }
-
-                it("should present SearchScreen") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting scan screen") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.scanFactory = { target }
-                    sut.toScan(from: parent)
-                }
-
-                it("should present qr screen") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting new group") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.newGroupFactory = { target }
-                    sut.toNewGroup(from: parent)
-                }
-
-                it("should present new group") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting group chat") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.groupChatFactory = { _ in target }
-                    sut.toGroupChat(with: .init(
-                        group: .dummy,
-                        members: [],
-                        lastMessage: nil
-                    ), from: parent)
-                }
-
-                it("should present group chat") {
-                    expect(replacer.didPresentFrom).to(be(parent))
-                    expect(replacer.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting group popup") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.groupPopupFactory = { _,_ in target }
-                    sut.toGroupPopup(with: 0, from: parent, { _,_  in })
-                }
-
-                it("should present group popup") {
-                    expect(fullscreenPresenter.didPresentFrom).to(be(parent))
-                }
-            }
-        }
-    }
-}
diff --git a/Tests/DefaultsTests/KeyObjectTests.swift b/Tests/DefaultsTests/KeyObjectTests.swift
index ffa4794fa650c140e883a19ab7540ae7a8d78192..e35e13ebf2d34edd1b5e5a3e2b1423699be7a21d 100644
--- a/Tests/DefaultsTests/KeyObjectTests.swift
+++ b/Tests/DefaultsTests/KeyObjectTests.swift
@@ -1,84 +1,83 @@
 import XCTest
-import DependencyInjection
 
 @testable import Defaults
 
 final class KeyObjectSpec: XCTestCase {
 
-    func testGetCachedValue() {
-        var didSetObject: Any?
-        var didSetObjectForKey: String?
+  func testGetCachedValue() {
+    var didSetObject: Any?
+    var didSetObjectForKey: String?
 
-        let sut = KeyObjectStore(
-            objectForKey: { _ in fatalError() },
-            setObjectForKey: { object, key in
-                didSetObject = object
-                didSetObjectForKey = key
-            }, removeObjectForKey: { _ in fatalError() }
-        )
+    let sut = KeyObjectStore(
+      objectForKey: { _ in fatalError() },
+      setObjectForKey: { object, key in
+        didSetObject = object
+        didSetObjectForKey = key
+      }, removeObjectForKey: { _ in fatalError() }
+    )
 
-        DependencyInjection.Container.shared.register(sut)
+    DI.Container.shared.register(sut)
 
-        @KeyObject(.email, defaultValue: "1234") var email: String
+    @KeyObject(.email, defaultValue: "1234") var email: String
 
-        email = "5678"
-        assert(didSetObject as! String == "5678")
-        assert(didSetObjectForKey == Key.email.rawValue)
-    }
+    email = "5678"
+    assert(didSetObject as! String == "5678")
+    assert(didSetObjectForKey == Key.email.rawValue)
+  }
 
-    func testGetDefaultValue() {
-        var didGetObjectForKey: String?
+  func testGetDefaultValue() {
+    var didGetObjectForKey: String?
 
-        let sut = KeyObjectStore(
-            objectForKey: { didGetObjectForKey = $0 },
-            setObjectForKey: { _,_ in fatalError() },
-            removeObjectForKey: { _ in fatalError() }
-        )
+    let sut = KeyObjectStore(
+      objectForKey: { didGetObjectForKey = $0 },
+      setObjectForKey: { _,_ in fatalError() },
+      removeObjectForKey: { _ in fatalError() }
+    )
 
-        DependencyInjection.Container.shared.register(sut)
+    DI.Container.shared.register(sut)
 
-        let defaultValue = "1234"
-        @KeyObject(.email, defaultValue: defaultValue) var email: String
+    let defaultValue = "1234"
+    @KeyObject(.email, defaultValue: defaultValue) var email: String
 
-        assert(email == defaultValue)
-        assert(didGetObjectForKey == Key.email.rawValue)
-    }
+    assert(email == defaultValue)
+    assert(didGetObjectForKey == Key.email.rawValue)
+  }
 
-    func testSetValue() {
-        var didSetObject: Any?
-        var didSetObjectForKey: String?
+  func testSetValue() {
+    var didSetObject: Any?
+    var didSetObjectForKey: String?
 
-        let sut = KeyObjectStore(
-            objectForKey: { _ in fatalError() },
-            setObjectForKey: { object, key in
-                didSetObject = object
-                didSetObjectForKey = key
-            }, removeObjectForKey: { _ in fatalError() }
-        )
+    let sut = KeyObjectStore(
+      objectForKey: { _ in fatalError() },
+      setObjectForKey: { object, key in
+        didSetObject = object
+        didSetObjectForKey = key
+      }, removeObjectForKey: { _ in fatalError() }
+    )
 
-        DependencyInjection.Container.shared.register(sut)
+    DI.Container.shared.register(sut)
 
-        @KeyObject(.phone, defaultValue: "1234") var phone: String
-        phone = "5678"
+    @KeyObject(.phone, defaultValue: "1234") var phone: String
+    phone = "5678"
 
-        assert(didSetObject as! String == "5678")
-        assert(didSetObjectForKey == Key.phone.rawValue)
-    }
+    assert(didSetObject as! String == "5678")
+    assert(didSetObjectForKey == Key.phone.rawValue)
+  }
 
-    func testRemovingValue() {
-        var didRemoveObjectForKey: String?
+  func testRemovingValue() {
+    var didRemoveObjectForKey: String?
 
-        let sut = KeyObjectStore(
-            objectForKey: { _ in fatalError() },
-            setObjectForKey: { _,_ in fatalError() },
-            removeObjectForKey: { didRemoveObjectForKey = $0 }
-        )
+    let sut = KeyObjectStore(
+      objectForKey: { _ in fatalError() },
+      setObjectForKey: { _,_ in fatalError() },
+      removeObjectForKey: { didRemoveObjectForKey = $0 }
+    )
 
-        DependencyInjection.Container.shared.register(sut)
+    DI.Container.shared.register(sut)
 
-        @KeyObject(.phone, defaultValue: "1234") var phone: String?
-        phone = nil
+    @KeyObject(.phone, defaultValue: "1234") var phone: String?
+    phone = nil
 
-        assert(didRemoveObjectForKey == Key.phone.rawValue)
-    }
+    assert(didRemoveObjectForKey == Key.phone.rawValue)
+  }
 }
diff --git a/Tests/DependencyInjectionTests/ContainerTests.swift b/Tests/DependencyInjectionTests/ContainerTests.swift
deleted file mode 100644
index b9de7d2eb871fd39d8a3cc948b0ec9a5e2037dec..0000000000000000000000000000000000000000
--- a/Tests/DependencyInjectionTests/ContainerTests.swift
+++ /dev/null
@@ -1,33 +0,0 @@
-import XCTest
-
-@testable import DependencyInjection
-
-final class ContainerTests: XCTestCase {
-    func testRegisterAndResolveDependency() {
-        let container = Container()
-        let dependency = TestDependency()
-        container.register(dependency as TestDependencyProtocol)
-        let resolvedDependency: TestDependencyProtocol = try! container.resolve()
-        
-        XCTAssert(resolvedDependency === dependency)
-    }
-    
-    func testResolveUnregisterredDependency() {
-        let container = Container()
-        do {
-            let _: TestDependencyProtocol = try container.resolve()
-            XCTFail("expected to throw an error")
-        } catch {
-            XCTAssertEqual(
-                error as? UnregisteredDependencyError,
-                UnregisteredDependencyError(
-                    type: String(describing: TestDependencyProtocol.self)
-                )
-            )
-        }
-    }
-}
-
-private protocol TestDependencyProtocol: AnyObject {}
-
-private class TestDependency: TestDependencyProtocol {}
diff --git a/Tests/DependencyInjectionTests/DependencyPropertyWrapperTests.swift b/Tests/DependencyInjectionTests/DependencyPropertyWrapperTests.swift
deleted file mode 100644
index 97bf45ef52c1c539b80dbb83292b4c2baa13077c..0000000000000000000000000000000000000000
--- a/Tests/DependencyInjectionTests/DependencyPropertyWrapperTests.swift
+++ /dev/null
@@ -1,20 +0,0 @@
-import XCTest
-@testable import DependencyInjection
-
-final class DependencyPropertyWrapperTests: XCTestCase {
-    func testPropertyGetter() {
-        struct Context {
-            static let container = Container()
-            @Dependency(container: container) var property: TestDependencyProtocol
-        }
-        
-        let dependency = TestDependency()
-        Context.container.register(dependency as TestDependencyProtocol)
-        
-        XCTAssert(Context().property === dependency)
-    }
-}
-
-private protocol TestDependencyProtocol: AnyObject {}
-
-private class TestDependency: TestDependencyProtocol {}
diff --git a/Tests/OnboardingFeatureTests/Coordinator/OnboardingCoordinatorSpec.swift b/Tests/OnboardingFeatureTests/Coordinator/OnboardingCoordinatorSpec.swift
deleted file mode 100644
index 2ffb72ee0e6560a6162eace8dcbd0fa788a87197..0000000000000000000000000000000000000000
--- a/Tests/OnboardingFeatureTests/Coordinator/OnboardingCoordinatorSpec.swift
+++ /dev/null
@@ -1,245 +0,0 @@
-import UIKit
-import Quick
-import Theme
-import Nimble
-import Combine
-import TestHelpers
-import DependencyInjection
-
-@testable import OnboardingFeature
-
-final class OnboardingCoordinatorSpec: QuickSpec {
-    override func spec() {
-        context("init") {
-            var sut: OnboardingCoordinator!
-            var pusher: PresenterDouble!
-            var replacer: PresenterDouble!
-            var bottomPresenter: PresenterDouble!
-            var chatsController: UIViewController!
-
-            beforeEach {
-                pusher = PresenterDouble()
-                replacer = PresenterDouble()
-                bottomPresenter = PresenterDouble()
-
-                chatsController = UIViewController()
-
-                DependencyInjection.Container.shared
-                    .register(StatusBarControllerDouble() as StatusBarStyleControlling)
-
-                sut = OnboardingCoordinator(chatListFactory: { chatsController })
-
-                sut.pusher = pusher
-                sut.replacer = replacer
-                sut.bottomPresenter = bottomPresenter
-            }
-
-            afterEach {
-                sut = nil
-                pusher = nil
-                replacer = nil
-                bottomPresenter = nil
-                chatsController = nil
-            }
-
-            context("when presenting success") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.successFactory = { _ in target }
-                    sut.toSuccess(isEmail: false, from: parent)
-                }
-
-                it("should present OnboardingSuccessController") {
-                    expect(replacer.didPresentFrom).to(be(parent))
-                    expect(replacer.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting username") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.usernameFactory = { _ in target }
-                    sut.toUsername(with: "", from: parent)
-                }
-
-                it("should present OnboardingUsernameController") {
-                    expect(replacer.didPresentFrom).to(be(parent))
-                    expect(replacer.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting chats") {
-                var parent: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    sut.toChats(from: parent)
-                }
-
-                it("should present ChatsController") {
-                    expect(replacer.didPresentFrom).to(be(parent))
-                    expect(replacer.didPresentTarget).to(be(chatsController))
-                }
-            }
-
-            context("when presenting email") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.emailFactory = { target }
-                    sut.toEmail(from: parent)
-                }
-
-                it("should present OnboardingEmailController") {
-                    expect(replacer.didPresentFrom).to(be(parent))
-                    expect(replacer.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting phone") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.phoneFactory = { target }
-                    sut.toPhone(from: parent)
-                }
-
-                it("should present OnboardingPhoneController") {
-                    expect(replacer.didPresentFrom).to(be(parent))
-                    expect(replacer.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting countries") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.countriesFactory = { _ in target }
-                    sut.toCountries(from: parent, { _ in })
-                }
-
-                it("should present CountriesController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting popup") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.toPopup(target, from: parent)
-                }
-
-                it("should present Popup") {
-                    expect(bottomPresenter.didPresentFrom).to(be(parent))
-                    expect(bottomPresenter.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting welcome") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.welcomeFactory = { target }
-                    sut.toWelcome(from: parent)
-                }
-
-                it("should present OnboardingWelcomeScreen") {
-                    expect(replacer.didPresentFrom).to(be(parent))
-                    expect(replacer.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting start") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.startFactory = { _ in target }
-                    sut.toStart(with: "ndf", from: parent)
-                }
-
-                it("should present OnboardingStartScreen") {
-                    expect(replacer.didPresentFrom).to(be(parent))
-                    expect(replacer.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting email confirmation") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.emailConfirmationFactory = { _,_ in target }
-                    sut.toEmailConfirmation(with: .init(content: ""), from: parent, completion: { _ in })
-                }
-
-                it("should present OnboardingEmailConfirmationScreen") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting phone confirmation") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.phoneConfirmationFactory = { _,_ in target }
-                    sut.toPhoneConfirmation(with: .init(content: ""), from: parent, completion: { _ in })
-                }
-
-                it("should present OnboardingPhoneConfirmationScreen") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-        }
-    }
-}
-
-// MARK: - StatusBarControllerDouble
-
-private final class StatusBarControllerDouble: StatusBarStyleControlling {
-    var didSetStyle: UIStatusBarStyle?
-
-    let style = CurrentValueSubject<UIStatusBarStyle, Never>(.lightContent)
-    var cancellables = Set<AnyCancellable>()
-
-    init() {
-        style
-            .receive(on: DispatchQueue.main)
-            .sink { [unowned self] in didSetStyle = $0 }
-            .store(in: &cancellables)
-    }
-}
diff --git a/Tests/ProfileFeatureTests/Coordinator/ProfileCoordinatorSpec.swift b/Tests/ProfileFeatureTests/Coordinator/ProfileCoordinatorSpec.swift
deleted file mode 100644
index 1352033a3a098ffcb773820ff2c4f7cd1defb0e0..0000000000000000000000000000000000000000
--- a/Tests/ProfileFeatureTests/Coordinator/ProfileCoordinatorSpec.swift
+++ /dev/null
@@ -1,140 +0,0 @@
-import UIKit
-import Quick
-import Nimble
-import TestHelpers
-
-@testable import ProfileFeature
-
-final class ProfileCoordinatorSpec: QuickSpec {
-    override func spec() {
-        context("init") {
-            var sut: ProfileCoordinator!
-            var pusher: PresenterDouble!
-            var presenter: PresenterDouble!
-            var bottomPresenter: PresenterDouble!
-
-            beforeEach {
-                sut = ProfileCoordinator()
-                pusher = PresenterDouble()
-                presenter = PresenterDouble()
-                bottomPresenter = PresenterDouble()
-
-                sut.pusher = pusher
-                sut.presenter = presenter
-                sut.bottomPresenter = bottomPresenter
-            }
-
-            afterEach {
-                sut = nil
-                pusher = nil
-                presenter = nil
-                bottomPresenter = nil
-            }
-
-            context("when presenting image picker") {
-                var parent: UIViewController!
-                var target: UIImagePickerController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIImagePickerController()
-                    sut.imagePickerFactory = { target }
-                    sut.toPhotos(from: parent)
-                }
-
-                it("should present UIImagePickerController") {
-                    expect(presenter.didPresentFrom).to(be(parent))
-                    expect(presenter.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting email") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.emailFactory = { target }
-                    sut.toEmail(from: parent)
-                }
-
-                it("should present ProfileEmailController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting phone") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.phoneFactory = { target }
-                    sut.toPhone(from: parent)
-                }
-
-                it("should present ProfilePhoneController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting popup") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.toPopup(target, from: parent)
-                }
-
-                it("should present Popup") {
-                    expect(bottomPresenter.didPresentFrom).to(be(parent))
-                    expect(bottomPresenter.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting countries") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.countriesFactory = { _ in target }
-                    sut.toCountries(from: parent, { _ in })
-                }
-
-                it("should present CountriesController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting code") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.codeFactory = { _,_ in target }
-
-                    sut.toCode(
-                        with: .init(content: ""),
-                        from: parent
-                    ) { _,_ in }
-                }
-
-                it("should present CodeController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-        }
-    }
-}
diff --git a/Tests/RequestsFeatureTests/Coordinator/RequestsCoordinatorSpec.swift b/Tests/RequestsFeatureTests/Coordinator/RequestsCoordinatorSpec.swift
deleted file mode 100644
index d27a5c47a5c30c6274e9206597058f32f37d4bf4..0000000000000000000000000000000000000000
--- a/Tests/RequestsFeatureTests/Coordinator/RequestsCoordinatorSpec.swift
+++ /dev/null
@@ -1,100 +0,0 @@
-import Quick
-import UIKit
-import Nimble
-import TestHelpers
-
-@testable import RequestsFeature
-
-final class RequestsCoordinatorSpec: QuickSpec {
-    override func spec() {
-        context("init") {
-            var sut: RequestsCoordinator!
-            var pusher: PresenterDouble!
-            var bottomPresenter: PresenterDouble!
-            var searchController: UIViewController!
-
-            beforeEach {
-                pusher = PresenterDouble()
-                bottomPresenter = PresenterDouble()
-                searchController = UIViewController()
-
-                sut = RequestsCoordinator(searchFactory: { searchController })
-
-                sut.pusher = pusher
-                sut.bottomPresenter = bottomPresenter
-            }
-
-            afterEach {
-                sut = nil
-                pusher = nil
-                bottomPresenter = nil
-                searchController = nil
-            }
-
-            context("when presenting search") {
-                var parent: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    sut.toSearch(from: parent)
-                }
-
-                it("should present SearchController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(searchController))
-                }
-            }
-
-            context("when presenting contact") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.contactFactory = { _ in target }
-                    sut.toContact(.dummy, from: parent)
-                }
-
-                it("should present ContactController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting VerifyingFactory") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.verifyingFactory = { target }
-                    sut.toVerifying(from: parent)
-                }
-
-                it("should present VerifyingScreen") {
-                    expect(bottomPresenter.didPresentFrom).to(be(parent))
-                    expect(bottomPresenter.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting nickname") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.nicknameFactory = { _,_ in target }
-                    sut.toNickname(from: parent, prefilled: "", { _ in })
-                }
-
-                it("should present NicknameController") {
-                    expect(bottomPresenter.didPresentFrom).to(be(parent))
-                    expect(bottomPresenter.didPresentTarget).to(be(target))
-                }
-            }
-        }
-    }
-}
diff --git a/Tests/ScanFeatureTests/Coordinator/ScanCoordinatorSpec.swift b/Tests/ScanFeatureTests/Coordinator/ScanCoordinatorSpec.swift
deleted file mode 100644
index 1d35eb46404cda5ea0de8c9f4328143b48a1a841..0000000000000000000000000000000000000000
--- a/Tests/ScanFeatureTests/Coordinator/ScanCoordinatorSpec.swift
+++ /dev/null
@@ -1,87 +0,0 @@
-import UIKit
-import Quick
-import Nimble
-import TestHelpers
-
-@testable import ScanFeature
-
-final class ScanCoordinatorSpec: QuickSpec {
-    override func spec() {
-        context("init") {
-            var sut: ScanCoordinator!
-            var pusher: PresenterDouble!
-            var replacer: PresenterDouble!
-
-            var contactsController: UIViewController!
-            var requestsController: UIViewController!
-
-            beforeEach {
-                pusher = PresenterDouble()
-                replacer = PresenterDouble()
-                contactsController = UIViewController()
-                requestsController = UIViewController()
-
-                sut = ScanCoordinator(
-                    contactsFactory: { contactsController },
-                    requestsFactory: { requestsController }
-                )
-
-                sut.pusher = pusher
-                sut.replacer = replacer
-            }
-
-            afterEach {
-                sut = nil
-                pusher = nil
-                replacer = nil
-                contactsController = nil
-                requestsController = nil
-            }
-
-            context("when presenting add") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.contactFactory = { _ in target }
-                    sut.toContact(.dummy, from: parent)
-                }
-
-                it("should present ContactController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting contacts") {
-                var parent: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    sut.toContacts(from: parent)
-                }
-
-                it("should present ContactListController") {
-                    expect(replacer.didPresentFrom).to(be(parent))
-                    expect(replacer.didPresentTarget).to(be(contactsController))
-                }
-            }
-
-            context("when presenting requests") {
-                var parent: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    sut.toRequests(from: parent)
-                }
-
-                it("should present RequestsController") {
-                    expect(replacer.didPresentFrom).to(be(parent))
-                    expect(replacer.didPresentTarget).to(be(requestsController))
-                }
-            }
-        }
-    }
-}
diff --git a/Tests/SearchFeatureTests/Coordinator/SearchCoordinatorSpec.swift b/Tests/SearchFeatureTests/Coordinator/SearchCoordinatorSpec.swift
deleted file mode 100644
index 7bb3736b612f2659be50ca77cf72199987ead3fa..0000000000000000000000000000000000000000
--- a/Tests/SearchFeatureTests/Coordinator/SearchCoordinatorSpec.swift
+++ /dev/null
@@ -1,81 +0,0 @@
-import UIKit
-import Quick
-import Nimble
-import Integration
-import TestHelpers
-
-@testable import SearchFeature
-
-final class SearchCoordinatorSpec: QuickSpec {
-    override func spec() {
-        context("init") {
-            var sut: SearchCoordinator!
-            var pusher: PresenterDouble!
-            var bottomPresenter: PresenterDouble!
-
-            beforeEach {
-                sut = SearchCoordinator()
-                pusher = PresenterDouble()
-                bottomPresenter = PresenterDouble()
-                sut.pusher = pusher
-                sut.bottomPresenter = bottomPresenter
-            }
-
-            afterEach {
-                sut = nil
-                pusher = nil
-                bottomPresenter = nil
-            }
-
-            context("when presenting add screen") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.contactFactory = { _ in target }
-                    sut.toContact(.dummy, from: parent)
-                }
-
-                it("should present AddScreen") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting popup") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.toPopup(target, from: parent)
-                }
-
-                it("should present Popup") {
-                    expect(bottomPresenter.didPresentFrom).to(be(parent))
-                    expect(bottomPresenter.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting countries") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.countriesFactory = { _ in target }
-                    sut.toCountries(from: parent, { _ in })
-                }
-
-                it("should present CountriesController") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-        }
-    }
-}
diff --git a/Tests/SettingsFeatureTests/Coordinator/SettingsCoordinatorSpec.swift b/Tests/SettingsFeatureTests/Coordinator/SettingsCoordinatorSpec.swift
deleted file mode 100644
index 610e7b9b3eeb85b4bbb3db5c2274efbf0a6d3edf..0000000000000000000000000000000000000000
--- a/Tests/SettingsFeatureTests/Coordinator/SettingsCoordinatorSpec.swift
+++ /dev/null
@@ -1,79 +0,0 @@
-import UIKit
-import Quick
-import Nimble
-import TestHelpers
-
-@testable import SettingsFeature
-
-final class SettingsCoordinatorSpec: QuickSpec {
-    override func spec() {
-        context("init") {
-            var sut: SettingsCoordinator!
-            var pusher: PresenterDouble!
-            var presenter: PresenterDouble!
-            var bottomPresenter: PresenterDouble!
-
-            beforeEach {
-                pusher = PresenterDouble()
-                presenter = PresenterDouble()
-                bottomPresenter = PresenterDouble()
-
-                sut = SettingsCoordinator()
-
-                sut.pusher = pusher
-                sut.presenter = presenter
-                sut.bottomPresenter = bottomPresenter
-            }
-
-            context("when presenting advanced settings") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.advancedFactory = { target }
-                    sut.toAdvanced(from: parent)
-                }
-
-                it("should present AdvancedScreen") {
-                    expect(pusher.didPresentFrom).to(be(parent))
-                    expect(pusher.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting Popup") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.toPopup(target, from: parent)
-                }
-
-                it("should present Popup") {
-                    expect(bottomPresenter.didPresentFrom).to(be(parent))
-                    expect(bottomPresenter.didPresentTarget).to(be(target))
-                }
-            }
-
-            context("when presenting ActivityViewController") {
-                var parent: UIViewController!
-                var target: UIViewController!
-
-                beforeEach {
-                    parent = UIViewController()
-                    target = UIViewController()
-                    sut.activityControllerFactory = { _ in target }
-                    sut.toActivityController(with: [0], from: parent)
-                }
-
-                it("should present ActivityViewController") {
-                    expect(presenter.didPresentFrom).to(be(parent))
-                    expect(presenter.didPresentTarget).to(be(target))
-                }
-            }
-        }
-    }
-}
diff --git a/Tests/ThemeTests/ThemeTests.swift b/Tests/ThemeTests/ThemeTests.swift
index 44f88ab56ce69345bf3c5c049a6e7c5137f6f16d..55049b48a6fdd1dd9bdc096336c1ca1da517f3a8 100644
--- a/Tests/ThemeTests/ThemeTests.swift
+++ b/Tests/ThemeTests/ThemeTests.swift
@@ -2,44 +2,43 @@ import Quick
 import Nimble
 import Defaults
 import Foundation
-import DependencyInjection
 
 @testable import Theme
 
 final class ThemeTests: QuickSpec {
-    override func spec() {
-        context("init") {
-            var sut: ThemeController!
-            var dictionary: NSMutableDictionary!
-
-            beforeEach {
-                dictionary = .init()
-
-                DependencyInjection.Container.shared
-                    .register(KeyObjectStore.mock(dictionary: dictionary))
-
-                sut = ThemeController()
-            }
-
-            afterEach {
-                dictionary = nil
-            }
-
-            it("should load .system a.k.a 0 from defaults") {
-                let theme = dictionary.value(forKey: Key.theme.rawValue) as? Int
-                expect(theme).to(equal(0))
-            }
-
-            context("when changing theme") {
-                beforeEach {
-                    sut.theme.send(.dark)
-                }
-
-                it("should save .dark") {
-                    let theme = dictionary.value(forKey: Key.theme.rawValue) as? Int
-                    expect(theme).to(equal(1))
-                }
-            }
+  override func spec() {
+    context("init") {
+      var sut: ThemeController!
+      var dictionary: NSMutableDictionary!
+
+      beforeEach {
+        dictionary = .init()
+
+        DI.Container.shared
+          .register(KeyObjectStore.mock(dictionary: dictionary))
+
+        sut = ThemeController()
+      }
+
+      afterEach {
+        dictionary = nil
+      }
+
+      it("should load .system a.k.a 0 from defaults") {
+        let theme = dictionary.value(forKey: Key.theme.rawValue) as? Int
+        expect(theme).to(equal(0))
+      }
+
+      context("when changing theme") {
+        beforeEach {
+          sut.theme.send(.dark)
+        }
+
+        it("should save .dark") {
+          let theme = dictionary.value(forKey: Key.theme.rawValue) as? Int
+          expect(theme).to(equal(1))
         }
+      }
     }
+  }
 }
diff --git a/XCFrameworks/Bindings.xcframework/Info.plist b/XCFrameworks/Bindings.xcframework/Info.plist
deleted file mode 100644
index 5da456bbdabbf3d610daca4ce17734b523413a53..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/Info.plist
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-	<key>AvailableLibraries</key>
-	<array>
-		<dict>
-			<key>LibraryIdentifier</key>
-			<string>ios-arm64</string>
-			<key>LibraryPath</key>
-			<string>Bindings.framework</string>
-			<key>SupportedArchitectures</key>
-			<array>
-				<string>arm64</string>
-			</array>
-			<key>SupportedPlatform</key>
-			<string>ios</string>
-		</dict>
-		<dict>
-			<key>LibraryIdentifier</key>
-			<string>ios-arm64_x86_64-simulator</string>
-			<key>LibraryPath</key>
-			<string>Bindings.framework</string>
-			<key>SupportedArchitectures</key>
-			<array>
-				<string>arm64</string>
-				<string>x86_64</string>
-			</array>
-			<key>SupportedPlatform</key>
-			<string>ios</string>
-			<key>SupportedPlatformVariant</key>
-			<string>simulator</string>
-		</dict>
-	</array>
-	<key>CFBundlePackageType</key>
-	<string>XFWK</string>
-	<key>XCFrameworkFormatVersion</key>
-	<string>1.0</string>
-</dict>
-</plist>
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Bindings b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Bindings
deleted file mode 100644
index 8d78f84d50a0b0719b795f7b342f6109c2b848ec..0000000000000000000000000000000000000000
Binary files a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Bindings and /dev/null differ
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Headers/Bindings.h b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Headers/Bindings.h
deleted file mode 100644
index 8906a7da239705b790cb2bb64de92f806640cb38..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Headers/Bindings.h
+++ /dev/null
@@ -1,13 +0,0 @@
-
-// Objective-C API for talking to the following Go packages
-//
-//	gitlab.com/elixxir/client/bindings
-//
-// File is generated by gomobile bind. Do not edit.
-#ifndef __Bindings_FRAMEWORK_H__
-#define __Bindings_FRAMEWORK_H__
-
-#include "Bindings.objc.h"
-#include "Universe.objc.h"
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Headers/Bindings.objc.h b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Headers/Bindings.objc.h
deleted file mode 100644
index 32bf6d116888f787ced27b01b95cb4e1b2c1138b..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Headers/Bindings.objc.h
+++ /dev/null
@@ -1,2083 +0,0 @@
-// Objective-C API for talking to gitlab.com/elixxir/client/bindings Go package.
-//   gobind -lang=objc gitlab.com/elixxir/client/bindings
-//
-// File is generated by gobind. Do not edit.
-
-#ifndef __Bindings_H__
-#define __Bindings_H__
-
-@import Foundation;
-#include "ref.h"
-#include "Universe.objc.h"
-
-
-@class BindingsBackup;
-@class BindingsBackupReport;
-@class BindingsClient;
-@class BindingsContact;
-@class BindingsContactList;
-@class BindingsDummyTraffic;
-@class BindingsFact;
-@class BindingsFactList;
-@class BindingsFilePartTracker;
-@class BindingsFileTransfer;
-@class BindingsGroup;
-@class BindingsGroupChat;
-@class BindingsGroupMember;
-@class BindingsGroupMembership;
-@class BindingsGroupMessageReceive;
-@class BindingsGroupReportDisk;
-@class BindingsGroupSendReport;
-@class BindingsIdList;
-@class BindingsIntList;
-@class BindingsManyNotificationForMeReport;
-@class BindingsMessage;
-@class BindingsNewGroupReport;
-@class BindingsNodeRegistrationsStatus;
-@class BindingsNotificationForMeReport;
-@class BindingsRestoreContactsReport;
-@class BindingsRoundList;
-@class BindingsSendReport;
-@class BindingsSendReportDisk;
-@class BindingsUnregister;
-@class BindingsUser;
-@class BindingsUserDiscovery;
-@protocol BindingsAuthConfirmCallback;
-@class BindingsAuthConfirmCallback;
-@protocol BindingsAuthRequestCallback;
-@class BindingsAuthRequestCallback;
-@protocol BindingsAuthResetNotificationCallback;
-@class BindingsAuthResetNotificationCallback;
-@protocol BindingsClientError;
-@class BindingsClientError;
-@protocol BindingsEventCallbackFunctionObject;
-@class BindingsEventCallbackFunctionObject;
-@protocol BindingsFileTransferReceiveFunc;
-@class BindingsFileTransferReceiveFunc;
-@protocol BindingsFileTransferReceivedProgressFunc;
-@class BindingsFileTransferReceivedProgressFunc;
-@protocol BindingsFileTransferSentProgressFunc;
-@class BindingsFileTransferSentProgressFunc;
-@protocol BindingsGroupReceiveFunc;
-@class BindingsGroupReceiveFunc;
-@protocol BindingsGroupRequestFunc;
-@class BindingsGroupRequestFunc;
-@protocol BindingsListener;
-@class BindingsListener;
-@protocol BindingsLogWriter;
-@class BindingsLogWriter;
-@protocol BindingsLookupCallback;
-@class BindingsLookupCallback;
-@protocol BindingsMessageDeliveryCallback;
-@class BindingsMessageDeliveryCallback;
-@protocol BindingsMultiLookupCallback;
-@class BindingsMultiLookupCallback;
-@protocol BindingsNetworkHealthCallback;
-@class BindingsNetworkHealthCallback;
-@protocol BindingsPreimageNotification;
-@class BindingsPreimageNotification;
-@protocol BindingsRestoreContactsUpdater;
-@class BindingsRestoreContactsUpdater;
-@protocol BindingsRoundCompletionCallback;
-@class BindingsRoundCompletionCallback;
-@protocol BindingsRoundEventCallback;
-@class BindingsRoundEventCallback;
-@protocol BindingsSearchCallback;
-@class BindingsSearchCallback;
-@protocol BindingsSingleSearchCallback;
-@class BindingsSingleSearchCallback;
-@protocol BindingsTimeSource;
-@class BindingsTimeSource;
-@protocol BindingsUpdateBackupFunc;
-@class BindingsUpdateBackupFunc;
-
-@protocol BindingsAuthConfirmCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)partner;
-@end
-
-@protocol BindingsAuthRequestCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@protocol BindingsAuthResetNotificationCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@protocol BindingsClientError <NSObject>
-- (void)report:(NSString* _Nullable)source message:(NSString* _Nullable)message trace:(NSString* _Nullable)trace;
-@end
-
-@protocol BindingsEventCallbackFunctionObject <NSObject>
-- (void)reportEvent:(long)priority category:(NSString* _Nullable)category evtType:(NSString* _Nullable)evtType details:(NSString* _Nullable)details;
-@end
-
-@protocol BindingsFileTransferReceiveFunc <NSObject>
-- (void)receiveCallback:(NSData* _Nullable)tid fileName:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType sender:(NSData* _Nullable)sender size:(long)size preview:(NSData* _Nullable)preview;
-@end
-
-@protocol BindingsFileTransferReceivedProgressFunc <NSObject>
-- (void)receivedProgressCallback:(BOOL)completed received:(long)received total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-@protocol BindingsFileTransferSentProgressFunc <NSObject>
-- (void)sentProgressCallback:(BOOL)completed sent:(long)sent arrived:(long)arrived total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-@protocol BindingsGroupReceiveFunc <NSObject>
-- (void)groupReceiveCallback:(BindingsGroupMessageReceive* _Nullable)msg;
-@end
-
-@protocol BindingsGroupRequestFunc <NSObject>
-- (void)groupRequestCallback:(BindingsGroup* _Nullable)g;
-@end
-
-@protocol BindingsListener <NSObject>
-/**
- * Hear is called to receive a message in the UI
- */
-- (void)hear:(BindingsMessage* _Nullable)message;
-/**
- * Returns a name, used for debugging
- */
-- (NSString* _Nonnull)name;
-@end
-
-@protocol BindingsLogWriter <NSObject>
-- (void)log:(NSString* _Nullable)p0;
-@end
-
-@protocol BindingsLookupCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsMessageDeliveryCallback <NSObject>
-- (void)eventCallback:(NSData* _Nullable)msgID delivered:(BOOL)delivered timedOut:(BOOL)timedOut roundResults:(NSData* _Nullable)roundResults;
-@end
-
-@protocol BindingsMultiLookupCallback <NSObject>
-- (void)callback:(BindingsContactList* _Nullable)Succeeded failed:(BindingsIdList* _Nullable)failed errors:(NSString* _Nullable)errors;
-@end
-
-@protocol BindingsNetworkHealthCallback <NSObject>
-- (void)callback:(BOOL)p0;
-@end
-
-@protocol BindingsPreimageNotification <NSObject>
-- (void)notify:(NSData* _Nullable)identity deleted:(BOOL)deleted;
-@end
-
-@protocol BindingsRestoreContactsUpdater <NSObject>
-/**
- * RestoreContactsCallback is called to report the current # of contacts
-that have been found and how many have been restored
-against the total number that need to be
-processed. If an error occurs it it set on the err variable as a
-plain string.
- */
-- (void)restoreContactsCallback:(long)numFound numRestored:(long)numRestored total:(long)total err:(NSString* _Nullable)err;
-@end
-
-@protocol BindingsRoundCompletionCallback <NSObject>
-- (void)eventCallback:(long)rid success:(BOOL)success timedOut:(BOOL)timedOut;
-@end
-
-@protocol BindingsRoundEventCallback <NSObject>
-- (void)eventCallback:(long)rid state:(long)state timedOut:(BOOL)timedOut;
-@end
-
-@protocol BindingsSearchCallback <NSObject>
-- (void)callback:(BindingsContactList* _Nullable)contacts error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsSingleSearchCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsTimeSource <NSObject>
-- (int64_t)nowMs;
-@end
-
-@protocol BindingsUpdateBackupFunc <NSObject>
-- (void)updateBackup:(NSData* _Nullable)encryptedBackup;
-@end
-
-@interface BindingsBackup : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * AddJson stores a passed in json string in the backup structure
- */
-- (void)addJson:(NSString* _Nullable)json;
-/**
- * IsBackupRunning returns true if the backup has been initialized and is
-running. Returns false if it has been stopped.
- */
-- (BOOL)isBackupRunning;
-/**
- * StopBackup stops the backup processes and deletes the user's password from
-storage. To enable backups again, call InitializeBackup.
- */
-- (BOOL)stopBackup:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsBackupReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field BackupReport.RestoredContacts with unsupported type: []*gitlab.com/xx_network/primitives/id.ID
-
-@property (nonatomic) NSString* _Nonnull params;
-@end
-
-/**
- * BindingsClient wraps the api.Client, implementing additional functions
-to support the gomobile Client interface
- */
-@interface BindingsClient : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BOOL)confirmAuthenticatedChannel:(NSData* _Nullable)recipientMarshaled ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteAllRequests clears all requests from Client's auth storage.
- */
-- (BOOL)deleteAllRequests:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteContact is a function which removes a contact from Client's storage
- */
-- (BOOL)deleteContact:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteReceiveRequests clears receive requests from Client's auth storage.
- */
-- (BOOL)deleteReceiveRequests:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteRequest will delete a request, agnostic of request type
-for the given partner ID. If no request exists for this
-partner ID an error will be returned.
- */
-- (BOOL)deleteRequest:(NSData* _Nullable)requesterUserId error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteSentRequests clears sent requests from Client's auth storage.
- */
-- (BOOL)deleteSentRequests:(NSError* _Nullable* _Nullable)error;
-// skipped method Client.GetInternalClient with unsupported parameter or return types
-
-/**
- * GetNodeRegistrationStatus returns a struct with the number of nodes the
-client is registered with and the number total.
- */
-- (BindingsNodeRegistrationsStatus* _Nullable)getNodeRegistrationStatus:(NSError* _Nullable* _Nullable)error;
-/**
- * GetPartners returns a list of
- */
-- (NSData* _Nullable)getPartners:(NSError* _Nullable* _Nullable)error;
-/**
- * GetPreferredBins returns the geographic bin or bins that the provided two
-character country code is a part of. The bins are returned as CSV.
- */
-- (NSString* _Nonnull)getPreferredBins:(NSString* _Nullable)countryCode error:(NSError* _Nullable* _Nullable)error;
-- (NSString* _Nonnull)getPreimages:(NSData* _Nullable)identity;
-// skipped method Client.GetRateLimitParams with unsupported parameter or return types
-
-- (NSString* _Nonnull)getRelationshipFingerprint:(NSData* _Nullable)partnerID error:(NSError* _Nullable* _Nullable)error;
-/**
- * Returns a user object from which all information about the current user
-can be gleaned
- */
-- (BindingsUser* _Nullable)getUser;
-/**
- * HasRunningProcessies checks if any background threads are running.
-returns true if none are running. This is meant to be
-used when NetworkFollowerStatus() returns Stopping.
-Due to the handling of comms on iOS, where the OS can
-block indefiently, it may not enter the stopped
-state apropreatly. This can be used instead.
- */
-- (BOOL)hasRunningProcessies;
-/**
- * returns true if the network is read to be in a healthy state where
-messages can be sent
- */
-- (BOOL)isNetworkHealthy;
-- (BindingsContact* _Nullable)makePrecannedAuthenticatedChannel:(long)precannedID error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the state of the network follower. Returns:
-Stopped 	- 0
-Starting - 1000
-Running	- 2000
-Stopping	- 3000
- */
-- (long)networkFollowerStatus;
-- (void)registerAuthCallbacks:(id<BindingsAuthRequestCallback> _Nullable)request confirm:(id<BindingsAuthConfirmCallback> _Nullable)confirm reset:(id<BindingsAuthResetNotificationCallback> _Nullable)reset;
-/**
- * RegisterClientErrorCallback registers the callback to handle errors from the
-long running threads controlled by StartNetworkFollower and StopNetworkFollower
- */
-- (void)registerClientErrorCallback:(id<BindingsClientError> _Nullable)clientError;
-/**
- * RegisterEventCallback records the given function to receive
-ReportableEvent objects. It returns the internal index
-of the callback so that it can be deleted later.
- */
-- (BOOL)registerEventCallback:(NSString* _Nullable)name myObj:(id<BindingsEventCallbackFunctionObject> _Nullable)myObj error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterForNotifications accepts firebase messaging token
- */
-- (BOOL)registerForNotifications:(NSString* _Nullable)token error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterListener records and installs a listener for messages
-matching specific uid, msgType, and/or username
-Returns a ListenerUnregister interface which can be
-
-to register for any userID, pass in an id with length 0 or an id with
-all zeroes
-
-to register for any message type, pass in a message type of 0
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types
- */
-- (BindingsUnregister* _Nullable)registerListener:(NSData* _Nullable)uid msgType:(long)msgType listener:(id<BindingsListener> _Nullable)listener error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterNetworkHealthCB registers the network health callback to be called
-any time the network health changes. Returns a unique ID that can be used to
-unregister the network health callback.
- */
-- (int64_t)registerNetworkHealthCB:(id<BindingsNetworkHealthCallback> _Nullable)nhc;
-- (void)registerPreimageCallback:(NSData* _Nullable)identity pin:(id<BindingsPreimageNotification> _Nullable)pin;
-/**
- * RegisterRoundEventsHandler registers a callback interface for round
-events.
-The rid is the round the event attaches to
-The timeoutMS is the number of milliseconds until the event fails, and the
-validStates are a list of states (one per byte) on which the event gets
-triggered
-States:
- 0x00 - PENDING (Never seen by client)
- 0x01 - PRECOMPUTING
- 0x02 - STANDBY
- 0x03 - QUEUED
- 0x04 - REALTIME
- 0x05 - COMPLETED
- 0x06 - FAILED
-These states are defined in elixxir/primitives/states/state.go
- */
-- (BindingsUnregister* _Nullable)registerRoundEventsHandler:(long)rid cb:(id<BindingsRoundEventCallback> _Nullable)cb timeoutMS:(long)timeoutMS il:(BindingsIntList* _Nullable)il;
-- (void)replayRequests;
-- (BOOL)requestAuthenticatedChannel:(NSData* _Nullable)recipientMarshaled meMarshaled:(NSData* _Nullable)meMarshaled message:(NSString* _Nullable)message ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-- (BOOL)resetSession:(NSData* _Nullable)recipientMarshaled meMarshaled:(NSData* _Nullable)meMarshaled message:(NSString* _Nullable)message ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * This will return the round the message was sent on if it is successfully sent
-This can be used to register a round event to learn about message delivery.
-on failure a round id of -1 is returned
- */
-- (BOOL)sendCmix:(NSData* _Nullable)recipient contents:(NSData* _Nullable)contents parameters:(NSString* _Nullable)parameters ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * SendE2E sends an end-to-end payload to the provided recipient with
-the provided msgType. Returns the list of rounds in which parts of
-the message were sent or an error if it fails.
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types
- */
-- (BindingsSendReport* _Nullable)sendE2E:(NSData* _Nullable)recipient payload:(NSData* _Nullable)payload messageType:(long)messageType parameters:(NSString* _Nullable)parameters error:(NSError* _Nullable* _Nullable)error;
-/**
- * SendUnsafe sends an unencrypted payload to the provided recipient
-with the provided msgType. Returns the list of rounds in which parts
-of the message were sent or an error if it fails.
-NOTE: Do not use this function unless you know what you are doing.
-This function always produces an error message in client logging.
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types with custom types
- */
-- (BindingsRoundList* _Nullable)sendUnsafe:(NSData* _Nullable)recipient payload:(NSData* _Nullable)payload messageType:(long)messageType parameters:(NSString* _Nullable)parameters error:(NSError* _Nullable* _Nullable)error;
-/**
- * SetProxiedBins updates the host pool filter that filters out gateways that
-are not in one of the specified bins. The provided bins should be CSV.
- */
-- (BOOL)setProxiedBins:(NSString* _Nullable)binStringsCSV error:(NSError* _Nullable* _Nullable)error;
-/**
- * StartNetworkFollower kicks off the tracking of the network. It starts
-long running network client threads and returns an object for checking
-state and stopping those threads.
-Call this when returning from sleep and close when going back to
-sleep.
-These threads may become a significant drain on battery when offline, ensure
-they are stopped if there is no internet access
-Threads Started:
-  - Network Follower (/network/follow.go)
-  	tracks the network events and hands them off to workers for handling
-  - Historical Round Retrieval (/network/rounds/historical.go)
-		Retrieves data about rounds which are too old to be stored by the client
-	 - Message Retrieval Worker Group (/network/rounds/retrieve.go)
-		Requests all messages in a given round from the gateway of the last node
-	 - Message Handling Worker Group (/network/message/handle.go)
-		Decrypts and partitions messages when signals via the Switchboard
-	 - Health Tracker (/network/health)
-		Via the network instance tracks the state of the network
-	 - Garbled Messages (/network/message/garbled.go)
-		Can be signaled to check all recent messages which could be be decoded
-		Uses a message store on disk for persistence
-	 - Critical Messages (/network/message/critical.go)
-		Ensures all protocol layer mandatory messages are sent
-		Uses a message store on disk for persistence
-	 - KeyExchange Trigger (/keyExchange/trigger.go)
-		Responds to sent rekeys and executes them
-  - KeyExchange Confirm (/keyExchange/confirm.go)
-		Responds to confirmations of successful rekey operations
- */
-- (BOOL)startNetworkFollower:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * StopNetworkFollower stops the network follower if it is running.
-It returns errors if the Follower is in the wrong status to stop or if it
-fails to stop it.
-if the network follower is running and this fails, the client object will
-most likely be in an unrecoverable state and need to be trashed.
- */
-- (BOOL)stopNetworkFollower:(NSError* _Nullable* _Nullable)error;
-/**
- * UnregisterEventCallback deletes the callback identified by the
-index. It returns an error if it fails.
- */
-- (void)unregisterEventCallback:(NSString* _Nullable)name;
-/**
- * UnregisterForNotifications unregister user for notifications
- */
-- (BOOL)unregisterForNotifications:(NSError* _Nullable* _Nullable)error;
-- (void)unregisterNetworkHealthCB:(int64_t)funcID;
-- (BOOL)verifyOwnership:(NSData* _Nullable)receivedMarshaled verifiedMarshaled:(NSData* _Nullable)verifiedMarshaled ret0_:(BOOL* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * WaitForMessageDelivery allows the caller to get notified if the rounds a
-message was sent in successfully completed. Under the hood, this uses an API
-which uses the internal round data, network historical round lookup, and
-waiting on network events to determine what has (or will) occur.
-
-The callbacks will return at timeoutMS if no state update occurs
-
-This function takes the marshaled send report to ensure a memory leak does
-not occur as a result of both sides of the bindings holding a reference to
-the same pointer.
- */
-- (BOOL)waitForMessageDelivery:(NSData* _Nullable)marshaledSendReport mdc:(id<BindingsMessageDeliveryCallback> _Nullable)mdc timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * WaitForNewtwork will block until either the network is healthy or the
-passed timeout. It will return true if the network is healthy
- */
-- (BOOL)waitForNetwork:(long)timeoutMS;
-/**
- * WaitForRoundCompletion allows the caller to get notified if a round
-has completed (or failed). Under the hood, this uses an API which uses the internal
-round data, network historical round lookup, and waiting on network events
-to determine what has (or will) occur.
-
-The callbacks will return at timeoutMS if no state update occurs
- */
-- (BOOL)waitForRoundCompletion:(long)roundID rec:(id<BindingsRoundCompletionCallback> _Nullable)rec timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- *  contact object
- */
-@interface BindingsContact : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped method Contact.GetAPIContact with unsupported parameter or return types
-
-/**
- * GetDHPublicKey returns the public key associated with the Contact.
- */
-- (NSData* _Nullable)getDHPublicKey;
-/**
- * Returns a fact list for adding and getting facts to and from the contact
- */
-- (BindingsFactList* _Nullable)getFactList;
-/**
- * GetID returns the user ID for this user.
- */
-- (NSData* _Nullable)getID;
-/**
- * GetDHPublicKey returns hash of a DH proof of key ownership.
- */
-- (NSData* _Nullable)getOwnershipProof;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsContactList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Gets a stored round ID at the given index
- */
-- (BindingsContact* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the number of round IDs stored
- */
-- (long)len;
-@end
-
-/**
- * DummyTraffic contains the file dummy traffic manager. The manager can be used
-to set and get the status of the send thread.
- */
-@interface BindingsDummyTraffic : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewDummyTrafficManager creates a DummyTraffic manager and initialises the
-dummy traffic send thread. Note that the manager does not start sending dummy
-traffic until its status is set to true using DummyTraffic.SetStatus.
-The maxNumMessages is the upper bound of the random number of messages sent
-each send. avgSendDeltaMS is the average duration, in milliseconds, to wait
-between sends. Sends occur every avgSendDeltaMS +/- a random duration with an
-upper bound of randomRangeMS.
- */
-- (nullable instancetype)initManager:(BindingsClient* _Nullable)client maxNumMessages:(long)maxNumMessages avgSendDeltaMS:(long)avgSendDeltaMS randomRangeMS:(long)randomRangeMS;
-/**
- * GetStatus returns the current state of the dummy traffic send thread. It has
-the following return values:
- true  = send thread is sending dummy messages
- false = send thread is paused/stopped and not sending dummy messages
-Note that this function does not return the status set by SetStatus directly;
-it returns the current status of the send thread, which means any call to
-SetStatus will have a small delay before it is returned by GetStatus.
- */
-- (BOOL)getStatus;
-/**
- * SetStatus sets the state of the dummy traffic send thread, which determines
-if the thread is running or paused. The possible statuses are:
- true  = send thread is sending dummy messages
- false = send thread is paused/stopped and not sending dummy messages
-Returns an error if the channel is full.
-Note that this function cannot change the status of the send thread if it has
-yet to be started or stopped.
- */
-- (BOOL)setStatus:(BOOL)status error:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsFact : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- *  fact object
-creates a new fact. The factType must be either:
- 0 - Username
- 1 - Email
- 2 - Phone Number
-The fact must be well formed for the type and must not include commas or
-semicolons. If it is not well formed, it will be rejected.  Phone numbers
-must have the two letter country codes appended.  For the complete set of
-validation, see /elixxir/primitives/fact/fact.go
- */
-- (nullable instancetype)init:(long)factType factStr:(NSString* _Nullable)factStr;
-- (NSString* _Nonnull)get;
-- (NSString* _Nonnull)stringify;
-- (long)type;
-@end
-
-@interface BindingsFactList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * FactList
- */
-- (nullable instancetype)init;
-- (BOOL)add:(NSString* _Nullable)factData factType:(long)factType error:(NSError* _Nullable* _Nullable)error;
-- (BindingsFact* _Nullable)get:(long)i;
-- (long)num;
-- (NSString* _Nonnull)stringify:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * FilePartTracker contains the interfaces.FilePartTracker.
- */
-@interface BindingsFilePartTracker : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetNumParts returns the total number of file parts in the transfer.
- */
-- (long)getNumParts;
-/**
- * GetPartStatus returns the status of the file part with the given part number.
-The possible values for the status are:
-0 = unsent
-1 = sent (sender has sent a part, but it has not arrived)
-2 = arrived (sender has sent a part, and it has arrived)
-3 = received (receiver has received a part)
- */
-- (long)getPartStatus:(long)partNum;
-@end
-
-/**
- * FileTransfer contains the file transfer manager.
- */
-@interface BindingsFileTransfer : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewFileTransferManager creates a new file transfer manager and starts the
-sending and receiving threads. The receiveFunc is called everytime a new file
-transfer is received.
-The parameters string contains file transfer network configuration options
-and is a JSON formatted string of the fileTransfer.Params object. If it is
-left empty, then defaults are used. It is highly recommended that defaults
-are used. If it is set, it must match the following format:
- {"MaxThroughput":150000,"SendTimeout":500000000}
-MaxThroughput is in bytes/sec and SendTimeout is in nanoseconds.
- */
-- (nullable instancetype)initManager:(BindingsClient* _Nullable)client receiveFunc:(id<BindingsFileTransferReceiveFunc> _Nullable)receiveFunc parameters:(NSString* _Nullable)parameters;
-/**
- * CloseSend deletes a sent file transfer from the sent transfer map and from
-storage once a transfer has completed or reached the retry limit. Returns an
-error if the transfer has not run out of retries.
- */
-- (BOOL)closeSend:(NSData* _Nullable)transferID error:(NSError* _Nullable* _Nullable)error;
-/**
- * GetMaxFileNameByteLength returns the maximum length, in bytes, allowed for a
-file name.
- */
-- (long)getMaxFileNameByteLength;
-/**
- * GetMaxFilePreviewSize returns the maximum file preview size, in bytes.
- */
-- (long)getMaxFilePreviewSize;
-/**
- * GetMaxFileSize returns the maximum file size, in bytes, allowed to be
-transferred.
- */
-- (long)getMaxFileSize;
-/**
- * GetMaxFileTypeByteLength returns the maximum length, in bytes, allowed for a
-file type.
- */
-- (long)getMaxFileTypeByteLength;
-/**
- * Receive returns the fully assembled file on the completion of the transfer.
-It deletes the transfer from the received transfer map and from storage.
-Returns an error if the transfer is not complete, the full file cannot be
-verified, or if the transfer cannot be found.
- */
-- (NSData* _Nullable)receive:(NSData* _Nullable)transferID error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterReceiveProgressCallback allows for the registration of a callback to
-track the progress of an individual received file transfer. The callback will
-be called immediately when added to report the current status of the
-transfer. It will then call every time a file part is received, the transfer
-completes, or an error occurs. It is called at most once ever period, which
-means if events occur faster than the period, then they will not be reported
-and instead, the progress will be reported once at the end of the period.
-Once the callback reports that the transfer has completed, the recipient
-can get the full file by calling Receive.
-The period is specified in milliseconds.
- */
-- (BOOL)registerReceiveProgressCallback:(NSData* _Nullable)transferID progressFunc:(id<BindingsFileTransferReceivedProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterSendProgressCallback allows for the registration of a callback to
-track the progress of an individual sent file transfer. The callback will be
-called immediately when added to report the current status of the transfer.
-It will then call every time a file part is sent, a file part arrives, the
-transfer completes, or an error occurs. It is called at most once every
-period, which means if events occur faster than the period, then they will
-not be reported and instead, the progress will be reported once at the end of
-the period.
-The period is specified in milliseconds.
- */
-- (BOOL)registerSendProgressCallback:(NSData* _Nullable)transferID progressFunc:(id<BindingsFileTransferSentProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * Send sends a file to the recipient. The sender must have an E2E relationship
-with the recipient.
-The file name is the name of the file to show a user. It has a max length of
-48 bytes.
-The file type identifies what type of file is being sent. It has a max length
-of 8 bytes.
-The file data cannot be larger than 256 kB
-The retry float is the total amount of data to send relative to the data
-size. Data will be resent on error and will resend up to [(1 + retry) *
-fileSize].
-The preview stores a preview of the data (such as a thumbnail) and is
-capped at 4 kB in size.
-Returns a unique transfer ID used to identify the transfer.
-PeriodMS is the duration, in milliseconds, to wait between progress callback
-calls. Set this large enough to prevent spamming.
- */
-- (NSData* _Nullable)send:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType fileData:(NSData* _Nullable)fileData recipientID:(NSData* _Nullable)recipientID retry:(float)retry preview:(NSData* _Nullable)preview progressFunc:(id<BindingsFileTransferSentProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * Group structure contains the identifying and membership information of a
-group chat.
- */
-@interface BindingsGroup : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetCreatedMS returns the time the group was created in milliseconds. This is
-also the time the group requests were sent.
- */
-- (int64_t)getCreatedMS;
-/**
- * GetCreatedNano returns the time the group was created in nanoseconds. This is
-also the time the group requests were sent.
- */
-- (int64_t)getCreatedNano;
-/**
- * GetID return the 33-byte unique group ID.
- */
-- (NSData* _Nullable)getID;
-/**
- * GetInitMessage returns initial message sent with the group request.
- */
-- (NSData* _Nullable)getInitMessage;
-/**
- * GetMembership returns a list of contacts, one for each member in the group.
-The list is in order; the first contact is the leader/creator of the group.
-All subsequent members are ordered by their ID.
- */
-- (BindingsGroupMembership* _Nullable)getMembership;
-/**
- * GetName returns the name set by the user for the group.
- */
-- (NSData* _Nullable)getName;
-/**
- * Serialize serializes the Group.
- */
-- (NSData* _Nullable)serialize;
-@end
-
-/**
- * GroupChat object contains the group chat manager.
- */
-@interface BindingsGroupChat : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetGroup returns the group with the group ID. If no group exists, then the
-error "failed to find group" is returned.
- */
-- (BindingsGroup* _Nullable)getGroup:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * GetGroups returns an IdList containing a list of group IDs that the user is a
-part of.
- */
-- (BindingsIdList* _Nullable)getGroups;
-/**
- * JoinGroup allows a user to join a group when they receive a request. The
-caller must pass in the serialized bytes of a Group.
- */
-- (BOOL)joinGroup:(NSData* _Nullable)serializedGroupData error:(NSError* _Nullable* _Nullable)error;
-/**
- * LeaveGroup deletes a group so a user no longer has access.
- */
-- (BOOL)leaveGroup:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * MakeGroup creates a new group and sends a group request to all members in the
-group. The ID of the new group, the rounds the requests were sent on, and the
-status of the send are contained in NewGroupReport.
- */
-- (BindingsNewGroupReport* _Nullable)makeGroup:(BindingsIdList* _Nullable)membership name:(NSData* _Nullable)name message:(NSData* _Nullable)message;
-/**
- * NumGroups returns the number of groups the user is a part of.
- */
-- (long)numGroups;
-/**
- * ResendRequest resends a group request to all members in the group. The rounds
-they were sent on and the status of the send are contained in NewGroupReport.
- */
-- (BindingsNewGroupReport* _Nullable)resendRequest:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * Send sends the message to the specified group. Returns the round the messages
-were sent on.
- */
-- (BindingsGroupSendReport* _Nullable)send:(NSData* _Nullable)groupIdBytes message:(NSData* _Nullable)message error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * //
-Member Structure
-//
-GroupMember represents a member in the group membership list.
- */
-@interface BindingsGroupMember : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupMember.Member with unsupported type: gitlab.com/elixxir/crypto/group.Member
-
-// skipped method GroupMember.DeepCopy with unsupported parameter or return types
-
-// skipped method GroupMember.Equal with unsupported parameter or return types
-
-/**
- * GetDhKey returns the byte representation of the public Diffie–Hellman key of
-the member.
- */
-- (NSData* _Nullable)getDhKey;
-/**
- * GetID returns the 33-byte user ID of the member.
- */
-- (NSData* _Nullable)getID;
-- (NSString* _Nonnull)goString;
-- (NSData* _Nullable)serialize;
-- (NSString* _Nonnull)string;
-@end
-
-/**
- * GroupMembership structure contains a list of members that are part of a
-group. The first member is the group leader.
- */
-@interface BindingsGroupMembership : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Get returns the member at the index. The member at index 0 is always the
-group leader. An error is returned if the index is out of range.
- */
-- (BindingsGroupMember* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Len returns the number of members in the group membership.
- */
-- (long)len;
-@end
-
-/**
- * GroupMessageReceive contains a group message, its ID, and its data that a
-user receives.
- */
-@interface BindingsGroupMessageReceive : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupMessageReceive.MessageReceive with unsupported type: gitlab.com/elixxir/client/groupChat.MessageReceive
-
-/**
- * GetEphemeralID returns the ephemeral ID of the recipient.
- */
-- (int64_t)getEphemeralID;
-/**
- * GetGroupID returns the 33-byte group ID.
- */
-- (NSData* _Nullable)getGroupID;
-/**
- * GetMessageID returns the message ID.
- */
-- (NSData* _Nullable)getMessageID;
-/**
- * GetPayload returns the message payload.
- */
-- (NSData* _Nullable)getPayload;
-/**
- * GetRecipientID returns the 33-byte user ID of the recipient.
- */
-- (NSData* _Nullable)getRecipientID;
-/**
- * GetRoundID returns the ID of the round the message was sent on.
- */
-- (int64_t)getRoundID;
-/**
- * GetRoundTimestampMS returns the timestamp, in milliseconds, of the round the
-message was sent on.
- */
-- (int64_t)getRoundTimestampMS;
-/**
- * GetRoundTimestampNano returns the timestamp, in nanoseconds, of the round the
-message was sent on.
- */
-- (int64_t)getRoundTimestampNano;
-/**
- * GetRoundURL returns the ID of the round the message was sent on.
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetSenderID returns the 33-byte user ID of the sender.
- */
-- (NSData* _Nullable)getSenderID;
-/**
- * GetTimestampMS returns the message timestamp in milliseconds.
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message timestamp in nanoseconds.
- */
-- (int64_t)getTimestampNano;
-- (NSString* _Nonnull)string;
-@end
-
-@interface BindingsGroupReportDisk : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupReportDisk.List with unsupported type: []gitlab.com/xx_network/primitives/id.Round
-
-@property (nonatomic) NSData* _Nullable grpId;
-@property (nonatomic) long status;
-@end
-
-/**
- * GroupSendReport is returned when sending a group message. It contains the
-round ID sent on and the timestamp of the send.
- */
-@interface BindingsGroupSendReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetMessageID returns the ID of the round that the send occurred on.
- */
-- (NSData* _Nullable)getMessageID;
-/**
- * GetRoundID returns the ID of the round that the send occurred on.
- */
-- (int64_t)getRoundID;
-/**
- * GetRoundURL returns the URL of the round that the send occurred on.
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetTimestampMS returns the timestamp of the send in milliseconds.
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the timestamp of the send in nanoseconds.
- */
-- (int64_t)getTimestampNano;
-@end
-
-/**
- *  ID list
-IdList contains a list of IDs.
- */
-@interface BindingsIdList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Add appends the ID bytes to the end of the list.
- */
-- (BOOL)add:(NSData* _Nullable)idBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * Get returns the ID at the index. An error is returned if the index is out of
-range.
- */
-- (NSData* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Len returns the number of IDs in the list.
- */
-- (long)len;
-@end
-
-@interface BindingsIntList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (void)add:(long)i;
-- (BOOL)get:(long)i ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-- (long)len;
-@end
-
-@interface BindingsManyNotificationForMeReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BindingsNotificationForMeReport* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-- (long)len;
-@end
-
-/**
- * Message is a message received from the cMix network in the clear
-or that has been decrypted using established E2E keys.
- */
-@interface BindingsMessage : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetID returns the id of the message
- */
-- (NSData* _Nullable)getID;
-/**
- * GetMessageType returns the message's type
- */
-- (long)getMessageType;
-/**
- * GetPayload returns the message's payload/contents
- */
-- (NSData* _Nullable)getPayload;
-/**
- * GetRoundId returns the message's round ID
- */
-- (int64_t)getRoundId;
-/**
- * GetRoundTimestampMS returns the message's round timestamp in milliseconds
- */
-- (int64_t)getRoundTimestampMS;
-/**
- * GetRoundTimestampNano returns the message's round timestamp in nanoseconds
- */
-- (int64_t)getRoundTimestampNano;
-/**
- * GetRoundURL returns the message's round URL
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetSender returns the message's sender ID, if available
- */
-- (NSData* _Nullable)getSender;
-/**
- * GetTimestampMS returns the message's timestamp in milliseconds
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message's timestamp in nanoseconds
- */
-- (int64_t)getTimestampNano;
-@end
-
-/**
- * NewGroupReport is returned when creating a new group and contains the ID of
-the group, a list of rounds that the group requests were sent on, and the
-status of the send.
- */
-@interface BindingsNewGroupReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetError returns the string of an error.
-Will be an empty string if no error occured
- */
-- (NSString* _Nonnull)getError;
-/**
- * GetGroup returns the Group.
- */
-- (BindingsGroup* _Nullable)getGroup;
-/**
- * GetRoundList returns the RoundList containing a list of rounds requests were
-sent on.
- */
-- (BindingsRoundList* _Nullable)getRoundList;
-/**
- * GetStatus returns the status of the requests sent when creating a new group.
-status = 0   an error occurred before any requests could be sent
-         1   all requests failed to send (call Resend Group)
-         2   some request failed and some succeeded (call Resend Group)
-         3,  all requests sent successfully (call Resend Group)
- */
-- (long)getStatus;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-- (BOOL)unmarshal:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * NodeRegistrationsStatus structure for returning node registration statuses
-for bindings.
- */
-@interface BindingsNodeRegistrationsStatus : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetRegistered returns the number of nodes registered with the client.
- */
-- (long)getRegistered;
-/**
- * GetTotal return the total of nodes currently in the network.
- */
-- (long)getTotal;
-@end
-
-@interface BindingsNotificationForMeReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BOOL)forMe;
-- (NSData* _Nullable)source;
-- (NSString* _Nonnull)type;
-@end
-
-/**
- * RestoreContactsReport is a gomobile friendly report structure
-for determining which IDs restored, which failed, and why.
- */
-@interface BindingsRestoreContactsReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetErrorAt returns the error string at index
- */
-- (NSString* _Nonnull)getErrorAt:(long)index;
-/**
- * GetFailedAt returns the failed ID at index
- */
-- (NSData* _Nullable)getFailedAt:(long)index;
-/**
- * GetRestoreContactsError returns an error string. Empty if no error.
- */
-- (NSString* _Nonnull)getRestoreContactsError;
-/**
- * GetRestoredAt returns the restored ID at index
- */
-- (NSData* _Nullable)getRestoredAt:(long)index;
-/**
- * LenFailed returns the length of the ID's failed.
- */
-- (long)lenFailed;
-/**
- * LenRestored returns the length of ID's restored.
- */
-- (long)lenRestored;
-@end
-
-@interface BindingsRoundList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Gets a stored round ID at the given index
- */
-- (BOOL)get:(long)i ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the number of round IDs stored
- */
-- (long)len;
-@end
-
-/**
- * the send report is the mechanisim by which sendE2E returns a single
- */
-@interface BindingsSendReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (NSData* _Nullable)getMessageID;
-- (BindingsRoundList* _Nullable)getRoundList;
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetTimestampMS returns the message's timestamp in milliseconds
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message's timestamp in nanoseconds
- */
-- (int64_t)getTimestampNano;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-- (BOOL)unmarshal:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsSendReportDisk : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field SendReportDisk.List with unsupported type: []gitlab.com/xx_network/primitives/id.Round
-
-@property (nonatomic) NSData* _Nullable mid;
-@property (nonatomic) int64_t ts;
-@end
-
-/**
- * Generic Unregister - a generic return used for all callbacks which can be
-unregistered
-Interface which allows the un-registration of a listener
- */
-@interface BindingsUnregister : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Call unregisters a callback
- */
-- (void)unregister;
-@end
-
-@interface BindingsUser : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BindingsContact* _Nullable)getContact;
-- (NSData* _Nullable)getE2EDhPrivateKey;
-- (NSData* _Nullable)getE2EDhPublicKey;
-- (NSData* _Nullable)getReceptionID;
-- (NSData* _Nullable)getReceptionRSAPrivateKeyPem;
-- (NSData* _Nullable)getReceptionRSAPublicKeyPem;
-- (NSData* _Nullable)getReceptionSalt;
-- (NSData* _Nullable)getTransmissionID;
-- (NSData* _Nullable)getTransmissionRSAPrivateKeyPem;
-- (NSData* _Nullable)getTransmissionRSAPublicKeyPem;
-- (NSData* _Nullable)getTransmissionSalt;
-- (BOOL)isPrecanned;
-@end
-
-@interface BindingsUserDiscovery : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewUserDiscovery returns a new user discovery object. Only call this once. It must be called
-after StartNetworkFollower is called and will fail if the network has never
-been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-- (nullable instancetype)init:(BindingsClient* _Nullable)client;
-/**
- * NewUserDiscoveryFromBackup returns a new user discovery object. It
-wil set up the manager with the backup data. Pass into it the backed up
-facts, one email and phone number each. This will add the registered facts
-to the backed Store. Any one of these fields may be empty,
-however both fields being empty will cause an error. Any other fact that is not
-an email or phone number will return an error. You may only add a fact for the
-accepted types once each. If you attempt to back up a fact type that has already
-been backed up, an error will be returned. Anytime an error is returned, it means
-the backup was not successful.
-NOTE: Do not use this as a direct store operation. This feature is intended to add facts
-to a backend store that have ALREADY BEEN REGISTERED on the account.
-THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend.
-Only call this once. It must be called after StartNetworkFollower
-is called and will fail if the network has never been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-- (nullable instancetype)initFromBackup:(BindingsClient* _Nullable)client email:(NSString* _Nullable)email phone:(NSString* _Nullable)phone;
-/**
- * AddFact adds a fact for the user to user discovery. Will only succeed if the
-user is already registered and the system does not have the fact currently
-registered for any user.
-Will fail if the fact string is not well formed.
-This does not complete the fact registration process, it returns a
-confirmation id instead. Over the communications system the fact is
-associated with, a code will be sent. This confirmation ID needs to be
-called along with the code to finalize the fact.
- */
-- (NSString* _Nonnull)addFact:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * ConfirmFact confirms a fact first registered via AddFact. The confirmation ID comes from
-AddFact while the code will come over the associated communications system
- */
-- (BOOL)confirmFact:(NSString* _Nullable)confirmationID code:(NSString* _Nullable)code error:(NSError* _Nullable* _Nullable)error;
-/**
- * Lookup the contact object associated with the given userID.  The
-id is the byte representation of an id.
-This will reject if that id is malformed. The LookupCallback will return
-the associated contact if it exists.
- */
-- (BOOL)lookup:(NSData* _Nullable)idBytes callback:(id<BindingsLookupCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * MultiLookup Looks for the contact object associated with all given userIDs.
-The ids are the byte representation of an id stored in an IDList object.
-This will reject if that id is malformed or if the indexing on the IDList
-object is wrong. The MultiLookupCallback will return with all contacts
-returned within the timeout.
- */
-- (BOOL)multiLookup:(BindingsIdList* _Nullable)ids callback:(id<BindingsMultiLookupCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * Register registers a user with user discovery. Will return an error if the
-network signatures are malformed or if the username is taken. Usernames
-cannot be changed after registration at this time. Will fail if the user is
-already registered.
-Identity does not go over cmix, it occurs over normal communications
- */
-- (BOOL)register:(NSString* _Nullable)username error:(NSError* _Nullable* _Nullable)error;
-/**
- * RemoveFact removes a previously confirmed fact.  Will fail if the passed fact string is
-not well-formed or if the fact is not associated with this client.
-Users cannot remove username facts and must instead remove the user.
- */
-- (BOOL)removeFact:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * RemoveUser deletes a user. The fact sent must be the username.
-This function preserves the username forever and makes it
-unusable.
- */
-- (BOOL)removeUser:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * Search for the passed Facts.  The factList is the stringification of a
-fact list object, look at /bindings/list.go for more on that object.
-This will reject if that object is malformed. The SearchCallback will return
-a list of contacts, each having the facts it hit against.
-This is NOT intended to be used to search for multiple users at once, that
-can have a privacy reduction. Instead, it is intended to be used to search
-for a user where multiple pieces of information is known.
- */
-- (BOOL)search:(NSString* _Nullable)fl callback:(id<BindingsSearchCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * SearchSingle searches for the passed Facts.  The fact is the stringification of a
-fact object, look at /bindings/contact.go for more on that object.
-This will reject if that object is malformed. The SearchCallback will return
-a list of contacts, each having the facts it hit against.
-This only searches for a single fact at a time. It is intended to make some
-simple use cases of the API easier.
- */
-- (BOOL)searchSingle:(NSString* _Nullable)f callback:(id<BindingsSingleSearchCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * SetAlternativeUserDiscovery sets the alternativeUd object within manager.
-Once set, any user discovery operation will go through the alternative
-user discovery service.
-To undo this operation, use UnsetAlternativeUserDiscovery.
-The contact file is the already read in bytes, not the file path for the contact file.
- */
-- (BOOL)setAlternativeUserDiscovery:(NSData* _Nullable)address cert:(NSData* _Nullable)cert contactFile:(NSData* _Nullable)contactFile error:(NSError* _Nullable* _Nullable)error;
-/**
- * UnsetAlternativeUserDiscovery clears out the information from
-the Manager object.
- */
-- (BOOL)unsetAlternativeUserDiscovery:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * Error codes
- */
-FOUNDATION_EXPORT NSString* _Nonnull const BindingsUnrecognizedCode;
-FOUNDATION_EXPORT NSString* _Nonnull const BindingsUnrecognizedMessage;
-
-/**
- * CompressJpeg takes a JPEG image in byte format
-and compresses it based on desired output size
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsCompressJpeg(NSData* _Nullable imgBytes, NSError* _Nullable* _Nullable error);
-
-/**
- * CompressJpegForPreview takes a JPEG image in byte format
-and compresses it based on desired output size
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsCompressJpegForPreview(NSData* _Nullable imgBytes, NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadAndVerifySignedNdfWithUrl retrieves the NDF from a specified URL.
-The NDF is processed into a protobuf containing a signature which
-is verified using the cert string passed in. The NDF is returned as marshaled
-byte data which may be used to start a client.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadAndVerifySignedNdfWithUrl(NSString* _Nullable url, NSString* _Nullable cert, NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadDAppRegistrationDB returns a []byte containing
-the JSON data describing registered dApps.
-See https://git.xx.network/elixxir/registered-dapps
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadDAppRegistrationDB(NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadErrorDB returns a []byte containing the JSON data
-describing client errors.
-See https://git.xx.network/elixxir/client-error-database/
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadErrorDB(NSError* _Nullable* _Nullable error);
-
-/**
- * DumpStack returns a string with the stack trace of every running thread.
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsDumpStack(NSError* _Nullable* _Nullable error);
-
-/**
- * EnableGrpcLogs sets GRPC trace logging
- */
-FOUNDATION_EXPORT void BindingsEnableGrpcLogs(id<BindingsLogWriter> _Nullable writer);
-
-/**
- * ErrorStringToUserFriendlyMessage takes a passed in errStr which will be
-a backend generated error. These may be error specifically written by
-the backend team or lower level errors gotten from low level dependencies.
-This function will parse the error string for common errors provided from
-errToUserErr to provide a more user-friendly error message for the front end.
-If the error is not common, some simple parsing is done on the error message
-to make it more user-accessible, removing backend specific jargon.
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsErrorStringToUserFriendlyMessage(NSString* _Nullable errStr);
-
-/**
- * GenerateSecret creates a secret password using a system-based
-pseudorandom number generator. It takes 1 parameter, `numBytes`,
-which should be set to 32, but can be set higher in certain cases.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsGenerateSecret(long numBytes);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetCMIXParams(NSError* _Nullable* _Nullable error);
-
-/**
- * returns a previously created client. IF be used if the garbage collector
-removes the client instance on the app side.  Is NOT thread safe relative to
-login, newClient, or newPrecannedClient
- */
-FOUNDATION_EXPORT BindingsClient* _Nullable BindingsGetClientSingleton(void);
-
-/**
- * GetDependencies returns the api DEPENDENCIES
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetDependencies(void);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetE2EParams(NSError* _Nullable* _Nullable error);
-
-/**
- * GetGitVersion rturns the api GITVERSION
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetGitVersion(void);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetNetworkParams(NSError* _Nullable* _Nullable error);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetUnsafeParams(NSError* _Nullable* _Nullable error);
-
-/**
- * GetVersion returns the api SEMVER
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetVersion(void);
-
-/**
- * InitializeBackup starts the backup processes that returns backup updates when
-they occur. Any time an event occurs that changes the contents of the backup,
-such as adding or deleting a contact, the backup is triggered and an
-encrypted backup is generated and returned on the updateBackupCb callback.
-Call this function only when enabling backup if it has not already been
-initialized or when the user wants to change their password.
-To resume backup process on app recovery, use ResumeBackup.
- */
-FOUNDATION_EXPORT BindingsBackup* _Nullable BindingsInitializeBackup(NSString* _Nullable password, id<BindingsUpdateBackupFunc> _Nullable updateBackupCb, BindingsClient* _Nullable c, NSError* _Nullable* _Nullable error);
-
-/**
- * LoadSecretWithMnemonic loads the secret stored from the call to
-StoreSecretWithMnemonic. The path given should be the same filepath
-as the path given in StoreSecretWithMnemonic. There should be a file
-in this path called ".recovery". This operation is not tied
-to client operations, as the user will not have a client when trying to
-recover their account.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsLoadSecretWithMnemonic(NSString* _Nullable mnemonic, NSString* _Nullable path, NSError* _Nullable* _Nullable error);
-
-/**
- * sets level of logging. All logs the set level and above will be displayed
-options are:
-	TRACE		- 0
-	DEBUG		- 1
-	INFO 		- 2
-	WARN		- 3
-	ERROR		- 4
-	CRITICAL	- 5
-	FATAL		- 6
-The default state without updates is: INFO
- */
-FOUNDATION_EXPORT BOOL BindingsLogLevel(long level, NSError* _Nullable* _Nullable error);
-
-/**
- * Login will load an existing client from the storageDir
-using the password. This will fail if the client doesn't exist or
-the password is incorrect.
-The password is passed as a byte array so that it can be cleared from
-memory and stored as securely as possible using the memguard library.
-Login does not block on network connection, and instead loads and
-starts subprocesses to perform network operations.
- */
-FOUNDATION_EXPORT BindingsClient* _Nullable BindingsLogin(NSString* _Nullable storageDir, NSData* _Nullable password, NSString* _Nullable parameters, NSError* _Nullable* _Nullable error);
-
-/**
- * MakeIdList creates a new empty IdList.
- */
-FOUNDATION_EXPORT BindingsIdList* _Nullable BindingsMakeIdList(void);
-
-FOUNDATION_EXPORT BindingsIntList* _Nullable BindingsMakeIntList(void);
-
-/**
- * NewClient creates client storage, generates keys, connects, and registers
-with the network. Note that this does not register a username/identity, but
-merely creates a new cryptographic identity for adding such information
-at a later date.
-
-Users of this function should delete the storage directory on error.
- */
-FOUNDATION_EXPORT BOOL BindingsNewClient(NSString* _Nullable network, NSString* _Nullable storageDir, NSData* _Nullable password, NSString* _Nullable regCode, NSError* _Nullable* _Nullable error);
-
-/**
- * NewClientFromBackup constructs a new Client from an encrypted backup. The backup
-is decrypted using the backupPassphrase. On success a successful client creation,
-the function will return a JSON encoded list of the E2E partners
-contained in the backup and a json-encoded string of the parameters stored in the backup
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsNewClientFromBackup(NSString* _Nullable ndfJSON, NSString* _Nullable storageDir, NSData* _Nullable sessionPassword, NSData* _Nullable backupPassphrase, NSData* _Nullable backupFileContents, NSError* _Nullable* _Nullable error);
-
-/**
- * NewDummyTrafficManager creates a DummyTraffic manager and initialises the
-dummy traffic send thread. Note that the manager does not start sending dummy
-traffic until its status is set to true using DummyTraffic.SetStatus.
-The maxNumMessages is the upper bound of the random number of messages sent
-each send. avgSendDeltaMS is the average duration, in milliseconds, to wait
-between sends. Sends occur every avgSendDeltaMS +/- a random duration with an
-upper bound of randomRangeMS.
- */
-FOUNDATION_EXPORT BindingsDummyTraffic* _Nullable BindingsNewDummyTrafficManager(BindingsClient* _Nullable client, long maxNumMessages, long avgSendDeltaMS, long randomRangeMS, NSError* _Nullable* _Nullable error);
-
-/**
- *  fact object
-creates a new fact. The factType must be either:
- 0 - Username
- 1 - Email
- 2 - Phone Number
-The fact must be well formed for the type and must not include commas or
-semicolons. If it is not well formed, it will be rejected.  Phone numbers
-must have the two letter country codes appended.  For the complete set of
-validation, see /elixxir/primitives/fact/fact.go
- */
-FOUNDATION_EXPORT BindingsFact* _Nullable BindingsNewFact(long factType, NSString* _Nullable factStr, NSError* _Nullable* _Nullable error);
-
-/**
- * FactList
- */
-FOUNDATION_EXPORT BindingsFactList* _Nullable BindingsNewFactList(void);
-
-/**
- * NewFileTransferManager creates a new file transfer manager and starts the
-sending and receiving threads. The receiveFunc is called everytime a new file
-transfer is received.
-The parameters string contains file transfer network configuration options
-and is a JSON formatted string of the fileTransfer.Params object. If it is
-left empty, then defaults are used. It is highly recommended that defaults
-are used. If it is set, it must match the following format:
- {"MaxThroughput":150000,"SendTimeout":500000000}
-MaxThroughput is in bytes/sec and SendTimeout is in nanoseconds.
- */
-FOUNDATION_EXPORT BindingsFileTransfer* _Nullable BindingsNewFileTransferManager(BindingsClient* _Nullable client, id<BindingsFileTransferReceiveFunc> _Nullable receiveFunc, NSString* _Nullable parameters, NSError* _Nullable* _Nullable error);
-
-/**
- * NewGroupManager creates a new group chat manager.
- */
-FOUNDATION_EXPORT BindingsGroupChat* _Nullable BindingsNewGroupManager(BindingsClient* _Nullable client, id<BindingsGroupRequestFunc> _Nullable requestFunc, id<BindingsGroupReceiveFunc> _Nullable receiveFunc, NSError* _Nullable* _Nullable error);
-
-/**
- * NewPrecannedClient creates an insecure user with predetermined keys with nodes
-It creates client storage, generates keys, connects, and registers
-with the network. Note that this does not register a username/identity, but
-merely creates a new cryptographic identity for adding such information
-at a later date.
-
-Users of this function should delete the storage directory on error.
- */
-FOUNDATION_EXPORT BOOL BindingsNewPrecannedClient(long precannedID, NSString* _Nullable network, NSString* _Nullable storageDir, NSData* _Nullable password, NSError* _Nullable* _Nullable error);
-
-/**
- * NewUserDiscovery returns a new user discovery object. Only call this once. It must be called
-after StartNetworkFollower is called and will fail if the network has never
-been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUserDiscovery(BindingsClient* _Nullable client, NSError* _Nullable* _Nullable error);
-
-/**
- * NewUserDiscoveryFromBackup returns a new user discovery object. It
-wil set up the manager with the backup data. Pass into it the backed up
-facts, one email and phone number each. This will add the registered facts
-to the backed Store. Any one of these fields may be empty,
-however both fields being empty will cause an error. Any other fact that is not
-an email or phone number will return an error. You may only add a fact for the
-accepted types once each. If you attempt to back up a fact type that has already
-been backed up, an error will be returned. Anytime an error is returned, it means
-the backup was not successful.
-NOTE: Do not use this as a direct store operation. This feature is intended to add facts
-to a backend store that have ALREADY BEEN REGISTERED on the account.
-THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend.
-Only call this once. It must be called after StartNetworkFollower
-is called and will fail if the network has never been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUserDiscoveryFromBackup(BindingsClient* _Nullable client, NSString* _Nullable email, NSString* _Nullable phone, NSError* _Nullable* _Nullable error);
-
-/**
- * NotificationsForMe Check if a notification received is for me
-It returns a NotificationForMeReport which contains a ForMe bool stating if it is for the caller,
-a Type, and a source. These are as follows:
-	TYPE       	SOURCE				DESCRIPTION
-	"default"	recipient user ID	A message with no association
-	"request"	sender user ID		A channel request has been received
-	"reset"	    sender user ID		A channel reset has been received
-	"confirm"	sender user ID		A channel request has been accepted
-	"silent"	sender user ID		A message which should not be notified on
-	"e2e"		sender user ID		reception of an E2E message
-	"group"		group ID			reception of a group chat message
- "endFT"     sender user ID		Last message sent confirming end of file transfer
- "groupRQ"   sender user ID		Request from sender to join a group chat
- */
-FOUNDATION_EXPORT BindingsManyNotificationForMeReport* _Nullable BindingsNotificationsForMe(NSString* _Nullable notifCSV, NSString* _Nullable preimages, NSError* _Nullable* _Nullable error);
-
-/**
- * RegisterLogWriter registers a callback on which logs are written.
- */
-FOUNDATION_EXPORT void BindingsRegisterLogWriter(id<BindingsLogWriter> _Nullable writer);
-
-/**
- * RestoreContactsFromBackup takes as input the jason output of the
-`NewClientFromBackup` function, unmarshals it into IDs, looks up
-each ID in user discovery, and initiates a session reset request.
-This function will not return until every id in the list has been sent a
-request. It should be called again and again until it completes.
-xxDK users should not use this function. This function is used by
-the mobile phone apps and are not intended to be part of the xxDK. It
-should be treated as internal functions specific to the phone apps.
- */
-FOUNDATION_EXPORT BindingsRestoreContactsReport* _Nullable BindingsRestoreContactsFromBackup(NSData* _Nullable backupPartnerIDs, BindingsClient* _Nullable client, BindingsUserDiscovery* _Nullable udManager, id<BindingsLookupCallback> _Nullable lookupCB, id<BindingsRestoreContactsUpdater> _Nullable updatesCb);
-
-/**
- * ResumeBackup starts the backup processes back up with a new callback after it
-has been initialized.
-Call this function only when resuming a backup that has already been
-initialized or to replace the callback.
-To start the backup for the first time or to use a new password, use
-InitializeBackup.
- */
-FOUNDATION_EXPORT BindingsBackup* _Nullable BindingsResumeBackup(id<BindingsUpdateBackupFunc> _Nullable cb, BindingsClient* _Nullable c, NSError* _Nullable* _Nullable error);
-
-/**
- * SetTimeSource sets the network time to a custom source.
- */
-FOUNDATION_EXPORT void BindingsSetTimeSource(id<BindingsTimeSource> _Nullable timeNow);
-
-/**
- * StoreSecretWithMnemonic stores the secret tied with the mnemonic to storage.
-Unlike other storage operations, this does not use EKV, as that is
-intrinsically tied to client operations, which the user will not have while
-trying to recover their account. As such, we store the encrypted data
-directly, with a specified path. Path will be a valid filepath in which the
-recover file will be stored as ".recovery".
-
-As an example, given "home/user/xxmessenger/storagePath",
-the recovery file will be stored at
-"home/user/xxmessenger/storagePath/.recovery"
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsStoreSecretWithMnemonic(NSData* _Nullable secret, NSString* _Nullable path, NSError* _Nullable* _Nullable error);
-
-/**
- * Unmarshals a marshaled contact object, returns an error if it fails
- */
-FOUNDATION_EXPORT BindingsContact* _Nullable BindingsUnmarshalContact(NSData* _Nullable b, NSError* _Nullable* _Nullable error);
-
-/**
- * Unmarshals a marshaled send report object, returns an error if it fails
- */
-FOUNDATION_EXPORT BindingsSendReport* _Nullable BindingsUnmarshalSendReport(NSData* _Nullable b, NSError* _Nullable* _Nullable error);
-
-/**
- * UpdateCommonErrors takes the passed in contents of a JSON file and updates the
-errToUserErr map with the contents of the json file. The JSON's expected format
-conform with the commented examples provides in errToUserErr above.
-NOTE that you should not pass in a file path, but a preloaded JSON file
- */
-FOUNDATION_EXPORT BOOL BindingsUpdateCommonErrors(NSString* _Nullable jsonFile, NSError* _Nullable* _Nullable error);
-
-// skipped function WrapAPIClient with unsupported parameter or return types
-
-
-// skipped function WrapUserDiscovery with unsupported parameter or return types
-
-
-@class BindingsAuthConfirmCallback;
-
-@class BindingsAuthRequestCallback;
-
-@class BindingsAuthResetNotificationCallback;
-
-@class BindingsClientError;
-
-@class BindingsEventCallbackFunctionObject;
-
-@class BindingsFileTransferReceiveFunc;
-
-@class BindingsFileTransferReceivedProgressFunc;
-
-@class BindingsFileTransferSentProgressFunc;
-
-@class BindingsGroupReceiveFunc;
-
-@class BindingsGroupRequestFunc;
-
-@class BindingsListener;
-
-@class BindingsLogWriter;
-
-@class BindingsLookupCallback;
-
-@class BindingsMessageDeliveryCallback;
-
-@class BindingsMultiLookupCallback;
-
-@class BindingsNetworkHealthCallback;
-
-@class BindingsPreimageNotification;
-
-@class BindingsRestoreContactsUpdater;
-
-@class BindingsRoundCompletionCallback;
-
-@class BindingsRoundEventCallback;
-
-@class BindingsSearchCallback;
-
-@class BindingsSingleSearchCallback;
-
-@class BindingsTimeSource;
-
-@class BindingsUpdateBackupFunc;
-
-/**
- * AuthConfirmCallback notifies the register whenever they receive an auth
-request confirmation
- */
-@interface BindingsAuthConfirmCallback : NSObject <goSeqRefInterface, BindingsAuthConfirmCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)partner;
-@end
-
-/**
- * AuthRequestCallback notifies the register whenever they receive an auth
-request
- */
-@interface BindingsAuthRequestCallback : NSObject <goSeqRefInterface, BindingsAuthRequestCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-/**
- * AuthRequestCallback notifies the register whenever they receive an auth
-request
- */
-@interface BindingsAuthResetNotificationCallback : NSObject <goSeqRefInterface, BindingsAuthResetNotificationCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@interface BindingsClientError : NSObject <goSeqRefInterface, BindingsClientError> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)report:(NSString* _Nullable)source message:(NSString* _Nullable)message trace:(NSString* _Nullable)trace;
-@end
-
-/**
- * EventCallbackFunctionObject bindings interface which contains function
-that implements the EventCallbackFunction
- */
-@interface BindingsEventCallbackFunctionObject : NSObject <goSeqRefInterface, BindingsEventCallbackFunctionObject> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)reportEvent:(long)priority category:(NSString* _Nullable)category evtType:(NSString* _Nullable)evtType details:(NSString* _Nullable)details;
-@end
-
-/**
- * FileTransferReceiveFunc contains a function callback that notifies the
-receiver of an incoming file transfer. It is called on the reception of the
-initial file transfer message.
- */
-@interface BindingsFileTransferReceiveFunc : NSObject <goSeqRefInterface, BindingsFileTransferReceiveFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)receiveCallback:(NSData* _Nullable)tid fileName:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType sender:(NSData* _Nullable)sender size:(long)size preview:(NSData* _Nullable)preview;
-@end
-
-/**
- * FileTransferReceivedProgressFunc contains a function callback that tracks the
-progress of receiving a file. It is called when a file part is received, the
-transfer completes, or on error.
- */
-@interface BindingsFileTransferReceivedProgressFunc : NSObject <goSeqRefInterface, BindingsFileTransferReceivedProgressFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)receivedProgressCallback:(BOOL)completed received:(long)received total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-/**
- * FileTransferSentProgressFunc contains a function callback that tracks the
-progress of sending a file. It is called when a file part is sent, a file
-part arrives, the transfer completes, or on error.
- */
-@interface BindingsFileTransferSentProgressFunc : NSObject <goSeqRefInterface, BindingsFileTransferSentProgressFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)sentProgressCallback:(BOOL)completed sent:(long)sent arrived:(long)arrived total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-/**
- * GroupReceiveFunc contains a function callback that is called when a group
-message is received.
- */
-@interface BindingsGroupReceiveFunc : NSObject <goSeqRefInterface, BindingsGroupReceiveFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)groupReceiveCallback:(BindingsGroupMessageReceive* _Nullable)msg;
-@end
-
-/**
- * GroupRequestFunc contains a function callback that is called when a group
-request is received.
- */
-@interface BindingsGroupRequestFunc : NSObject <goSeqRefInterface, BindingsGroupRequestFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)groupRequestCallback:(BindingsGroup* _Nullable)g;
-@end
-
-/**
- * Listener provides a callback to hear a message
-An object implementing this interface can be called back when the client
-gets a message of the type that the registerer specified at registration
-time.
- */
-@interface BindingsListener : NSObject <goSeqRefInterface, BindingsListener> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * Hear is called to receive a message in the UI
- */
-- (void)hear:(BindingsMessage* _Nullable)message;
-/**
- * Returns a name, used for debugging
- */
-- (NSString* _Nonnull)name;
-@end
-
-@interface BindingsLogWriter : NSObject <goSeqRefInterface, BindingsLogWriter> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)log:(NSString* _Nullable)p0;
-@end
-
-/**
- * LookupCallback returns the result of a single lookup
- */
-@interface BindingsLookupCallback : NSObject <goSeqRefInterface, BindingsLookupCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-/**
- * MessageDeliveryCallback gets called on the determination if all events
-related to a message send were successful.
- */
-@interface BindingsMessageDeliveryCallback : NSObject <goSeqRefInterface, BindingsMessageDeliveryCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(NSData* _Nullable)msgID delivered:(BOOL)delivered timedOut:(BOOL)timedOut roundResults:(NSData* _Nullable)roundResults;
-@end
-
-/**
- * MultiLookupCallback returns the result of many parallel lookups
- */
-@interface BindingsMultiLookupCallback : NSObject <goSeqRefInterface, BindingsMultiLookupCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContactList* _Nullable)Succeeded failed:(BindingsIdList* _Nullable)failed errors:(NSString* _Nullable)errors;
-@end
-
-/**
- * A callback when which is used to receive notification if network health
-changes
- */
-@interface BindingsNetworkHealthCallback : NSObject <goSeqRefInterface, BindingsNetworkHealthCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BOOL)p0;
-@end
-
-@interface BindingsPreimageNotification : NSObject <goSeqRefInterface, BindingsPreimageNotification> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)notify:(NSData* _Nullable)identity deleted:(BOOL)deleted;
-@end
-
-/**
- * RestoreContactsUpdater interface provides a callback function
-for receiving update information from RestoreContactsFromBackup.
- */
-@interface BindingsRestoreContactsUpdater : NSObject <goSeqRefInterface, BindingsRestoreContactsUpdater> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * RestoreContactsCallback is called to report the current # of contacts
-that have been found and how many have been restored
-against the total number that need to be
-processed. If an error occurs it it set on the err variable as a
-plain string.
- */
-- (void)restoreContactsCallback:(long)numFound numRestored:(long)numRestored total:(long)total err:(NSString* _Nullable)err;
-@end
-
-/**
- * RoundCompletionCallback is returned when the completion of a round is known.
- */
-@interface BindingsRoundCompletionCallback : NSObject <goSeqRefInterface, BindingsRoundCompletionCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(long)rid success:(BOOL)success timedOut:(BOOL)timedOut;
-@end
-
-/**
- * RoundEventCallback handles waiting on the exact state of a round on
-the cMix network.
- */
-@interface BindingsRoundEventCallback : NSObject <goSeqRefInterface, BindingsRoundEventCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(long)rid state:(long)state timedOut:(BOOL)timedOut;
-@end
-
-/**
- * SearchCallback returns the result of a search
- */
-@interface BindingsSearchCallback : NSObject <goSeqRefInterface, BindingsSearchCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContactList* _Nullable)contacts error:(NSString* _Nullable)error;
-@end
-
-/**
- * SingleSearchCallback returns the result of a single search
- */
-@interface BindingsSingleSearchCallback : NSObject <goSeqRefInterface, BindingsSingleSearchCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@interface BindingsTimeSource : NSObject <goSeqRefInterface, BindingsTimeSource> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (int64_t)nowMs;
-@end
-
-/**
- * UpdateBackupFunc contains a function callback that returns new backups.
- */
-@interface BindingsUpdateBackupFunc : NSObject <goSeqRefInterface, BindingsUpdateBackupFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)updateBackup:(NSData* _Nullable)encryptedBackup;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Headers/Universe.objc.h b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Headers/Universe.objc.h
deleted file mode 100644
index 019e7502d581983722a15bf30799e85cbc5dd766..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Headers/Universe.objc.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Objective-C API for talking to  Go package.
-//   gobind -lang=objc 
-//
-// File is generated by gobind. Do not edit.
-
-#ifndef __Universe_H__
-#define __Universe_H__
-
-@import Foundation;
-#include "ref.h"
-
-@protocol Universeerror;
-@class Universeerror;
-
-@protocol Universeerror <NSObject>
-- (NSString* _Nonnull)error;
-@end
-
-@class Universeerror;
-
-@interface Universeerror : NSError <goSeqRefInterface, Universeerror> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (NSString* _Nonnull)error;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Headers/ref.h b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Headers/ref.h
deleted file mode 100644
index b8036a4d85c7387f3def61473a071b5d8c4c8208..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Headers/ref.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#ifndef __GO_REF_HDR__
-#define __GO_REF_HDR__
-
-#include <Foundation/Foundation.h>
-
-// GoSeqRef is an object tagged with an integer for passing back and
-// forth across the language boundary. A GoSeqRef may represent either
-// an instance of a Go object, or an Objective-C object passed to Go.
-// The explicit allocation of a GoSeqRef is used to pin a Go object
-// when it is passed to Objective-C. The Go seq package maintains a
-// reference to the Go object in a map keyed by the refnum along with
-// a reference count. When the reference count reaches zero, the Go
-// seq package will clear the corresponding entry in the map.
-@interface GoSeqRef : NSObject {
-}
-@property(readonly) int32_t refnum;
-@property(strong) id obj; // NULL when representing a Go object.
-
-// new GoSeqRef object to proxy a Go object. The refnum must be
-// provided from Go side.
-- (instancetype)initWithRefnum:(int32_t)refnum obj:(id)obj;
-
-- (int32_t)incNum;
-
-@end
-
-@protocol goSeqRefInterface
--(GoSeqRef*) _ref;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Modules/module.modulemap b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Modules/module.modulemap
deleted file mode 100644
index 4316a5b24058edfc18ffb2dc7f7a982e8353441a..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Modules/module.modulemap
+++ /dev/null
@@ -1,8 +0,0 @@
-framework module "Bindings" {
-	header "ref.h"
-    header "Bindings.objc.h"
-    header "Universe.objc.h"
-    header "Bindings.h"
-
-    export *
-}
\ No newline at end of file
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Resources/Info.plist b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Resources/Info.plist
deleted file mode 100644
index 0d1a4b8ab9b1fc8e9357197398f73353470cb636..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Resources/Info.plist
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-    <plist version="1.0">
-      <dict>
-      </dict>
-    </plist>
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Bindings b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Bindings
deleted file mode 100644
index 8d78f84d50a0b0719b795f7b342f6109c2b848ec..0000000000000000000000000000000000000000
Binary files a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Bindings and /dev/null differ
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Bindings.h b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Bindings.h
deleted file mode 100644
index 8906a7da239705b790cb2bb64de92f806640cb38..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Bindings.h
+++ /dev/null
@@ -1,13 +0,0 @@
-
-// Objective-C API for talking to the following Go packages
-//
-//	gitlab.com/elixxir/client/bindings
-//
-// File is generated by gomobile bind. Do not edit.
-#ifndef __Bindings_FRAMEWORK_H__
-#define __Bindings_FRAMEWORK_H__
-
-#include "Bindings.objc.h"
-#include "Universe.objc.h"
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Bindings.objc.h b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Bindings.objc.h
deleted file mode 100644
index 32bf6d116888f787ced27b01b95cb4e1b2c1138b..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Bindings.objc.h
+++ /dev/null
@@ -1,2083 +0,0 @@
-// Objective-C API for talking to gitlab.com/elixxir/client/bindings Go package.
-//   gobind -lang=objc gitlab.com/elixxir/client/bindings
-//
-// File is generated by gobind. Do not edit.
-
-#ifndef __Bindings_H__
-#define __Bindings_H__
-
-@import Foundation;
-#include "ref.h"
-#include "Universe.objc.h"
-
-
-@class BindingsBackup;
-@class BindingsBackupReport;
-@class BindingsClient;
-@class BindingsContact;
-@class BindingsContactList;
-@class BindingsDummyTraffic;
-@class BindingsFact;
-@class BindingsFactList;
-@class BindingsFilePartTracker;
-@class BindingsFileTransfer;
-@class BindingsGroup;
-@class BindingsGroupChat;
-@class BindingsGroupMember;
-@class BindingsGroupMembership;
-@class BindingsGroupMessageReceive;
-@class BindingsGroupReportDisk;
-@class BindingsGroupSendReport;
-@class BindingsIdList;
-@class BindingsIntList;
-@class BindingsManyNotificationForMeReport;
-@class BindingsMessage;
-@class BindingsNewGroupReport;
-@class BindingsNodeRegistrationsStatus;
-@class BindingsNotificationForMeReport;
-@class BindingsRestoreContactsReport;
-@class BindingsRoundList;
-@class BindingsSendReport;
-@class BindingsSendReportDisk;
-@class BindingsUnregister;
-@class BindingsUser;
-@class BindingsUserDiscovery;
-@protocol BindingsAuthConfirmCallback;
-@class BindingsAuthConfirmCallback;
-@protocol BindingsAuthRequestCallback;
-@class BindingsAuthRequestCallback;
-@protocol BindingsAuthResetNotificationCallback;
-@class BindingsAuthResetNotificationCallback;
-@protocol BindingsClientError;
-@class BindingsClientError;
-@protocol BindingsEventCallbackFunctionObject;
-@class BindingsEventCallbackFunctionObject;
-@protocol BindingsFileTransferReceiveFunc;
-@class BindingsFileTransferReceiveFunc;
-@protocol BindingsFileTransferReceivedProgressFunc;
-@class BindingsFileTransferReceivedProgressFunc;
-@protocol BindingsFileTransferSentProgressFunc;
-@class BindingsFileTransferSentProgressFunc;
-@protocol BindingsGroupReceiveFunc;
-@class BindingsGroupReceiveFunc;
-@protocol BindingsGroupRequestFunc;
-@class BindingsGroupRequestFunc;
-@protocol BindingsListener;
-@class BindingsListener;
-@protocol BindingsLogWriter;
-@class BindingsLogWriter;
-@protocol BindingsLookupCallback;
-@class BindingsLookupCallback;
-@protocol BindingsMessageDeliveryCallback;
-@class BindingsMessageDeliveryCallback;
-@protocol BindingsMultiLookupCallback;
-@class BindingsMultiLookupCallback;
-@protocol BindingsNetworkHealthCallback;
-@class BindingsNetworkHealthCallback;
-@protocol BindingsPreimageNotification;
-@class BindingsPreimageNotification;
-@protocol BindingsRestoreContactsUpdater;
-@class BindingsRestoreContactsUpdater;
-@protocol BindingsRoundCompletionCallback;
-@class BindingsRoundCompletionCallback;
-@protocol BindingsRoundEventCallback;
-@class BindingsRoundEventCallback;
-@protocol BindingsSearchCallback;
-@class BindingsSearchCallback;
-@protocol BindingsSingleSearchCallback;
-@class BindingsSingleSearchCallback;
-@protocol BindingsTimeSource;
-@class BindingsTimeSource;
-@protocol BindingsUpdateBackupFunc;
-@class BindingsUpdateBackupFunc;
-
-@protocol BindingsAuthConfirmCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)partner;
-@end
-
-@protocol BindingsAuthRequestCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@protocol BindingsAuthResetNotificationCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@protocol BindingsClientError <NSObject>
-- (void)report:(NSString* _Nullable)source message:(NSString* _Nullable)message trace:(NSString* _Nullable)trace;
-@end
-
-@protocol BindingsEventCallbackFunctionObject <NSObject>
-- (void)reportEvent:(long)priority category:(NSString* _Nullable)category evtType:(NSString* _Nullable)evtType details:(NSString* _Nullable)details;
-@end
-
-@protocol BindingsFileTransferReceiveFunc <NSObject>
-- (void)receiveCallback:(NSData* _Nullable)tid fileName:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType sender:(NSData* _Nullable)sender size:(long)size preview:(NSData* _Nullable)preview;
-@end
-
-@protocol BindingsFileTransferReceivedProgressFunc <NSObject>
-- (void)receivedProgressCallback:(BOOL)completed received:(long)received total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-@protocol BindingsFileTransferSentProgressFunc <NSObject>
-- (void)sentProgressCallback:(BOOL)completed sent:(long)sent arrived:(long)arrived total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-@protocol BindingsGroupReceiveFunc <NSObject>
-- (void)groupReceiveCallback:(BindingsGroupMessageReceive* _Nullable)msg;
-@end
-
-@protocol BindingsGroupRequestFunc <NSObject>
-- (void)groupRequestCallback:(BindingsGroup* _Nullable)g;
-@end
-
-@protocol BindingsListener <NSObject>
-/**
- * Hear is called to receive a message in the UI
- */
-- (void)hear:(BindingsMessage* _Nullable)message;
-/**
- * Returns a name, used for debugging
- */
-- (NSString* _Nonnull)name;
-@end
-
-@protocol BindingsLogWriter <NSObject>
-- (void)log:(NSString* _Nullable)p0;
-@end
-
-@protocol BindingsLookupCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsMessageDeliveryCallback <NSObject>
-- (void)eventCallback:(NSData* _Nullable)msgID delivered:(BOOL)delivered timedOut:(BOOL)timedOut roundResults:(NSData* _Nullable)roundResults;
-@end
-
-@protocol BindingsMultiLookupCallback <NSObject>
-- (void)callback:(BindingsContactList* _Nullable)Succeeded failed:(BindingsIdList* _Nullable)failed errors:(NSString* _Nullable)errors;
-@end
-
-@protocol BindingsNetworkHealthCallback <NSObject>
-- (void)callback:(BOOL)p0;
-@end
-
-@protocol BindingsPreimageNotification <NSObject>
-- (void)notify:(NSData* _Nullable)identity deleted:(BOOL)deleted;
-@end
-
-@protocol BindingsRestoreContactsUpdater <NSObject>
-/**
- * RestoreContactsCallback is called to report the current # of contacts
-that have been found and how many have been restored
-against the total number that need to be
-processed. If an error occurs it it set on the err variable as a
-plain string.
- */
-- (void)restoreContactsCallback:(long)numFound numRestored:(long)numRestored total:(long)total err:(NSString* _Nullable)err;
-@end
-
-@protocol BindingsRoundCompletionCallback <NSObject>
-- (void)eventCallback:(long)rid success:(BOOL)success timedOut:(BOOL)timedOut;
-@end
-
-@protocol BindingsRoundEventCallback <NSObject>
-- (void)eventCallback:(long)rid state:(long)state timedOut:(BOOL)timedOut;
-@end
-
-@protocol BindingsSearchCallback <NSObject>
-- (void)callback:(BindingsContactList* _Nullable)contacts error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsSingleSearchCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsTimeSource <NSObject>
-- (int64_t)nowMs;
-@end
-
-@protocol BindingsUpdateBackupFunc <NSObject>
-- (void)updateBackup:(NSData* _Nullable)encryptedBackup;
-@end
-
-@interface BindingsBackup : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * AddJson stores a passed in json string in the backup structure
- */
-- (void)addJson:(NSString* _Nullable)json;
-/**
- * IsBackupRunning returns true if the backup has been initialized and is
-running. Returns false if it has been stopped.
- */
-- (BOOL)isBackupRunning;
-/**
- * StopBackup stops the backup processes and deletes the user's password from
-storage. To enable backups again, call InitializeBackup.
- */
-- (BOOL)stopBackup:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsBackupReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field BackupReport.RestoredContacts with unsupported type: []*gitlab.com/xx_network/primitives/id.ID
-
-@property (nonatomic) NSString* _Nonnull params;
-@end
-
-/**
- * BindingsClient wraps the api.Client, implementing additional functions
-to support the gomobile Client interface
- */
-@interface BindingsClient : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BOOL)confirmAuthenticatedChannel:(NSData* _Nullable)recipientMarshaled ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteAllRequests clears all requests from Client's auth storage.
- */
-- (BOOL)deleteAllRequests:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteContact is a function which removes a contact from Client's storage
- */
-- (BOOL)deleteContact:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteReceiveRequests clears receive requests from Client's auth storage.
- */
-- (BOOL)deleteReceiveRequests:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteRequest will delete a request, agnostic of request type
-for the given partner ID. If no request exists for this
-partner ID an error will be returned.
- */
-- (BOOL)deleteRequest:(NSData* _Nullable)requesterUserId error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteSentRequests clears sent requests from Client's auth storage.
- */
-- (BOOL)deleteSentRequests:(NSError* _Nullable* _Nullable)error;
-// skipped method Client.GetInternalClient with unsupported parameter or return types
-
-/**
- * GetNodeRegistrationStatus returns a struct with the number of nodes the
-client is registered with and the number total.
- */
-- (BindingsNodeRegistrationsStatus* _Nullable)getNodeRegistrationStatus:(NSError* _Nullable* _Nullable)error;
-/**
- * GetPartners returns a list of
- */
-- (NSData* _Nullable)getPartners:(NSError* _Nullable* _Nullable)error;
-/**
- * GetPreferredBins returns the geographic bin or bins that the provided two
-character country code is a part of. The bins are returned as CSV.
- */
-- (NSString* _Nonnull)getPreferredBins:(NSString* _Nullable)countryCode error:(NSError* _Nullable* _Nullable)error;
-- (NSString* _Nonnull)getPreimages:(NSData* _Nullable)identity;
-// skipped method Client.GetRateLimitParams with unsupported parameter or return types
-
-- (NSString* _Nonnull)getRelationshipFingerprint:(NSData* _Nullable)partnerID error:(NSError* _Nullable* _Nullable)error;
-/**
- * Returns a user object from which all information about the current user
-can be gleaned
- */
-- (BindingsUser* _Nullable)getUser;
-/**
- * HasRunningProcessies checks if any background threads are running.
-returns true if none are running. This is meant to be
-used when NetworkFollowerStatus() returns Stopping.
-Due to the handling of comms on iOS, where the OS can
-block indefiently, it may not enter the stopped
-state apropreatly. This can be used instead.
- */
-- (BOOL)hasRunningProcessies;
-/**
- * returns true if the network is read to be in a healthy state where
-messages can be sent
- */
-- (BOOL)isNetworkHealthy;
-- (BindingsContact* _Nullable)makePrecannedAuthenticatedChannel:(long)precannedID error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the state of the network follower. Returns:
-Stopped 	- 0
-Starting - 1000
-Running	- 2000
-Stopping	- 3000
- */
-- (long)networkFollowerStatus;
-- (void)registerAuthCallbacks:(id<BindingsAuthRequestCallback> _Nullable)request confirm:(id<BindingsAuthConfirmCallback> _Nullable)confirm reset:(id<BindingsAuthResetNotificationCallback> _Nullable)reset;
-/**
- * RegisterClientErrorCallback registers the callback to handle errors from the
-long running threads controlled by StartNetworkFollower and StopNetworkFollower
- */
-- (void)registerClientErrorCallback:(id<BindingsClientError> _Nullable)clientError;
-/**
- * RegisterEventCallback records the given function to receive
-ReportableEvent objects. It returns the internal index
-of the callback so that it can be deleted later.
- */
-- (BOOL)registerEventCallback:(NSString* _Nullable)name myObj:(id<BindingsEventCallbackFunctionObject> _Nullable)myObj error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterForNotifications accepts firebase messaging token
- */
-- (BOOL)registerForNotifications:(NSString* _Nullable)token error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterListener records and installs a listener for messages
-matching specific uid, msgType, and/or username
-Returns a ListenerUnregister interface which can be
-
-to register for any userID, pass in an id with length 0 or an id with
-all zeroes
-
-to register for any message type, pass in a message type of 0
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types
- */
-- (BindingsUnregister* _Nullable)registerListener:(NSData* _Nullable)uid msgType:(long)msgType listener:(id<BindingsListener> _Nullable)listener error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterNetworkHealthCB registers the network health callback to be called
-any time the network health changes. Returns a unique ID that can be used to
-unregister the network health callback.
- */
-- (int64_t)registerNetworkHealthCB:(id<BindingsNetworkHealthCallback> _Nullable)nhc;
-- (void)registerPreimageCallback:(NSData* _Nullable)identity pin:(id<BindingsPreimageNotification> _Nullable)pin;
-/**
- * RegisterRoundEventsHandler registers a callback interface for round
-events.
-The rid is the round the event attaches to
-The timeoutMS is the number of milliseconds until the event fails, and the
-validStates are a list of states (one per byte) on which the event gets
-triggered
-States:
- 0x00 - PENDING (Never seen by client)
- 0x01 - PRECOMPUTING
- 0x02 - STANDBY
- 0x03 - QUEUED
- 0x04 - REALTIME
- 0x05 - COMPLETED
- 0x06 - FAILED
-These states are defined in elixxir/primitives/states/state.go
- */
-- (BindingsUnregister* _Nullable)registerRoundEventsHandler:(long)rid cb:(id<BindingsRoundEventCallback> _Nullable)cb timeoutMS:(long)timeoutMS il:(BindingsIntList* _Nullable)il;
-- (void)replayRequests;
-- (BOOL)requestAuthenticatedChannel:(NSData* _Nullable)recipientMarshaled meMarshaled:(NSData* _Nullable)meMarshaled message:(NSString* _Nullable)message ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-- (BOOL)resetSession:(NSData* _Nullable)recipientMarshaled meMarshaled:(NSData* _Nullable)meMarshaled message:(NSString* _Nullable)message ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * This will return the round the message was sent on if it is successfully sent
-This can be used to register a round event to learn about message delivery.
-on failure a round id of -1 is returned
- */
-- (BOOL)sendCmix:(NSData* _Nullable)recipient contents:(NSData* _Nullable)contents parameters:(NSString* _Nullable)parameters ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * SendE2E sends an end-to-end payload to the provided recipient with
-the provided msgType. Returns the list of rounds in which parts of
-the message were sent or an error if it fails.
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types
- */
-- (BindingsSendReport* _Nullable)sendE2E:(NSData* _Nullable)recipient payload:(NSData* _Nullable)payload messageType:(long)messageType parameters:(NSString* _Nullable)parameters error:(NSError* _Nullable* _Nullable)error;
-/**
- * SendUnsafe sends an unencrypted payload to the provided recipient
-with the provided msgType. Returns the list of rounds in which parts
-of the message were sent or an error if it fails.
-NOTE: Do not use this function unless you know what you are doing.
-This function always produces an error message in client logging.
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types with custom types
- */
-- (BindingsRoundList* _Nullable)sendUnsafe:(NSData* _Nullable)recipient payload:(NSData* _Nullable)payload messageType:(long)messageType parameters:(NSString* _Nullable)parameters error:(NSError* _Nullable* _Nullable)error;
-/**
- * SetProxiedBins updates the host pool filter that filters out gateways that
-are not in one of the specified bins. The provided bins should be CSV.
- */
-- (BOOL)setProxiedBins:(NSString* _Nullable)binStringsCSV error:(NSError* _Nullable* _Nullable)error;
-/**
- * StartNetworkFollower kicks off the tracking of the network. It starts
-long running network client threads and returns an object for checking
-state and stopping those threads.
-Call this when returning from sleep and close when going back to
-sleep.
-These threads may become a significant drain on battery when offline, ensure
-they are stopped if there is no internet access
-Threads Started:
-  - Network Follower (/network/follow.go)
-  	tracks the network events and hands them off to workers for handling
-  - Historical Round Retrieval (/network/rounds/historical.go)
-		Retrieves data about rounds which are too old to be stored by the client
-	 - Message Retrieval Worker Group (/network/rounds/retrieve.go)
-		Requests all messages in a given round from the gateway of the last node
-	 - Message Handling Worker Group (/network/message/handle.go)
-		Decrypts and partitions messages when signals via the Switchboard
-	 - Health Tracker (/network/health)
-		Via the network instance tracks the state of the network
-	 - Garbled Messages (/network/message/garbled.go)
-		Can be signaled to check all recent messages which could be be decoded
-		Uses a message store on disk for persistence
-	 - Critical Messages (/network/message/critical.go)
-		Ensures all protocol layer mandatory messages are sent
-		Uses a message store on disk for persistence
-	 - KeyExchange Trigger (/keyExchange/trigger.go)
-		Responds to sent rekeys and executes them
-  - KeyExchange Confirm (/keyExchange/confirm.go)
-		Responds to confirmations of successful rekey operations
- */
-- (BOOL)startNetworkFollower:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * StopNetworkFollower stops the network follower if it is running.
-It returns errors if the Follower is in the wrong status to stop or if it
-fails to stop it.
-if the network follower is running and this fails, the client object will
-most likely be in an unrecoverable state and need to be trashed.
- */
-- (BOOL)stopNetworkFollower:(NSError* _Nullable* _Nullable)error;
-/**
- * UnregisterEventCallback deletes the callback identified by the
-index. It returns an error if it fails.
- */
-- (void)unregisterEventCallback:(NSString* _Nullable)name;
-/**
- * UnregisterForNotifications unregister user for notifications
- */
-- (BOOL)unregisterForNotifications:(NSError* _Nullable* _Nullable)error;
-- (void)unregisterNetworkHealthCB:(int64_t)funcID;
-- (BOOL)verifyOwnership:(NSData* _Nullable)receivedMarshaled verifiedMarshaled:(NSData* _Nullable)verifiedMarshaled ret0_:(BOOL* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * WaitForMessageDelivery allows the caller to get notified if the rounds a
-message was sent in successfully completed. Under the hood, this uses an API
-which uses the internal round data, network historical round lookup, and
-waiting on network events to determine what has (or will) occur.
-
-The callbacks will return at timeoutMS if no state update occurs
-
-This function takes the marshaled send report to ensure a memory leak does
-not occur as a result of both sides of the bindings holding a reference to
-the same pointer.
- */
-- (BOOL)waitForMessageDelivery:(NSData* _Nullable)marshaledSendReport mdc:(id<BindingsMessageDeliveryCallback> _Nullable)mdc timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * WaitForNewtwork will block until either the network is healthy or the
-passed timeout. It will return true if the network is healthy
- */
-- (BOOL)waitForNetwork:(long)timeoutMS;
-/**
- * WaitForRoundCompletion allows the caller to get notified if a round
-has completed (or failed). Under the hood, this uses an API which uses the internal
-round data, network historical round lookup, and waiting on network events
-to determine what has (or will) occur.
-
-The callbacks will return at timeoutMS if no state update occurs
- */
-- (BOOL)waitForRoundCompletion:(long)roundID rec:(id<BindingsRoundCompletionCallback> _Nullable)rec timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- *  contact object
- */
-@interface BindingsContact : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped method Contact.GetAPIContact with unsupported parameter or return types
-
-/**
- * GetDHPublicKey returns the public key associated with the Contact.
- */
-- (NSData* _Nullable)getDHPublicKey;
-/**
- * Returns a fact list for adding and getting facts to and from the contact
- */
-- (BindingsFactList* _Nullable)getFactList;
-/**
- * GetID returns the user ID for this user.
- */
-- (NSData* _Nullable)getID;
-/**
- * GetDHPublicKey returns hash of a DH proof of key ownership.
- */
-- (NSData* _Nullable)getOwnershipProof;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsContactList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Gets a stored round ID at the given index
- */
-- (BindingsContact* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the number of round IDs stored
- */
-- (long)len;
-@end
-
-/**
- * DummyTraffic contains the file dummy traffic manager. The manager can be used
-to set and get the status of the send thread.
- */
-@interface BindingsDummyTraffic : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewDummyTrafficManager creates a DummyTraffic manager and initialises the
-dummy traffic send thread. Note that the manager does not start sending dummy
-traffic until its status is set to true using DummyTraffic.SetStatus.
-The maxNumMessages is the upper bound of the random number of messages sent
-each send. avgSendDeltaMS is the average duration, in milliseconds, to wait
-between sends. Sends occur every avgSendDeltaMS +/- a random duration with an
-upper bound of randomRangeMS.
- */
-- (nullable instancetype)initManager:(BindingsClient* _Nullable)client maxNumMessages:(long)maxNumMessages avgSendDeltaMS:(long)avgSendDeltaMS randomRangeMS:(long)randomRangeMS;
-/**
- * GetStatus returns the current state of the dummy traffic send thread. It has
-the following return values:
- true  = send thread is sending dummy messages
- false = send thread is paused/stopped and not sending dummy messages
-Note that this function does not return the status set by SetStatus directly;
-it returns the current status of the send thread, which means any call to
-SetStatus will have a small delay before it is returned by GetStatus.
- */
-- (BOOL)getStatus;
-/**
- * SetStatus sets the state of the dummy traffic send thread, which determines
-if the thread is running or paused. The possible statuses are:
- true  = send thread is sending dummy messages
- false = send thread is paused/stopped and not sending dummy messages
-Returns an error if the channel is full.
-Note that this function cannot change the status of the send thread if it has
-yet to be started or stopped.
- */
-- (BOOL)setStatus:(BOOL)status error:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsFact : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- *  fact object
-creates a new fact. The factType must be either:
- 0 - Username
- 1 - Email
- 2 - Phone Number
-The fact must be well formed for the type and must not include commas or
-semicolons. If it is not well formed, it will be rejected.  Phone numbers
-must have the two letter country codes appended.  For the complete set of
-validation, see /elixxir/primitives/fact/fact.go
- */
-- (nullable instancetype)init:(long)factType factStr:(NSString* _Nullable)factStr;
-- (NSString* _Nonnull)get;
-- (NSString* _Nonnull)stringify;
-- (long)type;
-@end
-
-@interface BindingsFactList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * FactList
- */
-- (nullable instancetype)init;
-- (BOOL)add:(NSString* _Nullable)factData factType:(long)factType error:(NSError* _Nullable* _Nullable)error;
-- (BindingsFact* _Nullable)get:(long)i;
-- (long)num;
-- (NSString* _Nonnull)stringify:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * FilePartTracker contains the interfaces.FilePartTracker.
- */
-@interface BindingsFilePartTracker : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetNumParts returns the total number of file parts in the transfer.
- */
-- (long)getNumParts;
-/**
- * GetPartStatus returns the status of the file part with the given part number.
-The possible values for the status are:
-0 = unsent
-1 = sent (sender has sent a part, but it has not arrived)
-2 = arrived (sender has sent a part, and it has arrived)
-3 = received (receiver has received a part)
- */
-- (long)getPartStatus:(long)partNum;
-@end
-
-/**
- * FileTransfer contains the file transfer manager.
- */
-@interface BindingsFileTransfer : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewFileTransferManager creates a new file transfer manager and starts the
-sending and receiving threads. The receiveFunc is called everytime a new file
-transfer is received.
-The parameters string contains file transfer network configuration options
-and is a JSON formatted string of the fileTransfer.Params object. If it is
-left empty, then defaults are used. It is highly recommended that defaults
-are used. If it is set, it must match the following format:
- {"MaxThroughput":150000,"SendTimeout":500000000}
-MaxThroughput is in bytes/sec and SendTimeout is in nanoseconds.
- */
-- (nullable instancetype)initManager:(BindingsClient* _Nullable)client receiveFunc:(id<BindingsFileTransferReceiveFunc> _Nullable)receiveFunc parameters:(NSString* _Nullable)parameters;
-/**
- * CloseSend deletes a sent file transfer from the sent transfer map and from
-storage once a transfer has completed or reached the retry limit. Returns an
-error if the transfer has not run out of retries.
- */
-- (BOOL)closeSend:(NSData* _Nullable)transferID error:(NSError* _Nullable* _Nullable)error;
-/**
- * GetMaxFileNameByteLength returns the maximum length, in bytes, allowed for a
-file name.
- */
-- (long)getMaxFileNameByteLength;
-/**
- * GetMaxFilePreviewSize returns the maximum file preview size, in bytes.
- */
-- (long)getMaxFilePreviewSize;
-/**
- * GetMaxFileSize returns the maximum file size, in bytes, allowed to be
-transferred.
- */
-- (long)getMaxFileSize;
-/**
- * GetMaxFileTypeByteLength returns the maximum length, in bytes, allowed for a
-file type.
- */
-- (long)getMaxFileTypeByteLength;
-/**
- * Receive returns the fully assembled file on the completion of the transfer.
-It deletes the transfer from the received transfer map and from storage.
-Returns an error if the transfer is not complete, the full file cannot be
-verified, or if the transfer cannot be found.
- */
-- (NSData* _Nullable)receive:(NSData* _Nullable)transferID error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterReceiveProgressCallback allows for the registration of a callback to
-track the progress of an individual received file transfer. The callback will
-be called immediately when added to report the current status of the
-transfer. It will then call every time a file part is received, the transfer
-completes, or an error occurs. It is called at most once ever period, which
-means if events occur faster than the period, then they will not be reported
-and instead, the progress will be reported once at the end of the period.
-Once the callback reports that the transfer has completed, the recipient
-can get the full file by calling Receive.
-The period is specified in milliseconds.
- */
-- (BOOL)registerReceiveProgressCallback:(NSData* _Nullable)transferID progressFunc:(id<BindingsFileTransferReceivedProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterSendProgressCallback allows for the registration of a callback to
-track the progress of an individual sent file transfer. The callback will be
-called immediately when added to report the current status of the transfer.
-It will then call every time a file part is sent, a file part arrives, the
-transfer completes, or an error occurs. It is called at most once every
-period, which means if events occur faster than the period, then they will
-not be reported and instead, the progress will be reported once at the end of
-the period.
-The period is specified in milliseconds.
- */
-- (BOOL)registerSendProgressCallback:(NSData* _Nullable)transferID progressFunc:(id<BindingsFileTransferSentProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * Send sends a file to the recipient. The sender must have an E2E relationship
-with the recipient.
-The file name is the name of the file to show a user. It has a max length of
-48 bytes.
-The file type identifies what type of file is being sent. It has a max length
-of 8 bytes.
-The file data cannot be larger than 256 kB
-The retry float is the total amount of data to send relative to the data
-size. Data will be resent on error and will resend up to [(1 + retry) *
-fileSize].
-The preview stores a preview of the data (such as a thumbnail) and is
-capped at 4 kB in size.
-Returns a unique transfer ID used to identify the transfer.
-PeriodMS is the duration, in milliseconds, to wait between progress callback
-calls. Set this large enough to prevent spamming.
- */
-- (NSData* _Nullable)send:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType fileData:(NSData* _Nullable)fileData recipientID:(NSData* _Nullable)recipientID retry:(float)retry preview:(NSData* _Nullable)preview progressFunc:(id<BindingsFileTransferSentProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * Group structure contains the identifying and membership information of a
-group chat.
- */
-@interface BindingsGroup : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetCreatedMS returns the time the group was created in milliseconds. This is
-also the time the group requests were sent.
- */
-- (int64_t)getCreatedMS;
-/**
- * GetCreatedNano returns the time the group was created in nanoseconds. This is
-also the time the group requests were sent.
- */
-- (int64_t)getCreatedNano;
-/**
- * GetID return the 33-byte unique group ID.
- */
-- (NSData* _Nullable)getID;
-/**
- * GetInitMessage returns initial message sent with the group request.
- */
-- (NSData* _Nullable)getInitMessage;
-/**
- * GetMembership returns a list of contacts, one for each member in the group.
-The list is in order; the first contact is the leader/creator of the group.
-All subsequent members are ordered by their ID.
- */
-- (BindingsGroupMembership* _Nullable)getMembership;
-/**
- * GetName returns the name set by the user for the group.
- */
-- (NSData* _Nullable)getName;
-/**
- * Serialize serializes the Group.
- */
-- (NSData* _Nullable)serialize;
-@end
-
-/**
- * GroupChat object contains the group chat manager.
- */
-@interface BindingsGroupChat : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetGroup returns the group with the group ID. If no group exists, then the
-error "failed to find group" is returned.
- */
-- (BindingsGroup* _Nullable)getGroup:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * GetGroups returns an IdList containing a list of group IDs that the user is a
-part of.
- */
-- (BindingsIdList* _Nullable)getGroups;
-/**
- * JoinGroup allows a user to join a group when they receive a request. The
-caller must pass in the serialized bytes of a Group.
- */
-- (BOOL)joinGroup:(NSData* _Nullable)serializedGroupData error:(NSError* _Nullable* _Nullable)error;
-/**
- * LeaveGroup deletes a group so a user no longer has access.
- */
-- (BOOL)leaveGroup:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * MakeGroup creates a new group and sends a group request to all members in the
-group. The ID of the new group, the rounds the requests were sent on, and the
-status of the send are contained in NewGroupReport.
- */
-- (BindingsNewGroupReport* _Nullable)makeGroup:(BindingsIdList* _Nullable)membership name:(NSData* _Nullable)name message:(NSData* _Nullable)message;
-/**
- * NumGroups returns the number of groups the user is a part of.
- */
-- (long)numGroups;
-/**
- * ResendRequest resends a group request to all members in the group. The rounds
-they were sent on and the status of the send are contained in NewGroupReport.
- */
-- (BindingsNewGroupReport* _Nullable)resendRequest:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * Send sends the message to the specified group. Returns the round the messages
-were sent on.
- */
-- (BindingsGroupSendReport* _Nullable)send:(NSData* _Nullable)groupIdBytes message:(NSData* _Nullable)message error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * //
-Member Structure
-//
-GroupMember represents a member in the group membership list.
- */
-@interface BindingsGroupMember : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupMember.Member with unsupported type: gitlab.com/elixxir/crypto/group.Member
-
-// skipped method GroupMember.DeepCopy with unsupported parameter or return types
-
-// skipped method GroupMember.Equal with unsupported parameter or return types
-
-/**
- * GetDhKey returns the byte representation of the public Diffie–Hellman key of
-the member.
- */
-- (NSData* _Nullable)getDhKey;
-/**
- * GetID returns the 33-byte user ID of the member.
- */
-- (NSData* _Nullable)getID;
-- (NSString* _Nonnull)goString;
-- (NSData* _Nullable)serialize;
-- (NSString* _Nonnull)string;
-@end
-
-/**
- * GroupMembership structure contains a list of members that are part of a
-group. The first member is the group leader.
- */
-@interface BindingsGroupMembership : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Get returns the member at the index. The member at index 0 is always the
-group leader. An error is returned if the index is out of range.
- */
-- (BindingsGroupMember* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Len returns the number of members in the group membership.
- */
-- (long)len;
-@end
-
-/**
- * GroupMessageReceive contains a group message, its ID, and its data that a
-user receives.
- */
-@interface BindingsGroupMessageReceive : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupMessageReceive.MessageReceive with unsupported type: gitlab.com/elixxir/client/groupChat.MessageReceive
-
-/**
- * GetEphemeralID returns the ephemeral ID of the recipient.
- */
-- (int64_t)getEphemeralID;
-/**
- * GetGroupID returns the 33-byte group ID.
- */
-- (NSData* _Nullable)getGroupID;
-/**
- * GetMessageID returns the message ID.
- */
-- (NSData* _Nullable)getMessageID;
-/**
- * GetPayload returns the message payload.
- */
-- (NSData* _Nullable)getPayload;
-/**
- * GetRecipientID returns the 33-byte user ID of the recipient.
- */
-- (NSData* _Nullable)getRecipientID;
-/**
- * GetRoundID returns the ID of the round the message was sent on.
- */
-- (int64_t)getRoundID;
-/**
- * GetRoundTimestampMS returns the timestamp, in milliseconds, of the round the
-message was sent on.
- */
-- (int64_t)getRoundTimestampMS;
-/**
- * GetRoundTimestampNano returns the timestamp, in nanoseconds, of the round the
-message was sent on.
- */
-- (int64_t)getRoundTimestampNano;
-/**
- * GetRoundURL returns the ID of the round the message was sent on.
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetSenderID returns the 33-byte user ID of the sender.
- */
-- (NSData* _Nullable)getSenderID;
-/**
- * GetTimestampMS returns the message timestamp in milliseconds.
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message timestamp in nanoseconds.
- */
-- (int64_t)getTimestampNano;
-- (NSString* _Nonnull)string;
-@end
-
-@interface BindingsGroupReportDisk : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupReportDisk.List with unsupported type: []gitlab.com/xx_network/primitives/id.Round
-
-@property (nonatomic) NSData* _Nullable grpId;
-@property (nonatomic) long status;
-@end
-
-/**
- * GroupSendReport is returned when sending a group message. It contains the
-round ID sent on and the timestamp of the send.
- */
-@interface BindingsGroupSendReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetMessageID returns the ID of the round that the send occurred on.
- */
-- (NSData* _Nullable)getMessageID;
-/**
- * GetRoundID returns the ID of the round that the send occurred on.
- */
-- (int64_t)getRoundID;
-/**
- * GetRoundURL returns the URL of the round that the send occurred on.
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetTimestampMS returns the timestamp of the send in milliseconds.
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the timestamp of the send in nanoseconds.
- */
-- (int64_t)getTimestampNano;
-@end
-
-/**
- *  ID list
-IdList contains a list of IDs.
- */
-@interface BindingsIdList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Add appends the ID bytes to the end of the list.
- */
-- (BOOL)add:(NSData* _Nullable)idBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * Get returns the ID at the index. An error is returned if the index is out of
-range.
- */
-- (NSData* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Len returns the number of IDs in the list.
- */
-- (long)len;
-@end
-
-@interface BindingsIntList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (void)add:(long)i;
-- (BOOL)get:(long)i ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-- (long)len;
-@end
-
-@interface BindingsManyNotificationForMeReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BindingsNotificationForMeReport* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-- (long)len;
-@end
-
-/**
- * Message is a message received from the cMix network in the clear
-or that has been decrypted using established E2E keys.
- */
-@interface BindingsMessage : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetID returns the id of the message
- */
-- (NSData* _Nullable)getID;
-/**
- * GetMessageType returns the message's type
- */
-- (long)getMessageType;
-/**
- * GetPayload returns the message's payload/contents
- */
-- (NSData* _Nullable)getPayload;
-/**
- * GetRoundId returns the message's round ID
- */
-- (int64_t)getRoundId;
-/**
- * GetRoundTimestampMS returns the message's round timestamp in milliseconds
- */
-- (int64_t)getRoundTimestampMS;
-/**
- * GetRoundTimestampNano returns the message's round timestamp in nanoseconds
- */
-- (int64_t)getRoundTimestampNano;
-/**
- * GetRoundURL returns the message's round URL
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetSender returns the message's sender ID, if available
- */
-- (NSData* _Nullable)getSender;
-/**
- * GetTimestampMS returns the message's timestamp in milliseconds
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message's timestamp in nanoseconds
- */
-- (int64_t)getTimestampNano;
-@end
-
-/**
- * NewGroupReport is returned when creating a new group and contains the ID of
-the group, a list of rounds that the group requests were sent on, and the
-status of the send.
- */
-@interface BindingsNewGroupReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetError returns the string of an error.
-Will be an empty string if no error occured
- */
-- (NSString* _Nonnull)getError;
-/**
- * GetGroup returns the Group.
- */
-- (BindingsGroup* _Nullable)getGroup;
-/**
- * GetRoundList returns the RoundList containing a list of rounds requests were
-sent on.
- */
-- (BindingsRoundList* _Nullable)getRoundList;
-/**
- * GetStatus returns the status of the requests sent when creating a new group.
-status = 0   an error occurred before any requests could be sent
-         1   all requests failed to send (call Resend Group)
-         2   some request failed and some succeeded (call Resend Group)
-         3,  all requests sent successfully (call Resend Group)
- */
-- (long)getStatus;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-- (BOOL)unmarshal:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * NodeRegistrationsStatus structure for returning node registration statuses
-for bindings.
- */
-@interface BindingsNodeRegistrationsStatus : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetRegistered returns the number of nodes registered with the client.
- */
-- (long)getRegistered;
-/**
- * GetTotal return the total of nodes currently in the network.
- */
-- (long)getTotal;
-@end
-
-@interface BindingsNotificationForMeReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BOOL)forMe;
-- (NSData* _Nullable)source;
-- (NSString* _Nonnull)type;
-@end
-
-/**
- * RestoreContactsReport is a gomobile friendly report structure
-for determining which IDs restored, which failed, and why.
- */
-@interface BindingsRestoreContactsReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetErrorAt returns the error string at index
- */
-- (NSString* _Nonnull)getErrorAt:(long)index;
-/**
- * GetFailedAt returns the failed ID at index
- */
-- (NSData* _Nullable)getFailedAt:(long)index;
-/**
- * GetRestoreContactsError returns an error string. Empty if no error.
- */
-- (NSString* _Nonnull)getRestoreContactsError;
-/**
- * GetRestoredAt returns the restored ID at index
- */
-- (NSData* _Nullable)getRestoredAt:(long)index;
-/**
- * LenFailed returns the length of the ID's failed.
- */
-- (long)lenFailed;
-/**
- * LenRestored returns the length of ID's restored.
- */
-- (long)lenRestored;
-@end
-
-@interface BindingsRoundList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Gets a stored round ID at the given index
- */
-- (BOOL)get:(long)i ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the number of round IDs stored
- */
-- (long)len;
-@end
-
-/**
- * the send report is the mechanisim by which sendE2E returns a single
- */
-@interface BindingsSendReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (NSData* _Nullable)getMessageID;
-- (BindingsRoundList* _Nullable)getRoundList;
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetTimestampMS returns the message's timestamp in milliseconds
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message's timestamp in nanoseconds
- */
-- (int64_t)getTimestampNano;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-- (BOOL)unmarshal:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsSendReportDisk : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field SendReportDisk.List with unsupported type: []gitlab.com/xx_network/primitives/id.Round
-
-@property (nonatomic) NSData* _Nullable mid;
-@property (nonatomic) int64_t ts;
-@end
-
-/**
- * Generic Unregister - a generic return used for all callbacks which can be
-unregistered
-Interface which allows the un-registration of a listener
- */
-@interface BindingsUnregister : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Call unregisters a callback
- */
-- (void)unregister;
-@end
-
-@interface BindingsUser : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BindingsContact* _Nullable)getContact;
-- (NSData* _Nullable)getE2EDhPrivateKey;
-- (NSData* _Nullable)getE2EDhPublicKey;
-- (NSData* _Nullable)getReceptionID;
-- (NSData* _Nullable)getReceptionRSAPrivateKeyPem;
-- (NSData* _Nullable)getReceptionRSAPublicKeyPem;
-- (NSData* _Nullable)getReceptionSalt;
-- (NSData* _Nullable)getTransmissionID;
-- (NSData* _Nullable)getTransmissionRSAPrivateKeyPem;
-- (NSData* _Nullable)getTransmissionRSAPublicKeyPem;
-- (NSData* _Nullable)getTransmissionSalt;
-- (BOOL)isPrecanned;
-@end
-
-@interface BindingsUserDiscovery : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewUserDiscovery returns a new user discovery object. Only call this once. It must be called
-after StartNetworkFollower is called and will fail if the network has never
-been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-- (nullable instancetype)init:(BindingsClient* _Nullable)client;
-/**
- * NewUserDiscoveryFromBackup returns a new user discovery object. It
-wil set up the manager with the backup data. Pass into it the backed up
-facts, one email and phone number each. This will add the registered facts
-to the backed Store. Any one of these fields may be empty,
-however both fields being empty will cause an error. Any other fact that is not
-an email or phone number will return an error. You may only add a fact for the
-accepted types once each. If you attempt to back up a fact type that has already
-been backed up, an error will be returned. Anytime an error is returned, it means
-the backup was not successful.
-NOTE: Do not use this as a direct store operation. This feature is intended to add facts
-to a backend store that have ALREADY BEEN REGISTERED on the account.
-THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend.
-Only call this once. It must be called after StartNetworkFollower
-is called and will fail if the network has never been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-- (nullable instancetype)initFromBackup:(BindingsClient* _Nullable)client email:(NSString* _Nullable)email phone:(NSString* _Nullable)phone;
-/**
- * AddFact adds a fact for the user to user discovery. Will only succeed if the
-user is already registered and the system does not have the fact currently
-registered for any user.
-Will fail if the fact string is not well formed.
-This does not complete the fact registration process, it returns a
-confirmation id instead. Over the communications system the fact is
-associated with, a code will be sent. This confirmation ID needs to be
-called along with the code to finalize the fact.
- */
-- (NSString* _Nonnull)addFact:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * ConfirmFact confirms a fact first registered via AddFact. The confirmation ID comes from
-AddFact while the code will come over the associated communications system
- */
-- (BOOL)confirmFact:(NSString* _Nullable)confirmationID code:(NSString* _Nullable)code error:(NSError* _Nullable* _Nullable)error;
-/**
- * Lookup the contact object associated with the given userID.  The
-id is the byte representation of an id.
-This will reject if that id is malformed. The LookupCallback will return
-the associated contact if it exists.
- */
-- (BOOL)lookup:(NSData* _Nullable)idBytes callback:(id<BindingsLookupCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * MultiLookup Looks for the contact object associated with all given userIDs.
-The ids are the byte representation of an id stored in an IDList object.
-This will reject if that id is malformed or if the indexing on the IDList
-object is wrong. The MultiLookupCallback will return with all contacts
-returned within the timeout.
- */
-- (BOOL)multiLookup:(BindingsIdList* _Nullable)ids callback:(id<BindingsMultiLookupCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * Register registers a user with user discovery. Will return an error if the
-network signatures are malformed or if the username is taken. Usernames
-cannot be changed after registration at this time. Will fail if the user is
-already registered.
-Identity does not go over cmix, it occurs over normal communications
- */
-- (BOOL)register:(NSString* _Nullable)username error:(NSError* _Nullable* _Nullable)error;
-/**
- * RemoveFact removes a previously confirmed fact.  Will fail if the passed fact string is
-not well-formed or if the fact is not associated with this client.
-Users cannot remove username facts and must instead remove the user.
- */
-- (BOOL)removeFact:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * RemoveUser deletes a user. The fact sent must be the username.
-This function preserves the username forever and makes it
-unusable.
- */
-- (BOOL)removeUser:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * Search for the passed Facts.  The factList is the stringification of a
-fact list object, look at /bindings/list.go for more on that object.
-This will reject if that object is malformed. The SearchCallback will return
-a list of contacts, each having the facts it hit against.
-This is NOT intended to be used to search for multiple users at once, that
-can have a privacy reduction. Instead, it is intended to be used to search
-for a user where multiple pieces of information is known.
- */
-- (BOOL)search:(NSString* _Nullable)fl callback:(id<BindingsSearchCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * SearchSingle searches for the passed Facts.  The fact is the stringification of a
-fact object, look at /bindings/contact.go for more on that object.
-This will reject if that object is malformed. The SearchCallback will return
-a list of contacts, each having the facts it hit against.
-This only searches for a single fact at a time. It is intended to make some
-simple use cases of the API easier.
- */
-- (BOOL)searchSingle:(NSString* _Nullable)f callback:(id<BindingsSingleSearchCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * SetAlternativeUserDiscovery sets the alternativeUd object within manager.
-Once set, any user discovery operation will go through the alternative
-user discovery service.
-To undo this operation, use UnsetAlternativeUserDiscovery.
-The contact file is the already read in bytes, not the file path for the contact file.
- */
-- (BOOL)setAlternativeUserDiscovery:(NSData* _Nullable)address cert:(NSData* _Nullable)cert contactFile:(NSData* _Nullable)contactFile error:(NSError* _Nullable* _Nullable)error;
-/**
- * UnsetAlternativeUserDiscovery clears out the information from
-the Manager object.
- */
-- (BOOL)unsetAlternativeUserDiscovery:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * Error codes
- */
-FOUNDATION_EXPORT NSString* _Nonnull const BindingsUnrecognizedCode;
-FOUNDATION_EXPORT NSString* _Nonnull const BindingsUnrecognizedMessage;
-
-/**
- * CompressJpeg takes a JPEG image in byte format
-and compresses it based on desired output size
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsCompressJpeg(NSData* _Nullable imgBytes, NSError* _Nullable* _Nullable error);
-
-/**
- * CompressJpegForPreview takes a JPEG image in byte format
-and compresses it based on desired output size
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsCompressJpegForPreview(NSData* _Nullable imgBytes, NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadAndVerifySignedNdfWithUrl retrieves the NDF from a specified URL.
-The NDF is processed into a protobuf containing a signature which
-is verified using the cert string passed in. The NDF is returned as marshaled
-byte data which may be used to start a client.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadAndVerifySignedNdfWithUrl(NSString* _Nullable url, NSString* _Nullable cert, NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadDAppRegistrationDB returns a []byte containing
-the JSON data describing registered dApps.
-See https://git.xx.network/elixxir/registered-dapps
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadDAppRegistrationDB(NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadErrorDB returns a []byte containing the JSON data
-describing client errors.
-See https://git.xx.network/elixxir/client-error-database/
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadErrorDB(NSError* _Nullable* _Nullable error);
-
-/**
- * DumpStack returns a string with the stack trace of every running thread.
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsDumpStack(NSError* _Nullable* _Nullable error);
-
-/**
- * EnableGrpcLogs sets GRPC trace logging
- */
-FOUNDATION_EXPORT void BindingsEnableGrpcLogs(id<BindingsLogWriter> _Nullable writer);
-
-/**
- * ErrorStringToUserFriendlyMessage takes a passed in errStr which will be
-a backend generated error. These may be error specifically written by
-the backend team or lower level errors gotten from low level dependencies.
-This function will parse the error string for common errors provided from
-errToUserErr to provide a more user-friendly error message for the front end.
-If the error is not common, some simple parsing is done on the error message
-to make it more user-accessible, removing backend specific jargon.
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsErrorStringToUserFriendlyMessage(NSString* _Nullable errStr);
-
-/**
- * GenerateSecret creates a secret password using a system-based
-pseudorandom number generator. It takes 1 parameter, `numBytes`,
-which should be set to 32, but can be set higher in certain cases.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsGenerateSecret(long numBytes);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetCMIXParams(NSError* _Nullable* _Nullable error);
-
-/**
- * returns a previously created client. IF be used if the garbage collector
-removes the client instance on the app side.  Is NOT thread safe relative to
-login, newClient, or newPrecannedClient
- */
-FOUNDATION_EXPORT BindingsClient* _Nullable BindingsGetClientSingleton(void);
-
-/**
- * GetDependencies returns the api DEPENDENCIES
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetDependencies(void);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetE2EParams(NSError* _Nullable* _Nullable error);
-
-/**
- * GetGitVersion rturns the api GITVERSION
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetGitVersion(void);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetNetworkParams(NSError* _Nullable* _Nullable error);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetUnsafeParams(NSError* _Nullable* _Nullable error);
-
-/**
- * GetVersion returns the api SEMVER
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetVersion(void);
-
-/**
- * InitializeBackup starts the backup processes that returns backup updates when
-they occur. Any time an event occurs that changes the contents of the backup,
-such as adding or deleting a contact, the backup is triggered and an
-encrypted backup is generated and returned on the updateBackupCb callback.
-Call this function only when enabling backup if it has not already been
-initialized or when the user wants to change their password.
-To resume backup process on app recovery, use ResumeBackup.
- */
-FOUNDATION_EXPORT BindingsBackup* _Nullable BindingsInitializeBackup(NSString* _Nullable password, id<BindingsUpdateBackupFunc> _Nullable updateBackupCb, BindingsClient* _Nullable c, NSError* _Nullable* _Nullable error);
-
-/**
- * LoadSecretWithMnemonic loads the secret stored from the call to
-StoreSecretWithMnemonic. The path given should be the same filepath
-as the path given in StoreSecretWithMnemonic. There should be a file
-in this path called ".recovery". This operation is not tied
-to client operations, as the user will not have a client when trying to
-recover their account.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsLoadSecretWithMnemonic(NSString* _Nullable mnemonic, NSString* _Nullable path, NSError* _Nullable* _Nullable error);
-
-/**
- * sets level of logging. All logs the set level and above will be displayed
-options are:
-	TRACE		- 0
-	DEBUG		- 1
-	INFO 		- 2
-	WARN		- 3
-	ERROR		- 4
-	CRITICAL	- 5
-	FATAL		- 6
-The default state without updates is: INFO
- */
-FOUNDATION_EXPORT BOOL BindingsLogLevel(long level, NSError* _Nullable* _Nullable error);
-
-/**
- * Login will load an existing client from the storageDir
-using the password. This will fail if the client doesn't exist or
-the password is incorrect.
-The password is passed as a byte array so that it can be cleared from
-memory and stored as securely as possible using the memguard library.
-Login does not block on network connection, and instead loads and
-starts subprocesses to perform network operations.
- */
-FOUNDATION_EXPORT BindingsClient* _Nullable BindingsLogin(NSString* _Nullable storageDir, NSData* _Nullable password, NSString* _Nullable parameters, NSError* _Nullable* _Nullable error);
-
-/**
- * MakeIdList creates a new empty IdList.
- */
-FOUNDATION_EXPORT BindingsIdList* _Nullable BindingsMakeIdList(void);
-
-FOUNDATION_EXPORT BindingsIntList* _Nullable BindingsMakeIntList(void);
-
-/**
- * NewClient creates client storage, generates keys, connects, and registers
-with the network. Note that this does not register a username/identity, but
-merely creates a new cryptographic identity for adding such information
-at a later date.
-
-Users of this function should delete the storage directory on error.
- */
-FOUNDATION_EXPORT BOOL BindingsNewClient(NSString* _Nullable network, NSString* _Nullable storageDir, NSData* _Nullable password, NSString* _Nullable regCode, NSError* _Nullable* _Nullable error);
-
-/**
- * NewClientFromBackup constructs a new Client from an encrypted backup. The backup
-is decrypted using the backupPassphrase. On success a successful client creation,
-the function will return a JSON encoded list of the E2E partners
-contained in the backup and a json-encoded string of the parameters stored in the backup
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsNewClientFromBackup(NSString* _Nullable ndfJSON, NSString* _Nullable storageDir, NSData* _Nullable sessionPassword, NSData* _Nullable backupPassphrase, NSData* _Nullable backupFileContents, NSError* _Nullable* _Nullable error);
-
-/**
- * NewDummyTrafficManager creates a DummyTraffic manager and initialises the
-dummy traffic send thread. Note that the manager does not start sending dummy
-traffic until its status is set to true using DummyTraffic.SetStatus.
-The maxNumMessages is the upper bound of the random number of messages sent
-each send. avgSendDeltaMS is the average duration, in milliseconds, to wait
-between sends. Sends occur every avgSendDeltaMS +/- a random duration with an
-upper bound of randomRangeMS.
- */
-FOUNDATION_EXPORT BindingsDummyTraffic* _Nullable BindingsNewDummyTrafficManager(BindingsClient* _Nullable client, long maxNumMessages, long avgSendDeltaMS, long randomRangeMS, NSError* _Nullable* _Nullable error);
-
-/**
- *  fact object
-creates a new fact. The factType must be either:
- 0 - Username
- 1 - Email
- 2 - Phone Number
-The fact must be well formed for the type and must not include commas or
-semicolons. If it is not well formed, it will be rejected.  Phone numbers
-must have the two letter country codes appended.  For the complete set of
-validation, see /elixxir/primitives/fact/fact.go
- */
-FOUNDATION_EXPORT BindingsFact* _Nullable BindingsNewFact(long factType, NSString* _Nullable factStr, NSError* _Nullable* _Nullable error);
-
-/**
- * FactList
- */
-FOUNDATION_EXPORT BindingsFactList* _Nullable BindingsNewFactList(void);
-
-/**
- * NewFileTransferManager creates a new file transfer manager and starts the
-sending and receiving threads. The receiveFunc is called everytime a new file
-transfer is received.
-The parameters string contains file transfer network configuration options
-and is a JSON formatted string of the fileTransfer.Params object. If it is
-left empty, then defaults are used. It is highly recommended that defaults
-are used. If it is set, it must match the following format:
- {"MaxThroughput":150000,"SendTimeout":500000000}
-MaxThroughput is in bytes/sec and SendTimeout is in nanoseconds.
- */
-FOUNDATION_EXPORT BindingsFileTransfer* _Nullable BindingsNewFileTransferManager(BindingsClient* _Nullable client, id<BindingsFileTransferReceiveFunc> _Nullable receiveFunc, NSString* _Nullable parameters, NSError* _Nullable* _Nullable error);
-
-/**
- * NewGroupManager creates a new group chat manager.
- */
-FOUNDATION_EXPORT BindingsGroupChat* _Nullable BindingsNewGroupManager(BindingsClient* _Nullable client, id<BindingsGroupRequestFunc> _Nullable requestFunc, id<BindingsGroupReceiveFunc> _Nullable receiveFunc, NSError* _Nullable* _Nullable error);
-
-/**
- * NewPrecannedClient creates an insecure user with predetermined keys with nodes
-It creates client storage, generates keys, connects, and registers
-with the network. Note that this does not register a username/identity, but
-merely creates a new cryptographic identity for adding such information
-at a later date.
-
-Users of this function should delete the storage directory on error.
- */
-FOUNDATION_EXPORT BOOL BindingsNewPrecannedClient(long precannedID, NSString* _Nullable network, NSString* _Nullable storageDir, NSData* _Nullable password, NSError* _Nullable* _Nullable error);
-
-/**
- * NewUserDiscovery returns a new user discovery object. Only call this once. It must be called
-after StartNetworkFollower is called and will fail if the network has never
-been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUserDiscovery(BindingsClient* _Nullable client, NSError* _Nullable* _Nullable error);
-
-/**
- * NewUserDiscoveryFromBackup returns a new user discovery object. It
-wil set up the manager with the backup data. Pass into it the backed up
-facts, one email and phone number each. This will add the registered facts
-to the backed Store. Any one of these fields may be empty,
-however both fields being empty will cause an error. Any other fact that is not
-an email or phone number will return an error. You may only add a fact for the
-accepted types once each. If you attempt to back up a fact type that has already
-been backed up, an error will be returned. Anytime an error is returned, it means
-the backup was not successful.
-NOTE: Do not use this as a direct store operation. This feature is intended to add facts
-to a backend store that have ALREADY BEEN REGISTERED on the account.
-THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend.
-Only call this once. It must be called after StartNetworkFollower
-is called and will fail if the network has never been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUserDiscoveryFromBackup(BindingsClient* _Nullable client, NSString* _Nullable email, NSString* _Nullable phone, NSError* _Nullable* _Nullable error);
-
-/**
- * NotificationsForMe Check if a notification received is for me
-It returns a NotificationForMeReport which contains a ForMe bool stating if it is for the caller,
-a Type, and a source. These are as follows:
-	TYPE       	SOURCE				DESCRIPTION
-	"default"	recipient user ID	A message with no association
-	"request"	sender user ID		A channel request has been received
-	"reset"	    sender user ID		A channel reset has been received
-	"confirm"	sender user ID		A channel request has been accepted
-	"silent"	sender user ID		A message which should not be notified on
-	"e2e"		sender user ID		reception of an E2E message
-	"group"		group ID			reception of a group chat message
- "endFT"     sender user ID		Last message sent confirming end of file transfer
- "groupRQ"   sender user ID		Request from sender to join a group chat
- */
-FOUNDATION_EXPORT BindingsManyNotificationForMeReport* _Nullable BindingsNotificationsForMe(NSString* _Nullable notifCSV, NSString* _Nullable preimages, NSError* _Nullable* _Nullable error);
-
-/**
- * RegisterLogWriter registers a callback on which logs are written.
- */
-FOUNDATION_EXPORT void BindingsRegisterLogWriter(id<BindingsLogWriter> _Nullable writer);
-
-/**
- * RestoreContactsFromBackup takes as input the jason output of the
-`NewClientFromBackup` function, unmarshals it into IDs, looks up
-each ID in user discovery, and initiates a session reset request.
-This function will not return until every id in the list has been sent a
-request. It should be called again and again until it completes.
-xxDK users should not use this function. This function is used by
-the mobile phone apps and are not intended to be part of the xxDK. It
-should be treated as internal functions specific to the phone apps.
- */
-FOUNDATION_EXPORT BindingsRestoreContactsReport* _Nullable BindingsRestoreContactsFromBackup(NSData* _Nullable backupPartnerIDs, BindingsClient* _Nullable client, BindingsUserDiscovery* _Nullable udManager, id<BindingsLookupCallback> _Nullable lookupCB, id<BindingsRestoreContactsUpdater> _Nullable updatesCb);
-
-/**
- * ResumeBackup starts the backup processes back up with a new callback after it
-has been initialized.
-Call this function only when resuming a backup that has already been
-initialized or to replace the callback.
-To start the backup for the first time or to use a new password, use
-InitializeBackup.
- */
-FOUNDATION_EXPORT BindingsBackup* _Nullable BindingsResumeBackup(id<BindingsUpdateBackupFunc> _Nullable cb, BindingsClient* _Nullable c, NSError* _Nullable* _Nullable error);
-
-/**
- * SetTimeSource sets the network time to a custom source.
- */
-FOUNDATION_EXPORT void BindingsSetTimeSource(id<BindingsTimeSource> _Nullable timeNow);
-
-/**
- * StoreSecretWithMnemonic stores the secret tied with the mnemonic to storage.
-Unlike other storage operations, this does not use EKV, as that is
-intrinsically tied to client operations, which the user will not have while
-trying to recover their account. As such, we store the encrypted data
-directly, with a specified path. Path will be a valid filepath in which the
-recover file will be stored as ".recovery".
-
-As an example, given "home/user/xxmessenger/storagePath",
-the recovery file will be stored at
-"home/user/xxmessenger/storagePath/.recovery"
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsStoreSecretWithMnemonic(NSData* _Nullable secret, NSString* _Nullable path, NSError* _Nullable* _Nullable error);
-
-/**
- * Unmarshals a marshaled contact object, returns an error if it fails
- */
-FOUNDATION_EXPORT BindingsContact* _Nullable BindingsUnmarshalContact(NSData* _Nullable b, NSError* _Nullable* _Nullable error);
-
-/**
- * Unmarshals a marshaled send report object, returns an error if it fails
- */
-FOUNDATION_EXPORT BindingsSendReport* _Nullable BindingsUnmarshalSendReport(NSData* _Nullable b, NSError* _Nullable* _Nullable error);
-
-/**
- * UpdateCommonErrors takes the passed in contents of a JSON file and updates the
-errToUserErr map with the contents of the json file. The JSON's expected format
-conform with the commented examples provides in errToUserErr above.
-NOTE that you should not pass in a file path, but a preloaded JSON file
- */
-FOUNDATION_EXPORT BOOL BindingsUpdateCommonErrors(NSString* _Nullable jsonFile, NSError* _Nullable* _Nullable error);
-
-// skipped function WrapAPIClient with unsupported parameter or return types
-
-
-// skipped function WrapUserDiscovery with unsupported parameter or return types
-
-
-@class BindingsAuthConfirmCallback;
-
-@class BindingsAuthRequestCallback;
-
-@class BindingsAuthResetNotificationCallback;
-
-@class BindingsClientError;
-
-@class BindingsEventCallbackFunctionObject;
-
-@class BindingsFileTransferReceiveFunc;
-
-@class BindingsFileTransferReceivedProgressFunc;
-
-@class BindingsFileTransferSentProgressFunc;
-
-@class BindingsGroupReceiveFunc;
-
-@class BindingsGroupRequestFunc;
-
-@class BindingsListener;
-
-@class BindingsLogWriter;
-
-@class BindingsLookupCallback;
-
-@class BindingsMessageDeliveryCallback;
-
-@class BindingsMultiLookupCallback;
-
-@class BindingsNetworkHealthCallback;
-
-@class BindingsPreimageNotification;
-
-@class BindingsRestoreContactsUpdater;
-
-@class BindingsRoundCompletionCallback;
-
-@class BindingsRoundEventCallback;
-
-@class BindingsSearchCallback;
-
-@class BindingsSingleSearchCallback;
-
-@class BindingsTimeSource;
-
-@class BindingsUpdateBackupFunc;
-
-/**
- * AuthConfirmCallback notifies the register whenever they receive an auth
-request confirmation
- */
-@interface BindingsAuthConfirmCallback : NSObject <goSeqRefInterface, BindingsAuthConfirmCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)partner;
-@end
-
-/**
- * AuthRequestCallback notifies the register whenever they receive an auth
-request
- */
-@interface BindingsAuthRequestCallback : NSObject <goSeqRefInterface, BindingsAuthRequestCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-/**
- * AuthRequestCallback notifies the register whenever they receive an auth
-request
- */
-@interface BindingsAuthResetNotificationCallback : NSObject <goSeqRefInterface, BindingsAuthResetNotificationCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@interface BindingsClientError : NSObject <goSeqRefInterface, BindingsClientError> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)report:(NSString* _Nullable)source message:(NSString* _Nullable)message trace:(NSString* _Nullable)trace;
-@end
-
-/**
- * EventCallbackFunctionObject bindings interface which contains function
-that implements the EventCallbackFunction
- */
-@interface BindingsEventCallbackFunctionObject : NSObject <goSeqRefInterface, BindingsEventCallbackFunctionObject> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)reportEvent:(long)priority category:(NSString* _Nullable)category evtType:(NSString* _Nullable)evtType details:(NSString* _Nullable)details;
-@end
-
-/**
- * FileTransferReceiveFunc contains a function callback that notifies the
-receiver of an incoming file transfer. It is called on the reception of the
-initial file transfer message.
- */
-@interface BindingsFileTransferReceiveFunc : NSObject <goSeqRefInterface, BindingsFileTransferReceiveFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)receiveCallback:(NSData* _Nullable)tid fileName:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType sender:(NSData* _Nullable)sender size:(long)size preview:(NSData* _Nullable)preview;
-@end
-
-/**
- * FileTransferReceivedProgressFunc contains a function callback that tracks the
-progress of receiving a file. It is called when a file part is received, the
-transfer completes, or on error.
- */
-@interface BindingsFileTransferReceivedProgressFunc : NSObject <goSeqRefInterface, BindingsFileTransferReceivedProgressFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)receivedProgressCallback:(BOOL)completed received:(long)received total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-/**
- * FileTransferSentProgressFunc contains a function callback that tracks the
-progress of sending a file. It is called when a file part is sent, a file
-part arrives, the transfer completes, or on error.
- */
-@interface BindingsFileTransferSentProgressFunc : NSObject <goSeqRefInterface, BindingsFileTransferSentProgressFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)sentProgressCallback:(BOOL)completed sent:(long)sent arrived:(long)arrived total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-/**
- * GroupReceiveFunc contains a function callback that is called when a group
-message is received.
- */
-@interface BindingsGroupReceiveFunc : NSObject <goSeqRefInterface, BindingsGroupReceiveFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)groupReceiveCallback:(BindingsGroupMessageReceive* _Nullable)msg;
-@end
-
-/**
- * GroupRequestFunc contains a function callback that is called when a group
-request is received.
- */
-@interface BindingsGroupRequestFunc : NSObject <goSeqRefInterface, BindingsGroupRequestFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)groupRequestCallback:(BindingsGroup* _Nullable)g;
-@end
-
-/**
- * Listener provides a callback to hear a message
-An object implementing this interface can be called back when the client
-gets a message of the type that the registerer specified at registration
-time.
- */
-@interface BindingsListener : NSObject <goSeqRefInterface, BindingsListener> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * Hear is called to receive a message in the UI
- */
-- (void)hear:(BindingsMessage* _Nullable)message;
-/**
- * Returns a name, used for debugging
- */
-- (NSString* _Nonnull)name;
-@end
-
-@interface BindingsLogWriter : NSObject <goSeqRefInterface, BindingsLogWriter> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)log:(NSString* _Nullable)p0;
-@end
-
-/**
- * LookupCallback returns the result of a single lookup
- */
-@interface BindingsLookupCallback : NSObject <goSeqRefInterface, BindingsLookupCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-/**
- * MessageDeliveryCallback gets called on the determination if all events
-related to a message send were successful.
- */
-@interface BindingsMessageDeliveryCallback : NSObject <goSeqRefInterface, BindingsMessageDeliveryCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(NSData* _Nullable)msgID delivered:(BOOL)delivered timedOut:(BOOL)timedOut roundResults:(NSData* _Nullable)roundResults;
-@end
-
-/**
- * MultiLookupCallback returns the result of many parallel lookups
- */
-@interface BindingsMultiLookupCallback : NSObject <goSeqRefInterface, BindingsMultiLookupCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContactList* _Nullable)Succeeded failed:(BindingsIdList* _Nullable)failed errors:(NSString* _Nullable)errors;
-@end
-
-/**
- * A callback when which is used to receive notification if network health
-changes
- */
-@interface BindingsNetworkHealthCallback : NSObject <goSeqRefInterface, BindingsNetworkHealthCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BOOL)p0;
-@end
-
-@interface BindingsPreimageNotification : NSObject <goSeqRefInterface, BindingsPreimageNotification> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)notify:(NSData* _Nullable)identity deleted:(BOOL)deleted;
-@end
-
-/**
- * RestoreContactsUpdater interface provides a callback function
-for receiving update information from RestoreContactsFromBackup.
- */
-@interface BindingsRestoreContactsUpdater : NSObject <goSeqRefInterface, BindingsRestoreContactsUpdater> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * RestoreContactsCallback is called to report the current # of contacts
-that have been found and how many have been restored
-against the total number that need to be
-processed. If an error occurs it it set on the err variable as a
-plain string.
- */
-- (void)restoreContactsCallback:(long)numFound numRestored:(long)numRestored total:(long)total err:(NSString* _Nullable)err;
-@end
-
-/**
- * RoundCompletionCallback is returned when the completion of a round is known.
- */
-@interface BindingsRoundCompletionCallback : NSObject <goSeqRefInterface, BindingsRoundCompletionCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(long)rid success:(BOOL)success timedOut:(BOOL)timedOut;
-@end
-
-/**
- * RoundEventCallback handles waiting on the exact state of a round on
-the cMix network.
- */
-@interface BindingsRoundEventCallback : NSObject <goSeqRefInterface, BindingsRoundEventCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(long)rid state:(long)state timedOut:(BOOL)timedOut;
-@end
-
-/**
- * SearchCallback returns the result of a search
- */
-@interface BindingsSearchCallback : NSObject <goSeqRefInterface, BindingsSearchCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContactList* _Nullable)contacts error:(NSString* _Nullable)error;
-@end
-
-/**
- * SingleSearchCallback returns the result of a single search
- */
-@interface BindingsSingleSearchCallback : NSObject <goSeqRefInterface, BindingsSingleSearchCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@interface BindingsTimeSource : NSObject <goSeqRefInterface, BindingsTimeSource> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (int64_t)nowMs;
-@end
-
-/**
- * UpdateBackupFunc contains a function callback that returns new backups.
- */
-@interface BindingsUpdateBackupFunc : NSObject <goSeqRefInterface, BindingsUpdateBackupFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)updateBackup:(NSData* _Nullable)encryptedBackup;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Universe.objc.h b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Universe.objc.h
deleted file mode 100644
index 019e7502d581983722a15bf30799e85cbc5dd766..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Universe.objc.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Objective-C API for talking to  Go package.
-//   gobind -lang=objc 
-//
-// File is generated by gobind. Do not edit.
-
-#ifndef __Universe_H__
-#define __Universe_H__
-
-@import Foundation;
-#include "ref.h"
-
-@protocol Universeerror;
-@class Universeerror;
-
-@protocol Universeerror <NSObject>
-- (NSString* _Nonnull)error;
-@end
-
-@class Universeerror;
-
-@interface Universeerror : NSError <goSeqRefInterface, Universeerror> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (NSString* _Nonnull)error;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/ref.h b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/ref.h
deleted file mode 100644
index b8036a4d85c7387f3def61473a071b5d8c4c8208..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/ref.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#ifndef __GO_REF_HDR__
-#define __GO_REF_HDR__
-
-#include <Foundation/Foundation.h>
-
-// GoSeqRef is an object tagged with an integer for passing back and
-// forth across the language boundary. A GoSeqRef may represent either
-// an instance of a Go object, or an Objective-C object passed to Go.
-// The explicit allocation of a GoSeqRef is used to pin a Go object
-// when it is passed to Objective-C. The Go seq package maintains a
-// reference to the Go object in a map keyed by the refnum along with
-// a reference count. When the reference count reaches zero, the Go
-// seq package will clear the corresponding entry in the map.
-@interface GoSeqRef : NSObject {
-}
-@property(readonly) int32_t refnum;
-@property(strong) id obj; // NULL when representing a Go object.
-
-// new GoSeqRef object to proxy a Go object. The refnum must be
-// provided from Go side.
-- (instancetype)initWithRefnum:(int32_t)refnum obj:(id)obj;
-
-- (int32_t)incNum;
-
-@end
-
-@protocol goSeqRefInterface
--(GoSeqRef*) _ref;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Modules/module.modulemap b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Modules/module.modulemap
deleted file mode 100644
index 4316a5b24058edfc18ffb2dc7f7a982e8353441a..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Modules/module.modulemap
+++ /dev/null
@@ -1,8 +0,0 @@
-framework module "Bindings" {
-	header "ref.h"
-    header "Bindings.objc.h"
-    header "Universe.objc.h"
-    header "Bindings.h"
-
-    export *
-}
\ No newline at end of file
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Resources/Info.plist b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Resources/Info.plist
deleted file mode 100644
index 0d1a4b8ab9b1fc8e9357197398f73353470cb636..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Resources/Info.plist
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-    <plist version="1.0">
-      <dict>
-      </dict>
-    </plist>
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Bindings b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Bindings
deleted file mode 100644
index 8d78f84d50a0b0719b795f7b342f6109c2b848ec..0000000000000000000000000000000000000000
Binary files a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Bindings and /dev/null differ
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Headers/Bindings.h b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Headers/Bindings.h
deleted file mode 100644
index 8906a7da239705b790cb2bb64de92f806640cb38..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Headers/Bindings.h
+++ /dev/null
@@ -1,13 +0,0 @@
-
-// Objective-C API for talking to the following Go packages
-//
-//	gitlab.com/elixxir/client/bindings
-//
-// File is generated by gomobile bind. Do not edit.
-#ifndef __Bindings_FRAMEWORK_H__
-#define __Bindings_FRAMEWORK_H__
-
-#include "Bindings.objc.h"
-#include "Universe.objc.h"
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Headers/Bindings.objc.h b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Headers/Bindings.objc.h
deleted file mode 100644
index 32bf6d116888f787ced27b01b95cb4e1b2c1138b..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Headers/Bindings.objc.h
+++ /dev/null
@@ -1,2083 +0,0 @@
-// Objective-C API for talking to gitlab.com/elixxir/client/bindings Go package.
-//   gobind -lang=objc gitlab.com/elixxir/client/bindings
-//
-// File is generated by gobind. Do not edit.
-
-#ifndef __Bindings_H__
-#define __Bindings_H__
-
-@import Foundation;
-#include "ref.h"
-#include "Universe.objc.h"
-
-
-@class BindingsBackup;
-@class BindingsBackupReport;
-@class BindingsClient;
-@class BindingsContact;
-@class BindingsContactList;
-@class BindingsDummyTraffic;
-@class BindingsFact;
-@class BindingsFactList;
-@class BindingsFilePartTracker;
-@class BindingsFileTransfer;
-@class BindingsGroup;
-@class BindingsGroupChat;
-@class BindingsGroupMember;
-@class BindingsGroupMembership;
-@class BindingsGroupMessageReceive;
-@class BindingsGroupReportDisk;
-@class BindingsGroupSendReport;
-@class BindingsIdList;
-@class BindingsIntList;
-@class BindingsManyNotificationForMeReport;
-@class BindingsMessage;
-@class BindingsNewGroupReport;
-@class BindingsNodeRegistrationsStatus;
-@class BindingsNotificationForMeReport;
-@class BindingsRestoreContactsReport;
-@class BindingsRoundList;
-@class BindingsSendReport;
-@class BindingsSendReportDisk;
-@class BindingsUnregister;
-@class BindingsUser;
-@class BindingsUserDiscovery;
-@protocol BindingsAuthConfirmCallback;
-@class BindingsAuthConfirmCallback;
-@protocol BindingsAuthRequestCallback;
-@class BindingsAuthRequestCallback;
-@protocol BindingsAuthResetNotificationCallback;
-@class BindingsAuthResetNotificationCallback;
-@protocol BindingsClientError;
-@class BindingsClientError;
-@protocol BindingsEventCallbackFunctionObject;
-@class BindingsEventCallbackFunctionObject;
-@protocol BindingsFileTransferReceiveFunc;
-@class BindingsFileTransferReceiveFunc;
-@protocol BindingsFileTransferReceivedProgressFunc;
-@class BindingsFileTransferReceivedProgressFunc;
-@protocol BindingsFileTransferSentProgressFunc;
-@class BindingsFileTransferSentProgressFunc;
-@protocol BindingsGroupReceiveFunc;
-@class BindingsGroupReceiveFunc;
-@protocol BindingsGroupRequestFunc;
-@class BindingsGroupRequestFunc;
-@protocol BindingsListener;
-@class BindingsListener;
-@protocol BindingsLogWriter;
-@class BindingsLogWriter;
-@protocol BindingsLookupCallback;
-@class BindingsLookupCallback;
-@protocol BindingsMessageDeliveryCallback;
-@class BindingsMessageDeliveryCallback;
-@protocol BindingsMultiLookupCallback;
-@class BindingsMultiLookupCallback;
-@protocol BindingsNetworkHealthCallback;
-@class BindingsNetworkHealthCallback;
-@protocol BindingsPreimageNotification;
-@class BindingsPreimageNotification;
-@protocol BindingsRestoreContactsUpdater;
-@class BindingsRestoreContactsUpdater;
-@protocol BindingsRoundCompletionCallback;
-@class BindingsRoundCompletionCallback;
-@protocol BindingsRoundEventCallback;
-@class BindingsRoundEventCallback;
-@protocol BindingsSearchCallback;
-@class BindingsSearchCallback;
-@protocol BindingsSingleSearchCallback;
-@class BindingsSingleSearchCallback;
-@protocol BindingsTimeSource;
-@class BindingsTimeSource;
-@protocol BindingsUpdateBackupFunc;
-@class BindingsUpdateBackupFunc;
-
-@protocol BindingsAuthConfirmCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)partner;
-@end
-
-@protocol BindingsAuthRequestCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@protocol BindingsAuthResetNotificationCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@protocol BindingsClientError <NSObject>
-- (void)report:(NSString* _Nullable)source message:(NSString* _Nullable)message trace:(NSString* _Nullable)trace;
-@end
-
-@protocol BindingsEventCallbackFunctionObject <NSObject>
-- (void)reportEvent:(long)priority category:(NSString* _Nullable)category evtType:(NSString* _Nullable)evtType details:(NSString* _Nullable)details;
-@end
-
-@protocol BindingsFileTransferReceiveFunc <NSObject>
-- (void)receiveCallback:(NSData* _Nullable)tid fileName:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType sender:(NSData* _Nullable)sender size:(long)size preview:(NSData* _Nullable)preview;
-@end
-
-@protocol BindingsFileTransferReceivedProgressFunc <NSObject>
-- (void)receivedProgressCallback:(BOOL)completed received:(long)received total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-@protocol BindingsFileTransferSentProgressFunc <NSObject>
-- (void)sentProgressCallback:(BOOL)completed sent:(long)sent arrived:(long)arrived total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-@protocol BindingsGroupReceiveFunc <NSObject>
-- (void)groupReceiveCallback:(BindingsGroupMessageReceive* _Nullable)msg;
-@end
-
-@protocol BindingsGroupRequestFunc <NSObject>
-- (void)groupRequestCallback:(BindingsGroup* _Nullable)g;
-@end
-
-@protocol BindingsListener <NSObject>
-/**
- * Hear is called to receive a message in the UI
- */
-- (void)hear:(BindingsMessage* _Nullable)message;
-/**
- * Returns a name, used for debugging
- */
-- (NSString* _Nonnull)name;
-@end
-
-@protocol BindingsLogWriter <NSObject>
-- (void)log:(NSString* _Nullable)p0;
-@end
-
-@protocol BindingsLookupCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsMessageDeliveryCallback <NSObject>
-- (void)eventCallback:(NSData* _Nullable)msgID delivered:(BOOL)delivered timedOut:(BOOL)timedOut roundResults:(NSData* _Nullable)roundResults;
-@end
-
-@protocol BindingsMultiLookupCallback <NSObject>
-- (void)callback:(BindingsContactList* _Nullable)Succeeded failed:(BindingsIdList* _Nullable)failed errors:(NSString* _Nullable)errors;
-@end
-
-@protocol BindingsNetworkHealthCallback <NSObject>
-- (void)callback:(BOOL)p0;
-@end
-
-@protocol BindingsPreimageNotification <NSObject>
-- (void)notify:(NSData* _Nullable)identity deleted:(BOOL)deleted;
-@end
-
-@protocol BindingsRestoreContactsUpdater <NSObject>
-/**
- * RestoreContactsCallback is called to report the current # of contacts
-that have been found and how many have been restored
-against the total number that need to be
-processed. If an error occurs it it set on the err variable as a
-plain string.
- */
-- (void)restoreContactsCallback:(long)numFound numRestored:(long)numRestored total:(long)total err:(NSString* _Nullable)err;
-@end
-
-@protocol BindingsRoundCompletionCallback <NSObject>
-- (void)eventCallback:(long)rid success:(BOOL)success timedOut:(BOOL)timedOut;
-@end
-
-@protocol BindingsRoundEventCallback <NSObject>
-- (void)eventCallback:(long)rid state:(long)state timedOut:(BOOL)timedOut;
-@end
-
-@protocol BindingsSearchCallback <NSObject>
-- (void)callback:(BindingsContactList* _Nullable)contacts error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsSingleSearchCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsTimeSource <NSObject>
-- (int64_t)nowMs;
-@end
-
-@protocol BindingsUpdateBackupFunc <NSObject>
-- (void)updateBackup:(NSData* _Nullable)encryptedBackup;
-@end
-
-@interface BindingsBackup : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * AddJson stores a passed in json string in the backup structure
- */
-- (void)addJson:(NSString* _Nullable)json;
-/**
- * IsBackupRunning returns true if the backup has been initialized and is
-running. Returns false if it has been stopped.
- */
-- (BOOL)isBackupRunning;
-/**
- * StopBackup stops the backup processes and deletes the user's password from
-storage. To enable backups again, call InitializeBackup.
- */
-- (BOOL)stopBackup:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsBackupReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field BackupReport.RestoredContacts with unsupported type: []*gitlab.com/xx_network/primitives/id.ID
-
-@property (nonatomic) NSString* _Nonnull params;
-@end
-
-/**
- * BindingsClient wraps the api.Client, implementing additional functions
-to support the gomobile Client interface
- */
-@interface BindingsClient : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BOOL)confirmAuthenticatedChannel:(NSData* _Nullable)recipientMarshaled ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteAllRequests clears all requests from Client's auth storage.
- */
-- (BOOL)deleteAllRequests:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteContact is a function which removes a contact from Client's storage
- */
-- (BOOL)deleteContact:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteReceiveRequests clears receive requests from Client's auth storage.
- */
-- (BOOL)deleteReceiveRequests:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteRequest will delete a request, agnostic of request type
-for the given partner ID. If no request exists for this
-partner ID an error will be returned.
- */
-- (BOOL)deleteRequest:(NSData* _Nullable)requesterUserId error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteSentRequests clears sent requests from Client's auth storage.
- */
-- (BOOL)deleteSentRequests:(NSError* _Nullable* _Nullable)error;
-// skipped method Client.GetInternalClient with unsupported parameter or return types
-
-/**
- * GetNodeRegistrationStatus returns a struct with the number of nodes the
-client is registered with and the number total.
- */
-- (BindingsNodeRegistrationsStatus* _Nullable)getNodeRegistrationStatus:(NSError* _Nullable* _Nullable)error;
-/**
- * GetPartners returns a list of
- */
-- (NSData* _Nullable)getPartners:(NSError* _Nullable* _Nullable)error;
-/**
- * GetPreferredBins returns the geographic bin or bins that the provided two
-character country code is a part of. The bins are returned as CSV.
- */
-- (NSString* _Nonnull)getPreferredBins:(NSString* _Nullable)countryCode error:(NSError* _Nullable* _Nullable)error;
-- (NSString* _Nonnull)getPreimages:(NSData* _Nullable)identity;
-// skipped method Client.GetRateLimitParams with unsupported parameter or return types
-
-- (NSString* _Nonnull)getRelationshipFingerprint:(NSData* _Nullable)partnerID error:(NSError* _Nullable* _Nullable)error;
-/**
- * Returns a user object from which all information about the current user
-can be gleaned
- */
-- (BindingsUser* _Nullable)getUser;
-/**
- * HasRunningProcessies checks if any background threads are running.
-returns true if none are running. This is meant to be
-used when NetworkFollowerStatus() returns Stopping.
-Due to the handling of comms on iOS, where the OS can
-block indefiently, it may not enter the stopped
-state apropreatly. This can be used instead.
- */
-- (BOOL)hasRunningProcessies;
-/**
- * returns true if the network is read to be in a healthy state where
-messages can be sent
- */
-- (BOOL)isNetworkHealthy;
-- (BindingsContact* _Nullable)makePrecannedAuthenticatedChannel:(long)precannedID error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the state of the network follower. Returns:
-Stopped 	- 0
-Starting - 1000
-Running	- 2000
-Stopping	- 3000
- */
-- (long)networkFollowerStatus;
-- (void)registerAuthCallbacks:(id<BindingsAuthRequestCallback> _Nullable)request confirm:(id<BindingsAuthConfirmCallback> _Nullable)confirm reset:(id<BindingsAuthResetNotificationCallback> _Nullable)reset;
-/**
- * RegisterClientErrorCallback registers the callback to handle errors from the
-long running threads controlled by StartNetworkFollower and StopNetworkFollower
- */
-- (void)registerClientErrorCallback:(id<BindingsClientError> _Nullable)clientError;
-/**
- * RegisterEventCallback records the given function to receive
-ReportableEvent objects. It returns the internal index
-of the callback so that it can be deleted later.
- */
-- (BOOL)registerEventCallback:(NSString* _Nullable)name myObj:(id<BindingsEventCallbackFunctionObject> _Nullable)myObj error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterForNotifications accepts firebase messaging token
- */
-- (BOOL)registerForNotifications:(NSString* _Nullable)token error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterListener records and installs a listener for messages
-matching specific uid, msgType, and/or username
-Returns a ListenerUnregister interface which can be
-
-to register for any userID, pass in an id with length 0 or an id with
-all zeroes
-
-to register for any message type, pass in a message type of 0
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types
- */
-- (BindingsUnregister* _Nullable)registerListener:(NSData* _Nullable)uid msgType:(long)msgType listener:(id<BindingsListener> _Nullable)listener error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterNetworkHealthCB registers the network health callback to be called
-any time the network health changes. Returns a unique ID that can be used to
-unregister the network health callback.
- */
-- (int64_t)registerNetworkHealthCB:(id<BindingsNetworkHealthCallback> _Nullable)nhc;
-- (void)registerPreimageCallback:(NSData* _Nullable)identity pin:(id<BindingsPreimageNotification> _Nullable)pin;
-/**
- * RegisterRoundEventsHandler registers a callback interface for round
-events.
-The rid is the round the event attaches to
-The timeoutMS is the number of milliseconds until the event fails, and the
-validStates are a list of states (one per byte) on which the event gets
-triggered
-States:
- 0x00 - PENDING (Never seen by client)
- 0x01 - PRECOMPUTING
- 0x02 - STANDBY
- 0x03 - QUEUED
- 0x04 - REALTIME
- 0x05 - COMPLETED
- 0x06 - FAILED
-These states are defined in elixxir/primitives/states/state.go
- */
-- (BindingsUnregister* _Nullable)registerRoundEventsHandler:(long)rid cb:(id<BindingsRoundEventCallback> _Nullable)cb timeoutMS:(long)timeoutMS il:(BindingsIntList* _Nullable)il;
-- (void)replayRequests;
-- (BOOL)requestAuthenticatedChannel:(NSData* _Nullable)recipientMarshaled meMarshaled:(NSData* _Nullable)meMarshaled message:(NSString* _Nullable)message ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-- (BOOL)resetSession:(NSData* _Nullable)recipientMarshaled meMarshaled:(NSData* _Nullable)meMarshaled message:(NSString* _Nullable)message ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * This will return the round the message was sent on if it is successfully sent
-This can be used to register a round event to learn about message delivery.
-on failure a round id of -1 is returned
- */
-- (BOOL)sendCmix:(NSData* _Nullable)recipient contents:(NSData* _Nullable)contents parameters:(NSString* _Nullable)parameters ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * SendE2E sends an end-to-end payload to the provided recipient with
-the provided msgType. Returns the list of rounds in which parts of
-the message were sent or an error if it fails.
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types
- */
-- (BindingsSendReport* _Nullable)sendE2E:(NSData* _Nullable)recipient payload:(NSData* _Nullable)payload messageType:(long)messageType parameters:(NSString* _Nullable)parameters error:(NSError* _Nullable* _Nullable)error;
-/**
- * SendUnsafe sends an unencrypted payload to the provided recipient
-with the provided msgType. Returns the list of rounds in which parts
-of the message were sent or an error if it fails.
-NOTE: Do not use this function unless you know what you are doing.
-This function always produces an error message in client logging.
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types with custom types
- */
-- (BindingsRoundList* _Nullable)sendUnsafe:(NSData* _Nullable)recipient payload:(NSData* _Nullable)payload messageType:(long)messageType parameters:(NSString* _Nullable)parameters error:(NSError* _Nullable* _Nullable)error;
-/**
- * SetProxiedBins updates the host pool filter that filters out gateways that
-are not in one of the specified bins. The provided bins should be CSV.
- */
-- (BOOL)setProxiedBins:(NSString* _Nullable)binStringsCSV error:(NSError* _Nullable* _Nullable)error;
-/**
- * StartNetworkFollower kicks off the tracking of the network. It starts
-long running network client threads and returns an object for checking
-state and stopping those threads.
-Call this when returning from sleep and close when going back to
-sleep.
-These threads may become a significant drain on battery when offline, ensure
-they are stopped if there is no internet access
-Threads Started:
-  - Network Follower (/network/follow.go)
-  	tracks the network events and hands them off to workers for handling
-  - Historical Round Retrieval (/network/rounds/historical.go)
-		Retrieves data about rounds which are too old to be stored by the client
-	 - Message Retrieval Worker Group (/network/rounds/retrieve.go)
-		Requests all messages in a given round from the gateway of the last node
-	 - Message Handling Worker Group (/network/message/handle.go)
-		Decrypts and partitions messages when signals via the Switchboard
-	 - Health Tracker (/network/health)
-		Via the network instance tracks the state of the network
-	 - Garbled Messages (/network/message/garbled.go)
-		Can be signaled to check all recent messages which could be be decoded
-		Uses a message store on disk for persistence
-	 - Critical Messages (/network/message/critical.go)
-		Ensures all protocol layer mandatory messages are sent
-		Uses a message store on disk for persistence
-	 - KeyExchange Trigger (/keyExchange/trigger.go)
-		Responds to sent rekeys and executes them
-  - KeyExchange Confirm (/keyExchange/confirm.go)
-		Responds to confirmations of successful rekey operations
- */
-- (BOOL)startNetworkFollower:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * StopNetworkFollower stops the network follower if it is running.
-It returns errors if the Follower is in the wrong status to stop or if it
-fails to stop it.
-if the network follower is running and this fails, the client object will
-most likely be in an unrecoverable state and need to be trashed.
- */
-- (BOOL)stopNetworkFollower:(NSError* _Nullable* _Nullable)error;
-/**
- * UnregisterEventCallback deletes the callback identified by the
-index. It returns an error if it fails.
- */
-- (void)unregisterEventCallback:(NSString* _Nullable)name;
-/**
- * UnregisterForNotifications unregister user for notifications
- */
-- (BOOL)unregisterForNotifications:(NSError* _Nullable* _Nullable)error;
-- (void)unregisterNetworkHealthCB:(int64_t)funcID;
-- (BOOL)verifyOwnership:(NSData* _Nullable)receivedMarshaled verifiedMarshaled:(NSData* _Nullable)verifiedMarshaled ret0_:(BOOL* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * WaitForMessageDelivery allows the caller to get notified if the rounds a
-message was sent in successfully completed. Under the hood, this uses an API
-which uses the internal round data, network historical round lookup, and
-waiting on network events to determine what has (or will) occur.
-
-The callbacks will return at timeoutMS if no state update occurs
-
-This function takes the marshaled send report to ensure a memory leak does
-not occur as a result of both sides of the bindings holding a reference to
-the same pointer.
- */
-- (BOOL)waitForMessageDelivery:(NSData* _Nullable)marshaledSendReport mdc:(id<BindingsMessageDeliveryCallback> _Nullable)mdc timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * WaitForNewtwork will block until either the network is healthy or the
-passed timeout. It will return true if the network is healthy
- */
-- (BOOL)waitForNetwork:(long)timeoutMS;
-/**
- * WaitForRoundCompletion allows the caller to get notified if a round
-has completed (or failed). Under the hood, this uses an API which uses the internal
-round data, network historical round lookup, and waiting on network events
-to determine what has (or will) occur.
-
-The callbacks will return at timeoutMS if no state update occurs
- */
-- (BOOL)waitForRoundCompletion:(long)roundID rec:(id<BindingsRoundCompletionCallback> _Nullable)rec timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- *  contact object
- */
-@interface BindingsContact : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped method Contact.GetAPIContact with unsupported parameter or return types
-
-/**
- * GetDHPublicKey returns the public key associated with the Contact.
- */
-- (NSData* _Nullable)getDHPublicKey;
-/**
- * Returns a fact list for adding and getting facts to and from the contact
- */
-- (BindingsFactList* _Nullable)getFactList;
-/**
- * GetID returns the user ID for this user.
- */
-- (NSData* _Nullable)getID;
-/**
- * GetDHPublicKey returns hash of a DH proof of key ownership.
- */
-- (NSData* _Nullable)getOwnershipProof;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsContactList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Gets a stored round ID at the given index
- */
-- (BindingsContact* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the number of round IDs stored
- */
-- (long)len;
-@end
-
-/**
- * DummyTraffic contains the file dummy traffic manager. The manager can be used
-to set and get the status of the send thread.
- */
-@interface BindingsDummyTraffic : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewDummyTrafficManager creates a DummyTraffic manager and initialises the
-dummy traffic send thread. Note that the manager does not start sending dummy
-traffic until its status is set to true using DummyTraffic.SetStatus.
-The maxNumMessages is the upper bound of the random number of messages sent
-each send. avgSendDeltaMS is the average duration, in milliseconds, to wait
-between sends. Sends occur every avgSendDeltaMS +/- a random duration with an
-upper bound of randomRangeMS.
- */
-- (nullable instancetype)initManager:(BindingsClient* _Nullable)client maxNumMessages:(long)maxNumMessages avgSendDeltaMS:(long)avgSendDeltaMS randomRangeMS:(long)randomRangeMS;
-/**
- * GetStatus returns the current state of the dummy traffic send thread. It has
-the following return values:
- true  = send thread is sending dummy messages
- false = send thread is paused/stopped and not sending dummy messages
-Note that this function does not return the status set by SetStatus directly;
-it returns the current status of the send thread, which means any call to
-SetStatus will have a small delay before it is returned by GetStatus.
- */
-- (BOOL)getStatus;
-/**
- * SetStatus sets the state of the dummy traffic send thread, which determines
-if the thread is running or paused. The possible statuses are:
- true  = send thread is sending dummy messages
- false = send thread is paused/stopped and not sending dummy messages
-Returns an error if the channel is full.
-Note that this function cannot change the status of the send thread if it has
-yet to be started or stopped.
- */
-- (BOOL)setStatus:(BOOL)status error:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsFact : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- *  fact object
-creates a new fact. The factType must be either:
- 0 - Username
- 1 - Email
- 2 - Phone Number
-The fact must be well formed for the type and must not include commas or
-semicolons. If it is not well formed, it will be rejected.  Phone numbers
-must have the two letter country codes appended.  For the complete set of
-validation, see /elixxir/primitives/fact/fact.go
- */
-- (nullable instancetype)init:(long)factType factStr:(NSString* _Nullable)factStr;
-- (NSString* _Nonnull)get;
-- (NSString* _Nonnull)stringify;
-- (long)type;
-@end
-
-@interface BindingsFactList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * FactList
- */
-- (nullable instancetype)init;
-- (BOOL)add:(NSString* _Nullable)factData factType:(long)factType error:(NSError* _Nullable* _Nullable)error;
-- (BindingsFact* _Nullable)get:(long)i;
-- (long)num;
-- (NSString* _Nonnull)stringify:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * FilePartTracker contains the interfaces.FilePartTracker.
- */
-@interface BindingsFilePartTracker : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetNumParts returns the total number of file parts in the transfer.
- */
-- (long)getNumParts;
-/**
- * GetPartStatus returns the status of the file part with the given part number.
-The possible values for the status are:
-0 = unsent
-1 = sent (sender has sent a part, but it has not arrived)
-2 = arrived (sender has sent a part, and it has arrived)
-3 = received (receiver has received a part)
- */
-- (long)getPartStatus:(long)partNum;
-@end
-
-/**
- * FileTransfer contains the file transfer manager.
- */
-@interface BindingsFileTransfer : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewFileTransferManager creates a new file transfer manager and starts the
-sending and receiving threads. The receiveFunc is called everytime a new file
-transfer is received.
-The parameters string contains file transfer network configuration options
-and is a JSON formatted string of the fileTransfer.Params object. If it is
-left empty, then defaults are used. It is highly recommended that defaults
-are used. If it is set, it must match the following format:
- {"MaxThroughput":150000,"SendTimeout":500000000}
-MaxThroughput is in bytes/sec and SendTimeout is in nanoseconds.
- */
-- (nullable instancetype)initManager:(BindingsClient* _Nullable)client receiveFunc:(id<BindingsFileTransferReceiveFunc> _Nullable)receiveFunc parameters:(NSString* _Nullable)parameters;
-/**
- * CloseSend deletes a sent file transfer from the sent transfer map and from
-storage once a transfer has completed or reached the retry limit. Returns an
-error if the transfer has not run out of retries.
- */
-- (BOOL)closeSend:(NSData* _Nullable)transferID error:(NSError* _Nullable* _Nullable)error;
-/**
- * GetMaxFileNameByteLength returns the maximum length, in bytes, allowed for a
-file name.
- */
-- (long)getMaxFileNameByteLength;
-/**
- * GetMaxFilePreviewSize returns the maximum file preview size, in bytes.
- */
-- (long)getMaxFilePreviewSize;
-/**
- * GetMaxFileSize returns the maximum file size, in bytes, allowed to be
-transferred.
- */
-- (long)getMaxFileSize;
-/**
- * GetMaxFileTypeByteLength returns the maximum length, in bytes, allowed for a
-file type.
- */
-- (long)getMaxFileTypeByteLength;
-/**
- * Receive returns the fully assembled file on the completion of the transfer.
-It deletes the transfer from the received transfer map and from storage.
-Returns an error if the transfer is not complete, the full file cannot be
-verified, or if the transfer cannot be found.
- */
-- (NSData* _Nullable)receive:(NSData* _Nullable)transferID error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterReceiveProgressCallback allows for the registration of a callback to
-track the progress of an individual received file transfer. The callback will
-be called immediately when added to report the current status of the
-transfer. It will then call every time a file part is received, the transfer
-completes, or an error occurs. It is called at most once ever period, which
-means if events occur faster than the period, then they will not be reported
-and instead, the progress will be reported once at the end of the period.
-Once the callback reports that the transfer has completed, the recipient
-can get the full file by calling Receive.
-The period is specified in milliseconds.
- */
-- (BOOL)registerReceiveProgressCallback:(NSData* _Nullable)transferID progressFunc:(id<BindingsFileTransferReceivedProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterSendProgressCallback allows for the registration of a callback to
-track the progress of an individual sent file transfer. The callback will be
-called immediately when added to report the current status of the transfer.
-It will then call every time a file part is sent, a file part arrives, the
-transfer completes, or an error occurs. It is called at most once every
-period, which means if events occur faster than the period, then they will
-not be reported and instead, the progress will be reported once at the end of
-the period.
-The period is specified in milliseconds.
- */
-- (BOOL)registerSendProgressCallback:(NSData* _Nullable)transferID progressFunc:(id<BindingsFileTransferSentProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * Send sends a file to the recipient. The sender must have an E2E relationship
-with the recipient.
-The file name is the name of the file to show a user. It has a max length of
-48 bytes.
-The file type identifies what type of file is being sent. It has a max length
-of 8 bytes.
-The file data cannot be larger than 256 kB
-The retry float is the total amount of data to send relative to the data
-size. Data will be resent on error and will resend up to [(1 + retry) *
-fileSize].
-The preview stores a preview of the data (such as a thumbnail) and is
-capped at 4 kB in size.
-Returns a unique transfer ID used to identify the transfer.
-PeriodMS is the duration, in milliseconds, to wait between progress callback
-calls. Set this large enough to prevent spamming.
- */
-- (NSData* _Nullable)send:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType fileData:(NSData* _Nullable)fileData recipientID:(NSData* _Nullable)recipientID retry:(float)retry preview:(NSData* _Nullable)preview progressFunc:(id<BindingsFileTransferSentProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * Group structure contains the identifying and membership information of a
-group chat.
- */
-@interface BindingsGroup : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetCreatedMS returns the time the group was created in milliseconds. This is
-also the time the group requests were sent.
- */
-- (int64_t)getCreatedMS;
-/**
- * GetCreatedNano returns the time the group was created in nanoseconds. This is
-also the time the group requests were sent.
- */
-- (int64_t)getCreatedNano;
-/**
- * GetID return the 33-byte unique group ID.
- */
-- (NSData* _Nullable)getID;
-/**
- * GetInitMessage returns initial message sent with the group request.
- */
-- (NSData* _Nullable)getInitMessage;
-/**
- * GetMembership returns a list of contacts, one for each member in the group.
-The list is in order; the first contact is the leader/creator of the group.
-All subsequent members are ordered by their ID.
- */
-- (BindingsGroupMembership* _Nullable)getMembership;
-/**
- * GetName returns the name set by the user for the group.
- */
-- (NSData* _Nullable)getName;
-/**
- * Serialize serializes the Group.
- */
-- (NSData* _Nullable)serialize;
-@end
-
-/**
- * GroupChat object contains the group chat manager.
- */
-@interface BindingsGroupChat : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetGroup returns the group with the group ID. If no group exists, then the
-error "failed to find group" is returned.
- */
-- (BindingsGroup* _Nullable)getGroup:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * GetGroups returns an IdList containing a list of group IDs that the user is a
-part of.
- */
-- (BindingsIdList* _Nullable)getGroups;
-/**
- * JoinGroup allows a user to join a group when they receive a request. The
-caller must pass in the serialized bytes of a Group.
- */
-- (BOOL)joinGroup:(NSData* _Nullable)serializedGroupData error:(NSError* _Nullable* _Nullable)error;
-/**
- * LeaveGroup deletes a group so a user no longer has access.
- */
-- (BOOL)leaveGroup:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * MakeGroup creates a new group and sends a group request to all members in the
-group. The ID of the new group, the rounds the requests were sent on, and the
-status of the send are contained in NewGroupReport.
- */
-- (BindingsNewGroupReport* _Nullable)makeGroup:(BindingsIdList* _Nullable)membership name:(NSData* _Nullable)name message:(NSData* _Nullable)message;
-/**
- * NumGroups returns the number of groups the user is a part of.
- */
-- (long)numGroups;
-/**
- * ResendRequest resends a group request to all members in the group. The rounds
-they were sent on and the status of the send are contained in NewGroupReport.
- */
-- (BindingsNewGroupReport* _Nullable)resendRequest:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * Send sends the message to the specified group. Returns the round the messages
-were sent on.
- */
-- (BindingsGroupSendReport* _Nullable)send:(NSData* _Nullable)groupIdBytes message:(NSData* _Nullable)message error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * //
-Member Structure
-//
-GroupMember represents a member in the group membership list.
- */
-@interface BindingsGroupMember : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupMember.Member with unsupported type: gitlab.com/elixxir/crypto/group.Member
-
-// skipped method GroupMember.DeepCopy with unsupported parameter or return types
-
-// skipped method GroupMember.Equal with unsupported parameter or return types
-
-/**
- * GetDhKey returns the byte representation of the public Diffie–Hellman key of
-the member.
- */
-- (NSData* _Nullable)getDhKey;
-/**
- * GetID returns the 33-byte user ID of the member.
- */
-- (NSData* _Nullable)getID;
-- (NSString* _Nonnull)goString;
-- (NSData* _Nullable)serialize;
-- (NSString* _Nonnull)string;
-@end
-
-/**
- * GroupMembership structure contains a list of members that are part of a
-group. The first member is the group leader.
- */
-@interface BindingsGroupMembership : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Get returns the member at the index. The member at index 0 is always the
-group leader. An error is returned if the index is out of range.
- */
-- (BindingsGroupMember* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Len returns the number of members in the group membership.
- */
-- (long)len;
-@end
-
-/**
- * GroupMessageReceive contains a group message, its ID, and its data that a
-user receives.
- */
-@interface BindingsGroupMessageReceive : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupMessageReceive.MessageReceive with unsupported type: gitlab.com/elixxir/client/groupChat.MessageReceive
-
-/**
- * GetEphemeralID returns the ephemeral ID of the recipient.
- */
-- (int64_t)getEphemeralID;
-/**
- * GetGroupID returns the 33-byte group ID.
- */
-- (NSData* _Nullable)getGroupID;
-/**
- * GetMessageID returns the message ID.
- */
-- (NSData* _Nullable)getMessageID;
-/**
- * GetPayload returns the message payload.
- */
-- (NSData* _Nullable)getPayload;
-/**
- * GetRecipientID returns the 33-byte user ID of the recipient.
- */
-- (NSData* _Nullable)getRecipientID;
-/**
- * GetRoundID returns the ID of the round the message was sent on.
- */
-- (int64_t)getRoundID;
-/**
- * GetRoundTimestampMS returns the timestamp, in milliseconds, of the round the
-message was sent on.
- */
-- (int64_t)getRoundTimestampMS;
-/**
- * GetRoundTimestampNano returns the timestamp, in nanoseconds, of the round the
-message was sent on.
- */
-- (int64_t)getRoundTimestampNano;
-/**
- * GetRoundURL returns the ID of the round the message was sent on.
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetSenderID returns the 33-byte user ID of the sender.
- */
-- (NSData* _Nullable)getSenderID;
-/**
- * GetTimestampMS returns the message timestamp in milliseconds.
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message timestamp in nanoseconds.
- */
-- (int64_t)getTimestampNano;
-- (NSString* _Nonnull)string;
-@end
-
-@interface BindingsGroupReportDisk : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupReportDisk.List with unsupported type: []gitlab.com/xx_network/primitives/id.Round
-
-@property (nonatomic) NSData* _Nullable grpId;
-@property (nonatomic) long status;
-@end
-
-/**
- * GroupSendReport is returned when sending a group message. It contains the
-round ID sent on and the timestamp of the send.
- */
-@interface BindingsGroupSendReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetMessageID returns the ID of the round that the send occurred on.
- */
-- (NSData* _Nullable)getMessageID;
-/**
- * GetRoundID returns the ID of the round that the send occurred on.
- */
-- (int64_t)getRoundID;
-/**
- * GetRoundURL returns the URL of the round that the send occurred on.
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetTimestampMS returns the timestamp of the send in milliseconds.
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the timestamp of the send in nanoseconds.
- */
-- (int64_t)getTimestampNano;
-@end
-
-/**
- *  ID list
-IdList contains a list of IDs.
- */
-@interface BindingsIdList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Add appends the ID bytes to the end of the list.
- */
-- (BOOL)add:(NSData* _Nullable)idBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * Get returns the ID at the index. An error is returned if the index is out of
-range.
- */
-- (NSData* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Len returns the number of IDs in the list.
- */
-- (long)len;
-@end
-
-@interface BindingsIntList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (void)add:(long)i;
-- (BOOL)get:(long)i ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-- (long)len;
-@end
-
-@interface BindingsManyNotificationForMeReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BindingsNotificationForMeReport* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-- (long)len;
-@end
-
-/**
- * Message is a message received from the cMix network in the clear
-or that has been decrypted using established E2E keys.
- */
-@interface BindingsMessage : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetID returns the id of the message
- */
-- (NSData* _Nullable)getID;
-/**
- * GetMessageType returns the message's type
- */
-- (long)getMessageType;
-/**
- * GetPayload returns the message's payload/contents
- */
-- (NSData* _Nullable)getPayload;
-/**
- * GetRoundId returns the message's round ID
- */
-- (int64_t)getRoundId;
-/**
- * GetRoundTimestampMS returns the message's round timestamp in milliseconds
- */
-- (int64_t)getRoundTimestampMS;
-/**
- * GetRoundTimestampNano returns the message's round timestamp in nanoseconds
- */
-- (int64_t)getRoundTimestampNano;
-/**
- * GetRoundURL returns the message's round URL
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetSender returns the message's sender ID, if available
- */
-- (NSData* _Nullable)getSender;
-/**
- * GetTimestampMS returns the message's timestamp in milliseconds
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message's timestamp in nanoseconds
- */
-- (int64_t)getTimestampNano;
-@end
-
-/**
- * NewGroupReport is returned when creating a new group and contains the ID of
-the group, a list of rounds that the group requests were sent on, and the
-status of the send.
- */
-@interface BindingsNewGroupReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetError returns the string of an error.
-Will be an empty string if no error occured
- */
-- (NSString* _Nonnull)getError;
-/**
- * GetGroup returns the Group.
- */
-- (BindingsGroup* _Nullable)getGroup;
-/**
- * GetRoundList returns the RoundList containing a list of rounds requests were
-sent on.
- */
-- (BindingsRoundList* _Nullable)getRoundList;
-/**
- * GetStatus returns the status of the requests sent when creating a new group.
-status = 0   an error occurred before any requests could be sent
-         1   all requests failed to send (call Resend Group)
-         2   some request failed and some succeeded (call Resend Group)
-         3,  all requests sent successfully (call Resend Group)
- */
-- (long)getStatus;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-- (BOOL)unmarshal:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * NodeRegistrationsStatus structure for returning node registration statuses
-for bindings.
- */
-@interface BindingsNodeRegistrationsStatus : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetRegistered returns the number of nodes registered with the client.
- */
-- (long)getRegistered;
-/**
- * GetTotal return the total of nodes currently in the network.
- */
-- (long)getTotal;
-@end
-
-@interface BindingsNotificationForMeReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BOOL)forMe;
-- (NSData* _Nullable)source;
-- (NSString* _Nonnull)type;
-@end
-
-/**
- * RestoreContactsReport is a gomobile friendly report structure
-for determining which IDs restored, which failed, and why.
- */
-@interface BindingsRestoreContactsReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetErrorAt returns the error string at index
- */
-- (NSString* _Nonnull)getErrorAt:(long)index;
-/**
- * GetFailedAt returns the failed ID at index
- */
-- (NSData* _Nullable)getFailedAt:(long)index;
-/**
- * GetRestoreContactsError returns an error string. Empty if no error.
- */
-- (NSString* _Nonnull)getRestoreContactsError;
-/**
- * GetRestoredAt returns the restored ID at index
- */
-- (NSData* _Nullable)getRestoredAt:(long)index;
-/**
- * LenFailed returns the length of the ID's failed.
- */
-- (long)lenFailed;
-/**
- * LenRestored returns the length of ID's restored.
- */
-- (long)lenRestored;
-@end
-
-@interface BindingsRoundList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Gets a stored round ID at the given index
- */
-- (BOOL)get:(long)i ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the number of round IDs stored
- */
-- (long)len;
-@end
-
-/**
- * the send report is the mechanisim by which sendE2E returns a single
- */
-@interface BindingsSendReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (NSData* _Nullable)getMessageID;
-- (BindingsRoundList* _Nullable)getRoundList;
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetTimestampMS returns the message's timestamp in milliseconds
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message's timestamp in nanoseconds
- */
-- (int64_t)getTimestampNano;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-- (BOOL)unmarshal:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsSendReportDisk : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field SendReportDisk.List with unsupported type: []gitlab.com/xx_network/primitives/id.Round
-
-@property (nonatomic) NSData* _Nullable mid;
-@property (nonatomic) int64_t ts;
-@end
-
-/**
- * Generic Unregister - a generic return used for all callbacks which can be
-unregistered
-Interface which allows the un-registration of a listener
- */
-@interface BindingsUnregister : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Call unregisters a callback
- */
-- (void)unregister;
-@end
-
-@interface BindingsUser : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BindingsContact* _Nullable)getContact;
-- (NSData* _Nullable)getE2EDhPrivateKey;
-- (NSData* _Nullable)getE2EDhPublicKey;
-- (NSData* _Nullable)getReceptionID;
-- (NSData* _Nullable)getReceptionRSAPrivateKeyPem;
-- (NSData* _Nullable)getReceptionRSAPublicKeyPem;
-- (NSData* _Nullable)getReceptionSalt;
-- (NSData* _Nullable)getTransmissionID;
-- (NSData* _Nullable)getTransmissionRSAPrivateKeyPem;
-- (NSData* _Nullable)getTransmissionRSAPublicKeyPem;
-- (NSData* _Nullable)getTransmissionSalt;
-- (BOOL)isPrecanned;
-@end
-
-@interface BindingsUserDiscovery : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewUserDiscovery returns a new user discovery object. Only call this once. It must be called
-after StartNetworkFollower is called and will fail if the network has never
-been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-- (nullable instancetype)init:(BindingsClient* _Nullable)client;
-/**
- * NewUserDiscoveryFromBackup returns a new user discovery object. It
-wil set up the manager with the backup data. Pass into it the backed up
-facts, one email and phone number each. This will add the registered facts
-to the backed Store. Any one of these fields may be empty,
-however both fields being empty will cause an error. Any other fact that is not
-an email or phone number will return an error. You may only add a fact for the
-accepted types once each. If you attempt to back up a fact type that has already
-been backed up, an error will be returned. Anytime an error is returned, it means
-the backup was not successful.
-NOTE: Do not use this as a direct store operation. This feature is intended to add facts
-to a backend store that have ALREADY BEEN REGISTERED on the account.
-THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend.
-Only call this once. It must be called after StartNetworkFollower
-is called and will fail if the network has never been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-- (nullable instancetype)initFromBackup:(BindingsClient* _Nullable)client email:(NSString* _Nullable)email phone:(NSString* _Nullable)phone;
-/**
- * AddFact adds a fact for the user to user discovery. Will only succeed if the
-user is already registered and the system does not have the fact currently
-registered for any user.
-Will fail if the fact string is not well formed.
-This does not complete the fact registration process, it returns a
-confirmation id instead. Over the communications system the fact is
-associated with, a code will be sent. This confirmation ID needs to be
-called along with the code to finalize the fact.
- */
-- (NSString* _Nonnull)addFact:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * ConfirmFact confirms a fact first registered via AddFact. The confirmation ID comes from
-AddFact while the code will come over the associated communications system
- */
-- (BOOL)confirmFact:(NSString* _Nullable)confirmationID code:(NSString* _Nullable)code error:(NSError* _Nullable* _Nullable)error;
-/**
- * Lookup the contact object associated with the given userID.  The
-id is the byte representation of an id.
-This will reject if that id is malformed. The LookupCallback will return
-the associated contact if it exists.
- */
-- (BOOL)lookup:(NSData* _Nullable)idBytes callback:(id<BindingsLookupCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * MultiLookup Looks for the contact object associated with all given userIDs.
-The ids are the byte representation of an id stored in an IDList object.
-This will reject if that id is malformed or if the indexing on the IDList
-object is wrong. The MultiLookupCallback will return with all contacts
-returned within the timeout.
- */
-- (BOOL)multiLookup:(BindingsIdList* _Nullable)ids callback:(id<BindingsMultiLookupCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * Register registers a user with user discovery. Will return an error if the
-network signatures are malformed or if the username is taken. Usernames
-cannot be changed after registration at this time. Will fail if the user is
-already registered.
-Identity does not go over cmix, it occurs over normal communications
- */
-- (BOOL)register:(NSString* _Nullable)username error:(NSError* _Nullable* _Nullable)error;
-/**
- * RemoveFact removes a previously confirmed fact.  Will fail if the passed fact string is
-not well-formed or if the fact is not associated with this client.
-Users cannot remove username facts and must instead remove the user.
- */
-- (BOOL)removeFact:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * RemoveUser deletes a user. The fact sent must be the username.
-This function preserves the username forever and makes it
-unusable.
- */
-- (BOOL)removeUser:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * Search for the passed Facts.  The factList is the stringification of a
-fact list object, look at /bindings/list.go for more on that object.
-This will reject if that object is malformed. The SearchCallback will return
-a list of contacts, each having the facts it hit against.
-This is NOT intended to be used to search for multiple users at once, that
-can have a privacy reduction. Instead, it is intended to be used to search
-for a user where multiple pieces of information is known.
- */
-- (BOOL)search:(NSString* _Nullable)fl callback:(id<BindingsSearchCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * SearchSingle searches for the passed Facts.  The fact is the stringification of a
-fact object, look at /bindings/contact.go for more on that object.
-This will reject if that object is malformed. The SearchCallback will return
-a list of contacts, each having the facts it hit against.
-This only searches for a single fact at a time. It is intended to make some
-simple use cases of the API easier.
- */
-- (BOOL)searchSingle:(NSString* _Nullable)f callback:(id<BindingsSingleSearchCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * SetAlternativeUserDiscovery sets the alternativeUd object within manager.
-Once set, any user discovery operation will go through the alternative
-user discovery service.
-To undo this operation, use UnsetAlternativeUserDiscovery.
-The contact file is the already read in bytes, not the file path for the contact file.
- */
-- (BOOL)setAlternativeUserDiscovery:(NSData* _Nullable)address cert:(NSData* _Nullable)cert contactFile:(NSData* _Nullable)contactFile error:(NSError* _Nullable* _Nullable)error;
-/**
- * UnsetAlternativeUserDiscovery clears out the information from
-the Manager object.
- */
-- (BOOL)unsetAlternativeUserDiscovery:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * Error codes
- */
-FOUNDATION_EXPORT NSString* _Nonnull const BindingsUnrecognizedCode;
-FOUNDATION_EXPORT NSString* _Nonnull const BindingsUnrecognizedMessage;
-
-/**
- * CompressJpeg takes a JPEG image in byte format
-and compresses it based on desired output size
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsCompressJpeg(NSData* _Nullable imgBytes, NSError* _Nullable* _Nullable error);
-
-/**
- * CompressJpegForPreview takes a JPEG image in byte format
-and compresses it based on desired output size
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsCompressJpegForPreview(NSData* _Nullable imgBytes, NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadAndVerifySignedNdfWithUrl retrieves the NDF from a specified URL.
-The NDF is processed into a protobuf containing a signature which
-is verified using the cert string passed in. The NDF is returned as marshaled
-byte data which may be used to start a client.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadAndVerifySignedNdfWithUrl(NSString* _Nullable url, NSString* _Nullable cert, NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadDAppRegistrationDB returns a []byte containing
-the JSON data describing registered dApps.
-See https://git.xx.network/elixxir/registered-dapps
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadDAppRegistrationDB(NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadErrorDB returns a []byte containing the JSON data
-describing client errors.
-See https://git.xx.network/elixxir/client-error-database/
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadErrorDB(NSError* _Nullable* _Nullable error);
-
-/**
- * DumpStack returns a string with the stack trace of every running thread.
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsDumpStack(NSError* _Nullable* _Nullable error);
-
-/**
- * EnableGrpcLogs sets GRPC trace logging
- */
-FOUNDATION_EXPORT void BindingsEnableGrpcLogs(id<BindingsLogWriter> _Nullable writer);
-
-/**
- * ErrorStringToUserFriendlyMessage takes a passed in errStr which will be
-a backend generated error. These may be error specifically written by
-the backend team or lower level errors gotten from low level dependencies.
-This function will parse the error string for common errors provided from
-errToUserErr to provide a more user-friendly error message for the front end.
-If the error is not common, some simple parsing is done on the error message
-to make it more user-accessible, removing backend specific jargon.
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsErrorStringToUserFriendlyMessage(NSString* _Nullable errStr);
-
-/**
- * GenerateSecret creates a secret password using a system-based
-pseudorandom number generator. It takes 1 parameter, `numBytes`,
-which should be set to 32, but can be set higher in certain cases.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsGenerateSecret(long numBytes);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetCMIXParams(NSError* _Nullable* _Nullable error);
-
-/**
- * returns a previously created client. IF be used if the garbage collector
-removes the client instance on the app side.  Is NOT thread safe relative to
-login, newClient, or newPrecannedClient
- */
-FOUNDATION_EXPORT BindingsClient* _Nullable BindingsGetClientSingleton(void);
-
-/**
- * GetDependencies returns the api DEPENDENCIES
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetDependencies(void);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetE2EParams(NSError* _Nullable* _Nullable error);
-
-/**
- * GetGitVersion rturns the api GITVERSION
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetGitVersion(void);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetNetworkParams(NSError* _Nullable* _Nullable error);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetUnsafeParams(NSError* _Nullable* _Nullable error);
-
-/**
- * GetVersion returns the api SEMVER
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetVersion(void);
-
-/**
- * InitializeBackup starts the backup processes that returns backup updates when
-they occur. Any time an event occurs that changes the contents of the backup,
-such as adding or deleting a contact, the backup is triggered and an
-encrypted backup is generated and returned on the updateBackupCb callback.
-Call this function only when enabling backup if it has not already been
-initialized or when the user wants to change their password.
-To resume backup process on app recovery, use ResumeBackup.
- */
-FOUNDATION_EXPORT BindingsBackup* _Nullable BindingsInitializeBackup(NSString* _Nullable password, id<BindingsUpdateBackupFunc> _Nullable updateBackupCb, BindingsClient* _Nullable c, NSError* _Nullable* _Nullable error);
-
-/**
- * LoadSecretWithMnemonic loads the secret stored from the call to
-StoreSecretWithMnemonic. The path given should be the same filepath
-as the path given in StoreSecretWithMnemonic. There should be a file
-in this path called ".recovery". This operation is not tied
-to client operations, as the user will not have a client when trying to
-recover their account.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsLoadSecretWithMnemonic(NSString* _Nullable mnemonic, NSString* _Nullable path, NSError* _Nullable* _Nullable error);
-
-/**
- * sets level of logging. All logs the set level and above will be displayed
-options are:
-	TRACE		- 0
-	DEBUG		- 1
-	INFO 		- 2
-	WARN		- 3
-	ERROR		- 4
-	CRITICAL	- 5
-	FATAL		- 6
-The default state without updates is: INFO
- */
-FOUNDATION_EXPORT BOOL BindingsLogLevel(long level, NSError* _Nullable* _Nullable error);
-
-/**
- * Login will load an existing client from the storageDir
-using the password. This will fail if the client doesn't exist or
-the password is incorrect.
-The password is passed as a byte array so that it can be cleared from
-memory and stored as securely as possible using the memguard library.
-Login does not block on network connection, and instead loads and
-starts subprocesses to perform network operations.
- */
-FOUNDATION_EXPORT BindingsClient* _Nullable BindingsLogin(NSString* _Nullable storageDir, NSData* _Nullable password, NSString* _Nullable parameters, NSError* _Nullable* _Nullable error);
-
-/**
- * MakeIdList creates a new empty IdList.
- */
-FOUNDATION_EXPORT BindingsIdList* _Nullable BindingsMakeIdList(void);
-
-FOUNDATION_EXPORT BindingsIntList* _Nullable BindingsMakeIntList(void);
-
-/**
- * NewClient creates client storage, generates keys, connects, and registers
-with the network. Note that this does not register a username/identity, but
-merely creates a new cryptographic identity for adding such information
-at a later date.
-
-Users of this function should delete the storage directory on error.
- */
-FOUNDATION_EXPORT BOOL BindingsNewClient(NSString* _Nullable network, NSString* _Nullable storageDir, NSData* _Nullable password, NSString* _Nullable regCode, NSError* _Nullable* _Nullable error);
-
-/**
- * NewClientFromBackup constructs a new Client from an encrypted backup. The backup
-is decrypted using the backupPassphrase. On success a successful client creation,
-the function will return a JSON encoded list of the E2E partners
-contained in the backup and a json-encoded string of the parameters stored in the backup
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsNewClientFromBackup(NSString* _Nullable ndfJSON, NSString* _Nullable storageDir, NSData* _Nullable sessionPassword, NSData* _Nullable backupPassphrase, NSData* _Nullable backupFileContents, NSError* _Nullable* _Nullable error);
-
-/**
- * NewDummyTrafficManager creates a DummyTraffic manager and initialises the
-dummy traffic send thread. Note that the manager does not start sending dummy
-traffic until its status is set to true using DummyTraffic.SetStatus.
-The maxNumMessages is the upper bound of the random number of messages sent
-each send. avgSendDeltaMS is the average duration, in milliseconds, to wait
-between sends. Sends occur every avgSendDeltaMS +/- a random duration with an
-upper bound of randomRangeMS.
- */
-FOUNDATION_EXPORT BindingsDummyTraffic* _Nullable BindingsNewDummyTrafficManager(BindingsClient* _Nullable client, long maxNumMessages, long avgSendDeltaMS, long randomRangeMS, NSError* _Nullable* _Nullable error);
-
-/**
- *  fact object
-creates a new fact. The factType must be either:
- 0 - Username
- 1 - Email
- 2 - Phone Number
-The fact must be well formed for the type and must not include commas or
-semicolons. If it is not well formed, it will be rejected.  Phone numbers
-must have the two letter country codes appended.  For the complete set of
-validation, see /elixxir/primitives/fact/fact.go
- */
-FOUNDATION_EXPORT BindingsFact* _Nullable BindingsNewFact(long factType, NSString* _Nullable factStr, NSError* _Nullable* _Nullable error);
-
-/**
- * FactList
- */
-FOUNDATION_EXPORT BindingsFactList* _Nullable BindingsNewFactList(void);
-
-/**
- * NewFileTransferManager creates a new file transfer manager and starts the
-sending and receiving threads. The receiveFunc is called everytime a new file
-transfer is received.
-The parameters string contains file transfer network configuration options
-and is a JSON formatted string of the fileTransfer.Params object. If it is
-left empty, then defaults are used. It is highly recommended that defaults
-are used. If it is set, it must match the following format:
- {"MaxThroughput":150000,"SendTimeout":500000000}
-MaxThroughput is in bytes/sec and SendTimeout is in nanoseconds.
- */
-FOUNDATION_EXPORT BindingsFileTransfer* _Nullable BindingsNewFileTransferManager(BindingsClient* _Nullable client, id<BindingsFileTransferReceiveFunc> _Nullable receiveFunc, NSString* _Nullable parameters, NSError* _Nullable* _Nullable error);
-
-/**
- * NewGroupManager creates a new group chat manager.
- */
-FOUNDATION_EXPORT BindingsGroupChat* _Nullable BindingsNewGroupManager(BindingsClient* _Nullable client, id<BindingsGroupRequestFunc> _Nullable requestFunc, id<BindingsGroupReceiveFunc> _Nullable receiveFunc, NSError* _Nullable* _Nullable error);
-
-/**
- * NewPrecannedClient creates an insecure user with predetermined keys with nodes
-It creates client storage, generates keys, connects, and registers
-with the network. Note that this does not register a username/identity, but
-merely creates a new cryptographic identity for adding such information
-at a later date.
-
-Users of this function should delete the storage directory on error.
- */
-FOUNDATION_EXPORT BOOL BindingsNewPrecannedClient(long precannedID, NSString* _Nullable network, NSString* _Nullable storageDir, NSData* _Nullable password, NSError* _Nullable* _Nullable error);
-
-/**
- * NewUserDiscovery returns a new user discovery object. Only call this once. It must be called
-after StartNetworkFollower is called and will fail if the network has never
-been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUserDiscovery(BindingsClient* _Nullable client, NSError* _Nullable* _Nullable error);
-
-/**
- * NewUserDiscoveryFromBackup returns a new user discovery object. It
-wil set up the manager with the backup data. Pass into it the backed up
-facts, one email and phone number each. This will add the registered facts
-to the backed Store. Any one of these fields may be empty,
-however both fields being empty will cause an error. Any other fact that is not
-an email or phone number will return an error. You may only add a fact for the
-accepted types once each. If you attempt to back up a fact type that has already
-been backed up, an error will be returned. Anytime an error is returned, it means
-the backup was not successful.
-NOTE: Do not use this as a direct store operation. This feature is intended to add facts
-to a backend store that have ALREADY BEEN REGISTERED on the account.
-THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend.
-Only call this once. It must be called after StartNetworkFollower
-is called and will fail if the network has never been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUserDiscoveryFromBackup(BindingsClient* _Nullable client, NSString* _Nullable email, NSString* _Nullable phone, NSError* _Nullable* _Nullable error);
-
-/**
- * NotificationsForMe Check if a notification received is for me
-It returns a NotificationForMeReport which contains a ForMe bool stating if it is for the caller,
-a Type, and a source. These are as follows:
-	TYPE       	SOURCE				DESCRIPTION
-	"default"	recipient user ID	A message with no association
-	"request"	sender user ID		A channel request has been received
-	"reset"	    sender user ID		A channel reset has been received
-	"confirm"	sender user ID		A channel request has been accepted
-	"silent"	sender user ID		A message which should not be notified on
-	"e2e"		sender user ID		reception of an E2E message
-	"group"		group ID			reception of a group chat message
- "endFT"     sender user ID		Last message sent confirming end of file transfer
- "groupRQ"   sender user ID		Request from sender to join a group chat
- */
-FOUNDATION_EXPORT BindingsManyNotificationForMeReport* _Nullable BindingsNotificationsForMe(NSString* _Nullable notifCSV, NSString* _Nullable preimages, NSError* _Nullable* _Nullable error);
-
-/**
- * RegisterLogWriter registers a callback on which logs are written.
- */
-FOUNDATION_EXPORT void BindingsRegisterLogWriter(id<BindingsLogWriter> _Nullable writer);
-
-/**
- * RestoreContactsFromBackup takes as input the jason output of the
-`NewClientFromBackup` function, unmarshals it into IDs, looks up
-each ID in user discovery, and initiates a session reset request.
-This function will not return until every id in the list has been sent a
-request. It should be called again and again until it completes.
-xxDK users should not use this function. This function is used by
-the mobile phone apps and are not intended to be part of the xxDK. It
-should be treated as internal functions specific to the phone apps.
- */
-FOUNDATION_EXPORT BindingsRestoreContactsReport* _Nullable BindingsRestoreContactsFromBackup(NSData* _Nullable backupPartnerIDs, BindingsClient* _Nullable client, BindingsUserDiscovery* _Nullable udManager, id<BindingsLookupCallback> _Nullable lookupCB, id<BindingsRestoreContactsUpdater> _Nullable updatesCb);
-
-/**
- * ResumeBackup starts the backup processes back up with a new callback after it
-has been initialized.
-Call this function only when resuming a backup that has already been
-initialized or to replace the callback.
-To start the backup for the first time or to use a new password, use
-InitializeBackup.
- */
-FOUNDATION_EXPORT BindingsBackup* _Nullable BindingsResumeBackup(id<BindingsUpdateBackupFunc> _Nullable cb, BindingsClient* _Nullable c, NSError* _Nullable* _Nullable error);
-
-/**
- * SetTimeSource sets the network time to a custom source.
- */
-FOUNDATION_EXPORT void BindingsSetTimeSource(id<BindingsTimeSource> _Nullable timeNow);
-
-/**
- * StoreSecretWithMnemonic stores the secret tied with the mnemonic to storage.
-Unlike other storage operations, this does not use EKV, as that is
-intrinsically tied to client operations, which the user will not have while
-trying to recover their account. As such, we store the encrypted data
-directly, with a specified path. Path will be a valid filepath in which the
-recover file will be stored as ".recovery".
-
-As an example, given "home/user/xxmessenger/storagePath",
-the recovery file will be stored at
-"home/user/xxmessenger/storagePath/.recovery"
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsStoreSecretWithMnemonic(NSData* _Nullable secret, NSString* _Nullable path, NSError* _Nullable* _Nullable error);
-
-/**
- * Unmarshals a marshaled contact object, returns an error if it fails
- */
-FOUNDATION_EXPORT BindingsContact* _Nullable BindingsUnmarshalContact(NSData* _Nullable b, NSError* _Nullable* _Nullable error);
-
-/**
- * Unmarshals a marshaled send report object, returns an error if it fails
- */
-FOUNDATION_EXPORT BindingsSendReport* _Nullable BindingsUnmarshalSendReport(NSData* _Nullable b, NSError* _Nullable* _Nullable error);
-
-/**
- * UpdateCommonErrors takes the passed in contents of a JSON file and updates the
-errToUserErr map with the contents of the json file. The JSON's expected format
-conform with the commented examples provides in errToUserErr above.
-NOTE that you should not pass in a file path, but a preloaded JSON file
- */
-FOUNDATION_EXPORT BOOL BindingsUpdateCommonErrors(NSString* _Nullable jsonFile, NSError* _Nullable* _Nullable error);
-
-// skipped function WrapAPIClient with unsupported parameter or return types
-
-
-// skipped function WrapUserDiscovery with unsupported parameter or return types
-
-
-@class BindingsAuthConfirmCallback;
-
-@class BindingsAuthRequestCallback;
-
-@class BindingsAuthResetNotificationCallback;
-
-@class BindingsClientError;
-
-@class BindingsEventCallbackFunctionObject;
-
-@class BindingsFileTransferReceiveFunc;
-
-@class BindingsFileTransferReceivedProgressFunc;
-
-@class BindingsFileTransferSentProgressFunc;
-
-@class BindingsGroupReceiveFunc;
-
-@class BindingsGroupRequestFunc;
-
-@class BindingsListener;
-
-@class BindingsLogWriter;
-
-@class BindingsLookupCallback;
-
-@class BindingsMessageDeliveryCallback;
-
-@class BindingsMultiLookupCallback;
-
-@class BindingsNetworkHealthCallback;
-
-@class BindingsPreimageNotification;
-
-@class BindingsRestoreContactsUpdater;
-
-@class BindingsRoundCompletionCallback;
-
-@class BindingsRoundEventCallback;
-
-@class BindingsSearchCallback;
-
-@class BindingsSingleSearchCallback;
-
-@class BindingsTimeSource;
-
-@class BindingsUpdateBackupFunc;
-
-/**
- * AuthConfirmCallback notifies the register whenever they receive an auth
-request confirmation
- */
-@interface BindingsAuthConfirmCallback : NSObject <goSeqRefInterface, BindingsAuthConfirmCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)partner;
-@end
-
-/**
- * AuthRequestCallback notifies the register whenever they receive an auth
-request
- */
-@interface BindingsAuthRequestCallback : NSObject <goSeqRefInterface, BindingsAuthRequestCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-/**
- * AuthRequestCallback notifies the register whenever they receive an auth
-request
- */
-@interface BindingsAuthResetNotificationCallback : NSObject <goSeqRefInterface, BindingsAuthResetNotificationCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@interface BindingsClientError : NSObject <goSeqRefInterface, BindingsClientError> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)report:(NSString* _Nullable)source message:(NSString* _Nullable)message trace:(NSString* _Nullable)trace;
-@end
-
-/**
- * EventCallbackFunctionObject bindings interface which contains function
-that implements the EventCallbackFunction
- */
-@interface BindingsEventCallbackFunctionObject : NSObject <goSeqRefInterface, BindingsEventCallbackFunctionObject> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)reportEvent:(long)priority category:(NSString* _Nullable)category evtType:(NSString* _Nullable)evtType details:(NSString* _Nullable)details;
-@end
-
-/**
- * FileTransferReceiveFunc contains a function callback that notifies the
-receiver of an incoming file transfer. It is called on the reception of the
-initial file transfer message.
- */
-@interface BindingsFileTransferReceiveFunc : NSObject <goSeqRefInterface, BindingsFileTransferReceiveFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)receiveCallback:(NSData* _Nullable)tid fileName:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType sender:(NSData* _Nullable)sender size:(long)size preview:(NSData* _Nullable)preview;
-@end
-
-/**
- * FileTransferReceivedProgressFunc contains a function callback that tracks the
-progress of receiving a file. It is called when a file part is received, the
-transfer completes, or on error.
- */
-@interface BindingsFileTransferReceivedProgressFunc : NSObject <goSeqRefInterface, BindingsFileTransferReceivedProgressFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)receivedProgressCallback:(BOOL)completed received:(long)received total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-/**
- * FileTransferSentProgressFunc contains a function callback that tracks the
-progress of sending a file. It is called when a file part is sent, a file
-part arrives, the transfer completes, or on error.
- */
-@interface BindingsFileTransferSentProgressFunc : NSObject <goSeqRefInterface, BindingsFileTransferSentProgressFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)sentProgressCallback:(BOOL)completed sent:(long)sent arrived:(long)arrived total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-/**
- * GroupReceiveFunc contains a function callback that is called when a group
-message is received.
- */
-@interface BindingsGroupReceiveFunc : NSObject <goSeqRefInterface, BindingsGroupReceiveFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)groupReceiveCallback:(BindingsGroupMessageReceive* _Nullable)msg;
-@end
-
-/**
- * GroupRequestFunc contains a function callback that is called when a group
-request is received.
- */
-@interface BindingsGroupRequestFunc : NSObject <goSeqRefInterface, BindingsGroupRequestFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)groupRequestCallback:(BindingsGroup* _Nullable)g;
-@end
-
-/**
- * Listener provides a callback to hear a message
-An object implementing this interface can be called back when the client
-gets a message of the type that the registerer specified at registration
-time.
- */
-@interface BindingsListener : NSObject <goSeqRefInterface, BindingsListener> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * Hear is called to receive a message in the UI
- */
-- (void)hear:(BindingsMessage* _Nullable)message;
-/**
- * Returns a name, used for debugging
- */
-- (NSString* _Nonnull)name;
-@end
-
-@interface BindingsLogWriter : NSObject <goSeqRefInterface, BindingsLogWriter> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)log:(NSString* _Nullable)p0;
-@end
-
-/**
- * LookupCallback returns the result of a single lookup
- */
-@interface BindingsLookupCallback : NSObject <goSeqRefInterface, BindingsLookupCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-/**
- * MessageDeliveryCallback gets called on the determination if all events
-related to a message send were successful.
- */
-@interface BindingsMessageDeliveryCallback : NSObject <goSeqRefInterface, BindingsMessageDeliveryCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(NSData* _Nullable)msgID delivered:(BOOL)delivered timedOut:(BOOL)timedOut roundResults:(NSData* _Nullable)roundResults;
-@end
-
-/**
- * MultiLookupCallback returns the result of many parallel lookups
- */
-@interface BindingsMultiLookupCallback : NSObject <goSeqRefInterface, BindingsMultiLookupCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContactList* _Nullable)Succeeded failed:(BindingsIdList* _Nullable)failed errors:(NSString* _Nullable)errors;
-@end
-
-/**
- * A callback when which is used to receive notification if network health
-changes
- */
-@interface BindingsNetworkHealthCallback : NSObject <goSeqRefInterface, BindingsNetworkHealthCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BOOL)p0;
-@end
-
-@interface BindingsPreimageNotification : NSObject <goSeqRefInterface, BindingsPreimageNotification> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)notify:(NSData* _Nullable)identity deleted:(BOOL)deleted;
-@end
-
-/**
- * RestoreContactsUpdater interface provides a callback function
-for receiving update information from RestoreContactsFromBackup.
- */
-@interface BindingsRestoreContactsUpdater : NSObject <goSeqRefInterface, BindingsRestoreContactsUpdater> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * RestoreContactsCallback is called to report the current # of contacts
-that have been found and how many have been restored
-against the total number that need to be
-processed. If an error occurs it it set on the err variable as a
-plain string.
- */
-- (void)restoreContactsCallback:(long)numFound numRestored:(long)numRestored total:(long)total err:(NSString* _Nullable)err;
-@end
-
-/**
- * RoundCompletionCallback is returned when the completion of a round is known.
- */
-@interface BindingsRoundCompletionCallback : NSObject <goSeqRefInterface, BindingsRoundCompletionCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(long)rid success:(BOOL)success timedOut:(BOOL)timedOut;
-@end
-
-/**
- * RoundEventCallback handles waiting on the exact state of a round on
-the cMix network.
- */
-@interface BindingsRoundEventCallback : NSObject <goSeqRefInterface, BindingsRoundEventCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(long)rid state:(long)state timedOut:(BOOL)timedOut;
-@end
-
-/**
- * SearchCallback returns the result of a search
- */
-@interface BindingsSearchCallback : NSObject <goSeqRefInterface, BindingsSearchCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContactList* _Nullable)contacts error:(NSString* _Nullable)error;
-@end
-
-/**
- * SingleSearchCallback returns the result of a single search
- */
-@interface BindingsSingleSearchCallback : NSObject <goSeqRefInterface, BindingsSingleSearchCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@interface BindingsTimeSource : NSObject <goSeqRefInterface, BindingsTimeSource> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (int64_t)nowMs;
-@end
-
-/**
- * UpdateBackupFunc contains a function callback that returns new backups.
- */
-@interface BindingsUpdateBackupFunc : NSObject <goSeqRefInterface, BindingsUpdateBackupFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)updateBackup:(NSData* _Nullable)encryptedBackup;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Headers/Universe.objc.h b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Headers/Universe.objc.h
deleted file mode 100644
index 019e7502d581983722a15bf30799e85cbc5dd766..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Headers/Universe.objc.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Objective-C API for talking to  Go package.
-//   gobind -lang=objc 
-//
-// File is generated by gobind. Do not edit.
-
-#ifndef __Universe_H__
-#define __Universe_H__
-
-@import Foundation;
-#include "ref.h"
-
-@protocol Universeerror;
-@class Universeerror;
-
-@protocol Universeerror <NSObject>
-- (NSString* _Nonnull)error;
-@end
-
-@class Universeerror;
-
-@interface Universeerror : NSError <goSeqRefInterface, Universeerror> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (NSString* _Nonnull)error;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Headers/ref.h b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Headers/ref.h
deleted file mode 100644
index b8036a4d85c7387f3def61473a071b5d8c4c8208..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Headers/ref.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#ifndef __GO_REF_HDR__
-#define __GO_REF_HDR__
-
-#include <Foundation/Foundation.h>
-
-// GoSeqRef is an object tagged with an integer for passing back and
-// forth across the language boundary. A GoSeqRef may represent either
-// an instance of a Go object, or an Objective-C object passed to Go.
-// The explicit allocation of a GoSeqRef is used to pin a Go object
-// when it is passed to Objective-C. The Go seq package maintains a
-// reference to the Go object in a map keyed by the refnum along with
-// a reference count. When the reference count reaches zero, the Go
-// seq package will clear the corresponding entry in the map.
-@interface GoSeqRef : NSObject {
-}
-@property(readonly) int32_t refnum;
-@property(strong) id obj; // NULL when representing a Go object.
-
-// new GoSeqRef object to proxy a Go object. The refnum must be
-// provided from Go side.
-- (instancetype)initWithRefnum:(int32_t)refnum obj:(id)obj;
-
-- (int32_t)incNum;
-
-@end
-
-@protocol goSeqRefInterface
--(GoSeqRef*) _ref;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Modules/module.modulemap b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Modules/module.modulemap
deleted file mode 100644
index 4316a5b24058edfc18ffb2dc7f7a982e8353441a..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Modules/module.modulemap
+++ /dev/null
@@ -1,8 +0,0 @@
-framework module "Bindings" {
-	header "ref.h"
-    header "Bindings.objc.h"
-    header "Universe.objc.h"
-    header "Bindings.h"
-
-    export *
-}
\ No newline at end of file
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Resources/Info.plist b/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Resources/Info.plist
deleted file mode 100644
index 0d1a4b8ab9b1fc8e9357197398f73353470cb636..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/Current/Resources/Info.plist
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-    <plist version="1.0">
-      <dict>
-      </dict>
-    </plist>
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Bindings b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Bindings
deleted file mode 100644
index 1e3b1dc25b4d65bf1e21a5d169dc3ae4de6c4fd7..0000000000000000000000000000000000000000
Binary files a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Bindings and /dev/null differ
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Headers/Bindings.h b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Headers/Bindings.h
deleted file mode 100644
index 8906a7da239705b790cb2bb64de92f806640cb38..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Headers/Bindings.h
+++ /dev/null
@@ -1,13 +0,0 @@
-
-// Objective-C API for talking to the following Go packages
-//
-//	gitlab.com/elixxir/client/bindings
-//
-// File is generated by gomobile bind. Do not edit.
-#ifndef __Bindings_FRAMEWORK_H__
-#define __Bindings_FRAMEWORK_H__
-
-#include "Bindings.objc.h"
-#include "Universe.objc.h"
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Headers/Bindings.objc.h b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Headers/Bindings.objc.h
deleted file mode 100644
index 32bf6d116888f787ced27b01b95cb4e1b2c1138b..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Headers/Bindings.objc.h
+++ /dev/null
@@ -1,2083 +0,0 @@
-// Objective-C API for talking to gitlab.com/elixxir/client/bindings Go package.
-//   gobind -lang=objc gitlab.com/elixxir/client/bindings
-//
-// File is generated by gobind. Do not edit.
-
-#ifndef __Bindings_H__
-#define __Bindings_H__
-
-@import Foundation;
-#include "ref.h"
-#include "Universe.objc.h"
-
-
-@class BindingsBackup;
-@class BindingsBackupReport;
-@class BindingsClient;
-@class BindingsContact;
-@class BindingsContactList;
-@class BindingsDummyTraffic;
-@class BindingsFact;
-@class BindingsFactList;
-@class BindingsFilePartTracker;
-@class BindingsFileTransfer;
-@class BindingsGroup;
-@class BindingsGroupChat;
-@class BindingsGroupMember;
-@class BindingsGroupMembership;
-@class BindingsGroupMessageReceive;
-@class BindingsGroupReportDisk;
-@class BindingsGroupSendReport;
-@class BindingsIdList;
-@class BindingsIntList;
-@class BindingsManyNotificationForMeReport;
-@class BindingsMessage;
-@class BindingsNewGroupReport;
-@class BindingsNodeRegistrationsStatus;
-@class BindingsNotificationForMeReport;
-@class BindingsRestoreContactsReport;
-@class BindingsRoundList;
-@class BindingsSendReport;
-@class BindingsSendReportDisk;
-@class BindingsUnregister;
-@class BindingsUser;
-@class BindingsUserDiscovery;
-@protocol BindingsAuthConfirmCallback;
-@class BindingsAuthConfirmCallback;
-@protocol BindingsAuthRequestCallback;
-@class BindingsAuthRequestCallback;
-@protocol BindingsAuthResetNotificationCallback;
-@class BindingsAuthResetNotificationCallback;
-@protocol BindingsClientError;
-@class BindingsClientError;
-@protocol BindingsEventCallbackFunctionObject;
-@class BindingsEventCallbackFunctionObject;
-@protocol BindingsFileTransferReceiveFunc;
-@class BindingsFileTransferReceiveFunc;
-@protocol BindingsFileTransferReceivedProgressFunc;
-@class BindingsFileTransferReceivedProgressFunc;
-@protocol BindingsFileTransferSentProgressFunc;
-@class BindingsFileTransferSentProgressFunc;
-@protocol BindingsGroupReceiveFunc;
-@class BindingsGroupReceiveFunc;
-@protocol BindingsGroupRequestFunc;
-@class BindingsGroupRequestFunc;
-@protocol BindingsListener;
-@class BindingsListener;
-@protocol BindingsLogWriter;
-@class BindingsLogWriter;
-@protocol BindingsLookupCallback;
-@class BindingsLookupCallback;
-@protocol BindingsMessageDeliveryCallback;
-@class BindingsMessageDeliveryCallback;
-@protocol BindingsMultiLookupCallback;
-@class BindingsMultiLookupCallback;
-@protocol BindingsNetworkHealthCallback;
-@class BindingsNetworkHealthCallback;
-@protocol BindingsPreimageNotification;
-@class BindingsPreimageNotification;
-@protocol BindingsRestoreContactsUpdater;
-@class BindingsRestoreContactsUpdater;
-@protocol BindingsRoundCompletionCallback;
-@class BindingsRoundCompletionCallback;
-@protocol BindingsRoundEventCallback;
-@class BindingsRoundEventCallback;
-@protocol BindingsSearchCallback;
-@class BindingsSearchCallback;
-@protocol BindingsSingleSearchCallback;
-@class BindingsSingleSearchCallback;
-@protocol BindingsTimeSource;
-@class BindingsTimeSource;
-@protocol BindingsUpdateBackupFunc;
-@class BindingsUpdateBackupFunc;
-
-@protocol BindingsAuthConfirmCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)partner;
-@end
-
-@protocol BindingsAuthRequestCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@protocol BindingsAuthResetNotificationCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@protocol BindingsClientError <NSObject>
-- (void)report:(NSString* _Nullable)source message:(NSString* _Nullable)message trace:(NSString* _Nullable)trace;
-@end
-
-@protocol BindingsEventCallbackFunctionObject <NSObject>
-- (void)reportEvent:(long)priority category:(NSString* _Nullable)category evtType:(NSString* _Nullable)evtType details:(NSString* _Nullable)details;
-@end
-
-@protocol BindingsFileTransferReceiveFunc <NSObject>
-- (void)receiveCallback:(NSData* _Nullable)tid fileName:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType sender:(NSData* _Nullable)sender size:(long)size preview:(NSData* _Nullable)preview;
-@end
-
-@protocol BindingsFileTransferReceivedProgressFunc <NSObject>
-- (void)receivedProgressCallback:(BOOL)completed received:(long)received total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-@protocol BindingsFileTransferSentProgressFunc <NSObject>
-- (void)sentProgressCallback:(BOOL)completed sent:(long)sent arrived:(long)arrived total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-@protocol BindingsGroupReceiveFunc <NSObject>
-- (void)groupReceiveCallback:(BindingsGroupMessageReceive* _Nullable)msg;
-@end
-
-@protocol BindingsGroupRequestFunc <NSObject>
-- (void)groupRequestCallback:(BindingsGroup* _Nullable)g;
-@end
-
-@protocol BindingsListener <NSObject>
-/**
- * Hear is called to receive a message in the UI
- */
-- (void)hear:(BindingsMessage* _Nullable)message;
-/**
- * Returns a name, used for debugging
- */
-- (NSString* _Nonnull)name;
-@end
-
-@protocol BindingsLogWriter <NSObject>
-- (void)log:(NSString* _Nullable)p0;
-@end
-
-@protocol BindingsLookupCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsMessageDeliveryCallback <NSObject>
-- (void)eventCallback:(NSData* _Nullable)msgID delivered:(BOOL)delivered timedOut:(BOOL)timedOut roundResults:(NSData* _Nullable)roundResults;
-@end
-
-@protocol BindingsMultiLookupCallback <NSObject>
-- (void)callback:(BindingsContactList* _Nullable)Succeeded failed:(BindingsIdList* _Nullable)failed errors:(NSString* _Nullable)errors;
-@end
-
-@protocol BindingsNetworkHealthCallback <NSObject>
-- (void)callback:(BOOL)p0;
-@end
-
-@protocol BindingsPreimageNotification <NSObject>
-- (void)notify:(NSData* _Nullable)identity deleted:(BOOL)deleted;
-@end
-
-@protocol BindingsRestoreContactsUpdater <NSObject>
-/**
- * RestoreContactsCallback is called to report the current # of contacts
-that have been found and how many have been restored
-against the total number that need to be
-processed. If an error occurs it it set on the err variable as a
-plain string.
- */
-- (void)restoreContactsCallback:(long)numFound numRestored:(long)numRestored total:(long)total err:(NSString* _Nullable)err;
-@end
-
-@protocol BindingsRoundCompletionCallback <NSObject>
-- (void)eventCallback:(long)rid success:(BOOL)success timedOut:(BOOL)timedOut;
-@end
-
-@protocol BindingsRoundEventCallback <NSObject>
-- (void)eventCallback:(long)rid state:(long)state timedOut:(BOOL)timedOut;
-@end
-
-@protocol BindingsSearchCallback <NSObject>
-- (void)callback:(BindingsContactList* _Nullable)contacts error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsSingleSearchCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsTimeSource <NSObject>
-- (int64_t)nowMs;
-@end
-
-@protocol BindingsUpdateBackupFunc <NSObject>
-- (void)updateBackup:(NSData* _Nullable)encryptedBackup;
-@end
-
-@interface BindingsBackup : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * AddJson stores a passed in json string in the backup structure
- */
-- (void)addJson:(NSString* _Nullable)json;
-/**
- * IsBackupRunning returns true if the backup has been initialized and is
-running. Returns false if it has been stopped.
- */
-- (BOOL)isBackupRunning;
-/**
- * StopBackup stops the backup processes and deletes the user's password from
-storage. To enable backups again, call InitializeBackup.
- */
-- (BOOL)stopBackup:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsBackupReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field BackupReport.RestoredContacts with unsupported type: []*gitlab.com/xx_network/primitives/id.ID
-
-@property (nonatomic) NSString* _Nonnull params;
-@end
-
-/**
- * BindingsClient wraps the api.Client, implementing additional functions
-to support the gomobile Client interface
- */
-@interface BindingsClient : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BOOL)confirmAuthenticatedChannel:(NSData* _Nullable)recipientMarshaled ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteAllRequests clears all requests from Client's auth storage.
- */
-- (BOOL)deleteAllRequests:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteContact is a function which removes a contact from Client's storage
- */
-- (BOOL)deleteContact:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteReceiveRequests clears receive requests from Client's auth storage.
- */
-- (BOOL)deleteReceiveRequests:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteRequest will delete a request, agnostic of request type
-for the given partner ID. If no request exists for this
-partner ID an error will be returned.
- */
-- (BOOL)deleteRequest:(NSData* _Nullable)requesterUserId error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteSentRequests clears sent requests from Client's auth storage.
- */
-- (BOOL)deleteSentRequests:(NSError* _Nullable* _Nullable)error;
-// skipped method Client.GetInternalClient with unsupported parameter or return types
-
-/**
- * GetNodeRegistrationStatus returns a struct with the number of nodes the
-client is registered with and the number total.
- */
-- (BindingsNodeRegistrationsStatus* _Nullable)getNodeRegistrationStatus:(NSError* _Nullable* _Nullable)error;
-/**
- * GetPartners returns a list of
- */
-- (NSData* _Nullable)getPartners:(NSError* _Nullable* _Nullable)error;
-/**
- * GetPreferredBins returns the geographic bin or bins that the provided two
-character country code is a part of. The bins are returned as CSV.
- */
-- (NSString* _Nonnull)getPreferredBins:(NSString* _Nullable)countryCode error:(NSError* _Nullable* _Nullable)error;
-- (NSString* _Nonnull)getPreimages:(NSData* _Nullable)identity;
-// skipped method Client.GetRateLimitParams with unsupported parameter or return types
-
-- (NSString* _Nonnull)getRelationshipFingerprint:(NSData* _Nullable)partnerID error:(NSError* _Nullable* _Nullable)error;
-/**
- * Returns a user object from which all information about the current user
-can be gleaned
- */
-- (BindingsUser* _Nullable)getUser;
-/**
- * HasRunningProcessies checks if any background threads are running.
-returns true if none are running. This is meant to be
-used when NetworkFollowerStatus() returns Stopping.
-Due to the handling of comms on iOS, where the OS can
-block indefiently, it may not enter the stopped
-state apropreatly. This can be used instead.
- */
-- (BOOL)hasRunningProcessies;
-/**
- * returns true if the network is read to be in a healthy state where
-messages can be sent
- */
-- (BOOL)isNetworkHealthy;
-- (BindingsContact* _Nullable)makePrecannedAuthenticatedChannel:(long)precannedID error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the state of the network follower. Returns:
-Stopped 	- 0
-Starting - 1000
-Running	- 2000
-Stopping	- 3000
- */
-- (long)networkFollowerStatus;
-- (void)registerAuthCallbacks:(id<BindingsAuthRequestCallback> _Nullable)request confirm:(id<BindingsAuthConfirmCallback> _Nullable)confirm reset:(id<BindingsAuthResetNotificationCallback> _Nullable)reset;
-/**
- * RegisterClientErrorCallback registers the callback to handle errors from the
-long running threads controlled by StartNetworkFollower and StopNetworkFollower
- */
-- (void)registerClientErrorCallback:(id<BindingsClientError> _Nullable)clientError;
-/**
- * RegisterEventCallback records the given function to receive
-ReportableEvent objects. It returns the internal index
-of the callback so that it can be deleted later.
- */
-- (BOOL)registerEventCallback:(NSString* _Nullable)name myObj:(id<BindingsEventCallbackFunctionObject> _Nullable)myObj error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterForNotifications accepts firebase messaging token
- */
-- (BOOL)registerForNotifications:(NSString* _Nullable)token error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterListener records and installs a listener for messages
-matching specific uid, msgType, and/or username
-Returns a ListenerUnregister interface which can be
-
-to register for any userID, pass in an id with length 0 or an id with
-all zeroes
-
-to register for any message type, pass in a message type of 0
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types
- */
-- (BindingsUnregister* _Nullable)registerListener:(NSData* _Nullable)uid msgType:(long)msgType listener:(id<BindingsListener> _Nullable)listener error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterNetworkHealthCB registers the network health callback to be called
-any time the network health changes. Returns a unique ID that can be used to
-unregister the network health callback.
- */
-- (int64_t)registerNetworkHealthCB:(id<BindingsNetworkHealthCallback> _Nullable)nhc;
-- (void)registerPreimageCallback:(NSData* _Nullable)identity pin:(id<BindingsPreimageNotification> _Nullable)pin;
-/**
- * RegisterRoundEventsHandler registers a callback interface for round
-events.
-The rid is the round the event attaches to
-The timeoutMS is the number of milliseconds until the event fails, and the
-validStates are a list of states (one per byte) on which the event gets
-triggered
-States:
- 0x00 - PENDING (Never seen by client)
- 0x01 - PRECOMPUTING
- 0x02 - STANDBY
- 0x03 - QUEUED
- 0x04 - REALTIME
- 0x05 - COMPLETED
- 0x06 - FAILED
-These states are defined in elixxir/primitives/states/state.go
- */
-- (BindingsUnregister* _Nullable)registerRoundEventsHandler:(long)rid cb:(id<BindingsRoundEventCallback> _Nullable)cb timeoutMS:(long)timeoutMS il:(BindingsIntList* _Nullable)il;
-- (void)replayRequests;
-- (BOOL)requestAuthenticatedChannel:(NSData* _Nullable)recipientMarshaled meMarshaled:(NSData* _Nullable)meMarshaled message:(NSString* _Nullable)message ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-- (BOOL)resetSession:(NSData* _Nullable)recipientMarshaled meMarshaled:(NSData* _Nullable)meMarshaled message:(NSString* _Nullable)message ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * This will return the round the message was sent on if it is successfully sent
-This can be used to register a round event to learn about message delivery.
-on failure a round id of -1 is returned
- */
-- (BOOL)sendCmix:(NSData* _Nullable)recipient contents:(NSData* _Nullable)contents parameters:(NSString* _Nullable)parameters ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * SendE2E sends an end-to-end payload to the provided recipient with
-the provided msgType. Returns the list of rounds in which parts of
-the message were sent or an error if it fails.
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types
- */
-- (BindingsSendReport* _Nullable)sendE2E:(NSData* _Nullable)recipient payload:(NSData* _Nullable)payload messageType:(long)messageType parameters:(NSString* _Nullable)parameters error:(NSError* _Nullable* _Nullable)error;
-/**
- * SendUnsafe sends an unencrypted payload to the provided recipient
-with the provided msgType. Returns the list of rounds in which parts
-of the message were sent or an error if it fails.
-NOTE: Do not use this function unless you know what you are doing.
-This function always produces an error message in client logging.
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types with custom types
- */
-- (BindingsRoundList* _Nullable)sendUnsafe:(NSData* _Nullable)recipient payload:(NSData* _Nullable)payload messageType:(long)messageType parameters:(NSString* _Nullable)parameters error:(NSError* _Nullable* _Nullable)error;
-/**
- * SetProxiedBins updates the host pool filter that filters out gateways that
-are not in one of the specified bins. The provided bins should be CSV.
- */
-- (BOOL)setProxiedBins:(NSString* _Nullable)binStringsCSV error:(NSError* _Nullable* _Nullable)error;
-/**
- * StartNetworkFollower kicks off the tracking of the network. It starts
-long running network client threads and returns an object for checking
-state and stopping those threads.
-Call this when returning from sleep and close when going back to
-sleep.
-These threads may become a significant drain on battery when offline, ensure
-they are stopped if there is no internet access
-Threads Started:
-  - Network Follower (/network/follow.go)
-  	tracks the network events and hands them off to workers for handling
-  - Historical Round Retrieval (/network/rounds/historical.go)
-		Retrieves data about rounds which are too old to be stored by the client
-	 - Message Retrieval Worker Group (/network/rounds/retrieve.go)
-		Requests all messages in a given round from the gateway of the last node
-	 - Message Handling Worker Group (/network/message/handle.go)
-		Decrypts and partitions messages when signals via the Switchboard
-	 - Health Tracker (/network/health)
-		Via the network instance tracks the state of the network
-	 - Garbled Messages (/network/message/garbled.go)
-		Can be signaled to check all recent messages which could be be decoded
-		Uses a message store on disk for persistence
-	 - Critical Messages (/network/message/critical.go)
-		Ensures all protocol layer mandatory messages are sent
-		Uses a message store on disk for persistence
-	 - KeyExchange Trigger (/keyExchange/trigger.go)
-		Responds to sent rekeys and executes them
-  - KeyExchange Confirm (/keyExchange/confirm.go)
-		Responds to confirmations of successful rekey operations
- */
-- (BOOL)startNetworkFollower:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * StopNetworkFollower stops the network follower if it is running.
-It returns errors if the Follower is in the wrong status to stop or if it
-fails to stop it.
-if the network follower is running and this fails, the client object will
-most likely be in an unrecoverable state and need to be trashed.
- */
-- (BOOL)stopNetworkFollower:(NSError* _Nullable* _Nullable)error;
-/**
- * UnregisterEventCallback deletes the callback identified by the
-index. It returns an error if it fails.
- */
-- (void)unregisterEventCallback:(NSString* _Nullable)name;
-/**
- * UnregisterForNotifications unregister user for notifications
- */
-- (BOOL)unregisterForNotifications:(NSError* _Nullable* _Nullable)error;
-- (void)unregisterNetworkHealthCB:(int64_t)funcID;
-- (BOOL)verifyOwnership:(NSData* _Nullable)receivedMarshaled verifiedMarshaled:(NSData* _Nullable)verifiedMarshaled ret0_:(BOOL* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * WaitForMessageDelivery allows the caller to get notified if the rounds a
-message was sent in successfully completed. Under the hood, this uses an API
-which uses the internal round data, network historical round lookup, and
-waiting on network events to determine what has (or will) occur.
-
-The callbacks will return at timeoutMS if no state update occurs
-
-This function takes the marshaled send report to ensure a memory leak does
-not occur as a result of both sides of the bindings holding a reference to
-the same pointer.
- */
-- (BOOL)waitForMessageDelivery:(NSData* _Nullable)marshaledSendReport mdc:(id<BindingsMessageDeliveryCallback> _Nullable)mdc timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * WaitForNewtwork will block until either the network is healthy or the
-passed timeout. It will return true if the network is healthy
- */
-- (BOOL)waitForNetwork:(long)timeoutMS;
-/**
- * WaitForRoundCompletion allows the caller to get notified if a round
-has completed (or failed). Under the hood, this uses an API which uses the internal
-round data, network historical round lookup, and waiting on network events
-to determine what has (or will) occur.
-
-The callbacks will return at timeoutMS if no state update occurs
- */
-- (BOOL)waitForRoundCompletion:(long)roundID rec:(id<BindingsRoundCompletionCallback> _Nullable)rec timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- *  contact object
- */
-@interface BindingsContact : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped method Contact.GetAPIContact with unsupported parameter or return types
-
-/**
- * GetDHPublicKey returns the public key associated with the Contact.
- */
-- (NSData* _Nullable)getDHPublicKey;
-/**
- * Returns a fact list for adding and getting facts to and from the contact
- */
-- (BindingsFactList* _Nullable)getFactList;
-/**
- * GetID returns the user ID for this user.
- */
-- (NSData* _Nullable)getID;
-/**
- * GetDHPublicKey returns hash of a DH proof of key ownership.
- */
-- (NSData* _Nullable)getOwnershipProof;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsContactList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Gets a stored round ID at the given index
- */
-- (BindingsContact* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the number of round IDs stored
- */
-- (long)len;
-@end
-
-/**
- * DummyTraffic contains the file dummy traffic manager. The manager can be used
-to set and get the status of the send thread.
- */
-@interface BindingsDummyTraffic : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewDummyTrafficManager creates a DummyTraffic manager and initialises the
-dummy traffic send thread. Note that the manager does not start sending dummy
-traffic until its status is set to true using DummyTraffic.SetStatus.
-The maxNumMessages is the upper bound of the random number of messages sent
-each send. avgSendDeltaMS is the average duration, in milliseconds, to wait
-between sends. Sends occur every avgSendDeltaMS +/- a random duration with an
-upper bound of randomRangeMS.
- */
-- (nullable instancetype)initManager:(BindingsClient* _Nullable)client maxNumMessages:(long)maxNumMessages avgSendDeltaMS:(long)avgSendDeltaMS randomRangeMS:(long)randomRangeMS;
-/**
- * GetStatus returns the current state of the dummy traffic send thread. It has
-the following return values:
- true  = send thread is sending dummy messages
- false = send thread is paused/stopped and not sending dummy messages
-Note that this function does not return the status set by SetStatus directly;
-it returns the current status of the send thread, which means any call to
-SetStatus will have a small delay before it is returned by GetStatus.
- */
-- (BOOL)getStatus;
-/**
- * SetStatus sets the state of the dummy traffic send thread, which determines
-if the thread is running or paused. The possible statuses are:
- true  = send thread is sending dummy messages
- false = send thread is paused/stopped and not sending dummy messages
-Returns an error if the channel is full.
-Note that this function cannot change the status of the send thread if it has
-yet to be started or stopped.
- */
-- (BOOL)setStatus:(BOOL)status error:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsFact : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- *  fact object
-creates a new fact. The factType must be either:
- 0 - Username
- 1 - Email
- 2 - Phone Number
-The fact must be well formed for the type and must not include commas or
-semicolons. If it is not well formed, it will be rejected.  Phone numbers
-must have the two letter country codes appended.  For the complete set of
-validation, see /elixxir/primitives/fact/fact.go
- */
-- (nullable instancetype)init:(long)factType factStr:(NSString* _Nullable)factStr;
-- (NSString* _Nonnull)get;
-- (NSString* _Nonnull)stringify;
-- (long)type;
-@end
-
-@interface BindingsFactList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * FactList
- */
-- (nullable instancetype)init;
-- (BOOL)add:(NSString* _Nullable)factData factType:(long)factType error:(NSError* _Nullable* _Nullable)error;
-- (BindingsFact* _Nullable)get:(long)i;
-- (long)num;
-- (NSString* _Nonnull)stringify:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * FilePartTracker contains the interfaces.FilePartTracker.
- */
-@interface BindingsFilePartTracker : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetNumParts returns the total number of file parts in the transfer.
- */
-- (long)getNumParts;
-/**
- * GetPartStatus returns the status of the file part with the given part number.
-The possible values for the status are:
-0 = unsent
-1 = sent (sender has sent a part, but it has not arrived)
-2 = arrived (sender has sent a part, and it has arrived)
-3 = received (receiver has received a part)
- */
-- (long)getPartStatus:(long)partNum;
-@end
-
-/**
- * FileTransfer contains the file transfer manager.
- */
-@interface BindingsFileTransfer : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewFileTransferManager creates a new file transfer manager and starts the
-sending and receiving threads. The receiveFunc is called everytime a new file
-transfer is received.
-The parameters string contains file transfer network configuration options
-and is a JSON formatted string of the fileTransfer.Params object. If it is
-left empty, then defaults are used. It is highly recommended that defaults
-are used. If it is set, it must match the following format:
- {"MaxThroughput":150000,"SendTimeout":500000000}
-MaxThroughput is in bytes/sec and SendTimeout is in nanoseconds.
- */
-- (nullable instancetype)initManager:(BindingsClient* _Nullable)client receiveFunc:(id<BindingsFileTransferReceiveFunc> _Nullable)receiveFunc parameters:(NSString* _Nullable)parameters;
-/**
- * CloseSend deletes a sent file transfer from the sent transfer map and from
-storage once a transfer has completed or reached the retry limit. Returns an
-error if the transfer has not run out of retries.
- */
-- (BOOL)closeSend:(NSData* _Nullable)transferID error:(NSError* _Nullable* _Nullable)error;
-/**
- * GetMaxFileNameByteLength returns the maximum length, in bytes, allowed for a
-file name.
- */
-- (long)getMaxFileNameByteLength;
-/**
- * GetMaxFilePreviewSize returns the maximum file preview size, in bytes.
- */
-- (long)getMaxFilePreviewSize;
-/**
- * GetMaxFileSize returns the maximum file size, in bytes, allowed to be
-transferred.
- */
-- (long)getMaxFileSize;
-/**
- * GetMaxFileTypeByteLength returns the maximum length, in bytes, allowed for a
-file type.
- */
-- (long)getMaxFileTypeByteLength;
-/**
- * Receive returns the fully assembled file on the completion of the transfer.
-It deletes the transfer from the received transfer map and from storage.
-Returns an error if the transfer is not complete, the full file cannot be
-verified, or if the transfer cannot be found.
- */
-- (NSData* _Nullable)receive:(NSData* _Nullable)transferID error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterReceiveProgressCallback allows for the registration of a callback to
-track the progress of an individual received file transfer. The callback will
-be called immediately when added to report the current status of the
-transfer. It will then call every time a file part is received, the transfer
-completes, or an error occurs. It is called at most once ever period, which
-means if events occur faster than the period, then they will not be reported
-and instead, the progress will be reported once at the end of the period.
-Once the callback reports that the transfer has completed, the recipient
-can get the full file by calling Receive.
-The period is specified in milliseconds.
- */
-- (BOOL)registerReceiveProgressCallback:(NSData* _Nullable)transferID progressFunc:(id<BindingsFileTransferReceivedProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterSendProgressCallback allows for the registration of a callback to
-track the progress of an individual sent file transfer. The callback will be
-called immediately when added to report the current status of the transfer.
-It will then call every time a file part is sent, a file part arrives, the
-transfer completes, or an error occurs. It is called at most once every
-period, which means if events occur faster than the period, then they will
-not be reported and instead, the progress will be reported once at the end of
-the period.
-The period is specified in milliseconds.
- */
-- (BOOL)registerSendProgressCallback:(NSData* _Nullable)transferID progressFunc:(id<BindingsFileTransferSentProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * Send sends a file to the recipient. The sender must have an E2E relationship
-with the recipient.
-The file name is the name of the file to show a user. It has a max length of
-48 bytes.
-The file type identifies what type of file is being sent. It has a max length
-of 8 bytes.
-The file data cannot be larger than 256 kB
-The retry float is the total amount of data to send relative to the data
-size. Data will be resent on error and will resend up to [(1 + retry) *
-fileSize].
-The preview stores a preview of the data (such as a thumbnail) and is
-capped at 4 kB in size.
-Returns a unique transfer ID used to identify the transfer.
-PeriodMS is the duration, in milliseconds, to wait between progress callback
-calls. Set this large enough to prevent spamming.
- */
-- (NSData* _Nullable)send:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType fileData:(NSData* _Nullable)fileData recipientID:(NSData* _Nullable)recipientID retry:(float)retry preview:(NSData* _Nullable)preview progressFunc:(id<BindingsFileTransferSentProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * Group structure contains the identifying and membership information of a
-group chat.
- */
-@interface BindingsGroup : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetCreatedMS returns the time the group was created in milliseconds. This is
-also the time the group requests were sent.
- */
-- (int64_t)getCreatedMS;
-/**
- * GetCreatedNano returns the time the group was created in nanoseconds. This is
-also the time the group requests were sent.
- */
-- (int64_t)getCreatedNano;
-/**
- * GetID return the 33-byte unique group ID.
- */
-- (NSData* _Nullable)getID;
-/**
- * GetInitMessage returns initial message sent with the group request.
- */
-- (NSData* _Nullable)getInitMessage;
-/**
- * GetMembership returns a list of contacts, one for each member in the group.
-The list is in order; the first contact is the leader/creator of the group.
-All subsequent members are ordered by their ID.
- */
-- (BindingsGroupMembership* _Nullable)getMembership;
-/**
- * GetName returns the name set by the user for the group.
- */
-- (NSData* _Nullable)getName;
-/**
- * Serialize serializes the Group.
- */
-- (NSData* _Nullable)serialize;
-@end
-
-/**
- * GroupChat object contains the group chat manager.
- */
-@interface BindingsGroupChat : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetGroup returns the group with the group ID. If no group exists, then the
-error "failed to find group" is returned.
- */
-- (BindingsGroup* _Nullable)getGroup:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * GetGroups returns an IdList containing a list of group IDs that the user is a
-part of.
- */
-- (BindingsIdList* _Nullable)getGroups;
-/**
- * JoinGroup allows a user to join a group when they receive a request. The
-caller must pass in the serialized bytes of a Group.
- */
-- (BOOL)joinGroup:(NSData* _Nullable)serializedGroupData error:(NSError* _Nullable* _Nullable)error;
-/**
- * LeaveGroup deletes a group so a user no longer has access.
- */
-- (BOOL)leaveGroup:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * MakeGroup creates a new group and sends a group request to all members in the
-group. The ID of the new group, the rounds the requests were sent on, and the
-status of the send are contained in NewGroupReport.
- */
-- (BindingsNewGroupReport* _Nullable)makeGroup:(BindingsIdList* _Nullable)membership name:(NSData* _Nullable)name message:(NSData* _Nullable)message;
-/**
- * NumGroups returns the number of groups the user is a part of.
- */
-- (long)numGroups;
-/**
- * ResendRequest resends a group request to all members in the group. The rounds
-they were sent on and the status of the send are contained in NewGroupReport.
- */
-- (BindingsNewGroupReport* _Nullable)resendRequest:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * Send sends the message to the specified group. Returns the round the messages
-were sent on.
- */
-- (BindingsGroupSendReport* _Nullable)send:(NSData* _Nullable)groupIdBytes message:(NSData* _Nullable)message error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * //
-Member Structure
-//
-GroupMember represents a member in the group membership list.
- */
-@interface BindingsGroupMember : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupMember.Member with unsupported type: gitlab.com/elixxir/crypto/group.Member
-
-// skipped method GroupMember.DeepCopy with unsupported parameter or return types
-
-// skipped method GroupMember.Equal with unsupported parameter or return types
-
-/**
- * GetDhKey returns the byte representation of the public Diffie–Hellman key of
-the member.
- */
-- (NSData* _Nullable)getDhKey;
-/**
- * GetID returns the 33-byte user ID of the member.
- */
-- (NSData* _Nullable)getID;
-- (NSString* _Nonnull)goString;
-- (NSData* _Nullable)serialize;
-- (NSString* _Nonnull)string;
-@end
-
-/**
- * GroupMembership structure contains a list of members that are part of a
-group. The first member is the group leader.
- */
-@interface BindingsGroupMembership : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Get returns the member at the index. The member at index 0 is always the
-group leader. An error is returned if the index is out of range.
- */
-- (BindingsGroupMember* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Len returns the number of members in the group membership.
- */
-- (long)len;
-@end
-
-/**
- * GroupMessageReceive contains a group message, its ID, and its data that a
-user receives.
- */
-@interface BindingsGroupMessageReceive : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupMessageReceive.MessageReceive with unsupported type: gitlab.com/elixxir/client/groupChat.MessageReceive
-
-/**
- * GetEphemeralID returns the ephemeral ID of the recipient.
- */
-- (int64_t)getEphemeralID;
-/**
- * GetGroupID returns the 33-byte group ID.
- */
-- (NSData* _Nullable)getGroupID;
-/**
- * GetMessageID returns the message ID.
- */
-- (NSData* _Nullable)getMessageID;
-/**
- * GetPayload returns the message payload.
- */
-- (NSData* _Nullable)getPayload;
-/**
- * GetRecipientID returns the 33-byte user ID of the recipient.
- */
-- (NSData* _Nullable)getRecipientID;
-/**
- * GetRoundID returns the ID of the round the message was sent on.
- */
-- (int64_t)getRoundID;
-/**
- * GetRoundTimestampMS returns the timestamp, in milliseconds, of the round the
-message was sent on.
- */
-- (int64_t)getRoundTimestampMS;
-/**
- * GetRoundTimestampNano returns the timestamp, in nanoseconds, of the round the
-message was sent on.
- */
-- (int64_t)getRoundTimestampNano;
-/**
- * GetRoundURL returns the ID of the round the message was sent on.
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetSenderID returns the 33-byte user ID of the sender.
- */
-- (NSData* _Nullable)getSenderID;
-/**
- * GetTimestampMS returns the message timestamp in milliseconds.
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message timestamp in nanoseconds.
- */
-- (int64_t)getTimestampNano;
-- (NSString* _Nonnull)string;
-@end
-
-@interface BindingsGroupReportDisk : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupReportDisk.List with unsupported type: []gitlab.com/xx_network/primitives/id.Round
-
-@property (nonatomic) NSData* _Nullable grpId;
-@property (nonatomic) long status;
-@end
-
-/**
- * GroupSendReport is returned when sending a group message. It contains the
-round ID sent on and the timestamp of the send.
- */
-@interface BindingsGroupSendReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetMessageID returns the ID of the round that the send occurred on.
- */
-- (NSData* _Nullable)getMessageID;
-/**
- * GetRoundID returns the ID of the round that the send occurred on.
- */
-- (int64_t)getRoundID;
-/**
- * GetRoundURL returns the URL of the round that the send occurred on.
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetTimestampMS returns the timestamp of the send in milliseconds.
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the timestamp of the send in nanoseconds.
- */
-- (int64_t)getTimestampNano;
-@end
-
-/**
- *  ID list
-IdList contains a list of IDs.
- */
-@interface BindingsIdList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Add appends the ID bytes to the end of the list.
- */
-- (BOOL)add:(NSData* _Nullable)idBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * Get returns the ID at the index. An error is returned if the index is out of
-range.
- */
-- (NSData* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Len returns the number of IDs in the list.
- */
-- (long)len;
-@end
-
-@interface BindingsIntList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (void)add:(long)i;
-- (BOOL)get:(long)i ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-- (long)len;
-@end
-
-@interface BindingsManyNotificationForMeReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BindingsNotificationForMeReport* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-- (long)len;
-@end
-
-/**
- * Message is a message received from the cMix network in the clear
-or that has been decrypted using established E2E keys.
- */
-@interface BindingsMessage : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetID returns the id of the message
- */
-- (NSData* _Nullable)getID;
-/**
- * GetMessageType returns the message's type
- */
-- (long)getMessageType;
-/**
- * GetPayload returns the message's payload/contents
- */
-- (NSData* _Nullable)getPayload;
-/**
- * GetRoundId returns the message's round ID
- */
-- (int64_t)getRoundId;
-/**
- * GetRoundTimestampMS returns the message's round timestamp in milliseconds
- */
-- (int64_t)getRoundTimestampMS;
-/**
- * GetRoundTimestampNano returns the message's round timestamp in nanoseconds
- */
-- (int64_t)getRoundTimestampNano;
-/**
- * GetRoundURL returns the message's round URL
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetSender returns the message's sender ID, if available
- */
-- (NSData* _Nullable)getSender;
-/**
- * GetTimestampMS returns the message's timestamp in milliseconds
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message's timestamp in nanoseconds
- */
-- (int64_t)getTimestampNano;
-@end
-
-/**
- * NewGroupReport is returned when creating a new group and contains the ID of
-the group, a list of rounds that the group requests were sent on, and the
-status of the send.
- */
-@interface BindingsNewGroupReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetError returns the string of an error.
-Will be an empty string if no error occured
- */
-- (NSString* _Nonnull)getError;
-/**
- * GetGroup returns the Group.
- */
-- (BindingsGroup* _Nullable)getGroup;
-/**
- * GetRoundList returns the RoundList containing a list of rounds requests were
-sent on.
- */
-- (BindingsRoundList* _Nullable)getRoundList;
-/**
- * GetStatus returns the status of the requests sent when creating a new group.
-status = 0   an error occurred before any requests could be sent
-         1   all requests failed to send (call Resend Group)
-         2   some request failed and some succeeded (call Resend Group)
-         3,  all requests sent successfully (call Resend Group)
- */
-- (long)getStatus;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-- (BOOL)unmarshal:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * NodeRegistrationsStatus structure for returning node registration statuses
-for bindings.
- */
-@interface BindingsNodeRegistrationsStatus : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetRegistered returns the number of nodes registered with the client.
- */
-- (long)getRegistered;
-/**
- * GetTotal return the total of nodes currently in the network.
- */
-- (long)getTotal;
-@end
-
-@interface BindingsNotificationForMeReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BOOL)forMe;
-- (NSData* _Nullable)source;
-- (NSString* _Nonnull)type;
-@end
-
-/**
- * RestoreContactsReport is a gomobile friendly report structure
-for determining which IDs restored, which failed, and why.
- */
-@interface BindingsRestoreContactsReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetErrorAt returns the error string at index
- */
-- (NSString* _Nonnull)getErrorAt:(long)index;
-/**
- * GetFailedAt returns the failed ID at index
- */
-- (NSData* _Nullable)getFailedAt:(long)index;
-/**
- * GetRestoreContactsError returns an error string. Empty if no error.
- */
-- (NSString* _Nonnull)getRestoreContactsError;
-/**
- * GetRestoredAt returns the restored ID at index
- */
-- (NSData* _Nullable)getRestoredAt:(long)index;
-/**
- * LenFailed returns the length of the ID's failed.
- */
-- (long)lenFailed;
-/**
- * LenRestored returns the length of ID's restored.
- */
-- (long)lenRestored;
-@end
-
-@interface BindingsRoundList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Gets a stored round ID at the given index
- */
-- (BOOL)get:(long)i ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the number of round IDs stored
- */
-- (long)len;
-@end
-
-/**
- * the send report is the mechanisim by which sendE2E returns a single
- */
-@interface BindingsSendReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (NSData* _Nullable)getMessageID;
-- (BindingsRoundList* _Nullable)getRoundList;
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetTimestampMS returns the message's timestamp in milliseconds
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message's timestamp in nanoseconds
- */
-- (int64_t)getTimestampNano;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-- (BOOL)unmarshal:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsSendReportDisk : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field SendReportDisk.List with unsupported type: []gitlab.com/xx_network/primitives/id.Round
-
-@property (nonatomic) NSData* _Nullable mid;
-@property (nonatomic) int64_t ts;
-@end
-
-/**
- * Generic Unregister - a generic return used for all callbacks which can be
-unregistered
-Interface which allows the un-registration of a listener
- */
-@interface BindingsUnregister : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Call unregisters a callback
- */
-- (void)unregister;
-@end
-
-@interface BindingsUser : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BindingsContact* _Nullable)getContact;
-- (NSData* _Nullable)getE2EDhPrivateKey;
-- (NSData* _Nullable)getE2EDhPublicKey;
-- (NSData* _Nullable)getReceptionID;
-- (NSData* _Nullable)getReceptionRSAPrivateKeyPem;
-- (NSData* _Nullable)getReceptionRSAPublicKeyPem;
-- (NSData* _Nullable)getReceptionSalt;
-- (NSData* _Nullable)getTransmissionID;
-- (NSData* _Nullable)getTransmissionRSAPrivateKeyPem;
-- (NSData* _Nullable)getTransmissionRSAPublicKeyPem;
-- (NSData* _Nullable)getTransmissionSalt;
-- (BOOL)isPrecanned;
-@end
-
-@interface BindingsUserDiscovery : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewUserDiscovery returns a new user discovery object. Only call this once. It must be called
-after StartNetworkFollower is called and will fail if the network has never
-been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-- (nullable instancetype)init:(BindingsClient* _Nullable)client;
-/**
- * NewUserDiscoveryFromBackup returns a new user discovery object. It
-wil set up the manager with the backup data. Pass into it the backed up
-facts, one email and phone number each. This will add the registered facts
-to the backed Store. Any one of these fields may be empty,
-however both fields being empty will cause an error. Any other fact that is not
-an email or phone number will return an error. You may only add a fact for the
-accepted types once each. If you attempt to back up a fact type that has already
-been backed up, an error will be returned. Anytime an error is returned, it means
-the backup was not successful.
-NOTE: Do not use this as a direct store operation. This feature is intended to add facts
-to a backend store that have ALREADY BEEN REGISTERED on the account.
-THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend.
-Only call this once. It must be called after StartNetworkFollower
-is called and will fail if the network has never been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-- (nullable instancetype)initFromBackup:(BindingsClient* _Nullable)client email:(NSString* _Nullable)email phone:(NSString* _Nullable)phone;
-/**
- * AddFact adds a fact for the user to user discovery. Will only succeed if the
-user is already registered and the system does not have the fact currently
-registered for any user.
-Will fail if the fact string is not well formed.
-This does not complete the fact registration process, it returns a
-confirmation id instead. Over the communications system the fact is
-associated with, a code will be sent. This confirmation ID needs to be
-called along with the code to finalize the fact.
- */
-- (NSString* _Nonnull)addFact:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * ConfirmFact confirms a fact first registered via AddFact. The confirmation ID comes from
-AddFact while the code will come over the associated communications system
- */
-- (BOOL)confirmFact:(NSString* _Nullable)confirmationID code:(NSString* _Nullable)code error:(NSError* _Nullable* _Nullable)error;
-/**
- * Lookup the contact object associated with the given userID.  The
-id is the byte representation of an id.
-This will reject if that id is malformed. The LookupCallback will return
-the associated contact if it exists.
- */
-- (BOOL)lookup:(NSData* _Nullable)idBytes callback:(id<BindingsLookupCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * MultiLookup Looks for the contact object associated with all given userIDs.
-The ids are the byte representation of an id stored in an IDList object.
-This will reject if that id is malformed or if the indexing on the IDList
-object is wrong. The MultiLookupCallback will return with all contacts
-returned within the timeout.
- */
-- (BOOL)multiLookup:(BindingsIdList* _Nullable)ids callback:(id<BindingsMultiLookupCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * Register registers a user with user discovery. Will return an error if the
-network signatures are malformed or if the username is taken. Usernames
-cannot be changed after registration at this time. Will fail if the user is
-already registered.
-Identity does not go over cmix, it occurs over normal communications
- */
-- (BOOL)register:(NSString* _Nullable)username error:(NSError* _Nullable* _Nullable)error;
-/**
- * RemoveFact removes a previously confirmed fact.  Will fail if the passed fact string is
-not well-formed or if the fact is not associated with this client.
-Users cannot remove username facts and must instead remove the user.
- */
-- (BOOL)removeFact:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * RemoveUser deletes a user. The fact sent must be the username.
-This function preserves the username forever and makes it
-unusable.
- */
-- (BOOL)removeUser:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * Search for the passed Facts.  The factList is the stringification of a
-fact list object, look at /bindings/list.go for more on that object.
-This will reject if that object is malformed. The SearchCallback will return
-a list of contacts, each having the facts it hit against.
-This is NOT intended to be used to search for multiple users at once, that
-can have a privacy reduction. Instead, it is intended to be used to search
-for a user where multiple pieces of information is known.
- */
-- (BOOL)search:(NSString* _Nullable)fl callback:(id<BindingsSearchCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * SearchSingle searches for the passed Facts.  The fact is the stringification of a
-fact object, look at /bindings/contact.go for more on that object.
-This will reject if that object is malformed. The SearchCallback will return
-a list of contacts, each having the facts it hit against.
-This only searches for a single fact at a time. It is intended to make some
-simple use cases of the API easier.
- */
-- (BOOL)searchSingle:(NSString* _Nullable)f callback:(id<BindingsSingleSearchCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * SetAlternativeUserDiscovery sets the alternativeUd object within manager.
-Once set, any user discovery operation will go through the alternative
-user discovery service.
-To undo this operation, use UnsetAlternativeUserDiscovery.
-The contact file is the already read in bytes, not the file path for the contact file.
- */
-- (BOOL)setAlternativeUserDiscovery:(NSData* _Nullable)address cert:(NSData* _Nullable)cert contactFile:(NSData* _Nullable)contactFile error:(NSError* _Nullable* _Nullable)error;
-/**
- * UnsetAlternativeUserDiscovery clears out the information from
-the Manager object.
- */
-- (BOOL)unsetAlternativeUserDiscovery:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * Error codes
- */
-FOUNDATION_EXPORT NSString* _Nonnull const BindingsUnrecognizedCode;
-FOUNDATION_EXPORT NSString* _Nonnull const BindingsUnrecognizedMessage;
-
-/**
- * CompressJpeg takes a JPEG image in byte format
-and compresses it based on desired output size
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsCompressJpeg(NSData* _Nullable imgBytes, NSError* _Nullable* _Nullable error);
-
-/**
- * CompressJpegForPreview takes a JPEG image in byte format
-and compresses it based on desired output size
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsCompressJpegForPreview(NSData* _Nullable imgBytes, NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadAndVerifySignedNdfWithUrl retrieves the NDF from a specified URL.
-The NDF is processed into a protobuf containing a signature which
-is verified using the cert string passed in. The NDF is returned as marshaled
-byte data which may be used to start a client.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadAndVerifySignedNdfWithUrl(NSString* _Nullable url, NSString* _Nullable cert, NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadDAppRegistrationDB returns a []byte containing
-the JSON data describing registered dApps.
-See https://git.xx.network/elixxir/registered-dapps
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadDAppRegistrationDB(NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadErrorDB returns a []byte containing the JSON data
-describing client errors.
-See https://git.xx.network/elixxir/client-error-database/
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadErrorDB(NSError* _Nullable* _Nullable error);
-
-/**
- * DumpStack returns a string with the stack trace of every running thread.
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsDumpStack(NSError* _Nullable* _Nullable error);
-
-/**
- * EnableGrpcLogs sets GRPC trace logging
- */
-FOUNDATION_EXPORT void BindingsEnableGrpcLogs(id<BindingsLogWriter> _Nullable writer);
-
-/**
- * ErrorStringToUserFriendlyMessage takes a passed in errStr which will be
-a backend generated error. These may be error specifically written by
-the backend team or lower level errors gotten from low level dependencies.
-This function will parse the error string for common errors provided from
-errToUserErr to provide a more user-friendly error message for the front end.
-If the error is not common, some simple parsing is done on the error message
-to make it more user-accessible, removing backend specific jargon.
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsErrorStringToUserFriendlyMessage(NSString* _Nullable errStr);
-
-/**
- * GenerateSecret creates a secret password using a system-based
-pseudorandom number generator. It takes 1 parameter, `numBytes`,
-which should be set to 32, but can be set higher in certain cases.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsGenerateSecret(long numBytes);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetCMIXParams(NSError* _Nullable* _Nullable error);
-
-/**
- * returns a previously created client. IF be used if the garbage collector
-removes the client instance on the app side.  Is NOT thread safe relative to
-login, newClient, or newPrecannedClient
- */
-FOUNDATION_EXPORT BindingsClient* _Nullable BindingsGetClientSingleton(void);
-
-/**
- * GetDependencies returns the api DEPENDENCIES
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetDependencies(void);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetE2EParams(NSError* _Nullable* _Nullable error);
-
-/**
- * GetGitVersion rturns the api GITVERSION
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetGitVersion(void);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetNetworkParams(NSError* _Nullable* _Nullable error);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetUnsafeParams(NSError* _Nullable* _Nullable error);
-
-/**
- * GetVersion returns the api SEMVER
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetVersion(void);
-
-/**
- * InitializeBackup starts the backup processes that returns backup updates when
-they occur. Any time an event occurs that changes the contents of the backup,
-such as adding or deleting a contact, the backup is triggered and an
-encrypted backup is generated and returned on the updateBackupCb callback.
-Call this function only when enabling backup if it has not already been
-initialized or when the user wants to change their password.
-To resume backup process on app recovery, use ResumeBackup.
- */
-FOUNDATION_EXPORT BindingsBackup* _Nullable BindingsInitializeBackup(NSString* _Nullable password, id<BindingsUpdateBackupFunc> _Nullable updateBackupCb, BindingsClient* _Nullable c, NSError* _Nullable* _Nullable error);
-
-/**
- * LoadSecretWithMnemonic loads the secret stored from the call to
-StoreSecretWithMnemonic. The path given should be the same filepath
-as the path given in StoreSecretWithMnemonic. There should be a file
-in this path called ".recovery". This operation is not tied
-to client operations, as the user will not have a client when trying to
-recover their account.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsLoadSecretWithMnemonic(NSString* _Nullable mnemonic, NSString* _Nullable path, NSError* _Nullable* _Nullable error);
-
-/**
- * sets level of logging. All logs the set level and above will be displayed
-options are:
-	TRACE		- 0
-	DEBUG		- 1
-	INFO 		- 2
-	WARN		- 3
-	ERROR		- 4
-	CRITICAL	- 5
-	FATAL		- 6
-The default state without updates is: INFO
- */
-FOUNDATION_EXPORT BOOL BindingsLogLevel(long level, NSError* _Nullable* _Nullable error);
-
-/**
- * Login will load an existing client from the storageDir
-using the password. This will fail if the client doesn't exist or
-the password is incorrect.
-The password is passed as a byte array so that it can be cleared from
-memory and stored as securely as possible using the memguard library.
-Login does not block on network connection, and instead loads and
-starts subprocesses to perform network operations.
- */
-FOUNDATION_EXPORT BindingsClient* _Nullable BindingsLogin(NSString* _Nullable storageDir, NSData* _Nullable password, NSString* _Nullable parameters, NSError* _Nullable* _Nullable error);
-
-/**
- * MakeIdList creates a new empty IdList.
- */
-FOUNDATION_EXPORT BindingsIdList* _Nullable BindingsMakeIdList(void);
-
-FOUNDATION_EXPORT BindingsIntList* _Nullable BindingsMakeIntList(void);
-
-/**
- * NewClient creates client storage, generates keys, connects, and registers
-with the network. Note that this does not register a username/identity, but
-merely creates a new cryptographic identity for adding such information
-at a later date.
-
-Users of this function should delete the storage directory on error.
- */
-FOUNDATION_EXPORT BOOL BindingsNewClient(NSString* _Nullable network, NSString* _Nullable storageDir, NSData* _Nullable password, NSString* _Nullable regCode, NSError* _Nullable* _Nullable error);
-
-/**
- * NewClientFromBackup constructs a new Client from an encrypted backup. The backup
-is decrypted using the backupPassphrase. On success a successful client creation,
-the function will return a JSON encoded list of the E2E partners
-contained in the backup and a json-encoded string of the parameters stored in the backup
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsNewClientFromBackup(NSString* _Nullable ndfJSON, NSString* _Nullable storageDir, NSData* _Nullable sessionPassword, NSData* _Nullable backupPassphrase, NSData* _Nullable backupFileContents, NSError* _Nullable* _Nullable error);
-
-/**
- * NewDummyTrafficManager creates a DummyTraffic manager and initialises the
-dummy traffic send thread. Note that the manager does not start sending dummy
-traffic until its status is set to true using DummyTraffic.SetStatus.
-The maxNumMessages is the upper bound of the random number of messages sent
-each send. avgSendDeltaMS is the average duration, in milliseconds, to wait
-between sends. Sends occur every avgSendDeltaMS +/- a random duration with an
-upper bound of randomRangeMS.
- */
-FOUNDATION_EXPORT BindingsDummyTraffic* _Nullable BindingsNewDummyTrafficManager(BindingsClient* _Nullable client, long maxNumMessages, long avgSendDeltaMS, long randomRangeMS, NSError* _Nullable* _Nullable error);
-
-/**
- *  fact object
-creates a new fact. The factType must be either:
- 0 - Username
- 1 - Email
- 2 - Phone Number
-The fact must be well formed for the type and must not include commas or
-semicolons. If it is not well formed, it will be rejected.  Phone numbers
-must have the two letter country codes appended.  For the complete set of
-validation, see /elixxir/primitives/fact/fact.go
- */
-FOUNDATION_EXPORT BindingsFact* _Nullable BindingsNewFact(long factType, NSString* _Nullable factStr, NSError* _Nullable* _Nullable error);
-
-/**
- * FactList
- */
-FOUNDATION_EXPORT BindingsFactList* _Nullable BindingsNewFactList(void);
-
-/**
- * NewFileTransferManager creates a new file transfer manager and starts the
-sending and receiving threads. The receiveFunc is called everytime a new file
-transfer is received.
-The parameters string contains file transfer network configuration options
-and is a JSON formatted string of the fileTransfer.Params object. If it is
-left empty, then defaults are used. It is highly recommended that defaults
-are used. If it is set, it must match the following format:
- {"MaxThroughput":150000,"SendTimeout":500000000}
-MaxThroughput is in bytes/sec and SendTimeout is in nanoseconds.
- */
-FOUNDATION_EXPORT BindingsFileTransfer* _Nullable BindingsNewFileTransferManager(BindingsClient* _Nullable client, id<BindingsFileTransferReceiveFunc> _Nullable receiveFunc, NSString* _Nullable parameters, NSError* _Nullable* _Nullable error);
-
-/**
- * NewGroupManager creates a new group chat manager.
- */
-FOUNDATION_EXPORT BindingsGroupChat* _Nullable BindingsNewGroupManager(BindingsClient* _Nullable client, id<BindingsGroupRequestFunc> _Nullable requestFunc, id<BindingsGroupReceiveFunc> _Nullable receiveFunc, NSError* _Nullable* _Nullable error);
-
-/**
- * NewPrecannedClient creates an insecure user with predetermined keys with nodes
-It creates client storage, generates keys, connects, and registers
-with the network. Note that this does not register a username/identity, but
-merely creates a new cryptographic identity for adding such information
-at a later date.
-
-Users of this function should delete the storage directory on error.
- */
-FOUNDATION_EXPORT BOOL BindingsNewPrecannedClient(long precannedID, NSString* _Nullable network, NSString* _Nullable storageDir, NSData* _Nullable password, NSError* _Nullable* _Nullable error);
-
-/**
- * NewUserDiscovery returns a new user discovery object. Only call this once. It must be called
-after StartNetworkFollower is called and will fail if the network has never
-been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUserDiscovery(BindingsClient* _Nullable client, NSError* _Nullable* _Nullable error);
-
-/**
- * NewUserDiscoveryFromBackup returns a new user discovery object. It
-wil set up the manager with the backup data. Pass into it the backed up
-facts, one email and phone number each. This will add the registered facts
-to the backed Store. Any one of these fields may be empty,
-however both fields being empty will cause an error. Any other fact that is not
-an email or phone number will return an error. You may only add a fact for the
-accepted types once each. If you attempt to back up a fact type that has already
-been backed up, an error will be returned. Anytime an error is returned, it means
-the backup was not successful.
-NOTE: Do not use this as a direct store operation. This feature is intended to add facts
-to a backend store that have ALREADY BEEN REGISTERED on the account.
-THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend.
-Only call this once. It must be called after StartNetworkFollower
-is called and will fail if the network has never been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUserDiscoveryFromBackup(BindingsClient* _Nullable client, NSString* _Nullable email, NSString* _Nullable phone, NSError* _Nullable* _Nullable error);
-
-/**
- * NotificationsForMe Check if a notification received is for me
-It returns a NotificationForMeReport which contains a ForMe bool stating if it is for the caller,
-a Type, and a source. These are as follows:
-	TYPE       	SOURCE				DESCRIPTION
-	"default"	recipient user ID	A message with no association
-	"request"	sender user ID		A channel request has been received
-	"reset"	    sender user ID		A channel reset has been received
-	"confirm"	sender user ID		A channel request has been accepted
-	"silent"	sender user ID		A message which should not be notified on
-	"e2e"		sender user ID		reception of an E2E message
-	"group"		group ID			reception of a group chat message
- "endFT"     sender user ID		Last message sent confirming end of file transfer
- "groupRQ"   sender user ID		Request from sender to join a group chat
- */
-FOUNDATION_EXPORT BindingsManyNotificationForMeReport* _Nullable BindingsNotificationsForMe(NSString* _Nullable notifCSV, NSString* _Nullable preimages, NSError* _Nullable* _Nullable error);
-
-/**
- * RegisterLogWriter registers a callback on which logs are written.
- */
-FOUNDATION_EXPORT void BindingsRegisterLogWriter(id<BindingsLogWriter> _Nullable writer);
-
-/**
- * RestoreContactsFromBackup takes as input the jason output of the
-`NewClientFromBackup` function, unmarshals it into IDs, looks up
-each ID in user discovery, and initiates a session reset request.
-This function will not return until every id in the list has been sent a
-request. It should be called again and again until it completes.
-xxDK users should not use this function. This function is used by
-the mobile phone apps and are not intended to be part of the xxDK. It
-should be treated as internal functions specific to the phone apps.
- */
-FOUNDATION_EXPORT BindingsRestoreContactsReport* _Nullable BindingsRestoreContactsFromBackup(NSData* _Nullable backupPartnerIDs, BindingsClient* _Nullable client, BindingsUserDiscovery* _Nullable udManager, id<BindingsLookupCallback> _Nullable lookupCB, id<BindingsRestoreContactsUpdater> _Nullable updatesCb);
-
-/**
- * ResumeBackup starts the backup processes back up with a new callback after it
-has been initialized.
-Call this function only when resuming a backup that has already been
-initialized or to replace the callback.
-To start the backup for the first time or to use a new password, use
-InitializeBackup.
- */
-FOUNDATION_EXPORT BindingsBackup* _Nullable BindingsResumeBackup(id<BindingsUpdateBackupFunc> _Nullable cb, BindingsClient* _Nullable c, NSError* _Nullable* _Nullable error);
-
-/**
- * SetTimeSource sets the network time to a custom source.
- */
-FOUNDATION_EXPORT void BindingsSetTimeSource(id<BindingsTimeSource> _Nullable timeNow);
-
-/**
- * StoreSecretWithMnemonic stores the secret tied with the mnemonic to storage.
-Unlike other storage operations, this does not use EKV, as that is
-intrinsically tied to client operations, which the user will not have while
-trying to recover their account. As such, we store the encrypted data
-directly, with a specified path. Path will be a valid filepath in which the
-recover file will be stored as ".recovery".
-
-As an example, given "home/user/xxmessenger/storagePath",
-the recovery file will be stored at
-"home/user/xxmessenger/storagePath/.recovery"
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsStoreSecretWithMnemonic(NSData* _Nullable secret, NSString* _Nullable path, NSError* _Nullable* _Nullable error);
-
-/**
- * Unmarshals a marshaled contact object, returns an error if it fails
- */
-FOUNDATION_EXPORT BindingsContact* _Nullable BindingsUnmarshalContact(NSData* _Nullable b, NSError* _Nullable* _Nullable error);
-
-/**
- * Unmarshals a marshaled send report object, returns an error if it fails
- */
-FOUNDATION_EXPORT BindingsSendReport* _Nullable BindingsUnmarshalSendReport(NSData* _Nullable b, NSError* _Nullable* _Nullable error);
-
-/**
- * UpdateCommonErrors takes the passed in contents of a JSON file and updates the
-errToUserErr map with the contents of the json file. The JSON's expected format
-conform with the commented examples provides in errToUserErr above.
-NOTE that you should not pass in a file path, but a preloaded JSON file
- */
-FOUNDATION_EXPORT BOOL BindingsUpdateCommonErrors(NSString* _Nullable jsonFile, NSError* _Nullable* _Nullable error);
-
-// skipped function WrapAPIClient with unsupported parameter or return types
-
-
-// skipped function WrapUserDiscovery with unsupported parameter or return types
-
-
-@class BindingsAuthConfirmCallback;
-
-@class BindingsAuthRequestCallback;
-
-@class BindingsAuthResetNotificationCallback;
-
-@class BindingsClientError;
-
-@class BindingsEventCallbackFunctionObject;
-
-@class BindingsFileTransferReceiveFunc;
-
-@class BindingsFileTransferReceivedProgressFunc;
-
-@class BindingsFileTransferSentProgressFunc;
-
-@class BindingsGroupReceiveFunc;
-
-@class BindingsGroupRequestFunc;
-
-@class BindingsListener;
-
-@class BindingsLogWriter;
-
-@class BindingsLookupCallback;
-
-@class BindingsMessageDeliveryCallback;
-
-@class BindingsMultiLookupCallback;
-
-@class BindingsNetworkHealthCallback;
-
-@class BindingsPreimageNotification;
-
-@class BindingsRestoreContactsUpdater;
-
-@class BindingsRoundCompletionCallback;
-
-@class BindingsRoundEventCallback;
-
-@class BindingsSearchCallback;
-
-@class BindingsSingleSearchCallback;
-
-@class BindingsTimeSource;
-
-@class BindingsUpdateBackupFunc;
-
-/**
- * AuthConfirmCallback notifies the register whenever they receive an auth
-request confirmation
- */
-@interface BindingsAuthConfirmCallback : NSObject <goSeqRefInterface, BindingsAuthConfirmCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)partner;
-@end
-
-/**
- * AuthRequestCallback notifies the register whenever they receive an auth
-request
- */
-@interface BindingsAuthRequestCallback : NSObject <goSeqRefInterface, BindingsAuthRequestCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-/**
- * AuthRequestCallback notifies the register whenever they receive an auth
-request
- */
-@interface BindingsAuthResetNotificationCallback : NSObject <goSeqRefInterface, BindingsAuthResetNotificationCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@interface BindingsClientError : NSObject <goSeqRefInterface, BindingsClientError> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)report:(NSString* _Nullable)source message:(NSString* _Nullable)message trace:(NSString* _Nullable)trace;
-@end
-
-/**
- * EventCallbackFunctionObject bindings interface which contains function
-that implements the EventCallbackFunction
- */
-@interface BindingsEventCallbackFunctionObject : NSObject <goSeqRefInterface, BindingsEventCallbackFunctionObject> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)reportEvent:(long)priority category:(NSString* _Nullable)category evtType:(NSString* _Nullable)evtType details:(NSString* _Nullable)details;
-@end
-
-/**
- * FileTransferReceiveFunc contains a function callback that notifies the
-receiver of an incoming file transfer. It is called on the reception of the
-initial file transfer message.
- */
-@interface BindingsFileTransferReceiveFunc : NSObject <goSeqRefInterface, BindingsFileTransferReceiveFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)receiveCallback:(NSData* _Nullable)tid fileName:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType sender:(NSData* _Nullable)sender size:(long)size preview:(NSData* _Nullable)preview;
-@end
-
-/**
- * FileTransferReceivedProgressFunc contains a function callback that tracks the
-progress of receiving a file. It is called when a file part is received, the
-transfer completes, or on error.
- */
-@interface BindingsFileTransferReceivedProgressFunc : NSObject <goSeqRefInterface, BindingsFileTransferReceivedProgressFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)receivedProgressCallback:(BOOL)completed received:(long)received total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-/**
- * FileTransferSentProgressFunc contains a function callback that tracks the
-progress of sending a file. It is called when a file part is sent, a file
-part arrives, the transfer completes, or on error.
- */
-@interface BindingsFileTransferSentProgressFunc : NSObject <goSeqRefInterface, BindingsFileTransferSentProgressFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)sentProgressCallback:(BOOL)completed sent:(long)sent arrived:(long)arrived total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-/**
- * GroupReceiveFunc contains a function callback that is called when a group
-message is received.
- */
-@interface BindingsGroupReceiveFunc : NSObject <goSeqRefInterface, BindingsGroupReceiveFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)groupReceiveCallback:(BindingsGroupMessageReceive* _Nullable)msg;
-@end
-
-/**
- * GroupRequestFunc contains a function callback that is called when a group
-request is received.
- */
-@interface BindingsGroupRequestFunc : NSObject <goSeqRefInterface, BindingsGroupRequestFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)groupRequestCallback:(BindingsGroup* _Nullable)g;
-@end
-
-/**
- * Listener provides a callback to hear a message
-An object implementing this interface can be called back when the client
-gets a message of the type that the registerer specified at registration
-time.
- */
-@interface BindingsListener : NSObject <goSeqRefInterface, BindingsListener> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * Hear is called to receive a message in the UI
- */
-- (void)hear:(BindingsMessage* _Nullable)message;
-/**
- * Returns a name, used for debugging
- */
-- (NSString* _Nonnull)name;
-@end
-
-@interface BindingsLogWriter : NSObject <goSeqRefInterface, BindingsLogWriter> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)log:(NSString* _Nullable)p0;
-@end
-
-/**
- * LookupCallback returns the result of a single lookup
- */
-@interface BindingsLookupCallback : NSObject <goSeqRefInterface, BindingsLookupCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-/**
- * MessageDeliveryCallback gets called on the determination if all events
-related to a message send were successful.
- */
-@interface BindingsMessageDeliveryCallback : NSObject <goSeqRefInterface, BindingsMessageDeliveryCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(NSData* _Nullable)msgID delivered:(BOOL)delivered timedOut:(BOOL)timedOut roundResults:(NSData* _Nullable)roundResults;
-@end
-
-/**
- * MultiLookupCallback returns the result of many parallel lookups
- */
-@interface BindingsMultiLookupCallback : NSObject <goSeqRefInterface, BindingsMultiLookupCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContactList* _Nullable)Succeeded failed:(BindingsIdList* _Nullable)failed errors:(NSString* _Nullable)errors;
-@end
-
-/**
- * A callback when which is used to receive notification if network health
-changes
- */
-@interface BindingsNetworkHealthCallback : NSObject <goSeqRefInterface, BindingsNetworkHealthCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BOOL)p0;
-@end
-
-@interface BindingsPreimageNotification : NSObject <goSeqRefInterface, BindingsPreimageNotification> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)notify:(NSData* _Nullable)identity deleted:(BOOL)deleted;
-@end
-
-/**
- * RestoreContactsUpdater interface provides a callback function
-for receiving update information from RestoreContactsFromBackup.
- */
-@interface BindingsRestoreContactsUpdater : NSObject <goSeqRefInterface, BindingsRestoreContactsUpdater> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * RestoreContactsCallback is called to report the current # of contacts
-that have been found and how many have been restored
-against the total number that need to be
-processed. If an error occurs it it set on the err variable as a
-plain string.
- */
-- (void)restoreContactsCallback:(long)numFound numRestored:(long)numRestored total:(long)total err:(NSString* _Nullable)err;
-@end
-
-/**
- * RoundCompletionCallback is returned when the completion of a round is known.
- */
-@interface BindingsRoundCompletionCallback : NSObject <goSeqRefInterface, BindingsRoundCompletionCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(long)rid success:(BOOL)success timedOut:(BOOL)timedOut;
-@end
-
-/**
- * RoundEventCallback handles waiting on the exact state of a round on
-the cMix network.
- */
-@interface BindingsRoundEventCallback : NSObject <goSeqRefInterface, BindingsRoundEventCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(long)rid state:(long)state timedOut:(BOOL)timedOut;
-@end
-
-/**
- * SearchCallback returns the result of a search
- */
-@interface BindingsSearchCallback : NSObject <goSeqRefInterface, BindingsSearchCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContactList* _Nullable)contacts error:(NSString* _Nullable)error;
-@end
-
-/**
- * SingleSearchCallback returns the result of a single search
- */
-@interface BindingsSingleSearchCallback : NSObject <goSeqRefInterface, BindingsSingleSearchCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@interface BindingsTimeSource : NSObject <goSeqRefInterface, BindingsTimeSource> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (int64_t)nowMs;
-@end
-
-/**
- * UpdateBackupFunc contains a function callback that returns new backups.
- */
-@interface BindingsUpdateBackupFunc : NSObject <goSeqRefInterface, BindingsUpdateBackupFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)updateBackup:(NSData* _Nullable)encryptedBackup;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Headers/Universe.objc.h b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Headers/Universe.objc.h
deleted file mode 100644
index 019e7502d581983722a15bf30799e85cbc5dd766..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Headers/Universe.objc.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Objective-C API for talking to  Go package.
-//   gobind -lang=objc 
-//
-// File is generated by gobind. Do not edit.
-
-#ifndef __Universe_H__
-#define __Universe_H__
-
-@import Foundation;
-#include "ref.h"
-
-@protocol Universeerror;
-@class Universeerror;
-
-@protocol Universeerror <NSObject>
-- (NSString* _Nonnull)error;
-@end
-
-@class Universeerror;
-
-@interface Universeerror : NSError <goSeqRefInterface, Universeerror> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (NSString* _Nonnull)error;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Headers/ref.h b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Headers/ref.h
deleted file mode 100644
index b8036a4d85c7387f3def61473a071b5d8c4c8208..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Headers/ref.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#ifndef __GO_REF_HDR__
-#define __GO_REF_HDR__
-
-#include <Foundation/Foundation.h>
-
-// GoSeqRef is an object tagged with an integer for passing back and
-// forth across the language boundary. A GoSeqRef may represent either
-// an instance of a Go object, or an Objective-C object passed to Go.
-// The explicit allocation of a GoSeqRef is used to pin a Go object
-// when it is passed to Objective-C. The Go seq package maintains a
-// reference to the Go object in a map keyed by the refnum along with
-// a reference count. When the reference count reaches zero, the Go
-// seq package will clear the corresponding entry in the map.
-@interface GoSeqRef : NSObject {
-}
-@property(readonly) int32_t refnum;
-@property(strong) id obj; // NULL when representing a Go object.
-
-// new GoSeqRef object to proxy a Go object. The refnum must be
-// provided from Go side.
-- (instancetype)initWithRefnum:(int32_t)refnum obj:(id)obj;
-
-- (int32_t)incNum;
-
-@end
-
-@protocol goSeqRefInterface
--(GoSeqRef*) _ref;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Modules/module.modulemap b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Modules/module.modulemap
deleted file mode 100644
index 4316a5b24058edfc18ffb2dc7f7a982e8353441a..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Modules/module.modulemap
+++ /dev/null
@@ -1,8 +0,0 @@
-framework module "Bindings" {
-	header "ref.h"
-    header "Bindings.objc.h"
-    header "Universe.objc.h"
-    header "Bindings.h"
-
-    export *
-}
\ No newline at end of file
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Resources/Info.plist b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Resources/Info.plist
deleted file mode 100644
index 0d1a4b8ab9b1fc8e9357197398f73353470cb636..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Resources/Info.plist
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-    <plist version="1.0">
-      <dict>
-      </dict>
-    </plist>
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Bindings b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Bindings
deleted file mode 100644
index 1e3b1dc25b4d65bf1e21a5d169dc3ae4de6c4fd7..0000000000000000000000000000000000000000
Binary files a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Bindings and /dev/null differ
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Bindings.h b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Bindings.h
deleted file mode 100644
index 8906a7da239705b790cb2bb64de92f806640cb38..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Bindings.h
+++ /dev/null
@@ -1,13 +0,0 @@
-
-// Objective-C API for talking to the following Go packages
-//
-//	gitlab.com/elixxir/client/bindings
-//
-// File is generated by gomobile bind. Do not edit.
-#ifndef __Bindings_FRAMEWORK_H__
-#define __Bindings_FRAMEWORK_H__
-
-#include "Bindings.objc.h"
-#include "Universe.objc.h"
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Bindings.objc.h b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Bindings.objc.h
deleted file mode 100644
index 32bf6d116888f787ced27b01b95cb4e1b2c1138b..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Bindings.objc.h
+++ /dev/null
@@ -1,2083 +0,0 @@
-// Objective-C API for talking to gitlab.com/elixxir/client/bindings Go package.
-//   gobind -lang=objc gitlab.com/elixxir/client/bindings
-//
-// File is generated by gobind. Do not edit.
-
-#ifndef __Bindings_H__
-#define __Bindings_H__
-
-@import Foundation;
-#include "ref.h"
-#include "Universe.objc.h"
-
-
-@class BindingsBackup;
-@class BindingsBackupReport;
-@class BindingsClient;
-@class BindingsContact;
-@class BindingsContactList;
-@class BindingsDummyTraffic;
-@class BindingsFact;
-@class BindingsFactList;
-@class BindingsFilePartTracker;
-@class BindingsFileTransfer;
-@class BindingsGroup;
-@class BindingsGroupChat;
-@class BindingsGroupMember;
-@class BindingsGroupMembership;
-@class BindingsGroupMessageReceive;
-@class BindingsGroupReportDisk;
-@class BindingsGroupSendReport;
-@class BindingsIdList;
-@class BindingsIntList;
-@class BindingsManyNotificationForMeReport;
-@class BindingsMessage;
-@class BindingsNewGroupReport;
-@class BindingsNodeRegistrationsStatus;
-@class BindingsNotificationForMeReport;
-@class BindingsRestoreContactsReport;
-@class BindingsRoundList;
-@class BindingsSendReport;
-@class BindingsSendReportDisk;
-@class BindingsUnregister;
-@class BindingsUser;
-@class BindingsUserDiscovery;
-@protocol BindingsAuthConfirmCallback;
-@class BindingsAuthConfirmCallback;
-@protocol BindingsAuthRequestCallback;
-@class BindingsAuthRequestCallback;
-@protocol BindingsAuthResetNotificationCallback;
-@class BindingsAuthResetNotificationCallback;
-@protocol BindingsClientError;
-@class BindingsClientError;
-@protocol BindingsEventCallbackFunctionObject;
-@class BindingsEventCallbackFunctionObject;
-@protocol BindingsFileTransferReceiveFunc;
-@class BindingsFileTransferReceiveFunc;
-@protocol BindingsFileTransferReceivedProgressFunc;
-@class BindingsFileTransferReceivedProgressFunc;
-@protocol BindingsFileTransferSentProgressFunc;
-@class BindingsFileTransferSentProgressFunc;
-@protocol BindingsGroupReceiveFunc;
-@class BindingsGroupReceiveFunc;
-@protocol BindingsGroupRequestFunc;
-@class BindingsGroupRequestFunc;
-@protocol BindingsListener;
-@class BindingsListener;
-@protocol BindingsLogWriter;
-@class BindingsLogWriter;
-@protocol BindingsLookupCallback;
-@class BindingsLookupCallback;
-@protocol BindingsMessageDeliveryCallback;
-@class BindingsMessageDeliveryCallback;
-@protocol BindingsMultiLookupCallback;
-@class BindingsMultiLookupCallback;
-@protocol BindingsNetworkHealthCallback;
-@class BindingsNetworkHealthCallback;
-@protocol BindingsPreimageNotification;
-@class BindingsPreimageNotification;
-@protocol BindingsRestoreContactsUpdater;
-@class BindingsRestoreContactsUpdater;
-@protocol BindingsRoundCompletionCallback;
-@class BindingsRoundCompletionCallback;
-@protocol BindingsRoundEventCallback;
-@class BindingsRoundEventCallback;
-@protocol BindingsSearchCallback;
-@class BindingsSearchCallback;
-@protocol BindingsSingleSearchCallback;
-@class BindingsSingleSearchCallback;
-@protocol BindingsTimeSource;
-@class BindingsTimeSource;
-@protocol BindingsUpdateBackupFunc;
-@class BindingsUpdateBackupFunc;
-
-@protocol BindingsAuthConfirmCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)partner;
-@end
-
-@protocol BindingsAuthRequestCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@protocol BindingsAuthResetNotificationCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@protocol BindingsClientError <NSObject>
-- (void)report:(NSString* _Nullable)source message:(NSString* _Nullable)message trace:(NSString* _Nullable)trace;
-@end
-
-@protocol BindingsEventCallbackFunctionObject <NSObject>
-- (void)reportEvent:(long)priority category:(NSString* _Nullable)category evtType:(NSString* _Nullable)evtType details:(NSString* _Nullable)details;
-@end
-
-@protocol BindingsFileTransferReceiveFunc <NSObject>
-- (void)receiveCallback:(NSData* _Nullable)tid fileName:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType sender:(NSData* _Nullable)sender size:(long)size preview:(NSData* _Nullable)preview;
-@end
-
-@protocol BindingsFileTransferReceivedProgressFunc <NSObject>
-- (void)receivedProgressCallback:(BOOL)completed received:(long)received total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-@protocol BindingsFileTransferSentProgressFunc <NSObject>
-- (void)sentProgressCallback:(BOOL)completed sent:(long)sent arrived:(long)arrived total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-@protocol BindingsGroupReceiveFunc <NSObject>
-- (void)groupReceiveCallback:(BindingsGroupMessageReceive* _Nullable)msg;
-@end
-
-@protocol BindingsGroupRequestFunc <NSObject>
-- (void)groupRequestCallback:(BindingsGroup* _Nullable)g;
-@end
-
-@protocol BindingsListener <NSObject>
-/**
- * Hear is called to receive a message in the UI
- */
-- (void)hear:(BindingsMessage* _Nullable)message;
-/**
- * Returns a name, used for debugging
- */
-- (NSString* _Nonnull)name;
-@end
-
-@protocol BindingsLogWriter <NSObject>
-- (void)log:(NSString* _Nullable)p0;
-@end
-
-@protocol BindingsLookupCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsMessageDeliveryCallback <NSObject>
-- (void)eventCallback:(NSData* _Nullable)msgID delivered:(BOOL)delivered timedOut:(BOOL)timedOut roundResults:(NSData* _Nullable)roundResults;
-@end
-
-@protocol BindingsMultiLookupCallback <NSObject>
-- (void)callback:(BindingsContactList* _Nullable)Succeeded failed:(BindingsIdList* _Nullable)failed errors:(NSString* _Nullable)errors;
-@end
-
-@protocol BindingsNetworkHealthCallback <NSObject>
-- (void)callback:(BOOL)p0;
-@end
-
-@protocol BindingsPreimageNotification <NSObject>
-- (void)notify:(NSData* _Nullable)identity deleted:(BOOL)deleted;
-@end
-
-@protocol BindingsRestoreContactsUpdater <NSObject>
-/**
- * RestoreContactsCallback is called to report the current # of contacts
-that have been found and how many have been restored
-against the total number that need to be
-processed. If an error occurs it it set on the err variable as a
-plain string.
- */
-- (void)restoreContactsCallback:(long)numFound numRestored:(long)numRestored total:(long)total err:(NSString* _Nullable)err;
-@end
-
-@protocol BindingsRoundCompletionCallback <NSObject>
-- (void)eventCallback:(long)rid success:(BOOL)success timedOut:(BOOL)timedOut;
-@end
-
-@protocol BindingsRoundEventCallback <NSObject>
-- (void)eventCallback:(long)rid state:(long)state timedOut:(BOOL)timedOut;
-@end
-
-@protocol BindingsSearchCallback <NSObject>
-- (void)callback:(BindingsContactList* _Nullable)contacts error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsSingleSearchCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsTimeSource <NSObject>
-- (int64_t)nowMs;
-@end
-
-@protocol BindingsUpdateBackupFunc <NSObject>
-- (void)updateBackup:(NSData* _Nullable)encryptedBackup;
-@end
-
-@interface BindingsBackup : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * AddJson stores a passed in json string in the backup structure
- */
-- (void)addJson:(NSString* _Nullable)json;
-/**
- * IsBackupRunning returns true if the backup has been initialized and is
-running. Returns false if it has been stopped.
- */
-- (BOOL)isBackupRunning;
-/**
- * StopBackup stops the backup processes and deletes the user's password from
-storage. To enable backups again, call InitializeBackup.
- */
-- (BOOL)stopBackup:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsBackupReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field BackupReport.RestoredContacts with unsupported type: []*gitlab.com/xx_network/primitives/id.ID
-
-@property (nonatomic) NSString* _Nonnull params;
-@end
-
-/**
- * BindingsClient wraps the api.Client, implementing additional functions
-to support the gomobile Client interface
- */
-@interface BindingsClient : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BOOL)confirmAuthenticatedChannel:(NSData* _Nullable)recipientMarshaled ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteAllRequests clears all requests from Client's auth storage.
- */
-- (BOOL)deleteAllRequests:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteContact is a function which removes a contact from Client's storage
- */
-- (BOOL)deleteContact:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteReceiveRequests clears receive requests from Client's auth storage.
- */
-- (BOOL)deleteReceiveRequests:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteRequest will delete a request, agnostic of request type
-for the given partner ID. If no request exists for this
-partner ID an error will be returned.
- */
-- (BOOL)deleteRequest:(NSData* _Nullable)requesterUserId error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteSentRequests clears sent requests from Client's auth storage.
- */
-- (BOOL)deleteSentRequests:(NSError* _Nullable* _Nullable)error;
-// skipped method Client.GetInternalClient with unsupported parameter or return types
-
-/**
- * GetNodeRegistrationStatus returns a struct with the number of nodes the
-client is registered with and the number total.
- */
-- (BindingsNodeRegistrationsStatus* _Nullable)getNodeRegistrationStatus:(NSError* _Nullable* _Nullable)error;
-/**
- * GetPartners returns a list of
- */
-- (NSData* _Nullable)getPartners:(NSError* _Nullable* _Nullable)error;
-/**
- * GetPreferredBins returns the geographic bin or bins that the provided two
-character country code is a part of. The bins are returned as CSV.
- */
-- (NSString* _Nonnull)getPreferredBins:(NSString* _Nullable)countryCode error:(NSError* _Nullable* _Nullable)error;
-- (NSString* _Nonnull)getPreimages:(NSData* _Nullable)identity;
-// skipped method Client.GetRateLimitParams with unsupported parameter or return types
-
-- (NSString* _Nonnull)getRelationshipFingerprint:(NSData* _Nullable)partnerID error:(NSError* _Nullable* _Nullable)error;
-/**
- * Returns a user object from which all information about the current user
-can be gleaned
- */
-- (BindingsUser* _Nullable)getUser;
-/**
- * HasRunningProcessies checks if any background threads are running.
-returns true if none are running. This is meant to be
-used when NetworkFollowerStatus() returns Stopping.
-Due to the handling of comms on iOS, where the OS can
-block indefiently, it may not enter the stopped
-state apropreatly. This can be used instead.
- */
-- (BOOL)hasRunningProcessies;
-/**
- * returns true if the network is read to be in a healthy state where
-messages can be sent
- */
-- (BOOL)isNetworkHealthy;
-- (BindingsContact* _Nullable)makePrecannedAuthenticatedChannel:(long)precannedID error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the state of the network follower. Returns:
-Stopped 	- 0
-Starting - 1000
-Running	- 2000
-Stopping	- 3000
- */
-- (long)networkFollowerStatus;
-- (void)registerAuthCallbacks:(id<BindingsAuthRequestCallback> _Nullable)request confirm:(id<BindingsAuthConfirmCallback> _Nullable)confirm reset:(id<BindingsAuthResetNotificationCallback> _Nullable)reset;
-/**
- * RegisterClientErrorCallback registers the callback to handle errors from the
-long running threads controlled by StartNetworkFollower and StopNetworkFollower
- */
-- (void)registerClientErrorCallback:(id<BindingsClientError> _Nullable)clientError;
-/**
- * RegisterEventCallback records the given function to receive
-ReportableEvent objects. It returns the internal index
-of the callback so that it can be deleted later.
- */
-- (BOOL)registerEventCallback:(NSString* _Nullable)name myObj:(id<BindingsEventCallbackFunctionObject> _Nullable)myObj error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterForNotifications accepts firebase messaging token
- */
-- (BOOL)registerForNotifications:(NSString* _Nullable)token error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterListener records and installs a listener for messages
-matching specific uid, msgType, and/or username
-Returns a ListenerUnregister interface which can be
-
-to register for any userID, pass in an id with length 0 or an id with
-all zeroes
-
-to register for any message type, pass in a message type of 0
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types
- */
-- (BindingsUnregister* _Nullable)registerListener:(NSData* _Nullable)uid msgType:(long)msgType listener:(id<BindingsListener> _Nullable)listener error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterNetworkHealthCB registers the network health callback to be called
-any time the network health changes. Returns a unique ID that can be used to
-unregister the network health callback.
- */
-- (int64_t)registerNetworkHealthCB:(id<BindingsNetworkHealthCallback> _Nullable)nhc;
-- (void)registerPreimageCallback:(NSData* _Nullable)identity pin:(id<BindingsPreimageNotification> _Nullable)pin;
-/**
- * RegisterRoundEventsHandler registers a callback interface for round
-events.
-The rid is the round the event attaches to
-The timeoutMS is the number of milliseconds until the event fails, and the
-validStates are a list of states (one per byte) on which the event gets
-triggered
-States:
- 0x00 - PENDING (Never seen by client)
- 0x01 - PRECOMPUTING
- 0x02 - STANDBY
- 0x03 - QUEUED
- 0x04 - REALTIME
- 0x05 - COMPLETED
- 0x06 - FAILED
-These states are defined in elixxir/primitives/states/state.go
- */
-- (BindingsUnregister* _Nullable)registerRoundEventsHandler:(long)rid cb:(id<BindingsRoundEventCallback> _Nullable)cb timeoutMS:(long)timeoutMS il:(BindingsIntList* _Nullable)il;
-- (void)replayRequests;
-- (BOOL)requestAuthenticatedChannel:(NSData* _Nullable)recipientMarshaled meMarshaled:(NSData* _Nullable)meMarshaled message:(NSString* _Nullable)message ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-- (BOOL)resetSession:(NSData* _Nullable)recipientMarshaled meMarshaled:(NSData* _Nullable)meMarshaled message:(NSString* _Nullable)message ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * This will return the round the message was sent on if it is successfully sent
-This can be used to register a round event to learn about message delivery.
-on failure a round id of -1 is returned
- */
-- (BOOL)sendCmix:(NSData* _Nullable)recipient contents:(NSData* _Nullable)contents parameters:(NSString* _Nullable)parameters ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * SendE2E sends an end-to-end payload to the provided recipient with
-the provided msgType. Returns the list of rounds in which parts of
-the message were sent or an error if it fails.
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types
- */
-- (BindingsSendReport* _Nullable)sendE2E:(NSData* _Nullable)recipient payload:(NSData* _Nullable)payload messageType:(long)messageType parameters:(NSString* _Nullable)parameters error:(NSError* _Nullable* _Nullable)error;
-/**
- * SendUnsafe sends an unencrypted payload to the provided recipient
-with the provided msgType. Returns the list of rounds in which parts
-of the message were sent or an error if it fails.
-NOTE: Do not use this function unless you know what you are doing.
-This function always produces an error message in client logging.
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types with custom types
- */
-- (BindingsRoundList* _Nullable)sendUnsafe:(NSData* _Nullable)recipient payload:(NSData* _Nullable)payload messageType:(long)messageType parameters:(NSString* _Nullable)parameters error:(NSError* _Nullable* _Nullable)error;
-/**
- * SetProxiedBins updates the host pool filter that filters out gateways that
-are not in one of the specified bins. The provided bins should be CSV.
- */
-- (BOOL)setProxiedBins:(NSString* _Nullable)binStringsCSV error:(NSError* _Nullable* _Nullable)error;
-/**
- * StartNetworkFollower kicks off the tracking of the network. It starts
-long running network client threads and returns an object for checking
-state and stopping those threads.
-Call this when returning from sleep and close when going back to
-sleep.
-These threads may become a significant drain on battery when offline, ensure
-they are stopped if there is no internet access
-Threads Started:
-  - Network Follower (/network/follow.go)
-  	tracks the network events and hands them off to workers for handling
-  - Historical Round Retrieval (/network/rounds/historical.go)
-		Retrieves data about rounds which are too old to be stored by the client
-	 - Message Retrieval Worker Group (/network/rounds/retrieve.go)
-		Requests all messages in a given round from the gateway of the last node
-	 - Message Handling Worker Group (/network/message/handle.go)
-		Decrypts and partitions messages when signals via the Switchboard
-	 - Health Tracker (/network/health)
-		Via the network instance tracks the state of the network
-	 - Garbled Messages (/network/message/garbled.go)
-		Can be signaled to check all recent messages which could be be decoded
-		Uses a message store on disk for persistence
-	 - Critical Messages (/network/message/critical.go)
-		Ensures all protocol layer mandatory messages are sent
-		Uses a message store on disk for persistence
-	 - KeyExchange Trigger (/keyExchange/trigger.go)
-		Responds to sent rekeys and executes them
-  - KeyExchange Confirm (/keyExchange/confirm.go)
-		Responds to confirmations of successful rekey operations
- */
-- (BOOL)startNetworkFollower:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * StopNetworkFollower stops the network follower if it is running.
-It returns errors if the Follower is in the wrong status to stop or if it
-fails to stop it.
-if the network follower is running and this fails, the client object will
-most likely be in an unrecoverable state and need to be trashed.
- */
-- (BOOL)stopNetworkFollower:(NSError* _Nullable* _Nullable)error;
-/**
- * UnregisterEventCallback deletes the callback identified by the
-index. It returns an error if it fails.
- */
-- (void)unregisterEventCallback:(NSString* _Nullable)name;
-/**
- * UnregisterForNotifications unregister user for notifications
- */
-- (BOOL)unregisterForNotifications:(NSError* _Nullable* _Nullable)error;
-- (void)unregisterNetworkHealthCB:(int64_t)funcID;
-- (BOOL)verifyOwnership:(NSData* _Nullable)receivedMarshaled verifiedMarshaled:(NSData* _Nullable)verifiedMarshaled ret0_:(BOOL* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * WaitForMessageDelivery allows the caller to get notified if the rounds a
-message was sent in successfully completed. Under the hood, this uses an API
-which uses the internal round data, network historical round lookup, and
-waiting on network events to determine what has (or will) occur.
-
-The callbacks will return at timeoutMS if no state update occurs
-
-This function takes the marshaled send report to ensure a memory leak does
-not occur as a result of both sides of the bindings holding a reference to
-the same pointer.
- */
-- (BOOL)waitForMessageDelivery:(NSData* _Nullable)marshaledSendReport mdc:(id<BindingsMessageDeliveryCallback> _Nullable)mdc timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * WaitForNewtwork will block until either the network is healthy or the
-passed timeout. It will return true if the network is healthy
- */
-- (BOOL)waitForNetwork:(long)timeoutMS;
-/**
- * WaitForRoundCompletion allows the caller to get notified if a round
-has completed (or failed). Under the hood, this uses an API which uses the internal
-round data, network historical round lookup, and waiting on network events
-to determine what has (or will) occur.
-
-The callbacks will return at timeoutMS if no state update occurs
- */
-- (BOOL)waitForRoundCompletion:(long)roundID rec:(id<BindingsRoundCompletionCallback> _Nullable)rec timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- *  contact object
- */
-@interface BindingsContact : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped method Contact.GetAPIContact with unsupported parameter or return types
-
-/**
- * GetDHPublicKey returns the public key associated with the Contact.
- */
-- (NSData* _Nullable)getDHPublicKey;
-/**
- * Returns a fact list for adding and getting facts to and from the contact
- */
-- (BindingsFactList* _Nullable)getFactList;
-/**
- * GetID returns the user ID for this user.
- */
-- (NSData* _Nullable)getID;
-/**
- * GetDHPublicKey returns hash of a DH proof of key ownership.
- */
-- (NSData* _Nullable)getOwnershipProof;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsContactList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Gets a stored round ID at the given index
- */
-- (BindingsContact* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the number of round IDs stored
- */
-- (long)len;
-@end
-
-/**
- * DummyTraffic contains the file dummy traffic manager. The manager can be used
-to set and get the status of the send thread.
- */
-@interface BindingsDummyTraffic : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewDummyTrafficManager creates a DummyTraffic manager and initialises the
-dummy traffic send thread. Note that the manager does not start sending dummy
-traffic until its status is set to true using DummyTraffic.SetStatus.
-The maxNumMessages is the upper bound of the random number of messages sent
-each send. avgSendDeltaMS is the average duration, in milliseconds, to wait
-between sends. Sends occur every avgSendDeltaMS +/- a random duration with an
-upper bound of randomRangeMS.
- */
-- (nullable instancetype)initManager:(BindingsClient* _Nullable)client maxNumMessages:(long)maxNumMessages avgSendDeltaMS:(long)avgSendDeltaMS randomRangeMS:(long)randomRangeMS;
-/**
- * GetStatus returns the current state of the dummy traffic send thread. It has
-the following return values:
- true  = send thread is sending dummy messages
- false = send thread is paused/stopped and not sending dummy messages
-Note that this function does not return the status set by SetStatus directly;
-it returns the current status of the send thread, which means any call to
-SetStatus will have a small delay before it is returned by GetStatus.
- */
-- (BOOL)getStatus;
-/**
- * SetStatus sets the state of the dummy traffic send thread, which determines
-if the thread is running or paused. The possible statuses are:
- true  = send thread is sending dummy messages
- false = send thread is paused/stopped and not sending dummy messages
-Returns an error if the channel is full.
-Note that this function cannot change the status of the send thread if it has
-yet to be started or stopped.
- */
-- (BOOL)setStatus:(BOOL)status error:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsFact : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- *  fact object
-creates a new fact. The factType must be either:
- 0 - Username
- 1 - Email
- 2 - Phone Number
-The fact must be well formed for the type and must not include commas or
-semicolons. If it is not well formed, it will be rejected.  Phone numbers
-must have the two letter country codes appended.  For the complete set of
-validation, see /elixxir/primitives/fact/fact.go
- */
-- (nullable instancetype)init:(long)factType factStr:(NSString* _Nullable)factStr;
-- (NSString* _Nonnull)get;
-- (NSString* _Nonnull)stringify;
-- (long)type;
-@end
-
-@interface BindingsFactList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * FactList
- */
-- (nullable instancetype)init;
-- (BOOL)add:(NSString* _Nullable)factData factType:(long)factType error:(NSError* _Nullable* _Nullable)error;
-- (BindingsFact* _Nullable)get:(long)i;
-- (long)num;
-- (NSString* _Nonnull)stringify:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * FilePartTracker contains the interfaces.FilePartTracker.
- */
-@interface BindingsFilePartTracker : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetNumParts returns the total number of file parts in the transfer.
- */
-- (long)getNumParts;
-/**
- * GetPartStatus returns the status of the file part with the given part number.
-The possible values for the status are:
-0 = unsent
-1 = sent (sender has sent a part, but it has not arrived)
-2 = arrived (sender has sent a part, and it has arrived)
-3 = received (receiver has received a part)
- */
-- (long)getPartStatus:(long)partNum;
-@end
-
-/**
- * FileTransfer contains the file transfer manager.
- */
-@interface BindingsFileTransfer : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewFileTransferManager creates a new file transfer manager and starts the
-sending and receiving threads. The receiveFunc is called everytime a new file
-transfer is received.
-The parameters string contains file transfer network configuration options
-and is a JSON formatted string of the fileTransfer.Params object. If it is
-left empty, then defaults are used. It is highly recommended that defaults
-are used. If it is set, it must match the following format:
- {"MaxThroughput":150000,"SendTimeout":500000000}
-MaxThroughput is in bytes/sec and SendTimeout is in nanoseconds.
- */
-- (nullable instancetype)initManager:(BindingsClient* _Nullable)client receiveFunc:(id<BindingsFileTransferReceiveFunc> _Nullable)receiveFunc parameters:(NSString* _Nullable)parameters;
-/**
- * CloseSend deletes a sent file transfer from the sent transfer map and from
-storage once a transfer has completed or reached the retry limit. Returns an
-error if the transfer has not run out of retries.
- */
-- (BOOL)closeSend:(NSData* _Nullable)transferID error:(NSError* _Nullable* _Nullable)error;
-/**
- * GetMaxFileNameByteLength returns the maximum length, in bytes, allowed for a
-file name.
- */
-- (long)getMaxFileNameByteLength;
-/**
- * GetMaxFilePreviewSize returns the maximum file preview size, in bytes.
- */
-- (long)getMaxFilePreviewSize;
-/**
- * GetMaxFileSize returns the maximum file size, in bytes, allowed to be
-transferred.
- */
-- (long)getMaxFileSize;
-/**
- * GetMaxFileTypeByteLength returns the maximum length, in bytes, allowed for a
-file type.
- */
-- (long)getMaxFileTypeByteLength;
-/**
- * Receive returns the fully assembled file on the completion of the transfer.
-It deletes the transfer from the received transfer map and from storage.
-Returns an error if the transfer is not complete, the full file cannot be
-verified, or if the transfer cannot be found.
- */
-- (NSData* _Nullable)receive:(NSData* _Nullable)transferID error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterReceiveProgressCallback allows for the registration of a callback to
-track the progress of an individual received file transfer. The callback will
-be called immediately when added to report the current status of the
-transfer. It will then call every time a file part is received, the transfer
-completes, or an error occurs. It is called at most once ever period, which
-means if events occur faster than the period, then they will not be reported
-and instead, the progress will be reported once at the end of the period.
-Once the callback reports that the transfer has completed, the recipient
-can get the full file by calling Receive.
-The period is specified in milliseconds.
- */
-- (BOOL)registerReceiveProgressCallback:(NSData* _Nullable)transferID progressFunc:(id<BindingsFileTransferReceivedProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterSendProgressCallback allows for the registration of a callback to
-track the progress of an individual sent file transfer. The callback will be
-called immediately when added to report the current status of the transfer.
-It will then call every time a file part is sent, a file part arrives, the
-transfer completes, or an error occurs. It is called at most once every
-period, which means if events occur faster than the period, then they will
-not be reported and instead, the progress will be reported once at the end of
-the period.
-The period is specified in milliseconds.
- */
-- (BOOL)registerSendProgressCallback:(NSData* _Nullable)transferID progressFunc:(id<BindingsFileTransferSentProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * Send sends a file to the recipient. The sender must have an E2E relationship
-with the recipient.
-The file name is the name of the file to show a user. It has a max length of
-48 bytes.
-The file type identifies what type of file is being sent. It has a max length
-of 8 bytes.
-The file data cannot be larger than 256 kB
-The retry float is the total amount of data to send relative to the data
-size. Data will be resent on error and will resend up to [(1 + retry) *
-fileSize].
-The preview stores a preview of the data (such as a thumbnail) and is
-capped at 4 kB in size.
-Returns a unique transfer ID used to identify the transfer.
-PeriodMS is the duration, in milliseconds, to wait between progress callback
-calls. Set this large enough to prevent spamming.
- */
-- (NSData* _Nullable)send:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType fileData:(NSData* _Nullable)fileData recipientID:(NSData* _Nullable)recipientID retry:(float)retry preview:(NSData* _Nullable)preview progressFunc:(id<BindingsFileTransferSentProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * Group structure contains the identifying and membership information of a
-group chat.
- */
-@interface BindingsGroup : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetCreatedMS returns the time the group was created in milliseconds. This is
-also the time the group requests were sent.
- */
-- (int64_t)getCreatedMS;
-/**
- * GetCreatedNano returns the time the group was created in nanoseconds. This is
-also the time the group requests were sent.
- */
-- (int64_t)getCreatedNano;
-/**
- * GetID return the 33-byte unique group ID.
- */
-- (NSData* _Nullable)getID;
-/**
- * GetInitMessage returns initial message sent with the group request.
- */
-- (NSData* _Nullable)getInitMessage;
-/**
- * GetMembership returns a list of contacts, one for each member in the group.
-The list is in order; the first contact is the leader/creator of the group.
-All subsequent members are ordered by their ID.
- */
-- (BindingsGroupMembership* _Nullable)getMembership;
-/**
- * GetName returns the name set by the user for the group.
- */
-- (NSData* _Nullable)getName;
-/**
- * Serialize serializes the Group.
- */
-- (NSData* _Nullable)serialize;
-@end
-
-/**
- * GroupChat object contains the group chat manager.
- */
-@interface BindingsGroupChat : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetGroup returns the group with the group ID. If no group exists, then the
-error "failed to find group" is returned.
- */
-- (BindingsGroup* _Nullable)getGroup:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * GetGroups returns an IdList containing a list of group IDs that the user is a
-part of.
- */
-- (BindingsIdList* _Nullable)getGroups;
-/**
- * JoinGroup allows a user to join a group when they receive a request. The
-caller must pass in the serialized bytes of a Group.
- */
-- (BOOL)joinGroup:(NSData* _Nullable)serializedGroupData error:(NSError* _Nullable* _Nullable)error;
-/**
- * LeaveGroup deletes a group so a user no longer has access.
- */
-- (BOOL)leaveGroup:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * MakeGroup creates a new group and sends a group request to all members in the
-group. The ID of the new group, the rounds the requests were sent on, and the
-status of the send are contained in NewGroupReport.
- */
-- (BindingsNewGroupReport* _Nullable)makeGroup:(BindingsIdList* _Nullable)membership name:(NSData* _Nullable)name message:(NSData* _Nullable)message;
-/**
- * NumGroups returns the number of groups the user is a part of.
- */
-- (long)numGroups;
-/**
- * ResendRequest resends a group request to all members in the group. The rounds
-they were sent on and the status of the send are contained in NewGroupReport.
- */
-- (BindingsNewGroupReport* _Nullable)resendRequest:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * Send sends the message to the specified group. Returns the round the messages
-were sent on.
- */
-- (BindingsGroupSendReport* _Nullable)send:(NSData* _Nullable)groupIdBytes message:(NSData* _Nullable)message error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * //
-Member Structure
-//
-GroupMember represents a member in the group membership list.
- */
-@interface BindingsGroupMember : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupMember.Member with unsupported type: gitlab.com/elixxir/crypto/group.Member
-
-// skipped method GroupMember.DeepCopy with unsupported parameter or return types
-
-// skipped method GroupMember.Equal with unsupported parameter or return types
-
-/**
- * GetDhKey returns the byte representation of the public Diffie–Hellman key of
-the member.
- */
-- (NSData* _Nullable)getDhKey;
-/**
- * GetID returns the 33-byte user ID of the member.
- */
-- (NSData* _Nullable)getID;
-- (NSString* _Nonnull)goString;
-- (NSData* _Nullable)serialize;
-- (NSString* _Nonnull)string;
-@end
-
-/**
- * GroupMembership structure contains a list of members that are part of a
-group. The first member is the group leader.
- */
-@interface BindingsGroupMembership : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Get returns the member at the index. The member at index 0 is always the
-group leader. An error is returned if the index is out of range.
- */
-- (BindingsGroupMember* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Len returns the number of members in the group membership.
- */
-- (long)len;
-@end
-
-/**
- * GroupMessageReceive contains a group message, its ID, and its data that a
-user receives.
- */
-@interface BindingsGroupMessageReceive : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupMessageReceive.MessageReceive with unsupported type: gitlab.com/elixxir/client/groupChat.MessageReceive
-
-/**
- * GetEphemeralID returns the ephemeral ID of the recipient.
- */
-- (int64_t)getEphemeralID;
-/**
- * GetGroupID returns the 33-byte group ID.
- */
-- (NSData* _Nullable)getGroupID;
-/**
- * GetMessageID returns the message ID.
- */
-- (NSData* _Nullable)getMessageID;
-/**
- * GetPayload returns the message payload.
- */
-- (NSData* _Nullable)getPayload;
-/**
- * GetRecipientID returns the 33-byte user ID of the recipient.
- */
-- (NSData* _Nullable)getRecipientID;
-/**
- * GetRoundID returns the ID of the round the message was sent on.
- */
-- (int64_t)getRoundID;
-/**
- * GetRoundTimestampMS returns the timestamp, in milliseconds, of the round the
-message was sent on.
- */
-- (int64_t)getRoundTimestampMS;
-/**
- * GetRoundTimestampNano returns the timestamp, in nanoseconds, of the round the
-message was sent on.
- */
-- (int64_t)getRoundTimestampNano;
-/**
- * GetRoundURL returns the ID of the round the message was sent on.
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetSenderID returns the 33-byte user ID of the sender.
- */
-- (NSData* _Nullable)getSenderID;
-/**
- * GetTimestampMS returns the message timestamp in milliseconds.
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message timestamp in nanoseconds.
- */
-- (int64_t)getTimestampNano;
-- (NSString* _Nonnull)string;
-@end
-
-@interface BindingsGroupReportDisk : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupReportDisk.List with unsupported type: []gitlab.com/xx_network/primitives/id.Round
-
-@property (nonatomic) NSData* _Nullable grpId;
-@property (nonatomic) long status;
-@end
-
-/**
- * GroupSendReport is returned when sending a group message. It contains the
-round ID sent on and the timestamp of the send.
- */
-@interface BindingsGroupSendReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetMessageID returns the ID of the round that the send occurred on.
- */
-- (NSData* _Nullable)getMessageID;
-/**
- * GetRoundID returns the ID of the round that the send occurred on.
- */
-- (int64_t)getRoundID;
-/**
- * GetRoundURL returns the URL of the round that the send occurred on.
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetTimestampMS returns the timestamp of the send in milliseconds.
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the timestamp of the send in nanoseconds.
- */
-- (int64_t)getTimestampNano;
-@end
-
-/**
- *  ID list
-IdList contains a list of IDs.
- */
-@interface BindingsIdList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Add appends the ID bytes to the end of the list.
- */
-- (BOOL)add:(NSData* _Nullable)idBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * Get returns the ID at the index. An error is returned if the index is out of
-range.
- */
-- (NSData* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Len returns the number of IDs in the list.
- */
-- (long)len;
-@end
-
-@interface BindingsIntList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (void)add:(long)i;
-- (BOOL)get:(long)i ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-- (long)len;
-@end
-
-@interface BindingsManyNotificationForMeReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BindingsNotificationForMeReport* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-- (long)len;
-@end
-
-/**
- * Message is a message received from the cMix network in the clear
-or that has been decrypted using established E2E keys.
- */
-@interface BindingsMessage : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetID returns the id of the message
- */
-- (NSData* _Nullable)getID;
-/**
- * GetMessageType returns the message's type
- */
-- (long)getMessageType;
-/**
- * GetPayload returns the message's payload/contents
- */
-- (NSData* _Nullable)getPayload;
-/**
- * GetRoundId returns the message's round ID
- */
-- (int64_t)getRoundId;
-/**
- * GetRoundTimestampMS returns the message's round timestamp in milliseconds
- */
-- (int64_t)getRoundTimestampMS;
-/**
- * GetRoundTimestampNano returns the message's round timestamp in nanoseconds
- */
-- (int64_t)getRoundTimestampNano;
-/**
- * GetRoundURL returns the message's round URL
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetSender returns the message's sender ID, if available
- */
-- (NSData* _Nullable)getSender;
-/**
- * GetTimestampMS returns the message's timestamp in milliseconds
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message's timestamp in nanoseconds
- */
-- (int64_t)getTimestampNano;
-@end
-
-/**
- * NewGroupReport is returned when creating a new group and contains the ID of
-the group, a list of rounds that the group requests were sent on, and the
-status of the send.
- */
-@interface BindingsNewGroupReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetError returns the string of an error.
-Will be an empty string if no error occured
- */
-- (NSString* _Nonnull)getError;
-/**
- * GetGroup returns the Group.
- */
-- (BindingsGroup* _Nullable)getGroup;
-/**
- * GetRoundList returns the RoundList containing a list of rounds requests were
-sent on.
- */
-- (BindingsRoundList* _Nullable)getRoundList;
-/**
- * GetStatus returns the status of the requests sent when creating a new group.
-status = 0   an error occurred before any requests could be sent
-         1   all requests failed to send (call Resend Group)
-         2   some request failed and some succeeded (call Resend Group)
-         3,  all requests sent successfully (call Resend Group)
- */
-- (long)getStatus;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-- (BOOL)unmarshal:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * NodeRegistrationsStatus structure for returning node registration statuses
-for bindings.
- */
-@interface BindingsNodeRegistrationsStatus : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetRegistered returns the number of nodes registered with the client.
- */
-- (long)getRegistered;
-/**
- * GetTotal return the total of nodes currently in the network.
- */
-- (long)getTotal;
-@end
-
-@interface BindingsNotificationForMeReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BOOL)forMe;
-- (NSData* _Nullable)source;
-- (NSString* _Nonnull)type;
-@end
-
-/**
- * RestoreContactsReport is a gomobile friendly report structure
-for determining which IDs restored, which failed, and why.
- */
-@interface BindingsRestoreContactsReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetErrorAt returns the error string at index
- */
-- (NSString* _Nonnull)getErrorAt:(long)index;
-/**
- * GetFailedAt returns the failed ID at index
- */
-- (NSData* _Nullable)getFailedAt:(long)index;
-/**
- * GetRestoreContactsError returns an error string. Empty if no error.
- */
-- (NSString* _Nonnull)getRestoreContactsError;
-/**
- * GetRestoredAt returns the restored ID at index
- */
-- (NSData* _Nullable)getRestoredAt:(long)index;
-/**
- * LenFailed returns the length of the ID's failed.
- */
-- (long)lenFailed;
-/**
- * LenRestored returns the length of ID's restored.
- */
-- (long)lenRestored;
-@end
-
-@interface BindingsRoundList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Gets a stored round ID at the given index
- */
-- (BOOL)get:(long)i ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the number of round IDs stored
- */
-- (long)len;
-@end
-
-/**
- * the send report is the mechanisim by which sendE2E returns a single
- */
-@interface BindingsSendReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (NSData* _Nullable)getMessageID;
-- (BindingsRoundList* _Nullable)getRoundList;
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetTimestampMS returns the message's timestamp in milliseconds
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message's timestamp in nanoseconds
- */
-- (int64_t)getTimestampNano;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-- (BOOL)unmarshal:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsSendReportDisk : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field SendReportDisk.List with unsupported type: []gitlab.com/xx_network/primitives/id.Round
-
-@property (nonatomic) NSData* _Nullable mid;
-@property (nonatomic) int64_t ts;
-@end
-
-/**
- * Generic Unregister - a generic return used for all callbacks which can be
-unregistered
-Interface which allows the un-registration of a listener
- */
-@interface BindingsUnregister : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Call unregisters a callback
- */
-- (void)unregister;
-@end
-
-@interface BindingsUser : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BindingsContact* _Nullable)getContact;
-- (NSData* _Nullable)getE2EDhPrivateKey;
-- (NSData* _Nullable)getE2EDhPublicKey;
-- (NSData* _Nullable)getReceptionID;
-- (NSData* _Nullable)getReceptionRSAPrivateKeyPem;
-- (NSData* _Nullable)getReceptionRSAPublicKeyPem;
-- (NSData* _Nullable)getReceptionSalt;
-- (NSData* _Nullable)getTransmissionID;
-- (NSData* _Nullable)getTransmissionRSAPrivateKeyPem;
-- (NSData* _Nullable)getTransmissionRSAPublicKeyPem;
-- (NSData* _Nullable)getTransmissionSalt;
-- (BOOL)isPrecanned;
-@end
-
-@interface BindingsUserDiscovery : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewUserDiscovery returns a new user discovery object. Only call this once. It must be called
-after StartNetworkFollower is called and will fail if the network has never
-been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-- (nullable instancetype)init:(BindingsClient* _Nullable)client;
-/**
- * NewUserDiscoveryFromBackup returns a new user discovery object. It
-wil set up the manager with the backup data. Pass into it the backed up
-facts, one email and phone number each. This will add the registered facts
-to the backed Store. Any one of these fields may be empty,
-however both fields being empty will cause an error. Any other fact that is not
-an email or phone number will return an error. You may only add a fact for the
-accepted types once each. If you attempt to back up a fact type that has already
-been backed up, an error will be returned. Anytime an error is returned, it means
-the backup was not successful.
-NOTE: Do not use this as a direct store operation. This feature is intended to add facts
-to a backend store that have ALREADY BEEN REGISTERED on the account.
-THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend.
-Only call this once. It must be called after StartNetworkFollower
-is called and will fail if the network has never been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-- (nullable instancetype)initFromBackup:(BindingsClient* _Nullable)client email:(NSString* _Nullable)email phone:(NSString* _Nullable)phone;
-/**
- * AddFact adds a fact for the user to user discovery. Will only succeed if the
-user is already registered and the system does not have the fact currently
-registered for any user.
-Will fail if the fact string is not well formed.
-This does not complete the fact registration process, it returns a
-confirmation id instead. Over the communications system the fact is
-associated with, a code will be sent. This confirmation ID needs to be
-called along with the code to finalize the fact.
- */
-- (NSString* _Nonnull)addFact:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * ConfirmFact confirms a fact first registered via AddFact. The confirmation ID comes from
-AddFact while the code will come over the associated communications system
- */
-- (BOOL)confirmFact:(NSString* _Nullable)confirmationID code:(NSString* _Nullable)code error:(NSError* _Nullable* _Nullable)error;
-/**
- * Lookup the contact object associated with the given userID.  The
-id is the byte representation of an id.
-This will reject if that id is malformed. The LookupCallback will return
-the associated contact if it exists.
- */
-- (BOOL)lookup:(NSData* _Nullable)idBytes callback:(id<BindingsLookupCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * MultiLookup Looks for the contact object associated with all given userIDs.
-The ids are the byte representation of an id stored in an IDList object.
-This will reject if that id is malformed or if the indexing on the IDList
-object is wrong. The MultiLookupCallback will return with all contacts
-returned within the timeout.
- */
-- (BOOL)multiLookup:(BindingsIdList* _Nullable)ids callback:(id<BindingsMultiLookupCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * Register registers a user with user discovery. Will return an error if the
-network signatures are malformed or if the username is taken. Usernames
-cannot be changed after registration at this time. Will fail if the user is
-already registered.
-Identity does not go over cmix, it occurs over normal communications
- */
-- (BOOL)register:(NSString* _Nullable)username error:(NSError* _Nullable* _Nullable)error;
-/**
- * RemoveFact removes a previously confirmed fact.  Will fail if the passed fact string is
-not well-formed or if the fact is not associated with this client.
-Users cannot remove username facts and must instead remove the user.
- */
-- (BOOL)removeFact:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * RemoveUser deletes a user. The fact sent must be the username.
-This function preserves the username forever and makes it
-unusable.
- */
-- (BOOL)removeUser:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * Search for the passed Facts.  The factList is the stringification of a
-fact list object, look at /bindings/list.go for more on that object.
-This will reject if that object is malformed. The SearchCallback will return
-a list of contacts, each having the facts it hit against.
-This is NOT intended to be used to search for multiple users at once, that
-can have a privacy reduction. Instead, it is intended to be used to search
-for a user where multiple pieces of information is known.
- */
-- (BOOL)search:(NSString* _Nullable)fl callback:(id<BindingsSearchCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * SearchSingle searches for the passed Facts.  The fact is the stringification of a
-fact object, look at /bindings/contact.go for more on that object.
-This will reject if that object is malformed. The SearchCallback will return
-a list of contacts, each having the facts it hit against.
-This only searches for a single fact at a time. It is intended to make some
-simple use cases of the API easier.
- */
-- (BOOL)searchSingle:(NSString* _Nullable)f callback:(id<BindingsSingleSearchCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * SetAlternativeUserDiscovery sets the alternativeUd object within manager.
-Once set, any user discovery operation will go through the alternative
-user discovery service.
-To undo this operation, use UnsetAlternativeUserDiscovery.
-The contact file is the already read in bytes, not the file path for the contact file.
- */
-- (BOOL)setAlternativeUserDiscovery:(NSData* _Nullable)address cert:(NSData* _Nullable)cert contactFile:(NSData* _Nullable)contactFile error:(NSError* _Nullable* _Nullable)error;
-/**
- * UnsetAlternativeUserDiscovery clears out the information from
-the Manager object.
- */
-- (BOOL)unsetAlternativeUserDiscovery:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * Error codes
- */
-FOUNDATION_EXPORT NSString* _Nonnull const BindingsUnrecognizedCode;
-FOUNDATION_EXPORT NSString* _Nonnull const BindingsUnrecognizedMessage;
-
-/**
- * CompressJpeg takes a JPEG image in byte format
-and compresses it based on desired output size
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsCompressJpeg(NSData* _Nullable imgBytes, NSError* _Nullable* _Nullable error);
-
-/**
- * CompressJpegForPreview takes a JPEG image in byte format
-and compresses it based on desired output size
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsCompressJpegForPreview(NSData* _Nullable imgBytes, NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadAndVerifySignedNdfWithUrl retrieves the NDF from a specified URL.
-The NDF is processed into a protobuf containing a signature which
-is verified using the cert string passed in. The NDF is returned as marshaled
-byte data which may be used to start a client.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadAndVerifySignedNdfWithUrl(NSString* _Nullable url, NSString* _Nullable cert, NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadDAppRegistrationDB returns a []byte containing
-the JSON data describing registered dApps.
-See https://git.xx.network/elixxir/registered-dapps
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadDAppRegistrationDB(NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadErrorDB returns a []byte containing the JSON data
-describing client errors.
-See https://git.xx.network/elixxir/client-error-database/
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadErrorDB(NSError* _Nullable* _Nullable error);
-
-/**
- * DumpStack returns a string with the stack trace of every running thread.
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsDumpStack(NSError* _Nullable* _Nullable error);
-
-/**
- * EnableGrpcLogs sets GRPC trace logging
- */
-FOUNDATION_EXPORT void BindingsEnableGrpcLogs(id<BindingsLogWriter> _Nullable writer);
-
-/**
- * ErrorStringToUserFriendlyMessage takes a passed in errStr which will be
-a backend generated error. These may be error specifically written by
-the backend team or lower level errors gotten from low level dependencies.
-This function will parse the error string for common errors provided from
-errToUserErr to provide a more user-friendly error message for the front end.
-If the error is not common, some simple parsing is done on the error message
-to make it more user-accessible, removing backend specific jargon.
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsErrorStringToUserFriendlyMessage(NSString* _Nullable errStr);
-
-/**
- * GenerateSecret creates a secret password using a system-based
-pseudorandom number generator. It takes 1 parameter, `numBytes`,
-which should be set to 32, but can be set higher in certain cases.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsGenerateSecret(long numBytes);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetCMIXParams(NSError* _Nullable* _Nullable error);
-
-/**
- * returns a previously created client. IF be used if the garbage collector
-removes the client instance on the app side.  Is NOT thread safe relative to
-login, newClient, or newPrecannedClient
- */
-FOUNDATION_EXPORT BindingsClient* _Nullable BindingsGetClientSingleton(void);
-
-/**
- * GetDependencies returns the api DEPENDENCIES
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetDependencies(void);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetE2EParams(NSError* _Nullable* _Nullable error);
-
-/**
- * GetGitVersion rturns the api GITVERSION
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetGitVersion(void);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetNetworkParams(NSError* _Nullable* _Nullable error);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetUnsafeParams(NSError* _Nullable* _Nullable error);
-
-/**
- * GetVersion returns the api SEMVER
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetVersion(void);
-
-/**
- * InitializeBackup starts the backup processes that returns backup updates when
-they occur. Any time an event occurs that changes the contents of the backup,
-such as adding or deleting a contact, the backup is triggered and an
-encrypted backup is generated and returned on the updateBackupCb callback.
-Call this function only when enabling backup if it has not already been
-initialized or when the user wants to change their password.
-To resume backup process on app recovery, use ResumeBackup.
- */
-FOUNDATION_EXPORT BindingsBackup* _Nullable BindingsInitializeBackup(NSString* _Nullable password, id<BindingsUpdateBackupFunc> _Nullable updateBackupCb, BindingsClient* _Nullable c, NSError* _Nullable* _Nullable error);
-
-/**
- * LoadSecretWithMnemonic loads the secret stored from the call to
-StoreSecretWithMnemonic. The path given should be the same filepath
-as the path given in StoreSecretWithMnemonic. There should be a file
-in this path called ".recovery". This operation is not tied
-to client operations, as the user will not have a client when trying to
-recover their account.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsLoadSecretWithMnemonic(NSString* _Nullable mnemonic, NSString* _Nullable path, NSError* _Nullable* _Nullable error);
-
-/**
- * sets level of logging. All logs the set level and above will be displayed
-options are:
-	TRACE		- 0
-	DEBUG		- 1
-	INFO 		- 2
-	WARN		- 3
-	ERROR		- 4
-	CRITICAL	- 5
-	FATAL		- 6
-The default state without updates is: INFO
- */
-FOUNDATION_EXPORT BOOL BindingsLogLevel(long level, NSError* _Nullable* _Nullable error);
-
-/**
- * Login will load an existing client from the storageDir
-using the password. This will fail if the client doesn't exist or
-the password is incorrect.
-The password is passed as a byte array so that it can be cleared from
-memory and stored as securely as possible using the memguard library.
-Login does not block on network connection, and instead loads and
-starts subprocesses to perform network operations.
- */
-FOUNDATION_EXPORT BindingsClient* _Nullable BindingsLogin(NSString* _Nullable storageDir, NSData* _Nullable password, NSString* _Nullable parameters, NSError* _Nullable* _Nullable error);
-
-/**
- * MakeIdList creates a new empty IdList.
- */
-FOUNDATION_EXPORT BindingsIdList* _Nullable BindingsMakeIdList(void);
-
-FOUNDATION_EXPORT BindingsIntList* _Nullable BindingsMakeIntList(void);
-
-/**
- * NewClient creates client storage, generates keys, connects, and registers
-with the network. Note that this does not register a username/identity, but
-merely creates a new cryptographic identity for adding such information
-at a later date.
-
-Users of this function should delete the storage directory on error.
- */
-FOUNDATION_EXPORT BOOL BindingsNewClient(NSString* _Nullable network, NSString* _Nullable storageDir, NSData* _Nullable password, NSString* _Nullable regCode, NSError* _Nullable* _Nullable error);
-
-/**
- * NewClientFromBackup constructs a new Client from an encrypted backup. The backup
-is decrypted using the backupPassphrase. On success a successful client creation,
-the function will return a JSON encoded list of the E2E partners
-contained in the backup and a json-encoded string of the parameters stored in the backup
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsNewClientFromBackup(NSString* _Nullable ndfJSON, NSString* _Nullable storageDir, NSData* _Nullable sessionPassword, NSData* _Nullable backupPassphrase, NSData* _Nullable backupFileContents, NSError* _Nullable* _Nullable error);
-
-/**
- * NewDummyTrafficManager creates a DummyTraffic manager and initialises the
-dummy traffic send thread. Note that the manager does not start sending dummy
-traffic until its status is set to true using DummyTraffic.SetStatus.
-The maxNumMessages is the upper bound of the random number of messages sent
-each send. avgSendDeltaMS is the average duration, in milliseconds, to wait
-between sends. Sends occur every avgSendDeltaMS +/- a random duration with an
-upper bound of randomRangeMS.
- */
-FOUNDATION_EXPORT BindingsDummyTraffic* _Nullable BindingsNewDummyTrafficManager(BindingsClient* _Nullable client, long maxNumMessages, long avgSendDeltaMS, long randomRangeMS, NSError* _Nullable* _Nullable error);
-
-/**
- *  fact object
-creates a new fact. The factType must be either:
- 0 - Username
- 1 - Email
- 2 - Phone Number
-The fact must be well formed for the type and must not include commas or
-semicolons. If it is not well formed, it will be rejected.  Phone numbers
-must have the two letter country codes appended.  For the complete set of
-validation, see /elixxir/primitives/fact/fact.go
- */
-FOUNDATION_EXPORT BindingsFact* _Nullable BindingsNewFact(long factType, NSString* _Nullable factStr, NSError* _Nullable* _Nullable error);
-
-/**
- * FactList
- */
-FOUNDATION_EXPORT BindingsFactList* _Nullable BindingsNewFactList(void);
-
-/**
- * NewFileTransferManager creates a new file transfer manager and starts the
-sending and receiving threads. The receiveFunc is called everytime a new file
-transfer is received.
-The parameters string contains file transfer network configuration options
-and is a JSON formatted string of the fileTransfer.Params object. If it is
-left empty, then defaults are used. It is highly recommended that defaults
-are used. If it is set, it must match the following format:
- {"MaxThroughput":150000,"SendTimeout":500000000}
-MaxThroughput is in bytes/sec and SendTimeout is in nanoseconds.
- */
-FOUNDATION_EXPORT BindingsFileTransfer* _Nullable BindingsNewFileTransferManager(BindingsClient* _Nullable client, id<BindingsFileTransferReceiveFunc> _Nullable receiveFunc, NSString* _Nullable parameters, NSError* _Nullable* _Nullable error);
-
-/**
- * NewGroupManager creates a new group chat manager.
- */
-FOUNDATION_EXPORT BindingsGroupChat* _Nullable BindingsNewGroupManager(BindingsClient* _Nullable client, id<BindingsGroupRequestFunc> _Nullable requestFunc, id<BindingsGroupReceiveFunc> _Nullable receiveFunc, NSError* _Nullable* _Nullable error);
-
-/**
- * NewPrecannedClient creates an insecure user with predetermined keys with nodes
-It creates client storage, generates keys, connects, and registers
-with the network. Note that this does not register a username/identity, but
-merely creates a new cryptographic identity for adding such information
-at a later date.
-
-Users of this function should delete the storage directory on error.
- */
-FOUNDATION_EXPORT BOOL BindingsNewPrecannedClient(long precannedID, NSString* _Nullable network, NSString* _Nullable storageDir, NSData* _Nullable password, NSError* _Nullable* _Nullable error);
-
-/**
- * NewUserDiscovery returns a new user discovery object. Only call this once. It must be called
-after StartNetworkFollower is called and will fail if the network has never
-been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUserDiscovery(BindingsClient* _Nullable client, NSError* _Nullable* _Nullable error);
-
-/**
- * NewUserDiscoveryFromBackup returns a new user discovery object. It
-wil set up the manager with the backup data. Pass into it the backed up
-facts, one email and phone number each. This will add the registered facts
-to the backed Store. Any one of these fields may be empty,
-however both fields being empty will cause an error. Any other fact that is not
-an email or phone number will return an error. You may only add a fact for the
-accepted types once each. If you attempt to back up a fact type that has already
-been backed up, an error will be returned. Anytime an error is returned, it means
-the backup was not successful.
-NOTE: Do not use this as a direct store operation. This feature is intended to add facts
-to a backend store that have ALREADY BEEN REGISTERED on the account.
-THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend.
-Only call this once. It must be called after StartNetworkFollower
-is called and will fail if the network has never been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUserDiscoveryFromBackup(BindingsClient* _Nullable client, NSString* _Nullable email, NSString* _Nullable phone, NSError* _Nullable* _Nullable error);
-
-/**
- * NotificationsForMe Check if a notification received is for me
-It returns a NotificationForMeReport which contains a ForMe bool stating if it is for the caller,
-a Type, and a source. These are as follows:
-	TYPE       	SOURCE				DESCRIPTION
-	"default"	recipient user ID	A message with no association
-	"request"	sender user ID		A channel request has been received
-	"reset"	    sender user ID		A channel reset has been received
-	"confirm"	sender user ID		A channel request has been accepted
-	"silent"	sender user ID		A message which should not be notified on
-	"e2e"		sender user ID		reception of an E2E message
-	"group"		group ID			reception of a group chat message
- "endFT"     sender user ID		Last message sent confirming end of file transfer
- "groupRQ"   sender user ID		Request from sender to join a group chat
- */
-FOUNDATION_EXPORT BindingsManyNotificationForMeReport* _Nullable BindingsNotificationsForMe(NSString* _Nullable notifCSV, NSString* _Nullable preimages, NSError* _Nullable* _Nullable error);
-
-/**
- * RegisterLogWriter registers a callback on which logs are written.
- */
-FOUNDATION_EXPORT void BindingsRegisterLogWriter(id<BindingsLogWriter> _Nullable writer);
-
-/**
- * RestoreContactsFromBackup takes as input the jason output of the
-`NewClientFromBackup` function, unmarshals it into IDs, looks up
-each ID in user discovery, and initiates a session reset request.
-This function will not return until every id in the list has been sent a
-request. It should be called again and again until it completes.
-xxDK users should not use this function. This function is used by
-the mobile phone apps and are not intended to be part of the xxDK. It
-should be treated as internal functions specific to the phone apps.
- */
-FOUNDATION_EXPORT BindingsRestoreContactsReport* _Nullable BindingsRestoreContactsFromBackup(NSData* _Nullable backupPartnerIDs, BindingsClient* _Nullable client, BindingsUserDiscovery* _Nullable udManager, id<BindingsLookupCallback> _Nullable lookupCB, id<BindingsRestoreContactsUpdater> _Nullable updatesCb);
-
-/**
- * ResumeBackup starts the backup processes back up with a new callback after it
-has been initialized.
-Call this function only when resuming a backup that has already been
-initialized or to replace the callback.
-To start the backup for the first time or to use a new password, use
-InitializeBackup.
- */
-FOUNDATION_EXPORT BindingsBackup* _Nullable BindingsResumeBackup(id<BindingsUpdateBackupFunc> _Nullable cb, BindingsClient* _Nullable c, NSError* _Nullable* _Nullable error);
-
-/**
- * SetTimeSource sets the network time to a custom source.
- */
-FOUNDATION_EXPORT void BindingsSetTimeSource(id<BindingsTimeSource> _Nullable timeNow);
-
-/**
- * StoreSecretWithMnemonic stores the secret tied with the mnemonic to storage.
-Unlike other storage operations, this does not use EKV, as that is
-intrinsically tied to client operations, which the user will not have while
-trying to recover their account. As such, we store the encrypted data
-directly, with a specified path. Path will be a valid filepath in which the
-recover file will be stored as ".recovery".
-
-As an example, given "home/user/xxmessenger/storagePath",
-the recovery file will be stored at
-"home/user/xxmessenger/storagePath/.recovery"
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsStoreSecretWithMnemonic(NSData* _Nullable secret, NSString* _Nullable path, NSError* _Nullable* _Nullable error);
-
-/**
- * Unmarshals a marshaled contact object, returns an error if it fails
- */
-FOUNDATION_EXPORT BindingsContact* _Nullable BindingsUnmarshalContact(NSData* _Nullable b, NSError* _Nullable* _Nullable error);
-
-/**
- * Unmarshals a marshaled send report object, returns an error if it fails
- */
-FOUNDATION_EXPORT BindingsSendReport* _Nullable BindingsUnmarshalSendReport(NSData* _Nullable b, NSError* _Nullable* _Nullable error);
-
-/**
- * UpdateCommonErrors takes the passed in contents of a JSON file and updates the
-errToUserErr map with the contents of the json file. The JSON's expected format
-conform with the commented examples provides in errToUserErr above.
-NOTE that you should not pass in a file path, but a preloaded JSON file
- */
-FOUNDATION_EXPORT BOOL BindingsUpdateCommonErrors(NSString* _Nullable jsonFile, NSError* _Nullable* _Nullable error);
-
-// skipped function WrapAPIClient with unsupported parameter or return types
-
-
-// skipped function WrapUserDiscovery with unsupported parameter or return types
-
-
-@class BindingsAuthConfirmCallback;
-
-@class BindingsAuthRequestCallback;
-
-@class BindingsAuthResetNotificationCallback;
-
-@class BindingsClientError;
-
-@class BindingsEventCallbackFunctionObject;
-
-@class BindingsFileTransferReceiveFunc;
-
-@class BindingsFileTransferReceivedProgressFunc;
-
-@class BindingsFileTransferSentProgressFunc;
-
-@class BindingsGroupReceiveFunc;
-
-@class BindingsGroupRequestFunc;
-
-@class BindingsListener;
-
-@class BindingsLogWriter;
-
-@class BindingsLookupCallback;
-
-@class BindingsMessageDeliveryCallback;
-
-@class BindingsMultiLookupCallback;
-
-@class BindingsNetworkHealthCallback;
-
-@class BindingsPreimageNotification;
-
-@class BindingsRestoreContactsUpdater;
-
-@class BindingsRoundCompletionCallback;
-
-@class BindingsRoundEventCallback;
-
-@class BindingsSearchCallback;
-
-@class BindingsSingleSearchCallback;
-
-@class BindingsTimeSource;
-
-@class BindingsUpdateBackupFunc;
-
-/**
- * AuthConfirmCallback notifies the register whenever they receive an auth
-request confirmation
- */
-@interface BindingsAuthConfirmCallback : NSObject <goSeqRefInterface, BindingsAuthConfirmCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)partner;
-@end
-
-/**
- * AuthRequestCallback notifies the register whenever they receive an auth
-request
- */
-@interface BindingsAuthRequestCallback : NSObject <goSeqRefInterface, BindingsAuthRequestCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-/**
- * AuthRequestCallback notifies the register whenever they receive an auth
-request
- */
-@interface BindingsAuthResetNotificationCallback : NSObject <goSeqRefInterface, BindingsAuthResetNotificationCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@interface BindingsClientError : NSObject <goSeqRefInterface, BindingsClientError> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)report:(NSString* _Nullable)source message:(NSString* _Nullable)message trace:(NSString* _Nullable)trace;
-@end
-
-/**
- * EventCallbackFunctionObject bindings interface which contains function
-that implements the EventCallbackFunction
- */
-@interface BindingsEventCallbackFunctionObject : NSObject <goSeqRefInterface, BindingsEventCallbackFunctionObject> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)reportEvent:(long)priority category:(NSString* _Nullable)category evtType:(NSString* _Nullable)evtType details:(NSString* _Nullable)details;
-@end
-
-/**
- * FileTransferReceiveFunc contains a function callback that notifies the
-receiver of an incoming file transfer. It is called on the reception of the
-initial file transfer message.
- */
-@interface BindingsFileTransferReceiveFunc : NSObject <goSeqRefInterface, BindingsFileTransferReceiveFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)receiveCallback:(NSData* _Nullable)tid fileName:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType sender:(NSData* _Nullable)sender size:(long)size preview:(NSData* _Nullable)preview;
-@end
-
-/**
- * FileTransferReceivedProgressFunc contains a function callback that tracks the
-progress of receiving a file. It is called when a file part is received, the
-transfer completes, or on error.
- */
-@interface BindingsFileTransferReceivedProgressFunc : NSObject <goSeqRefInterface, BindingsFileTransferReceivedProgressFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)receivedProgressCallback:(BOOL)completed received:(long)received total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-/**
- * FileTransferSentProgressFunc contains a function callback that tracks the
-progress of sending a file. It is called when a file part is sent, a file
-part arrives, the transfer completes, or on error.
- */
-@interface BindingsFileTransferSentProgressFunc : NSObject <goSeqRefInterface, BindingsFileTransferSentProgressFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)sentProgressCallback:(BOOL)completed sent:(long)sent arrived:(long)arrived total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-/**
- * GroupReceiveFunc contains a function callback that is called when a group
-message is received.
- */
-@interface BindingsGroupReceiveFunc : NSObject <goSeqRefInterface, BindingsGroupReceiveFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)groupReceiveCallback:(BindingsGroupMessageReceive* _Nullable)msg;
-@end
-
-/**
- * GroupRequestFunc contains a function callback that is called when a group
-request is received.
- */
-@interface BindingsGroupRequestFunc : NSObject <goSeqRefInterface, BindingsGroupRequestFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)groupRequestCallback:(BindingsGroup* _Nullable)g;
-@end
-
-/**
- * Listener provides a callback to hear a message
-An object implementing this interface can be called back when the client
-gets a message of the type that the registerer specified at registration
-time.
- */
-@interface BindingsListener : NSObject <goSeqRefInterface, BindingsListener> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * Hear is called to receive a message in the UI
- */
-- (void)hear:(BindingsMessage* _Nullable)message;
-/**
- * Returns a name, used for debugging
- */
-- (NSString* _Nonnull)name;
-@end
-
-@interface BindingsLogWriter : NSObject <goSeqRefInterface, BindingsLogWriter> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)log:(NSString* _Nullable)p0;
-@end
-
-/**
- * LookupCallback returns the result of a single lookup
- */
-@interface BindingsLookupCallback : NSObject <goSeqRefInterface, BindingsLookupCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-/**
- * MessageDeliveryCallback gets called on the determination if all events
-related to a message send were successful.
- */
-@interface BindingsMessageDeliveryCallback : NSObject <goSeqRefInterface, BindingsMessageDeliveryCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(NSData* _Nullable)msgID delivered:(BOOL)delivered timedOut:(BOOL)timedOut roundResults:(NSData* _Nullable)roundResults;
-@end
-
-/**
- * MultiLookupCallback returns the result of many parallel lookups
- */
-@interface BindingsMultiLookupCallback : NSObject <goSeqRefInterface, BindingsMultiLookupCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContactList* _Nullable)Succeeded failed:(BindingsIdList* _Nullable)failed errors:(NSString* _Nullable)errors;
-@end
-
-/**
- * A callback when which is used to receive notification if network health
-changes
- */
-@interface BindingsNetworkHealthCallback : NSObject <goSeqRefInterface, BindingsNetworkHealthCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BOOL)p0;
-@end
-
-@interface BindingsPreimageNotification : NSObject <goSeqRefInterface, BindingsPreimageNotification> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)notify:(NSData* _Nullable)identity deleted:(BOOL)deleted;
-@end
-
-/**
- * RestoreContactsUpdater interface provides a callback function
-for receiving update information from RestoreContactsFromBackup.
- */
-@interface BindingsRestoreContactsUpdater : NSObject <goSeqRefInterface, BindingsRestoreContactsUpdater> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * RestoreContactsCallback is called to report the current # of contacts
-that have been found and how many have been restored
-against the total number that need to be
-processed. If an error occurs it it set on the err variable as a
-plain string.
- */
-- (void)restoreContactsCallback:(long)numFound numRestored:(long)numRestored total:(long)total err:(NSString* _Nullable)err;
-@end
-
-/**
- * RoundCompletionCallback is returned when the completion of a round is known.
- */
-@interface BindingsRoundCompletionCallback : NSObject <goSeqRefInterface, BindingsRoundCompletionCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(long)rid success:(BOOL)success timedOut:(BOOL)timedOut;
-@end
-
-/**
- * RoundEventCallback handles waiting on the exact state of a round on
-the cMix network.
- */
-@interface BindingsRoundEventCallback : NSObject <goSeqRefInterface, BindingsRoundEventCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(long)rid state:(long)state timedOut:(BOOL)timedOut;
-@end
-
-/**
- * SearchCallback returns the result of a search
- */
-@interface BindingsSearchCallback : NSObject <goSeqRefInterface, BindingsSearchCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContactList* _Nullable)contacts error:(NSString* _Nullable)error;
-@end
-
-/**
- * SingleSearchCallback returns the result of a single search
- */
-@interface BindingsSingleSearchCallback : NSObject <goSeqRefInterface, BindingsSingleSearchCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@interface BindingsTimeSource : NSObject <goSeqRefInterface, BindingsTimeSource> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (int64_t)nowMs;
-@end
-
-/**
- * UpdateBackupFunc contains a function callback that returns new backups.
- */
-@interface BindingsUpdateBackupFunc : NSObject <goSeqRefInterface, BindingsUpdateBackupFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)updateBackup:(NSData* _Nullable)encryptedBackup;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Universe.objc.h b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Universe.objc.h
deleted file mode 100644
index 019e7502d581983722a15bf30799e85cbc5dd766..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Universe.objc.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Objective-C API for talking to  Go package.
-//   gobind -lang=objc 
-//
-// File is generated by gobind. Do not edit.
-
-#ifndef __Universe_H__
-#define __Universe_H__
-
-@import Foundation;
-#include "ref.h"
-
-@protocol Universeerror;
-@class Universeerror;
-
-@protocol Universeerror <NSObject>
-- (NSString* _Nonnull)error;
-@end
-
-@class Universeerror;
-
-@interface Universeerror : NSError <goSeqRefInterface, Universeerror> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (NSString* _Nonnull)error;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/ref.h b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/ref.h
deleted file mode 100644
index b8036a4d85c7387f3def61473a071b5d8c4c8208..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/ref.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#ifndef __GO_REF_HDR__
-#define __GO_REF_HDR__
-
-#include <Foundation/Foundation.h>
-
-// GoSeqRef is an object tagged with an integer for passing back and
-// forth across the language boundary. A GoSeqRef may represent either
-// an instance of a Go object, or an Objective-C object passed to Go.
-// The explicit allocation of a GoSeqRef is used to pin a Go object
-// when it is passed to Objective-C. The Go seq package maintains a
-// reference to the Go object in a map keyed by the refnum along with
-// a reference count. When the reference count reaches zero, the Go
-// seq package will clear the corresponding entry in the map.
-@interface GoSeqRef : NSObject {
-}
-@property(readonly) int32_t refnum;
-@property(strong) id obj; // NULL when representing a Go object.
-
-// new GoSeqRef object to proxy a Go object. The refnum must be
-// provided from Go side.
-- (instancetype)initWithRefnum:(int32_t)refnum obj:(id)obj;
-
-- (int32_t)incNum;
-
-@end
-
-@protocol goSeqRefInterface
--(GoSeqRef*) _ref;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Modules/module.modulemap b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Modules/module.modulemap
deleted file mode 100644
index 4316a5b24058edfc18ffb2dc7f7a982e8353441a..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Modules/module.modulemap
+++ /dev/null
@@ -1,8 +0,0 @@
-framework module "Bindings" {
-	header "ref.h"
-    header "Bindings.objc.h"
-    header "Universe.objc.h"
-    header "Bindings.h"
-
-    export *
-}
\ No newline at end of file
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Resources/Info.plist b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Resources/Info.plist
deleted file mode 100644
index 0d1a4b8ab9b1fc8e9357197398f73353470cb636..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Resources/Info.plist
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-    <plist version="1.0">
-      <dict>
-      </dict>
-    </plist>
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Bindings b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Bindings
deleted file mode 100644
index 1e3b1dc25b4d65bf1e21a5d169dc3ae4de6c4fd7..0000000000000000000000000000000000000000
Binary files a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Bindings and /dev/null differ
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Headers/Bindings.h b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Headers/Bindings.h
deleted file mode 100644
index 8906a7da239705b790cb2bb64de92f806640cb38..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Headers/Bindings.h
+++ /dev/null
@@ -1,13 +0,0 @@
-
-// Objective-C API for talking to the following Go packages
-//
-//	gitlab.com/elixxir/client/bindings
-//
-// File is generated by gomobile bind. Do not edit.
-#ifndef __Bindings_FRAMEWORK_H__
-#define __Bindings_FRAMEWORK_H__
-
-#include "Bindings.objc.h"
-#include "Universe.objc.h"
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Headers/Bindings.objc.h b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Headers/Bindings.objc.h
deleted file mode 100644
index 32bf6d116888f787ced27b01b95cb4e1b2c1138b..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Headers/Bindings.objc.h
+++ /dev/null
@@ -1,2083 +0,0 @@
-// Objective-C API for talking to gitlab.com/elixxir/client/bindings Go package.
-//   gobind -lang=objc gitlab.com/elixxir/client/bindings
-//
-// File is generated by gobind. Do not edit.
-
-#ifndef __Bindings_H__
-#define __Bindings_H__
-
-@import Foundation;
-#include "ref.h"
-#include "Universe.objc.h"
-
-
-@class BindingsBackup;
-@class BindingsBackupReport;
-@class BindingsClient;
-@class BindingsContact;
-@class BindingsContactList;
-@class BindingsDummyTraffic;
-@class BindingsFact;
-@class BindingsFactList;
-@class BindingsFilePartTracker;
-@class BindingsFileTransfer;
-@class BindingsGroup;
-@class BindingsGroupChat;
-@class BindingsGroupMember;
-@class BindingsGroupMembership;
-@class BindingsGroupMessageReceive;
-@class BindingsGroupReportDisk;
-@class BindingsGroupSendReport;
-@class BindingsIdList;
-@class BindingsIntList;
-@class BindingsManyNotificationForMeReport;
-@class BindingsMessage;
-@class BindingsNewGroupReport;
-@class BindingsNodeRegistrationsStatus;
-@class BindingsNotificationForMeReport;
-@class BindingsRestoreContactsReport;
-@class BindingsRoundList;
-@class BindingsSendReport;
-@class BindingsSendReportDisk;
-@class BindingsUnregister;
-@class BindingsUser;
-@class BindingsUserDiscovery;
-@protocol BindingsAuthConfirmCallback;
-@class BindingsAuthConfirmCallback;
-@protocol BindingsAuthRequestCallback;
-@class BindingsAuthRequestCallback;
-@protocol BindingsAuthResetNotificationCallback;
-@class BindingsAuthResetNotificationCallback;
-@protocol BindingsClientError;
-@class BindingsClientError;
-@protocol BindingsEventCallbackFunctionObject;
-@class BindingsEventCallbackFunctionObject;
-@protocol BindingsFileTransferReceiveFunc;
-@class BindingsFileTransferReceiveFunc;
-@protocol BindingsFileTransferReceivedProgressFunc;
-@class BindingsFileTransferReceivedProgressFunc;
-@protocol BindingsFileTransferSentProgressFunc;
-@class BindingsFileTransferSentProgressFunc;
-@protocol BindingsGroupReceiveFunc;
-@class BindingsGroupReceiveFunc;
-@protocol BindingsGroupRequestFunc;
-@class BindingsGroupRequestFunc;
-@protocol BindingsListener;
-@class BindingsListener;
-@protocol BindingsLogWriter;
-@class BindingsLogWriter;
-@protocol BindingsLookupCallback;
-@class BindingsLookupCallback;
-@protocol BindingsMessageDeliveryCallback;
-@class BindingsMessageDeliveryCallback;
-@protocol BindingsMultiLookupCallback;
-@class BindingsMultiLookupCallback;
-@protocol BindingsNetworkHealthCallback;
-@class BindingsNetworkHealthCallback;
-@protocol BindingsPreimageNotification;
-@class BindingsPreimageNotification;
-@protocol BindingsRestoreContactsUpdater;
-@class BindingsRestoreContactsUpdater;
-@protocol BindingsRoundCompletionCallback;
-@class BindingsRoundCompletionCallback;
-@protocol BindingsRoundEventCallback;
-@class BindingsRoundEventCallback;
-@protocol BindingsSearchCallback;
-@class BindingsSearchCallback;
-@protocol BindingsSingleSearchCallback;
-@class BindingsSingleSearchCallback;
-@protocol BindingsTimeSource;
-@class BindingsTimeSource;
-@protocol BindingsUpdateBackupFunc;
-@class BindingsUpdateBackupFunc;
-
-@protocol BindingsAuthConfirmCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)partner;
-@end
-
-@protocol BindingsAuthRequestCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@protocol BindingsAuthResetNotificationCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@protocol BindingsClientError <NSObject>
-- (void)report:(NSString* _Nullable)source message:(NSString* _Nullable)message trace:(NSString* _Nullable)trace;
-@end
-
-@protocol BindingsEventCallbackFunctionObject <NSObject>
-- (void)reportEvent:(long)priority category:(NSString* _Nullable)category evtType:(NSString* _Nullable)evtType details:(NSString* _Nullable)details;
-@end
-
-@protocol BindingsFileTransferReceiveFunc <NSObject>
-- (void)receiveCallback:(NSData* _Nullable)tid fileName:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType sender:(NSData* _Nullable)sender size:(long)size preview:(NSData* _Nullable)preview;
-@end
-
-@protocol BindingsFileTransferReceivedProgressFunc <NSObject>
-- (void)receivedProgressCallback:(BOOL)completed received:(long)received total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-@protocol BindingsFileTransferSentProgressFunc <NSObject>
-- (void)sentProgressCallback:(BOOL)completed sent:(long)sent arrived:(long)arrived total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-@protocol BindingsGroupReceiveFunc <NSObject>
-- (void)groupReceiveCallback:(BindingsGroupMessageReceive* _Nullable)msg;
-@end
-
-@protocol BindingsGroupRequestFunc <NSObject>
-- (void)groupRequestCallback:(BindingsGroup* _Nullable)g;
-@end
-
-@protocol BindingsListener <NSObject>
-/**
- * Hear is called to receive a message in the UI
- */
-- (void)hear:(BindingsMessage* _Nullable)message;
-/**
- * Returns a name, used for debugging
- */
-- (NSString* _Nonnull)name;
-@end
-
-@protocol BindingsLogWriter <NSObject>
-- (void)log:(NSString* _Nullable)p0;
-@end
-
-@protocol BindingsLookupCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsMessageDeliveryCallback <NSObject>
-- (void)eventCallback:(NSData* _Nullable)msgID delivered:(BOOL)delivered timedOut:(BOOL)timedOut roundResults:(NSData* _Nullable)roundResults;
-@end
-
-@protocol BindingsMultiLookupCallback <NSObject>
-- (void)callback:(BindingsContactList* _Nullable)Succeeded failed:(BindingsIdList* _Nullable)failed errors:(NSString* _Nullable)errors;
-@end
-
-@protocol BindingsNetworkHealthCallback <NSObject>
-- (void)callback:(BOOL)p0;
-@end
-
-@protocol BindingsPreimageNotification <NSObject>
-- (void)notify:(NSData* _Nullable)identity deleted:(BOOL)deleted;
-@end
-
-@protocol BindingsRestoreContactsUpdater <NSObject>
-/**
- * RestoreContactsCallback is called to report the current # of contacts
-that have been found and how many have been restored
-against the total number that need to be
-processed. If an error occurs it it set on the err variable as a
-plain string.
- */
-- (void)restoreContactsCallback:(long)numFound numRestored:(long)numRestored total:(long)total err:(NSString* _Nullable)err;
-@end
-
-@protocol BindingsRoundCompletionCallback <NSObject>
-- (void)eventCallback:(long)rid success:(BOOL)success timedOut:(BOOL)timedOut;
-@end
-
-@protocol BindingsRoundEventCallback <NSObject>
-- (void)eventCallback:(long)rid state:(long)state timedOut:(BOOL)timedOut;
-@end
-
-@protocol BindingsSearchCallback <NSObject>
-- (void)callback:(BindingsContactList* _Nullable)contacts error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsSingleSearchCallback <NSObject>
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@protocol BindingsTimeSource <NSObject>
-- (int64_t)nowMs;
-@end
-
-@protocol BindingsUpdateBackupFunc <NSObject>
-- (void)updateBackup:(NSData* _Nullable)encryptedBackup;
-@end
-
-@interface BindingsBackup : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * AddJson stores a passed in json string in the backup structure
- */
-- (void)addJson:(NSString* _Nullable)json;
-/**
- * IsBackupRunning returns true if the backup has been initialized and is
-running. Returns false if it has been stopped.
- */
-- (BOOL)isBackupRunning;
-/**
- * StopBackup stops the backup processes and deletes the user's password from
-storage. To enable backups again, call InitializeBackup.
- */
-- (BOOL)stopBackup:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsBackupReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field BackupReport.RestoredContacts with unsupported type: []*gitlab.com/xx_network/primitives/id.ID
-
-@property (nonatomic) NSString* _Nonnull params;
-@end
-
-/**
- * BindingsClient wraps the api.Client, implementing additional functions
-to support the gomobile Client interface
- */
-@interface BindingsClient : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BOOL)confirmAuthenticatedChannel:(NSData* _Nullable)recipientMarshaled ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteAllRequests clears all requests from Client's auth storage.
- */
-- (BOOL)deleteAllRequests:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteContact is a function which removes a contact from Client's storage
- */
-- (BOOL)deleteContact:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteReceiveRequests clears receive requests from Client's auth storage.
- */
-- (BOOL)deleteReceiveRequests:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteRequest will delete a request, agnostic of request type
-for the given partner ID. If no request exists for this
-partner ID an error will be returned.
- */
-- (BOOL)deleteRequest:(NSData* _Nullable)requesterUserId error:(NSError* _Nullable* _Nullable)error;
-/**
- * DeleteSentRequests clears sent requests from Client's auth storage.
- */
-- (BOOL)deleteSentRequests:(NSError* _Nullable* _Nullable)error;
-// skipped method Client.GetInternalClient with unsupported parameter or return types
-
-/**
- * GetNodeRegistrationStatus returns a struct with the number of nodes the
-client is registered with and the number total.
- */
-- (BindingsNodeRegistrationsStatus* _Nullable)getNodeRegistrationStatus:(NSError* _Nullable* _Nullable)error;
-/**
- * GetPartners returns a list of
- */
-- (NSData* _Nullable)getPartners:(NSError* _Nullable* _Nullable)error;
-/**
- * GetPreferredBins returns the geographic bin or bins that the provided two
-character country code is a part of. The bins are returned as CSV.
- */
-- (NSString* _Nonnull)getPreferredBins:(NSString* _Nullable)countryCode error:(NSError* _Nullable* _Nullable)error;
-- (NSString* _Nonnull)getPreimages:(NSData* _Nullable)identity;
-// skipped method Client.GetRateLimitParams with unsupported parameter or return types
-
-- (NSString* _Nonnull)getRelationshipFingerprint:(NSData* _Nullable)partnerID error:(NSError* _Nullable* _Nullable)error;
-/**
- * Returns a user object from which all information about the current user
-can be gleaned
- */
-- (BindingsUser* _Nullable)getUser;
-/**
- * HasRunningProcessies checks if any background threads are running.
-returns true if none are running. This is meant to be
-used when NetworkFollowerStatus() returns Stopping.
-Due to the handling of comms on iOS, where the OS can
-block indefiently, it may not enter the stopped
-state apropreatly. This can be used instead.
- */
-- (BOOL)hasRunningProcessies;
-/**
- * returns true if the network is read to be in a healthy state where
-messages can be sent
- */
-- (BOOL)isNetworkHealthy;
-- (BindingsContact* _Nullable)makePrecannedAuthenticatedChannel:(long)precannedID error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the state of the network follower. Returns:
-Stopped 	- 0
-Starting - 1000
-Running	- 2000
-Stopping	- 3000
- */
-- (long)networkFollowerStatus;
-- (void)registerAuthCallbacks:(id<BindingsAuthRequestCallback> _Nullable)request confirm:(id<BindingsAuthConfirmCallback> _Nullable)confirm reset:(id<BindingsAuthResetNotificationCallback> _Nullable)reset;
-/**
- * RegisterClientErrorCallback registers the callback to handle errors from the
-long running threads controlled by StartNetworkFollower and StopNetworkFollower
- */
-- (void)registerClientErrorCallback:(id<BindingsClientError> _Nullable)clientError;
-/**
- * RegisterEventCallback records the given function to receive
-ReportableEvent objects. It returns the internal index
-of the callback so that it can be deleted later.
- */
-- (BOOL)registerEventCallback:(NSString* _Nullable)name myObj:(id<BindingsEventCallbackFunctionObject> _Nullable)myObj error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterForNotifications accepts firebase messaging token
- */
-- (BOOL)registerForNotifications:(NSString* _Nullable)token error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterListener records and installs a listener for messages
-matching specific uid, msgType, and/or username
-Returns a ListenerUnregister interface which can be
-
-to register for any userID, pass in an id with length 0 or an id with
-all zeroes
-
-to register for any message type, pass in a message type of 0
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types
- */
-- (BindingsUnregister* _Nullable)registerListener:(NSData* _Nullable)uid msgType:(long)msgType listener:(id<BindingsListener> _Nullable)listener error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterNetworkHealthCB registers the network health callback to be called
-any time the network health changes. Returns a unique ID that can be used to
-unregister the network health callback.
- */
-- (int64_t)registerNetworkHealthCB:(id<BindingsNetworkHealthCallback> _Nullable)nhc;
-- (void)registerPreimageCallback:(NSData* _Nullable)identity pin:(id<BindingsPreimageNotification> _Nullable)pin;
-/**
- * RegisterRoundEventsHandler registers a callback interface for round
-events.
-The rid is the round the event attaches to
-The timeoutMS is the number of milliseconds until the event fails, and the
-validStates are a list of states (one per byte) on which the event gets
-triggered
-States:
- 0x00 - PENDING (Never seen by client)
- 0x01 - PRECOMPUTING
- 0x02 - STANDBY
- 0x03 - QUEUED
- 0x04 - REALTIME
- 0x05 - COMPLETED
- 0x06 - FAILED
-These states are defined in elixxir/primitives/states/state.go
- */
-- (BindingsUnregister* _Nullable)registerRoundEventsHandler:(long)rid cb:(id<BindingsRoundEventCallback> _Nullable)cb timeoutMS:(long)timeoutMS il:(BindingsIntList* _Nullable)il;
-- (void)replayRequests;
-- (BOOL)requestAuthenticatedChannel:(NSData* _Nullable)recipientMarshaled meMarshaled:(NSData* _Nullable)meMarshaled message:(NSString* _Nullable)message ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-- (BOOL)resetSession:(NSData* _Nullable)recipientMarshaled meMarshaled:(NSData* _Nullable)meMarshaled message:(NSString* _Nullable)message ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * This will return the round the message was sent on if it is successfully sent
-This can be used to register a round event to learn about message delivery.
-on failure a round id of -1 is returned
- */
-- (BOOL)sendCmix:(NSData* _Nullable)recipient contents:(NSData* _Nullable)contents parameters:(NSString* _Nullable)parameters ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * SendE2E sends an end-to-end payload to the provided recipient with
-the provided msgType. Returns the list of rounds in which parts of
-the message were sent or an error if it fails.
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types
- */
-- (BindingsSendReport* _Nullable)sendE2E:(NSData* _Nullable)recipient payload:(NSData* _Nullable)payload messageType:(long)messageType parameters:(NSString* _Nullable)parameters error:(NSError* _Nullable* _Nullable)error;
-/**
- * SendUnsafe sends an unencrypted payload to the provided recipient
-with the provided msgType. Returns the list of rounds in which parts
-of the message were sent or an error if it fails.
-NOTE: Do not use this function unless you know what you are doing.
-This function always produces an error message in client logging.
-
-Message Types can be found in client/interfaces/message/type.go
-Make sure to not conflict with ANY default message types with custom types
- */
-- (BindingsRoundList* _Nullable)sendUnsafe:(NSData* _Nullable)recipient payload:(NSData* _Nullable)payload messageType:(long)messageType parameters:(NSString* _Nullable)parameters error:(NSError* _Nullable* _Nullable)error;
-/**
- * SetProxiedBins updates the host pool filter that filters out gateways that
-are not in one of the specified bins. The provided bins should be CSV.
- */
-- (BOOL)setProxiedBins:(NSString* _Nullable)binStringsCSV error:(NSError* _Nullable* _Nullable)error;
-/**
- * StartNetworkFollower kicks off the tracking of the network. It starts
-long running network client threads and returns an object for checking
-state and stopping those threads.
-Call this when returning from sleep and close when going back to
-sleep.
-These threads may become a significant drain on battery when offline, ensure
-they are stopped if there is no internet access
-Threads Started:
-  - Network Follower (/network/follow.go)
-  	tracks the network events and hands them off to workers for handling
-  - Historical Round Retrieval (/network/rounds/historical.go)
-		Retrieves data about rounds which are too old to be stored by the client
-	 - Message Retrieval Worker Group (/network/rounds/retrieve.go)
-		Requests all messages in a given round from the gateway of the last node
-	 - Message Handling Worker Group (/network/message/handle.go)
-		Decrypts and partitions messages when signals via the Switchboard
-	 - Health Tracker (/network/health)
-		Via the network instance tracks the state of the network
-	 - Garbled Messages (/network/message/garbled.go)
-		Can be signaled to check all recent messages which could be be decoded
-		Uses a message store on disk for persistence
-	 - Critical Messages (/network/message/critical.go)
-		Ensures all protocol layer mandatory messages are sent
-		Uses a message store on disk for persistence
-	 - KeyExchange Trigger (/keyExchange/trigger.go)
-		Responds to sent rekeys and executes them
-  - KeyExchange Confirm (/keyExchange/confirm.go)
-		Responds to confirmations of successful rekey operations
- */
-- (BOOL)startNetworkFollower:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * StopNetworkFollower stops the network follower if it is running.
-It returns errors if the Follower is in the wrong status to stop or if it
-fails to stop it.
-if the network follower is running and this fails, the client object will
-most likely be in an unrecoverable state and need to be trashed.
- */
-- (BOOL)stopNetworkFollower:(NSError* _Nullable* _Nullable)error;
-/**
- * UnregisterEventCallback deletes the callback identified by the
-index. It returns an error if it fails.
- */
-- (void)unregisterEventCallback:(NSString* _Nullable)name;
-/**
- * UnregisterForNotifications unregister user for notifications
- */
-- (BOOL)unregisterForNotifications:(NSError* _Nullable* _Nullable)error;
-- (void)unregisterNetworkHealthCB:(int64_t)funcID;
-- (BOOL)verifyOwnership:(NSData* _Nullable)receivedMarshaled verifiedMarshaled:(NSData* _Nullable)verifiedMarshaled ret0_:(BOOL* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * WaitForMessageDelivery allows the caller to get notified if the rounds a
-message was sent in successfully completed. Under the hood, this uses an API
-which uses the internal round data, network historical round lookup, and
-waiting on network events to determine what has (or will) occur.
-
-The callbacks will return at timeoutMS if no state update occurs
-
-This function takes the marshaled send report to ensure a memory leak does
-not occur as a result of both sides of the bindings holding a reference to
-the same pointer.
- */
-- (BOOL)waitForMessageDelivery:(NSData* _Nullable)marshaledSendReport mdc:(id<BindingsMessageDeliveryCallback> _Nullable)mdc timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * WaitForNewtwork will block until either the network is healthy or the
-passed timeout. It will return true if the network is healthy
- */
-- (BOOL)waitForNetwork:(long)timeoutMS;
-/**
- * WaitForRoundCompletion allows the caller to get notified if a round
-has completed (or failed). Under the hood, this uses an API which uses the internal
-round data, network historical round lookup, and waiting on network events
-to determine what has (or will) occur.
-
-The callbacks will return at timeoutMS if no state update occurs
- */
-- (BOOL)waitForRoundCompletion:(long)roundID rec:(id<BindingsRoundCompletionCallback> _Nullable)rec timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- *  contact object
- */
-@interface BindingsContact : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped method Contact.GetAPIContact with unsupported parameter or return types
-
-/**
- * GetDHPublicKey returns the public key associated with the Contact.
- */
-- (NSData* _Nullable)getDHPublicKey;
-/**
- * Returns a fact list for adding and getting facts to and from the contact
- */
-- (BindingsFactList* _Nullable)getFactList;
-/**
- * GetID returns the user ID for this user.
- */
-- (NSData* _Nullable)getID;
-/**
- * GetDHPublicKey returns hash of a DH proof of key ownership.
- */
-- (NSData* _Nullable)getOwnershipProof;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsContactList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Gets a stored round ID at the given index
- */
-- (BindingsContact* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the number of round IDs stored
- */
-- (long)len;
-@end
-
-/**
- * DummyTraffic contains the file dummy traffic manager. The manager can be used
-to set and get the status of the send thread.
- */
-@interface BindingsDummyTraffic : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewDummyTrafficManager creates a DummyTraffic manager and initialises the
-dummy traffic send thread. Note that the manager does not start sending dummy
-traffic until its status is set to true using DummyTraffic.SetStatus.
-The maxNumMessages is the upper bound of the random number of messages sent
-each send. avgSendDeltaMS is the average duration, in milliseconds, to wait
-between sends. Sends occur every avgSendDeltaMS +/- a random duration with an
-upper bound of randomRangeMS.
- */
-- (nullable instancetype)initManager:(BindingsClient* _Nullable)client maxNumMessages:(long)maxNumMessages avgSendDeltaMS:(long)avgSendDeltaMS randomRangeMS:(long)randomRangeMS;
-/**
- * GetStatus returns the current state of the dummy traffic send thread. It has
-the following return values:
- true  = send thread is sending dummy messages
- false = send thread is paused/stopped and not sending dummy messages
-Note that this function does not return the status set by SetStatus directly;
-it returns the current status of the send thread, which means any call to
-SetStatus will have a small delay before it is returned by GetStatus.
- */
-- (BOOL)getStatus;
-/**
- * SetStatus sets the state of the dummy traffic send thread, which determines
-if the thread is running or paused. The possible statuses are:
- true  = send thread is sending dummy messages
- false = send thread is paused/stopped and not sending dummy messages
-Returns an error if the channel is full.
-Note that this function cannot change the status of the send thread if it has
-yet to be started or stopped.
- */
-- (BOOL)setStatus:(BOOL)status error:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsFact : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- *  fact object
-creates a new fact. The factType must be either:
- 0 - Username
- 1 - Email
- 2 - Phone Number
-The fact must be well formed for the type and must not include commas or
-semicolons. If it is not well formed, it will be rejected.  Phone numbers
-must have the two letter country codes appended.  For the complete set of
-validation, see /elixxir/primitives/fact/fact.go
- */
-- (nullable instancetype)init:(long)factType factStr:(NSString* _Nullable)factStr;
-- (NSString* _Nonnull)get;
-- (NSString* _Nonnull)stringify;
-- (long)type;
-@end
-
-@interface BindingsFactList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * FactList
- */
-- (nullable instancetype)init;
-- (BOOL)add:(NSString* _Nullable)factData factType:(long)factType error:(NSError* _Nullable* _Nullable)error;
-- (BindingsFact* _Nullable)get:(long)i;
-- (long)num;
-- (NSString* _Nonnull)stringify:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * FilePartTracker contains the interfaces.FilePartTracker.
- */
-@interface BindingsFilePartTracker : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetNumParts returns the total number of file parts in the transfer.
- */
-- (long)getNumParts;
-/**
- * GetPartStatus returns the status of the file part with the given part number.
-The possible values for the status are:
-0 = unsent
-1 = sent (sender has sent a part, but it has not arrived)
-2 = arrived (sender has sent a part, and it has arrived)
-3 = received (receiver has received a part)
- */
-- (long)getPartStatus:(long)partNum;
-@end
-
-/**
- * FileTransfer contains the file transfer manager.
- */
-@interface BindingsFileTransfer : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewFileTransferManager creates a new file transfer manager and starts the
-sending and receiving threads. The receiveFunc is called everytime a new file
-transfer is received.
-The parameters string contains file transfer network configuration options
-and is a JSON formatted string of the fileTransfer.Params object. If it is
-left empty, then defaults are used. It is highly recommended that defaults
-are used. If it is set, it must match the following format:
- {"MaxThroughput":150000,"SendTimeout":500000000}
-MaxThroughput is in bytes/sec and SendTimeout is in nanoseconds.
- */
-- (nullable instancetype)initManager:(BindingsClient* _Nullable)client receiveFunc:(id<BindingsFileTransferReceiveFunc> _Nullable)receiveFunc parameters:(NSString* _Nullable)parameters;
-/**
- * CloseSend deletes a sent file transfer from the sent transfer map and from
-storage once a transfer has completed or reached the retry limit. Returns an
-error if the transfer has not run out of retries.
- */
-- (BOOL)closeSend:(NSData* _Nullable)transferID error:(NSError* _Nullable* _Nullable)error;
-/**
- * GetMaxFileNameByteLength returns the maximum length, in bytes, allowed for a
-file name.
- */
-- (long)getMaxFileNameByteLength;
-/**
- * GetMaxFilePreviewSize returns the maximum file preview size, in bytes.
- */
-- (long)getMaxFilePreviewSize;
-/**
- * GetMaxFileSize returns the maximum file size, in bytes, allowed to be
-transferred.
- */
-- (long)getMaxFileSize;
-/**
- * GetMaxFileTypeByteLength returns the maximum length, in bytes, allowed for a
-file type.
- */
-- (long)getMaxFileTypeByteLength;
-/**
- * Receive returns the fully assembled file on the completion of the transfer.
-It deletes the transfer from the received transfer map and from storage.
-Returns an error if the transfer is not complete, the full file cannot be
-verified, or if the transfer cannot be found.
- */
-- (NSData* _Nullable)receive:(NSData* _Nullable)transferID error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterReceiveProgressCallback allows for the registration of a callback to
-track the progress of an individual received file transfer. The callback will
-be called immediately when added to report the current status of the
-transfer. It will then call every time a file part is received, the transfer
-completes, or an error occurs. It is called at most once ever period, which
-means if events occur faster than the period, then they will not be reported
-and instead, the progress will be reported once at the end of the period.
-Once the callback reports that the transfer has completed, the recipient
-can get the full file by calling Receive.
-The period is specified in milliseconds.
- */
-- (BOOL)registerReceiveProgressCallback:(NSData* _Nullable)transferID progressFunc:(id<BindingsFileTransferReceivedProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * RegisterSendProgressCallback allows for the registration of a callback to
-track the progress of an individual sent file transfer. The callback will be
-called immediately when added to report the current status of the transfer.
-It will then call every time a file part is sent, a file part arrives, the
-transfer completes, or an error occurs. It is called at most once every
-period, which means if events occur faster than the period, then they will
-not be reported and instead, the progress will be reported once at the end of
-the period.
-The period is specified in milliseconds.
- */
-- (BOOL)registerSendProgressCallback:(NSData* _Nullable)transferID progressFunc:(id<BindingsFileTransferSentProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * Send sends a file to the recipient. The sender must have an E2E relationship
-with the recipient.
-The file name is the name of the file to show a user. It has a max length of
-48 bytes.
-The file type identifies what type of file is being sent. It has a max length
-of 8 bytes.
-The file data cannot be larger than 256 kB
-The retry float is the total amount of data to send relative to the data
-size. Data will be resent on error and will resend up to [(1 + retry) *
-fileSize].
-The preview stores a preview of the data (such as a thumbnail) and is
-capped at 4 kB in size.
-Returns a unique transfer ID used to identify the transfer.
-PeriodMS is the duration, in milliseconds, to wait between progress callback
-calls. Set this large enough to prevent spamming.
- */
-- (NSData* _Nullable)send:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType fileData:(NSData* _Nullable)fileData recipientID:(NSData* _Nullable)recipientID retry:(float)retry preview:(NSData* _Nullable)preview progressFunc:(id<BindingsFileTransferSentProgressFunc> _Nullable)progressFunc periodMS:(long)periodMS error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * Group structure contains the identifying and membership information of a
-group chat.
- */
-@interface BindingsGroup : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetCreatedMS returns the time the group was created in milliseconds. This is
-also the time the group requests were sent.
- */
-- (int64_t)getCreatedMS;
-/**
- * GetCreatedNano returns the time the group was created in nanoseconds. This is
-also the time the group requests were sent.
- */
-- (int64_t)getCreatedNano;
-/**
- * GetID return the 33-byte unique group ID.
- */
-- (NSData* _Nullable)getID;
-/**
- * GetInitMessage returns initial message sent with the group request.
- */
-- (NSData* _Nullable)getInitMessage;
-/**
- * GetMembership returns a list of contacts, one for each member in the group.
-The list is in order; the first contact is the leader/creator of the group.
-All subsequent members are ordered by their ID.
- */
-- (BindingsGroupMembership* _Nullable)getMembership;
-/**
- * GetName returns the name set by the user for the group.
- */
-- (NSData* _Nullable)getName;
-/**
- * Serialize serializes the Group.
- */
-- (NSData* _Nullable)serialize;
-@end
-
-/**
- * GroupChat object contains the group chat manager.
- */
-@interface BindingsGroupChat : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetGroup returns the group with the group ID. If no group exists, then the
-error "failed to find group" is returned.
- */
-- (BindingsGroup* _Nullable)getGroup:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * GetGroups returns an IdList containing a list of group IDs that the user is a
-part of.
- */
-- (BindingsIdList* _Nullable)getGroups;
-/**
- * JoinGroup allows a user to join a group when they receive a request. The
-caller must pass in the serialized bytes of a Group.
- */
-- (BOOL)joinGroup:(NSData* _Nullable)serializedGroupData error:(NSError* _Nullable* _Nullable)error;
-/**
- * LeaveGroup deletes a group so a user no longer has access.
- */
-- (BOOL)leaveGroup:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * MakeGroup creates a new group and sends a group request to all members in the
-group. The ID of the new group, the rounds the requests were sent on, and the
-status of the send are contained in NewGroupReport.
- */
-- (BindingsNewGroupReport* _Nullable)makeGroup:(BindingsIdList* _Nullable)membership name:(NSData* _Nullable)name message:(NSData* _Nullable)message;
-/**
- * NumGroups returns the number of groups the user is a part of.
- */
-- (long)numGroups;
-/**
- * ResendRequest resends a group request to all members in the group. The rounds
-they were sent on and the status of the send are contained in NewGroupReport.
- */
-- (BindingsNewGroupReport* _Nullable)resendRequest:(NSData* _Nullable)groupIdBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * Send sends the message to the specified group. Returns the round the messages
-were sent on.
- */
-- (BindingsGroupSendReport* _Nullable)send:(NSData* _Nullable)groupIdBytes message:(NSData* _Nullable)message error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * //
-Member Structure
-//
-GroupMember represents a member in the group membership list.
- */
-@interface BindingsGroupMember : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupMember.Member with unsupported type: gitlab.com/elixxir/crypto/group.Member
-
-// skipped method GroupMember.DeepCopy with unsupported parameter or return types
-
-// skipped method GroupMember.Equal with unsupported parameter or return types
-
-/**
- * GetDhKey returns the byte representation of the public Diffie–Hellman key of
-the member.
- */
-- (NSData* _Nullable)getDhKey;
-/**
- * GetID returns the 33-byte user ID of the member.
- */
-- (NSData* _Nullable)getID;
-- (NSString* _Nonnull)goString;
-- (NSData* _Nullable)serialize;
-- (NSString* _Nonnull)string;
-@end
-
-/**
- * GroupMembership structure contains a list of members that are part of a
-group. The first member is the group leader.
- */
-@interface BindingsGroupMembership : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Get returns the member at the index. The member at index 0 is always the
-group leader. An error is returned if the index is out of range.
- */
-- (BindingsGroupMember* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Len returns the number of members in the group membership.
- */
-- (long)len;
-@end
-
-/**
- * GroupMessageReceive contains a group message, its ID, and its data that a
-user receives.
- */
-@interface BindingsGroupMessageReceive : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupMessageReceive.MessageReceive with unsupported type: gitlab.com/elixxir/client/groupChat.MessageReceive
-
-/**
- * GetEphemeralID returns the ephemeral ID of the recipient.
- */
-- (int64_t)getEphemeralID;
-/**
- * GetGroupID returns the 33-byte group ID.
- */
-- (NSData* _Nullable)getGroupID;
-/**
- * GetMessageID returns the message ID.
- */
-- (NSData* _Nullable)getMessageID;
-/**
- * GetPayload returns the message payload.
- */
-- (NSData* _Nullable)getPayload;
-/**
- * GetRecipientID returns the 33-byte user ID of the recipient.
- */
-- (NSData* _Nullable)getRecipientID;
-/**
- * GetRoundID returns the ID of the round the message was sent on.
- */
-- (int64_t)getRoundID;
-/**
- * GetRoundTimestampMS returns the timestamp, in milliseconds, of the round the
-message was sent on.
- */
-- (int64_t)getRoundTimestampMS;
-/**
- * GetRoundTimestampNano returns the timestamp, in nanoseconds, of the round the
-message was sent on.
- */
-- (int64_t)getRoundTimestampNano;
-/**
- * GetRoundURL returns the ID of the round the message was sent on.
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetSenderID returns the 33-byte user ID of the sender.
- */
-- (NSData* _Nullable)getSenderID;
-/**
- * GetTimestampMS returns the message timestamp in milliseconds.
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message timestamp in nanoseconds.
- */
-- (int64_t)getTimestampNano;
-- (NSString* _Nonnull)string;
-@end
-
-@interface BindingsGroupReportDisk : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field GroupReportDisk.List with unsupported type: []gitlab.com/xx_network/primitives/id.Round
-
-@property (nonatomic) NSData* _Nullable grpId;
-@property (nonatomic) long status;
-@end
-
-/**
- * GroupSendReport is returned when sending a group message. It contains the
-round ID sent on and the timestamp of the send.
- */
-@interface BindingsGroupSendReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetMessageID returns the ID of the round that the send occurred on.
- */
-- (NSData* _Nullable)getMessageID;
-/**
- * GetRoundID returns the ID of the round that the send occurred on.
- */
-- (int64_t)getRoundID;
-/**
- * GetRoundURL returns the URL of the round that the send occurred on.
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetTimestampMS returns the timestamp of the send in milliseconds.
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the timestamp of the send in nanoseconds.
- */
-- (int64_t)getTimestampNano;
-@end
-
-/**
- *  ID list
-IdList contains a list of IDs.
- */
-@interface BindingsIdList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Add appends the ID bytes to the end of the list.
- */
-- (BOOL)add:(NSData* _Nullable)idBytes error:(NSError* _Nullable* _Nullable)error;
-/**
- * Get returns the ID at the index. An error is returned if the index is out of
-range.
- */
-- (NSData* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-/**
- * Len returns the number of IDs in the list.
- */
-- (long)len;
-@end
-
-@interface BindingsIntList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (void)add:(long)i;
-- (BOOL)get:(long)i ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-- (long)len;
-@end
-
-@interface BindingsManyNotificationForMeReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BindingsNotificationForMeReport* _Nullable)get:(long)i error:(NSError* _Nullable* _Nullable)error;
-- (long)len;
-@end
-
-/**
- * Message is a message received from the cMix network in the clear
-or that has been decrypted using established E2E keys.
- */
-@interface BindingsMessage : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetID returns the id of the message
- */
-- (NSData* _Nullable)getID;
-/**
- * GetMessageType returns the message's type
- */
-- (long)getMessageType;
-/**
- * GetPayload returns the message's payload/contents
- */
-- (NSData* _Nullable)getPayload;
-/**
- * GetRoundId returns the message's round ID
- */
-- (int64_t)getRoundId;
-/**
- * GetRoundTimestampMS returns the message's round timestamp in milliseconds
- */
-- (int64_t)getRoundTimestampMS;
-/**
- * GetRoundTimestampNano returns the message's round timestamp in nanoseconds
- */
-- (int64_t)getRoundTimestampNano;
-/**
- * GetRoundURL returns the message's round URL
- */
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetSender returns the message's sender ID, if available
- */
-- (NSData* _Nullable)getSender;
-/**
- * GetTimestampMS returns the message's timestamp in milliseconds
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message's timestamp in nanoseconds
- */
-- (int64_t)getTimestampNano;
-@end
-
-/**
- * NewGroupReport is returned when creating a new group and contains the ID of
-the group, a list of rounds that the group requests were sent on, and the
-status of the send.
- */
-@interface BindingsNewGroupReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetError returns the string of an error.
-Will be an empty string if no error occured
- */
-- (NSString* _Nonnull)getError;
-/**
- * GetGroup returns the Group.
- */
-- (BindingsGroup* _Nullable)getGroup;
-/**
- * GetRoundList returns the RoundList containing a list of rounds requests were
-sent on.
- */
-- (BindingsRoundList* _Nullable)getRoundList;
-/**
- * GetStatus returns the status of the requests sent when creating a new group.
-status = 0   an error occurred before any requests could be sent
-         1   all requests failed to send (call Resend Group)
-         2   some request failed and some succeeded (call Resend Group)
-         3,  all requests sent successfully (call Resend Group)
- */
-- (long)getStatus;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-- (BOOL)unmarshal:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * NodeRegistrationsStatus structure for returning node registration statuses
-for bindings.
- */
-@interface BindingsNodeRegistrationsStatus : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetRegistered returns the number of nodes registered with the client.
- */
-- (long)getRegistered;
-/**
- * GetTotal return the total of nodes currently in the network.
- */
-- (long)getTotal;
-@end
-
-@interface BindingsNotificationForMeReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BOOL)forMe;
-- (NSData* _Nullable)source;
-- (NSString* _Nonnull)type;
-@end
-
-/**
- * RestoreContactsReport is a gomobile friendly report structure
-for determining which IDs restored, which failed, and why.
- */
-@interface BindingsRestoreContactsReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * GetErrorAt returns the error string at index
- */
-- (NSString* _Nonnull)getErrorAt:(long)index;
-/**
- * GetFailedAt returns the failed ID at index
- */
-- (NSData* _Nullable)getFailedAt:(long)index;
-/**
- * GetRestoreContactsError returns an error string. Empty if no error.
- */
-- (NSString* _Nonnull)getRestoreContactsError;
-/**
- * GetRestoredAt returns the restored ID at index
- */
-- (NSData* _Nullable)getRestoredAt:(long)index;
-/**
- * LenFailed returns the length of the ID's failed.
- */
-- (long)lenFailed;
-/**
- * LenRestored returns the length of ID's restored.
- */
-- (long)lenRestored;
-@end
-
-@interface BindingsRoundList : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Gets a stored round ID at the given index
- */
-- (BOOL)get:(long)i ret0_:(long* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
-/**
- * Gets the number of round IDs stored
- */
-- (long)len;
-@end
-
-/**
- * the send report is the mechanisim by which sendE2E returns a single
- */
-@interface BindingsSendReport : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (NSData* _Nullable)getMessageID;
-- (BindingsRoundList* _Nullable)getRoundList;
-- (NSString* _Nonnull)getRoundURL;
-/**
- * GetTimestampMS returns the message's timestamp in milliseconds
- */
-- (int64_t)getTimestampMS;
-/**
- * GetTimestampNano returns the message's timestamp in nanoseconds
- */
-- (int64_t)getTimestampNano;
-- (NSData* _Nullable)marshal:(NSError* _Nullable* _Nullable)error;
-- (BOOL)unmarshal:(NSData* _Nullable)b error:(NSError* _Nullable* _Nullable)error;
-@end
-
-@interface BindingsSendReportDisk : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-// skipped field SendReportDisk.List with unsupported type: []gitlab.com/xx_network/primitives/id.Round
-
-@property (nonatomic) NSData* _Nullable mid;
-@property (nonatomic) int64_t ts;
-@end
-
-/**
- * Generic Unregister - a generic return used for all callbacks which can be
-unregistered
-Interface which allows the un-registration of a listener
- */
-@interface BindingsUnregister : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-/**
- * Call unregisters a callback
- */
-- (void)unregister;
-@end
-
-@interface BindingsUser : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (nonnull instancetype)init;
-- (BindingsContact* _Nullable)getContact;
-- (NSData* _Nullable)getE2EDhPrivateKey;
-- (NSData* _Nullable)getE2EDhPublicKey;
-- (NSData* _Nullable)getReceptionID;
-- (NSData* _Nullable)getReceptionRSAPrivateKeyPem;
-- (NSData* _Nullable)getReceptionRSAPublicKeyPem;
-- (NSData* _Nullable)getReceptionSalt;
-- (NSData* _Nullable)getTransmissionID;
-- (NSData* _Nullable)getTransmissionRSAPrivateKeyPem;
-- (NSData* _Nullable)getTransmissionRSAPublicKeyPem;
-- (NSData* _Nullable)getTransmissionSalt;
-- (BOOL)isPrecanned;
-@end
-
-@interface BindingsUserDiscovery : NSObject <goSeqRefInterface> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * NewUserDiscovery returns a new user discovery object. Only call this once. It must be called
-after StartNetworkFollower is called and will fail if the network has never
-been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-- (nullable instancetype)init:(BindingsClient* _Nullable)client;
-/**
- * NewUserDiscoveryFromBackup returns a new user discovery object. It
-wil set up the manager with the backup data. Pass into it the backed up
-facts, one email and phone number each. This will add the registered facts
-to the backed Store. Any one of these fields may be empty,
-however both fields being empty will cause an error. Any other fact that is not
-an email or phone number will return an error. You may only add a fact for the
-accepted types once each. If you attempt to back up a fact type that has already
-been backed up, an error will be returned. Anytime an error is returned, it means
-the backup was not successful.
-NOTE: Do not use this as a direct store operation. This feature is intended to add facts
-to a backend store that have ALREADY BEEN REGISTERED on the account.
-THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend.
-Only call this once. It must be called after StartNetworkFollower
-is called and will fail if the network has never been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-- (nullable instancetype)initFromBackup:(BindingsClient* _Nullable)client email:(NSString* _Nullable)email phone:(NSString* _Nullable)phone;
-/**
- * AddFact adds a fact for the user to user discovery. Will only succeed if the
-user is already registered and the system does not have the fact currently
-registered for any user.
-Will fail if the fact string is not well formed.
-This does not complete the fact registration process, it returns a
-confirmation id instead. Over the communications system the fact is
-associated with, a code will be sent. This confirmation ID needs to be
-called along with the code to finalize the fact.
- */
-- (NSString* _Nonnull)addFact:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * ConfirmFact confirms a fact first registered via AddFact. The confirmation ID comes from
-AddFact while the code will come over the associated communications system
- */
-- (BOOL)confirmFact:(NSString* _Nullable)confirmationID code:(NSString* _Nullable)code error:(NSError* _Nullable* _Nullable)error;
-/**
- * Lookup the contact object associated with the given userID.  The
-id is the byte representation of an id.
-This will reject if that id is malformed. The LookupCallback will return
-the associated contact if it exists.
- */
-- (BOOL)lookup:(NSData* _Nullable)idBytes callback:(id<BindingsLookupCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * MultiLookup Looks for the contact object associated with all given userIDs.
-The ids are the byte representation of an id stored in an IDList object.
-This will reject if that id is malformed or if the indexing on the IDList
-object is wrong. The MultiLookupCallback will return with all contacts
-returned within the timeout.
- */
-- (BOOL)multiLookup:(BindingsIdList* _Nullable)ids callback:(id<BindingsMultiLookupCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * Register registers a user with user discovery. Will return an error if the
-network signatures are malformed or if the username is taken. Usernames
-cannot be changed after registration at this time. Will fail if the user is
-already registered.
-Identity does not go over cmix, it occurs over normal communications
- */
-- (BOOL)register:(NSString* _Nullable)username error:(NSError* _Nullable* _Nullable)error;
-/**
- * RemoveFact removes a previously confirmed fact.  Will fail if the passed fact string is
-not well-formed or if the fact is not associated with this client.
-Users cannot remove username facts and must instead remove the user.
- */
-- (BOOL)removeFact:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * RemoveUser deletes a user. The fact sent must be the username.
-This function preserves the username forever and makes it
-unusable.
- */
-- (BOOL)removeUser:(NSString* _Nullable)fStr error:(NSError* _Nullable* _Nullable)error;
-/**
- * Search for the passed Facts.  The factList is the stringification of a
-fact list object, look at /bindings/list.go for more on that object.
-This will reject if that object is malformed. The SearchCallback will return
-a list of contacts, each having the facts it hit against.
-This is NOT intended to be used to search for multiple users at once, that
-can have a privacy reduction. Instead, it is intended to be used to search
-for a user where multiple pieces of information is known.
- */
-- (BOOL)search:(NSString* _Nullable)fl callback:(id<BindingsSearchCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * SearchSingle searches for the passed Facts.  The fact is the stringification of a
-fact object, look at /bindings/contact.go for more on that object.
-This will reject if that object is malformed. The SearchCallback will return
-a list of contacts, each having the facts it hit against.
-This only searches for a single fact at a time. It is intended to make some
-simple use cases of the API easier.
- */
-- (BOOL)searchSingle:(NSString* _Nullable)f callback:(id<BindingsSingleSearchCallback> _Nullable)callback timeoutMS:(long)timeoutMS error:(NSError* _Nullable* _Nullable)error;
-/**
- * SetAlternativeUserDiscovery sets the alternativeUd object within manager.
-Once set, any user discovery operation will go through the alternative
-user discovery service.
-To undo this operation, use UnsetAlternativeUserDiscovery.
-The contact file is the already read in bytes, not the file path for the contact file.
- */
-- (BOOL)setAlternativeUserDiscovery:(NSData* _Nullable)address cert:(NSData* _Nullable)cert contactFile:(NSData* _Nullable)contactFile error:(NSError* _Nullable* _Nullable)error;
-/**
- * UnsetAlternativeUserDiscovery clears out the information from
-the Manager object.
- */
-- (BOOL)unsetAlternativeUserDiscovery:(NSError* _Nullable* _Nullable)error;
-@end
-
-/**
- * Error codes
- */
-FOUNDATION_EXPORT NSString* _Nonnull const BindingsUnrecognizedCode;
-FOUNDATION_EXPORT NSString* _Nonnull const BindingsUnrecognizedMessage;
-
-/**
- * CompressJpeg takes a JPEG image in byte format
-and compresses it based on desired output size
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsCompressJpeg(NSData* _Nullable imgBytes, NSError* _Nullable* _Nullable error);
-
-/**
- * CompressJpegForPreview takes a JPEG image in byte format
-and compresses it based on desired output size
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsCompressJpegForPreview(NSData* _Nullable imgBytes, NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadAndVerifySignedNdfWithUrl retrieves the NDF from a specified URL.
-The NDF is processed into a protobuf containing a signature which
-is verified using the cert string passed in. The NDF is returned as marshaled
-byte data which may be used to start a client.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadAndVerifySignedNdfWithUrl(NSString* _Nullable url, NSString* _Nullable cert, NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadDAppRegistrationDB returns a []byte containing
-the JSON data describing registered dApps.
-See https://git.xx.network/elixxir/registered-dapps
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadDAppRegistrationDB(NSError* _Nullable* _Nullable error);
-
-/**
- * DownloadErrorDB returns a []byte containing the JSON data
-describing client errors.
-See https://git.xx.network/elixxir/client-error-database/
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsDownloadErrorDB(NSError* _Nullable* _Nullable error);
-
-/**
- * DumpStack returns a string with the stack trace of every running thread.
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsDumpStack(NSError* _Nullable* _Nullable error);
-
-/**
- * EnableGrpcLogs sets GRPC trace logging
- */
-FOUNDATION_EXPORT void BindingsEnableGrpcLogs(id<BindingsLogWriter> _Nullable writer);
-
-/**
- * ErrorStringToUserFriendlyMessage takes a passed in errStr which will be
-a backend generated error. These may be error specifically written by
-the backend team or lower level errors gotten from low level dependencies.
-This function will parse the error string for common errors provided from
-errToUserErr to provide a more user-friendly error message for the front end.
-If the error is not common, some simple parsing is done on the error message
-to make it more user-accessible, removing backend specific jargon.
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsErrorStringToUserFriendlyMessage(NSString* _Nullable errStr);
-
-/**
- * GenerateSecret creates a secret password using a system-based
-pseudorandom number generator. It takes 1 parameter, `numBytes`,
-which should be set to 32, but can be set higher in certain cases.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsGenerateSecret(long numBytes);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetCMIXParams(NSError* _Nullable* _Nullable error);
-
-/**
- * returns a previously created client. IF be used if the garbage collector
-removes the client instance on the app side.  Is NOT thread safe relative to
-login, newClient, or newPrecannedClient
- */
-FOUNDATION_EXPORT BindingsClient* _Nullable BindingsGetClientSingleton(void);
-
-/**
- * GetDependencies returns the api DEPENDENCIES
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetDependencies(void);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetE2EParams(NSError* _Nullable* _Nullable error);
-
-/**
- * GetGitVersion rturns the api GITVERSION
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetGitVersion(void);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetNetworkParams(NSError* _Nullable* _Nullable error);
-
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetUnsafeParams(NSError* _Nullable* _Nullable error);
-
-/**
- * GetVersion returns the api SEMVER
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsGetVersion(void);
-
-/**
- * InitializeBackup starts the backup processes that returns backup updates when
-they occur. Any time an event occurs that changes the contents of the backup,
-such as adding or deleting a contact, the backup is triggered and an
-encrypted backup is generated and returned on the updateBackupCb callback.
-Call this function only when enabling backup if it has not already been
-initialized or when the user wants to change their password.
-To resume backup process on app recovery, use ResumeBackup.
- */
-FOUNDATION_EXPORT BindingsBackup* _Nullable BindingsInitializeBackup(NSString* _Nullable password, id<BindingsUpdateBackupFunc> _Nullable updateBackupCb, BindingsClient* _Nullable c, NSError* _Nullable* _Nullable error);
-
-/**
- * LoadSecretWithMnemonic loads the secret stored from the call to
-StoreSecretWithMnemonic. The path given should be the same filepath
-as the path given in StoreSecretWithMnemonic. There should be a file
-in this path called ".recovery". This operation is not tied
-to client operations, as the user will not have a client when trying to
-recover their account.
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsLoadSecretWithMnemonic(NSString* _Nullable mnemonic, NSString* _Nullable path, NSError* _Nullable* _Nullable error);
-
-/**
- * sets level of logging. All logs the set level and above will be displayed
-options are:
-	TRACE		- 0
-	DEBUG		- 1
-	INFO 		- 2
-	WARN		- 3
-	ERROR		- 4
-	CRITICAL	- 5
-	FATAL		- 6
-The default state without updates is: INFO
- */
-FOUNDATION_EXPORT BOOL BindingsLogLevel(long level, NSError* _Nullable* _Nullable error);
-
-/**
- * Login will load an existing client from the storageDir
-using the password. This will fail if the client doesn't exist or
-the password is incorrect.
-The password is passed as a byte array so that it can be cleared from
-memory and stored as securely as possible using the memguard library.
-Login does not block on network connection, and instead loads and
-starts subprocesses to perform network operations.
- */
-FOUNDATION_EXPORT BindingsClient* _Nullable BindingsLogin(NSString* _Nullable storageDir, NSData* _Nullable password, NSString* _Nullable parameters, NSError* _Nullable* _Nullable error);
-
-/**
- * MakeIdList creates a new empty IdList.
- */
-FOUNDATION_EXPORT BindingsIdList* _Nullable BindingsMakeIdList(void);
-
-FOUNDATION_EXPORT BindingsIntList* _Nullable BindingsMakeIntList(void);
-
-/**
- * NewClient creates client storage, generates keys, connects, and registers
-with the network. Note that this does not register a username/identity, but
-merely creates a new cryptographic identity for adding such information
-at a later date.
-
-Users of this function should delete the storage directory on error.
- */
-FOUNDATION_EXPORT BOOL BindingsNewClient(NSString* _Nullable network, NSString* _Nullable storageDir, NSData* _Nullable password, NSString* _Nullable regCode, NSError* _Nullable* _Nullable error);
-
-/**
- * NewClientFromBackup constructs a new Client from an encrypted backup. The backup
-is decrypted using the backupPassphrase. On success a successful client creation,
-the function will return a JSON encoded list of the E2E partners
-contained in the backup and a json-encoded string of the parameters stored in the backup
- */
-FOUNDATION_EXPORT NSData* _Nullable BindingsNewClientFromBackup(NSString* _Nullable ndfJSON, NSString* _Nullable storageDir, NSData* _Nullable sessionPassword, NSData* _Nullable backupPassphrase, NSData* _Nullable backupFileContents, NSError* _Nullable* _Nullable error);
-
-/**
- * NewDummyTrafficManager creates a DummyTraffic manager and initialises the
-dummy traffic send thread. Note that the manager does not start sending dummy
-traffic until its status is set to true using DummyTraffic.SetStatus.
-The maxNumMessages is the upper bound of the random number of messages sent
-each send. avgSendDeltaMS is the average duration, in milliseconds, to wait
-between sends. Sends occur every avgSendDeltaMS +/- a random duration with an
-upper bound of randomRangeMS.
- */
-FOUNDATION_EXPORT BindingsDummyTraffic* _Nullable BindingsNewDummyTrafficManager(BindingsClient* _Nullable client, long maxNumMessages, long avgSendDeltaMS, long randomRangeMS, NSError* _Nullable* _Nullable error);
-
-/**
- *  fact object
-creates a new fact. The factType must be either:
- 0 - Username
- 1 - Email
- 2 - Phone Number
-The fact must be well formed for the type and must not include commas or
-semicolons. If it is not well formed, it will be rejected.  Phone numbers
-must have the two letter country codes appended.  For the complete set of
-validation, see /elixxir/primitives/fact/fact.go
- */
-FOUNDATION_EXPORT BindingsFact* _Nullable BindingsNewFact(long factType, NSString* _Nullable factStr, NSError* _Nullable* _Nullable error);
-
-/**
- * FactList
- */
-FOUNDATION_EXPORT BindingsFactList* _Nullable BindingsNewFactList(void);
-
-/**
- * NewFileTransferManager creates a new file transfer manager and starts the
-sending and receiving threads. The receiveFunc is called everytime a new file
-transfer is received.
-The parameters string contains file transfer network configuration options
-and is a JSON formatted string of the fileTransfer.Params object. If it is
-left empty, then defaults are used. It is highly recommended that defaults
-are used. If it is set, it must match the following format:
- {"MaxThroughput":150000,"SendTimeout":500000000}
-MaxThroughput is in bytes/sec and SendTimeout is in nanoseconds.
- */
-FOUNDATION_EXPORT BindingsFileTransfer* _Nullable BindingsNewFileTransferManager(BindingsClient* _Nullable client, id<BindingsFileTransferReceiveFunc> _Nullable receiveFunc, NSString* _Nullable parameters, NSError* _Nullable* _Nullable error);
-
-/**
- * NewGroupManager creates a new group chat manager.
- */
-FOUNDATION_EXPORT BindingsGroupChat* _Nullable BindingsNewGroupManager(BindingsClient* _Nullable client, id<BindingsGroupRequestFunc> _Nullable requestFunc, id<BindingsGroupReceiveFunc> _Nullable receiveFunc, NSError* _Nullable* _Nullable error);
-
-/**
- * NewPrecannedClient creates an insecure user with predetermined keys with nodes
-It creates client storage, generates keys, connects, and registers
-with the network. Note that this does not register a username/identity, but
-merely creates a new cryptographic identity for adding such information
-at a later date.
-
-Users of this function should delete the storage directory on error.
- */
-FOUNDATION_EXPORT BOOL BindingsNewPrecannedClient(long precannedID, NSString* _Nullable network, NSString* _Nullable storageDir, NSData* _Nullable password, NSError* _Nullable* _Nullable error);
-
-/**
- * NewUserDiscovery returns a new user discovery object. Only call this once. It must be called
-after StartNetworkFollower is called and will fail if the network has never
-been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUserDiscovery(BindingsClient* _Nullable client, NSError* _Nullable* _Nullable error);
-
-/**
- * NewUserDiscoveryFromBackup returns a new user discovery object. It
-wil set up the manager with the backup data. Pass into it the backed up
-facts, one email and phone number each. This will add the registered facts
-to the backed Store. Any one of these fields may be empty,
-however both fields being empty will cause an error. Any other fact that is not
-an email or phone number will return an error. You may only add a fact for the
-accepted types once each. If you attempt to back up a fact type that has already
-been backed up, an error will be returned. Anytime an error is returned, it means
-the backup was not successful.
-NOTE: Do not use this as a direct store operation. This feature is intended to add facts
-to a backend store that have ALREADY BEEN REGISTERED on the account.
-THIS IS NOT FOR ADDING NEWLY REGISTERED FACTS. That is handled on the backend.
-Only call this once. It must be called after StartNetworkFollower
-is called and will fail if the network has never been contacted.
-This function technically has a memory leak because it causes both sides of
-the bindings to think the other is in charge of the client object.
-In general this is not an issue because the client object should exist
-for the life of the program.
-This must be called while start network follower is running.
- */
-FOUNDATION_EXPORT BindingsUserDiscovery* _Nullable BindingsNewUserDiscoveryFromBackup(BindingsClient* _Nullable client, NSString* _Nullable email, NSString* _Nullable phone, NSError* _Nullable* _Nullable error);
-
-/**
- * NotificationsForMe Check if a notification received is for me
-It returns a NotificationForMeReport which contains a ForMe bool stating if it is for the caller,
-a Type, and a source. These are as follows:
-	TYPE       	SOURCE				DESCRIPTION
-	"default"	recipient user ID	A message with no association
-	"request"	sender user ID		A channel request has been received
-	"reset"	    sender user ID		A channel reset has been received
-	"confirm"	sender user ID		A channel request has been accepted
-	"silent"	sender user ID		A message which should not be notified on
-	"e2e"		sender user ID		reception of an E2E message
-	"group"		group ID			reception of a group chat message
- "endFT"     sender user ID		Last message sent confirming end of file transfer
- "groupRQ"   sender user ID		Request from sender to join a group chat
- */
-FOUNDATION_EXPORT BindingsManyNotificationForMeReport* _Nullable BindingsNotificationsForMe(NSString* _Nullable notifCSV, NSString* _Nullable preimages, NSError* _Nullable* _Nullable error);
-
-/**
- * RegisterLogWriter registers a callback on which logs are written.
- */
-FOUNDATION_EXPORT void BindingsRegisterLogWriter(id<BindingsLogWriter> _Nullable writer);
-
-/**
- * RestoreContactsFromBackup takes as input the jason output of the
-`NewClientFromBackup` function, unmarshals it into IDs, looks up
-each ID in user discovery, and initiates a session reset request.
-This function will not return until every id in the list has been sent a
-request. It should be called again and again until it completes.
-xxDK users should not use this function. This function is used by
-the mobile phone apps and are not intended to be part of the xxDK. It
-should be treated as internal functions specific to the phone apps.
- */
-FOUNDATION_EXPORT BindingsRestoreContactsReport* _Nullable BindingsRestoreContactsFromBackup(NSData* _Nullable backupPartnerIDs, BindingsClient* _Nullable client, BindingsUserDiscovery* _Nullable udManager, id<BindingsLookupCallback> _Nullable lookupCB, id<BindingsRestoreContactsUpdater> _Nullable updatesCb);
-
-/**
- * ResumeBackup starts the backup processes back up with a new callback after it
-has been initialized.
-Call this function only when resuming a backup that has already been
-initialized or to replace the callback.
-To start the backup for the first time or to use a new password, use
-InitializeBackup.
- */
-FOUNDATION_EXPORT BindingsBackup* _Nullable BindingsResumeBackup(id<BindingsUpdateBackupFunc> _Nullable cb, BindingsClient* _Nullable c, NSError* _Nullable* _Nullable error);
-
-/**
- * SetTimeSource sets the network time to a custom source.
- */
-FOUNDATION_EXPORT void BindingsSetTimeSource(id<BindingsTimeSource> _Nullable timeNow);
-
-/**
- * StoreSecretWithMnemonic stores the secret tied with the mnemonic to storage.
-Unlike other storage operations, this does not use EKV, as that is
-intrinsically tied to client operations, which the user will not have while
-trying to recover their account. As such, we store the encrypted data
-directly, with a specified path. Path will be a valid filepath in which the
-recover file will be stored as ".recovery".
-
-As an example, given "home/user/xxmessenger/storagePath",
-the recovery file will be stored at
-"home/user/xxmessenger/storagePath/.recovery"
- */
-FOUNDATION_EXPORT NSString* _Nonnull BindingsStoreSecretWithMnemonic(NSData* _Nullable secret, NSString* _Nullable path, NSError* _Nullable* _Nullable error);
-
-/**
- * Unmarshals a marshaled contact object, returns an error if it fails
- */
-FOUNDATION_EXPORT BindingsContact* _Nullable BindingsUnmarshalContact(NSData* _Nullable b, NSError* _Nullable* _Nullable error);
-
-/**
- * Unmarshals a marshaled send report object, returns an error if it fails
- */
-FOUNDATION_EXPORT BindingsSendReport* _Nullable BindingsUnmarshalSendReport(NSData* _Nullable b, NSError* _Nullable* _Nullable error);
-
-/**
- * UpdateCommonErrors takes the passed in contents of a JSON file and updates the
-errToUserErr map with the contents of the json file. The JSON's expected format
-conform with the commented examples provides in errToUserErr above.
-NOTE that you should not pass in a file path, but a preloaded JSON file
- */
-FOUNDATION_EXPORT BOOL BindingsUpdateCommonErrors(NSString* _Nullable jsonFile, NSError* _Nullable* _Nullable error);
-
-// skipped function WrapAPIClient with unsupported parameter or return types
-
-
-// skipped function WrapUserDiscovery with unsupported parameter or return types
-
-
-@class BindingsAuthConfirmCallback;
-
-@class BindingsAuthRequestCallback;
-
-@class BindingsAuthResetNotificationCallback;
-
-@class BindingsClientError;
-
-@class BindingsEventCallbackFunctionObject;
-
-@class BindingsFileTransferReceiveFunc;
-
-@class BindingsFileTransferReceivedProgressFunc;
-
-@class BindingsFileTransferSentProgressFunc;
-
-@class BindingsGroupReceiveFunc;
-
-@class BindingsGroupRequestFunc;
-
-@class BindingsListener;
-
-@class BindingsLogWriter;
-
-@class BindingsLookupCallback;
-
-@class BindingsMessageDeliveryCallback;
-
-@class BindingsMultiLookupCallback;
-
-@class BindingsNetworkHealthCallback;
-
-@class BindingsPreimageNotification;
-
-@class BindingsRestoreContactsUpdater;
-
-@class BindingsRoundCompletionCallback;
-
-@class BindingsRoundEventCallback;
-
-@class BindingsSearchCallback;
-
-@class BindingsSingleSearchCallback;
-
-@class BindingsTimeSource;
-
-@class BindingsUpdateBackupFunc;
-
-/**
- * AuthConfirmCallback notifies the register whenever they receive an auth
-request confirmation
- */
-@interface BindingsAuthConfirmCallback : NSObject <goSeqRefInterface, BindingsAuthConfirmCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)partner;
-@end
-
-/**
- * AuthRequestCallback notifies the register whenever they receive an auth
-request
- */
-@interface BindingsAuthRequestCallback : NSObject <goSeqRefInterface, BindingsAuthRequestCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-/**
- * AuthRequestCallback notifies the register whenever they receive an auth
-request
- */
-@interface BindingsAuthResetNotificationCallback : NSObject <goSeqRefInterface, BindingsAuthResetNotificationCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)requestor;
-@end
-
-@interface BindingsClientError : NSObject <goSeqRefInterface, BindingsClientError> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)report:(NSString* _Nullable)source message:(NSString* _Nullable)message trace:(NSString* _Nullable)trace;
-@end
-
-/**
- * EventCallbackFunctionObject bindings interface which contains function
-that implements the EventCallbackFunction
- */
-@interface BindingsEventCallbackFunctionObject : NSObject <goSeqRefInterface, BindingsEventCallbackFunctionObject> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)reportEvent:(long)priority category:(NSString* _Nullable)category evtType:(NSString* _Nullable)evtType details:(NSString* _Nullable)details;
-@end
-
-/**
- * FileTransferReceiveFunc contains a function callback that notifies the
-receiver of an incoming file transfer. It is called on the reception of the
-initial file transfer message.
- */
-@interface BindingsFileTransferReceiveFunc : NSObject <goSeqRefInterface, BindingsFileTransferReceiveFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)receiveCallback:(NSData* _Nullable)tid fileName:(NSString* _Nullable)fileName fileType:(NSString* _Nullable)fileType sender:(NSData* _Nullable)sender size:(long)size preview:(NSData* _Nullable)preview;
-@end
-
-/**
- * FileTransferReceivedProgressFunc contains a function callback that tracks the
-progress of receiving a file. It is called when a file part is received, the
-transfer completes, or on error.
- */
-@interface BindingsFileTransferReceivedProgressFunc : NSObject <goSeqRefInterface, BindingsFileTransferReceivedProgressFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)receivedProgressCallback:(BOOL)completed received:(long)received total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-/**
- * FileTransferSentProgressFunc contains a function callback that tracks the
-progress of sending a file. It is called when a file part is sent, a file
-part arrives, the transfer completes, or on error.
- */
-@interface BindingsFileTransferSentProgressFunc : NSObject <goSeqRefInterface, BindingsFileTransferSentProgressFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)sentProgressCallback:(BOOL)completed sent:(long)sent arrived:(long)arrived total:(long)total t:(BindingsFilePartTracker* _Nullable)t err:(NSError* _Nullable)err;
-@end
-
-/**
- * GroupReceiveFunc contains a function callback that is called when a group
-message is received.
- */
-@interface BindingsGroupReceiveFunc : NSObject <goSeqRefInterface, BindingsGroupReceiveFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)groupReceiveCallback:(BindingsGroupMessageReceive* _Nullable)msg;
-@end
-
-/**
- * GroupRequestFunc contains a function callback that is called when a group
-request is received.
- */
-@interface BindingsGroupRequestFunc : NSObject <goSeqRefInterface, BindingsGroupRequestFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)groupRequestCallback:(BindingsGroup* _Nullable)g;
-@end
-
-/**
- * Listener provides a callback to hear a message
-An object implementing this interface can be called back when the client
-gets a message of the type that the registerer specified at registration
-time.
- */
-@interface BindingsListener : NSObject <goSeqRefInterface, BindingsListener> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * Hear is called to receive a message in the UI
- */
-- (void)hear:(BindingsMessage* _Nullable)message;
-/**
- * Returns a name, used for debugging
- */
-- (NSString* _Nonnull)name;
-@end
-
-@interface BindingsLogWriter : NSObject <goSeqRefInterface, BindingsLogWriter> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)log:(NSString* _Nullable)p0;
-@end
-
-/**
- * LookupCallback returns the result of a single lookup
- */
-@interface BindingsLookupCallback : NSObject <goSeqRefInterface, BindingsLookupCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-/**
- * MessageDeliveryCallback gets called on the determination if all events
-related to a message send were successful.
- */
-@interface BindingsMessageDeliveryCallback : NSObject <goSeqRefInterface, BindingsMessageDeliveryCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(NSData* _Nullable)msgID delivered:(BOOL)delivered timedOut:(BOOL)timedOut roundResults:(NSData* _Nullable)roundResults;
-@end
-
-/**
- * MultiLookupCallback returns the result of many parallel lookups
- */
-@interface BindingsMultiLookupCallback : NSObject <goSeqRefInterface, BindingsMultiLookupCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContactList* _Nullable)Succeeded failed:(BindingsIdList* _Nullable)failed errors:(NSString* _Nullable)errors;
-@end
-
-/**
- * A callback when which is used to receive notification if network health
-changes
- */
-@interface BindingsNetworkHealthCallback : NSObject <goSeqRefInterface, BindingsNetworkHealthCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BOOL)p0;
-@end
-
-@interface BindingsPreimageNotification : NSObject <goSeqRefInterface, BindingsPreimageNotification> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)notify:(NSData* _Nullable)identity deleted:(BOOL)deleted;
-@end
-
-/**
- * RestoreContactsUpdater interface provides a callback function
-for receiving update information from RestoreContactsFromBackup.
- */
-@interface BindingsRestoreContactsUpdater : NSObject <goSeqRefInterface, BindingsRestoreContactsUpdater> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-/**
- * RestoreContactsCallback is called to report the current # of contacts
-that have been found and how many have been restored
-against the total number that need to be
-processed. If an error occurs it it set on the err variable as a
-plain string.
- */
-- (void)restoreContactsCallback:(long)numFound numRestored:(long)numRestored total:(long)total err:(NSString* _Nullable)err;
-@end
-
-/**
- * RoundCompletionCallback is returned when the completion of a round is known.
- */
-@interface BindingsRoundCompletionCallback : NSObject <goSeqRefInterface, BindingsRoundCompletionCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(long)rid success:(BOOL)success timedOut:(BOOL)timedOut;
-@end
-
-/**
- * RoundEventCallback handles waiting on the exact state of a round on
-the cMix network.
- */
-@interface BindingsRoundEventCallback : NSObject <goSeqRefInterface, BindingsRoundEventCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)eventCallback:(long)rid state:(long)state timedOut:(BOOL)timedOut;
-@end
-
-/**
- * SearchCallback returns the result of a search
- */
-@interface BindingsSearchCallback : NSObject <goSeqRefInterface, BindingsSearchCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContactList* _Nullable)contacts error:(NSString* _Nullable)error;
-@end
-
-/**
- * SingleSearchCallback returns the result of a single search
- */
-@interface BindingsSingleSearchCallback : NSObject <goSeqRefInterface, BindingsSingleSearchCallback> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)callback:(BindingsContact* _Nullable)contact error:(NSString* _Nullable)error;
-@end
-
-@interface BindingsTimeSource : NSObject <goSeqRefInterface, BindingsTimeSource> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (int64_t)nowMs;
-@end
-
-/**
- * UpdateBackupFunc contains a function callback that returns new backups.
- */
-@interface BindingsUpdateBackupFunc : NSObject <goSeqRefInterface, BindingsUpdateBackupFunc> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (void)updateBackup:(NSData* _Nullable)encryptedBackup;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Headers/Universe.objc.h b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Headers/Universe.objc.h
deleted file mode 100644
index 019e7502d581983722a15bf30799e85cbc5dd766..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Headers/Universe.objc.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Objective-C API for talking to  Go package.
-//   gobind -lang=objc 
-//
-// File is generated by gobind. Do not edit.
-
-#ifndef __Universe_H__
-#define __Universe_H__
-
-@import Foundation;
-#include "ref.h"
-
-@protocol Universeerror;
-@class Universeerror;
-
-@protocol Universeerror <NSObject>
-- (NSString* _Nonnull)error;
-@end
-
-@class Universeerror;
-
-@interface Universeerror : NSError <goSeqRefInterface, Universeerror> {
-}
-@property(strong, readonly) _Nonnull id _ref;
-
-- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
-- (NSString* _Nonnull)error;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Headers/ref.h b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Headers/ref.h
deleted file mode 100644
index b8036a4d85c7387f3def61473a071b5d8c4c8208..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Headers/ref.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-#ifndef __GO_REF_HDR__
-#define __GO_REF_HDR__
-
-#include <Foundation/Foundation.h>
-
-// GoSeqRef is an object tagged with an integer for passing back and
-// forth across the language boundary. A GoSeqRef may represent either
-// an instance of a Go object, or an Objective-C object passed to Go.
-// The explicit allocation of a GoSeqRef is used to pin a Go object
-// when it is passed to Objective-C. The Go seq package maintains a
-// reference to the Go object in a map keyed by the refnum along with
-// a reference count. When the reference count reaches zero, the Go
-// seq package will clear the corresponding entry in the map.
-@interface GoSeqRef : NSObject {
-}
-@property(readonly) int32_t refnum;
-@property(strong) id obj; // NULL when representing a Go object.
-
-// new GoSeqRef object to proxy a Go object. The refnum must be
-// provided from Go side.
-- (instancetype)initWithRefnum:(int32_t)refnum obj:(id)obj;
-
-- (int32_t)incNum;
-
-@end
-
-@protocol goSeqRefInterface
--(GoSeqRef*) _ref;
-@end
-
-#endif
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Modules/module.modulemap b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Modules/module.modulemap
deleted file mode 100644
index 4316a5b24058edfc18ffb2dc7f7a982e8353441a..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Modules/module.modulemap
+++ /dev/null
@@ -1,8 +0,0 @@
-framework module "Bindings" {
-	header "ref.h"
-    header "Bindings.objc.h"
-    header "Universe.objc.h"
-    header "Bindings.h"
-
-    export *
-}
\ No newline at end of file
diff --git a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Resources/Info.plist b/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Resources/Info.plist
deleted file mode 100644
index 0d1a4b8ab9b1fc8e9357197398f73353470cb636..0000000000000000000000000000000000000000
--- a/XCFrameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/Current/Resources/Info.plist
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-    <plist version="1.0">
-      <dict>
-      </dict>
-    </plist>
diff --git a/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved b/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 2edfffa23ebd11df1784b06aa0e734df8c56269a..09979e25994dabe0b38332b1604fd2681b242cb6 100644
--- a/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/client-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -5,8 +5,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/firebase/abseil-cpp-SwiftPM.git",
       "state" : {
-        "revision" : "fffc3c2729be5747390ad02d5100291a0d9ad26a",
-        "version" : "0.20200225.4"
+        "revision" : "583de9bd60f66b40e78d08599cc92036c2e7e4e1",
+        "version" : "0.20220203.2"
       }
     },
     {
@@ -14,8 +14,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/Alamofire/Alamofire.git",
       "state" : {
-        "revision" : "f82c23a8a7ef8dc1a49a8bfc6a96883e79121864",
-        "version" : "5.5.0"
+        "revision" : "78424be314842833c04bc3bef5b72e85fff99204",
+        "version" : "5.6.4"
       }
     },
     {
@@ -23,8 +23,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/openid/AppAuth-iOS.git",
       "state" : {
-        "revision" : "01131d68346c8ae552961c768d583c715fbe1410",
-        "version" : "1.4.0"
+        "revision" : "3d36a58a2b736f7bc499453e996a704929b25080",
+        "version" : "1.6.0"
       }
     },
     {
@@ -41,8 +41,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/firebase/boringssl-SwiftPM.git",
       "state" : {
-        "revision" : "734a8247442fde37df4364c21f6a0085b6a36728",
-        "version" : "0.7.2"
+        "revision" : "dd3eda2b05a3f459fc3073695ad1b28659066eab",
+        "version" : "0.9.1"
       }
     },
     {
@@ -50,8 +50,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/ekazaev/ChatLayout",
       "state" : {
-        "revision" : "d0edb6f3ae716a26842467c540a6bee909b80360",
-        "version" : "1.1.14"
+        "revision" : "b7fe23d3ab6c174da6fad66383bfc359c212f465",
+        "version" : "1.2.7"
       }
     },
     {
@@ -59,8 +59,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://git.xx.network/elixxir/client-ios-db.git",
       "state" : {
-        "revision" : "f8e3e0088de8301d6c4816e12f0aca1d6f02a280",
-        "version" : "1.1.0"
+        "revision" : "16480177beaa6bc80a1c6be25812abd9bcb850ea",
+        "version" : "1.2.0"
       }
     },
     {
@@ -68,26 +68,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/pointfreeco/combine-schedulers",
       "state" : {
-        "revision" : "4cf088c29a20f52be0f2ca54992b492c54e0076b",
-        "version" : "0.5.3"
-      }
-    },
-    {
-      "identity" : "cwlcatchexception",
-      "kind" : "remoteSourceControl",
-      "location" : "https://github.com/mattgallagher/CwlCatchException.git",
-      "state" : {
-        "revision" : "35f9e770f54ce62dd8526470f14c6e137cef3eea",
-        "version" : "2.1.1"
-      }
-    },
-    {
-      "identity" : "cwlpreconditiontesting",
-      "kind" : "remoteSourceControl",
-      "location" : "https://github.com/mattgallagher/CwlPreconditionTesting.git",
-      "state" : {
-        "revision" : "fb7a26374e8570ff5c68142e5c83406d6abae0d8",
-        "version" : "2.0.2"
+        "revision" : "aa3e575929f2bcc5bad012bd2575eae716cbcdf7",
+        "version" : "0.8.0"
       }
     },
     {
@@ -95,17 +77,17 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/ra1028/DifferenceKit",
       "state" : {
-        "revision" : "62745d7780deef4a023a792a1f8f763ec7bf9705",
-        "version" : "1.2.0"
+        "revision" : "073b9671ce2b9b5b96398611427a1f929927e428",
+        "version" : "1.3.0"
       }
     },
     {
-      "identity" : "fileprovider",
+      "identity" : "elixxir-dapps-sdk-swift",
       "kind" : "remoteSourceControl",
-      "location" : "https://github.com/amosavian/FileProvider.git",
+      "location" : "https://git.xx.network/elixxir/elixxir-dapps-sdk-swift",
       "state" : {
-        "revision" : "abf68a62541a4193c8d106367ddb3648e8ab693f",
-        "version" : "0.26.0"
+        "revision" : "c433b384a1bd49627bb8e15bc1dc0ae4cb9a8ec1",
+        "version" : "1.0.0"
       }
     },
     {
@@ -113,8 +95,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/firebase/firebase-ios-sdk.git",
       "state" : {
-        "revision" : "08686f04881483d2bc098b2696e674c0ba135e47",
-        "version" : "8.10.0"
+        "revision" : "111d8d6ad1a1afd6c8e9561d26e55ab1e74fcb42",
+        "version" : "8.15.0"
       }
     },
     {
@@ -122,8 +104,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/google/google-api-objectivec-client-for-rest",
       "state" : {
-        "revision" : "22e0bb02729d60db396e8b90d8189313cd86ba53",
-        "version" : "1.6.0"
+        "revision" : "0078161fcc2900ca06213a192bdfa5ece8ee58f1",
+        "version" : "2.0.1"
       }
     },
     {
@@ -131,8 +113,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/google/GoogleAppMeasurement.git",
       "state" : {
-        "revision" : "9b2f6aca5b4685c45f9f5481f19bee8e7982c538",
-        "version" : "8.9.1"
+        "revision" : "ef819db8c58657a6ca367322e73f3b6322afe0a2",
+        "version" : "8.15.0"
       }
     },
     {
@@ -140,8 +122,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/google/GoogleDataTransport.git",
       "state" : {
-        "revision" : "15ccdfd25ac55b9239b82809531ff26605e7556e",
-        "version" : "9.1.2"
+        "revision" : "5056b15c5acbb90cd214fe4d6138bdf5a740e5a8",
+        "version" : "9.2.0"
       }
     },
     {
@@ -149,8 +131,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/google/GoogleSignIn-iOS",
       "state" : {
-        "revision" : "60ca2bfd218ccb194a746a79b41d9d50eb7e3af0",
-        "version" : "6.1.0"
+        "revision" : "9c9b36af86a4dd3da16048a36cf37351e63ccfe1",
+        "version" : "6.2.4"
       }
     },
     {
@@ -158,8 +140,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/google/GoogleUtilities.git",
       "state" : {
-        "revision" : "b3bb0c5551fb3f80ca939829639ab5b093edd14f",
-        "version" : "7.7.0"
+        "revision" : "68ea347bdb1a69e2d2ae2e25cd085b6ef92f64cb",
+        "version" : "7.9.0"
       }
     },
     {
@@ -167,17 +149,17 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/groue/GRDB.swift",
       "state" : {
-        "revision" : "23f4254ae36fa19aecd73047c0577a9f49850d1c",
-        "version" : "5.26.0"
+        "revision" : "0ac435744a4c67c4ec23a4a671c0d53ce1fee7c6",
+        "version" : "6.0.0"
       }
     },
     {
-      "identity" : "grpc-swiftpm",
+      "identity" : "grpc-ios",
       "kind" : "remoteSourceControl",
-      "location" : "https://github.com/firebase/grpc-SwiftPM.git",
+      "location" : "https://github.com/grpc/grpc-ios.git",
       "state" : {
-        "revision" : "fb405dd2c7901485f7e158b24e3a0a47e4efd8b5",
-        "version" : "1.28.4"
+        "revision" : "8440b914756e0d26d4f4d054a1c1581daedfc5b6",
+        "version" : "1.44.3-grpc"
       }
     },
     {
@@ -185,8 +167,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/google/gtm-session-fetcher.git",
       "state" : {
-        "revision" : "bc6a19702ac76ac4e488b68148710eb815f9bc56",
-        "version" : "1.7.0"
+        "revision" : "4e9bbf2808b8fee444e84a48f5f3c12641987d3e",
+        "version" : "1.7.2"
       }
     },
     {
@@ -194,14 +176,14 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/google/GTMAppAuth.git",
       "state" : {
-        "revision" : "40f4103fb52109032c05599a0c39ad43edbdf80a",
-        "version" : "1.2.2"
+        "revision" : "6dee0cde8a1b223737a5159e55e6b4ec16bbbdd9",
+        "version" : "1.3.1"
       }
     },
     {
       "identity" : "keychainaccess",
       "kind" : "remoteSourceControl",
-      "location" : "https://github.com/kishikawakatsumi/KeychainAccess",
+      "location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
       "state" : {
         "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
         "version" : "4.2.2"
@@ -216,15 +198,6 @@
         "version" : "1.22.2"
       }
     },
-    {
-      "identity" : "libssh2prebuild",
-      "kind" : "remoteSourceControl",
-      "location" : "https://github.com/DimaRU/Libssh2Prebuild.git",
-      "state" : {
-        "branch" : "1.10.0+OpenSSL_1_1_1o",
-        "revision" : "a91bcf205a6cbc84144f840c44145656abbd266a"
-      }
-    },
     {
       "identity" : "nanopb",
       "kind" : "remoteSourceControl",
@@ -234,31 +207,22 @@
         "version" : "2.30908.0"
       }
     },
-    {
-      "identity" : "nimble",
-      "kind" : "remoteSourceControl",
-      "location" : "https://github.com/Quick/Nimble",
-      "state" : {
-        "revision" : "c93f16c25af5770f0d3e6af27c9634640946b068",
-        "version" : "9.2.1"
-      }
-    },
     {
       "identity" : "promises",
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/google/promises.git",
       "state" : {
-        "revision" : "611337c330350c9c1823ad6d671e7f936af5ee13",
-        "version" : "2.0.0"
+        "revision" : "3e4e743631e86c8c70dbc6efdc7beaa6e90fd3bb",
+        "version" : "2.1.1"
       }
     },
     {
-      "identity" : "quick",
+      "identity" : "pulse",
       "kind" : "remoteSourceControl",
-      "location" : "https://github.com/Quick/Quick",
+      "location" : "https://github.com/kean/Pulse.git",
       "state" : {
-        "revision" : "8cce6acd38f965f5baa3167b939f86500314022b",
-        "version" : "3.1.2"
+        "revision" : "6b682c529d98a38e6fdffee2a8bfa40c8de30821",
+        "version" : "2.1.3"
       }
     },
     {
@@ -275,8 +239,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/darrarski/ScrollViewController",
       "state" : {
-        "revision" : "9a52bb056504bb4766ddb5ac518097dd48736303",
-        "version" : "1.2.0"
+        "revision" : "288999c7b9b0246aee0cfe3b7a066546038fd4b8",
+        "version" : "1.3.0"
       }
     },
     {
@@ -284,7 +248,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/darrarski/Shout.git",
       "state" : {
-        "revision" : "df5a662293f0ac15eeb4f2fd3ffd0c07b73d0de0"
+        "revision" : "cda197619ac395b9a2c3ddd1c6c8fad16114b830",
+        "version" : "0.5.5"
       }
     },
     {
@@ -292,8 +257,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/SnapKit/SnapKit",
       "state" : {
-        "revision" : "d458564516e5676af9c70b4f4b2a9178294f1bc6",
-        "version" : "5.0.1"
+        "revision" : "f222cbdf325885926566172f6f5f06af95473158",
+        "version" : "5.6.0"
       }
     },
     {
@@ -301,8 +266,17 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/pointfreeco/swift-case-paths",
       "state" : {
-        "revision" : "241301b67d8551c26d8f09bd2c0e52cc49f18007",
-        "version" : "0.8.0"
+        "revision" : "bb436421f57269fbcfe7360735985321585a86e5",
+        "version" : "0.10.1"
+      }
+    },
+    {
+      "identity" : "swift-clocks",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/pointfreeco/swift-clocks",
+      "state" : {
+        "revision" : "20b25ca0dd88ebfb9111ec937814ddc5a8880172",
+        "version" : "0.2.0"
       }
     },
     {
@@ -310,8 +284,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/apple/swift-collections",
       "state" : {
-        "revision" : "48254824bb4248676bf7ce56014ff57b142b77eb",
-        "version" : "1.0.2"
+        "revision" : "f504716c27d2e5d4144fa4794b12129301d17729",
+        "version" : "1.0.3"
       }
     },
     {
@@ -319,17 +293,17 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/pointfreeco/swift-composable-architecture.git",
       "state" : {
-        "revision" : "313dd217dcd1d0478118ec5d15225fd473c1564a",
-        "version" : "0.32.0"
+        "revision" : "1fcd53fc875bade47d850749ea53c324f74fd64d",
+        "version" : "0.45.0"
       }
     },
     {
       "identity" : "swift-custom-dump",
       "kind" : "remoteSourceControl",
-      "location" : "https://github.com/pointfreeco/swift-custom-dump",
+      "location" : "https://github.com/pointfreeco/swift-custom-dump.git",
       "state" : {
-        "revision" : "21ec1d717c07cea5a026979cb0471dd95c7087e7",
-        "version" : "0.5.0"
+        "revision" : "819d9d370cd721c9d87671e29d947279292e4541",
+        "version" : "0.6.0"
       }
     },
     {
@@ -337,8 +311,17 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/pointfreeco/swift-identified-collections",
       "state" : {
-        "revision" : "680bf440178a78a627b1c2c64c0855f6523ad5b9",
-        "version" : "0.3.2"
+        "revision" : "bfb0d43e75a15b6dfac770bf33479e8393884a36",
+        "version" : "0.4.1"
+      }
+    },
+    {
+      "identity" : "swift-log",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/apple/swift-log.git",
+      "state" : {
+        "revision" : "6fe203dc33195667ce1759bf0182975e4653ba1c",
+        "version" : "1.4.4"
       }
     },
     {
@@ -346,8 +329,8 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/apple/swift-protobuf",
       "state" : {
-        "revision" : "7e2c5f3cbbeea68e004915e3a8961e20bd11d824",
-        "version" : "1.18.0"
+        "revision" : "88c7d15e1242fdb6ecbafbc7926426a19be1e98a",
+        "version" : "1.20.2"
       }
     },
     {
@@ -355,35 +338,35 @@
       "kind" : "remoteSourceControl",
       "location" : "https://github.com/swiftcsv/SwiftCSV.git",
       "state" : {
-        "revision" : "048a1d3c2950b9c151ef9364b36f91baadc2c28c",
-        "version" : "0.8.0"
+        "revision" : "96fa14b92e88e0befdbc8bc31c7c2c9594a30060",
+        "version" : "0.8.1"
       }
     },
     {
-      "identity" : "swiftybeaver",
+      "identity" : "swiftydropbox",
       "kind" : "remoteSourceControl",
-      "location" : "https://github.com/SwiftyBeaver/SwiftyBeaver.git",
+      "location" : "https://github.com/dropbox/SwiftyDropbox.git",
       "state" : {
-        "revision" : "2c039501d6eeb4d4cd4aec4a8d884ad28862e044",
-        "version" : "1.9.5"
+        "revision" : "ff2da46242267837818b40ea1062a045840bc944",
+        "version" : "9.1.0"
       }
     },
     {
-      "identity" : "swiftydropbox",
+      "identity" : "xctest-dynamic-overlay",
       "kind" : "remoteSourceControl",
-      "location" : "https://github.com/dropbox/SwiftyDropbox.git",
+      "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay.git",
       "state" : {
-        "revision" : "7af87d903be1cf0af0e76e0394d992943055894e",
-        "version" : "8.2.1"
+        "revision" : "16e6409ee82e1b81390bdffbf217b9c08ab32784",
+        "version" : "0.5.0"
       }
     },
     {
-      "identity" : "xctest-dynamic-overlay",
+      "identity" : "xxm-cloud-providers",
       "kind" : "remoteSourceControl",
-      "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
+      "location" : "https://git.xx.network/elixxir/xxm-cloud-providers.git",
       "state" : {
-        "revision" : "ef8e14e7ce1c0c304c644c6ba365d06c468ded6b",
-        "version" : "0.3.3"
+        "revision" : "7e2fe50560f4c9c477d549e9c68a4c87d686d44c",
+        "version" : "1.0.2"
       }
     }
   ],
diff --git a/run_swiftgen.sh b/run_swiftgen.sh
index 63ba2cb744a4016ea02e72cff976714ccc44f1dc..adfb40051d165d4ae1211553b486d776c6e4bd49 100755
--- a/run_swiftgen.sh
+++ b/run_swiftgen.sh
@@ -1,2 +1,2 @@
 #!/bin/bash
-swiftgen config run --config Sources/Shared/swiftgen.yml
+swiftgen config run --config Sources/AppResources/swiftgen.yml