swarm: conversation view

- implement conversation view in swiftUI
- handle chat with multiple participants
- fix read status
- add contacts messages

Gitlab: #173
Change-Id: Icd0e0141f4a054b4c35c1a625a109add1c80d18c
diff --git a/Ring/Ring.xcodeproj/project.pbxproj b/Ring/Ring.xcodeproj/project.pbxproj
index abb7c57..bf4e1d7 100644
--- a/Ring/Ring.xcodeproj/project.pbxproj
+++ b/Ring/Ring.xcodeproj/project.pbxproj
@@ -206,6 +206,8 @@
 		26074FDD24F7FFF500374570 /* PreviewViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 26074FDC24F7FFF500374570 /* PreviewViewController.storyboard */; };
 		2607B1CE293192BF00F0107C /* WhirlyGlobe.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6452143D24B4AB44007203D5 /* WhirlyGlobe.framework */; };
 		2607B1CF293192BF00F0107C /* WhirlyGlobe.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6452143D24B4AB44007203D5 /* WhirlyGlobe.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+		260C73F129196B66005C513F /* MessageHistoryVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 260C73F029196B66005C513F /* MessageHistoryVM.swift */; };
+		260C73F329196C6C005C513F /* MessageStackVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 260C73F229196C6C005C513F /* MessageStackVM.swift */; };
 		263B7158246D9390007044C4 /* SmartListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263B7157246D9390007044C4 /* SmartListCell.swift */; };
 		263B715A246D9556007044C4 /* IncognitoSmartListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263B7159246D9556007044C4 /* IncognitoSmartListCell.swift */; };
 		263B715C246D96E5007044C4 /* IncognitoSmartListCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 263B715B246D96E5007044C4 /* IncognitoSmartListCell.xib */; };
@@ -213,6 +215,12 @@
 		2659F65C27483A27009107F1 /* DecodingAdapterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2659F65B27483A27009107F1 /* DecodingAdapterDelegate.swift */; };
 		265C436A286254C900B4BE73 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 265C4369286254C900B4BE73 /* Constants.swift */; };
 		265C436B286254C900B4BE73 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 265C4369286254C900B4BE73 /* Constants.swift */; };
+		265DFAF32929C01400834B97 /* MessageRowVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 265DFAF22929C01400834B97 /* MessageRowVM.swift */; };
+		265DFB03292EB94B00834B97 /* MessageContainerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 265DFB02292EB94B00834B97 /* MessageContainerModel.swift */; };
+		265DFB07292FBC4200834B97 /* TransferHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 265DFB06292FBC4200834B97 /* TransferHelper.swift */; };
+		265DFB09292FD25000834B97 /* DefaultTransferView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 265DFB08292FD25000834B97 /* DefaultTransferView.swift */; };
+		265DFB0B292FD41100834B97 /* PlayerSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 265DFB0A292FD41100834B97 /* PlayerSwiftUI.swift */; };
+		265DFB1129302A8700834B97 /* ContactMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 265DFB1029302A8700834B97 /* ContactMessageView.swift */; };
 		2662FC79246B1E1700FA7782 /* JamiSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2662FC78246B1E1700FA7782 /* JamiSearchView.swift */; };
 		2662FC7B246B216B00FA7782 /* JamiSearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2662FC7A246B216B00FA7782 /* JamiSearchViewModel.swift */; };
 		2662FC7D246B78E800FA7782 /* IncognitoSmartListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2662FC7C246B78E800FA7782 /* IncognitoSmartListViewController.swift */; };
@@ -222,6 +230,7 @@
 		2673D62E252624AB000C56CB /* ConferenceMenuItemsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2673D62D252624AB000C56CB /* ConferenceMenuItemsManager.swift */; };
 		2673D630252657B0000C56CB /* ConferenceLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2673D62F252657B0000C56CB /* ConferenceLayout.swift */; };
 		2673D63225267785000C56CB /* ConferenceLayoutHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2673D63125267785000C56CB /* ConferenceLayoutHelper.swift */; };
+		2679E42C29328352007E4639 /* ConcurrentDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2679E42B29328352007E4639 /* ConcurrentDictionary.swift */; };
 		267AD77E252B979F00047593 /* ConferenceParticipant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 267AD77D252B979F00047593 /* ConferenceParticipant.swift */; };
 		2682CC6D25110E36003E65E1 /* ContactPickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2682CC6C25110E36003E65E1 /* ContactPickerDelegate.swift */; };
 		268AA5C12472D42700B654A0 /* ConfirmationAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 268AA5C02472D42700B654A0 /* ConfirmationAlert.swift */; };
@@ -311,6 +320,9 @@
 		269DA08128D0D634007D51D6 /* libfmt.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 269DA05128D0D2ED007D51D6 /* libfmt.a */; };
 		269DA08228D0D68F007D51D6 /* libavutil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 04399AB61D1C341A00E99CD9 /* libavutil.a */; };
 		269DA08628D0DFE3007D51D6 /* libyaml-cpp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 04399AE31D1C341A00E99CD9 /* libyaml-cpp.a */; };
+		269DA09928E23D37007D51D6 /* MessageRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 269DA09828E23D37007D51D6 /* MessageRowView.swift */; };
+		269DA09D28E23F57007D51D6 /* MessagesListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 269DA09C28E23F57007D51D6 /* MessagesListView.swift */; };
+		269DA09F28E244F1007D51D6 /* MessagesListVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 269DA09E28E244F1007D51D6 /* MessagesListVM.swift */; };
 		26A5CE3B26BD00E700E147EA /* Array+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26A5CE3826BD00E700E147EA /* Array+Helper.swift */; };
 		26A88C07266FFFC800888EED /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26A88C06266FFFC800888EED /* NotificationService.swift */; };
 		26A88C0B266FFFC800888EED /* jamiNotificationExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 26A88C04266FFFC800888EED /* jamiNotificationExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@@ -336,6 +348,7 @@
 		26BB4389267017D400019CF6 /* Utils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 26BB4388267017D400019CF6 /* Utils.mm */; };
 		26BB438B2670191E00019CF6 /* AdapterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BB438A2670191E00019CF6 /* AdapterDelegate.swift */; };
 		26BB438D267019FC00019CF6 /* AdapterService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BB438C267019FC00019CF6 /* AdapterService.swift */; };
+		26BC15EE29302B00003FC8D1 /* ContactMessageVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BC15ED29302B00003FC8D1 /* ContactMessageVM.swift */; };
 		26C6A2602927EC4E007786B8 /* SwarmInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26C6A25F2927EC4E007786B8 /* SwarmInfo.swift */; };
 		26CE5AB82523968500DE6F90 /* ConferenceActionsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26CE5AB72523968500DE6F90 /* ConferenceActionsMenu.swift */; };
 		26D08AB12693474300E37574 /* InvitationViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 26D08AB02693474300E37574 /* InvitationViewController.storyboard */; };
@@ -345,6 +358,10 @@
 		26D08ABB2696293100E37574 /* RequestsAdapterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26D08ABA2696293100E37574 /* RequestsAdapterDelegate.swift */; };
 		26D08ABE2696296300E37574 /* RequestsAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 26D08ABD2696296300E37574 /* RequestsAdapter.mm */; };
 		26DA813224B641A5006C6E23 /* ProfilesAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 26DA813124B641A5006C6E23 /* ProfilesAdapter.mm */; };
+		26EF35E728E3401800D97E14 /* ReplyHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EF35E628E3401800D97E14 /* ReplyHistory.swift */; };
+		26EF35E928E3847100D97E14 /* MessageContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EF35E828E3847100D97E14 /* MessageContentView.swift */; };
+		26EF35EB28E38DA200D97E14 /* MessageContentVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EF35EA28E38DA200D97E14 /* MessageContentVM.swift */; };
+		26EF35EF29075A5300D97E14 /* MessageStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EF35EE29075A5300D97E14 /* MessageStackView.swift */; };
 		4430A66B236CBA7D00747177 /* ContactPickerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4430A66A236CBA7D00747177 /* ContactPickerSection.swift */; };
 		4430A66D236CBC5900747177 /* ContactPickerViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4430A66C236CBC5900747177 /* ContactPickerViewController.storyboard */; };
 		4430A66F236CBC6900747177 /* ContactPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4430A66E236CBC6900747177 /* ContactPickerViewController.swift */; };
@@ -800,6 +817,8 @@
 		26074FD824F7FF9500374570 /* PreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewViewController.swift; sourceTree = "<group>"; };
 		26074FDA24F7FFC100374570 /* PreviewContollerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewContollerModel.swift; sourceTree = "<group>"; };
 		26074FDC24F7FFF500374570 /* PreviewViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = PreviewViewController.storyboard; sourceTree = "<group>"; };
+		260C73F029196B66005C513F /* MessageHistoryVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageHistoryVM.swift; sourceTree = "<group>"; };
+		260C73F229196C6C005C513F /* MessageStackVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStackVM.swift; sourceTree = "<group>"; };
 		262AA981262C724700DC34AD /* libfmt.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libfmt.a; path = ../DEPS/arm64/lib/libfmt.a; sourceTree = "<group>"; };
 		26376721245315E600CDC51F /* Debug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Debug.entitlements; sourceTree = "<group>"; };
 		263B7157246D9390007044C4 /* SmartListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartListCell.swift; sourceTree = "<group>"; };
@@ -809,6 +828,12 @@
 		2659F65B27483A27009107F1 /* DecodingAdapterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodingAdapterDelegate.swift; sourceTree = "<group>"; };
 		265C4369286254C900B4BE73 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
 		265C436C2862561700B4BE73 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = Ring/Constants/Constants.swift; sourceTree = "<group>"; };
+		265DFAF22929C01400834B97 /* MessageRowVM.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRowVM.swift; sourceTree = "<group>"; };
+		265DFB02292EB94B00834B97 /* MessageContainerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContainerModel.swift; sourceTree = "<group>"; };
+		265DFB06292FBC4200834B97 /* TransferHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransferHelper.swift; sourceTree = "<group>"; };
+		265DFB08292FD25000834B97 /* DefaultTransferView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTransferView.swift; sourceTree = "<group>"; };
+		265DFB0A292FD41100834B97 /* PlayerSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerSwiftUI.swift; sourceTree = "<group>"; };
+		265DFB1029302A8700834B97 /* ContactMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactMessageView.swift; sourceTree = "<group>"; };
 		265E976226E8F610008801C0 /* NotificationService-debug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "NotificationService-debug.entitlements"; sourceTree = "<group>"; };
 		2662FC78246B1E1700FA7782 /* JamiSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JamiSearchView.swift; sourceTree = "<group>"; };
 		2662FC7A246B216B00FA7782 /* JamiSearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JamiSearchViewModel.swift; sourceTree = "<group>"; };
@@ -820,6 +845,7 @@
 		2673D62D252624AB000C56CB /* ConferenceMenuItemsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConferenceMenuItemsManager.swift; sourceTree = "<group>"; };
 		2673D62F252657B0000C56CB /* ConferenceLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConferenceLayout.swift; sourceTree = "<group>"; };
 		2673D63125267785000C56CB /* ConferenceLayoutHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConferenceLayoutHelper.swift; sourceTree = "<group>"; };
+		2679E42B29328352007E4639 /* ConcurrentDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConcurrentDictionary.swift; sourceTree = "<group>"; };
 		267AD77D252B979F00047593 /* ConferenceParticipant.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConferenceParticipant.swift; sourceTree = "<group>"; };
 		2682CC6C25110E36003E65E1 /* ContactPickerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPickerDelegate.swift; sourceTree = "<group>"; };
 		268AA5C02472D42700B654A0 /* ConfirmationAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationAlert.swift; sourceTree = "<group>"; };
@@ -830,6 +856,9 @@
 		269DA05128D0D2ED007D51D6 /* libfmt.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libfmt.a; path = ../fat/lib/libfmt.a; sourceTree = "<group>"; };
 		269DA05228D0D2EF007D51D6 /* libgit2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgit2.a; path = ../fat/lib/libgit2.a; sourceTree = "<group>"; };
 		269DA08428D0DA18007D51D6 /* libyaml-cpp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libyaml-cpp.a"; path = "../DEPS/x86_64/lib/libyaml-cpp.a"; sourceTree = "<group>"; };
+		269DA09828E23D37007D51D6 /* MessageRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRowView.swift; sourceTree = "<group>"; };
+		269DA09C28E23F57007D51D6 /* MessagesListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesListView.swift; sourceTree = "<group>"; };
+		269DA09E28E244F1007D51D6 /* MessagesListVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesListVM.swift; sourceTree = "<group>"; };
 		26A5CE3826BD00E700E147EA /* Array+Helper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Helper.swift"; sourceTree = "<group>"; };
 		26A88C04266FFFC800888EED /* jamiNotificationExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = jamiNotificationExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
 		26A88C06266FFFC800888EED /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
@@ -895,6 +924,7 @@
 		26BB4388267017D400019CF6 /* Utils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Utils.mm; sourceTree = "<group>"; };
 		26BB438A2670191E00019CF6 /* AdapterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdapterDelegate.swift; sourceTree = "<group>"; };
 		26BB438C267019FC00019CF6 /* AdapterService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdapterService.swift; sourceTree = "<group>"; };
+		26BC15ED29302B00003FC8D1 /* ContactMessageVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactMessageVM.swift; sourceTree = "<group>"; };
 		26C6A25F2927EC4E007786B8 /* SwarmInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwarmInfo.swift; sourceTree = "<group>"; };
 		26C92D2626F6AA1100DEEF75 /* NotificationPrefixHeader.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationPrefixHeader.pch; sourceTree = "<group>"; };
 		26CE5AB72523968500DE6F90 /* ConferenceActionsMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConferenceActionsMenu.swift; sourceTree = "<group>"; };
@@ -907,6 +937,10 @@
 		26D08ABD2696296300E37574 /* RequestsAdapter.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RequestsAdapter.mm; sourceTree = "<group>"; };
 		26DA813024B641A5006C6E23 /* ProfilesAdapter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProfilesAdapter.h; sourceTree = "<group>"; };
 		26DA813124B641A5006C6E23 /* ProfilesAdapter.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ProfilesAdapter.mm; sourceTree = "<group>"; };
+		26EF35E628E3401800D97E14 /* ReplyHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyHistory.swift; sourceTree = "<group>"; };
+		26EF35E828E3847100D97E14 /* MessageContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContentView.swift; sourceTree = "<group>"; };
+		26EF35EA28E38DA200D97E14 /* MessageContentVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContentVM.swift; sourceTree = "<group>"; };
+		26EF35EE29075A5300D97E14 /* MessageStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStackView.swift; sourceTree = "<group>"; };
 		26F5E11A26F91D98001BECCE /* NotificationService-release.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "NotificationService-release.entitlements"; sourceTree = "<group>"; };
 		4430A66A236CBA7D00747177 /* ContactPickerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPickerSection.swift; sourceTree = "<group>"; };
 		4430A66C236CBC5900747177 /* ContactPickerViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ContactPickerViewController.storyboard; sourceTree = "<group>"; };
@@ -1960,6 +1994,7 @@
 			isa = PBXGroup;
 			children = (
 				1D4EE840291EF98800ED2010 /* SwarmCreationModel */,
+				265DFAFF292EB91F00834B97 /* MessageSwiftUI */,
 				1A2D18F01F292D6100B2C785 /* Cells */,
 				1A2D18B21F2915C500B2C785 /* ConversationViewController.storyboard */,
 				1A2D18CC1F29182500B2C785 /* ConversationViewController.swift */,
@@ -2112,6 +2147,46 @@
 			path = Preview;
 			sourceTree = "<group>";
 		};
+		265DFAFF292EB91F00834B97 /* MessageSwiftUI */ = {
+			isa = PBXGroup;
+			children = (
+				265DFB05292F061400834B97 /* ViewModels */,
+				265DFB04292F060800834B97 /* Views */,
+			);
+			path = MessageSwiftUI;
+			sourceTree = "<group>";
+		};
+		265DFB04292F060800834B97 /* Views */ = {
+			isa = PBXGroup;
+			children = (
+				26EF35EE29075A5300D97E14 /* MessageStackView.swift */,
+				269DA09C28E23F57007D51D6 /* MessagesListView.swift */,
+				26EF35E628E3401800D97E14 /* ReplyHistory.swift */,
+				26EF35E828E3847100D97E14 /* MessageContentView.swift */,
+				269DA09828E23D37007D51D6 /* MessageRowView.swift */,
+				265DFB08292FD25000834B97 /* DefaultTransferView.swift */,
+				265DFB0A292FD41100834B97 /* PlayerSwiftUI.swift */,
+				265DFB1029302A8700834B97 /* ContactMessageView.swift */,
+			);
+			path = Views;
+			sourceTree = "<group>";
+		};
+		265DFB05292F061400834B97 /* ViewModels */ = {
+			isa = PBXGroup;
+			children = (
+				260C73F029196B66005C513F /* MessageHistoryVM.swift */,
+				269DA09E28E244F1007D51D6 /* MessagesListVM.swift */,
+				26EF35EA28E38DA200D97E14 /* MessageContentVM.swift */,
+				265DFAF22929C01400834B97 /* MessageRowVM.swift */,
+				260C73F229196C6C005C513F /* MessageStackVM.swift */,
+				265DFB02292EB94B00834B97 /* MessageContainerModel.swift */,
+				265DFB06292FBC4200834B97 /* TransferHelper.swift */,
+				26BC15ED29302B00003FC8D1 /* ContactMessageVM.swift */,
+				2679E42B29328352007E4639 /* ConcurrentDictionary.swift */,
+			);
+			path = ViewModels;
+			sourceTree = "<group>";
+		};
 		2662FC77246B1DD600FA7782 /* JamiSearchView */ = {
 			isa = PBXGroup;
 			children = (
@@ -2606,7 +2681,7 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "${PROJECT_DIR}/swiftgen/swiftgen.sh\n";
+			shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\n${PROJECT_DIR}/swiftgen/swiftgen.sh\n";
 		};
 		6236602D200914C9002598C1 /* ShellScript */ = {
 			isa = PBXShellScriptBuildPhase;
@@ -2637,10 +2712,12 @@
 				1A2D18AC1F29149D00B2C785 /* MeCoordinator.swift in Sources */,
 				1A2D18C51F29180700B2C785 /* ContactModel.swift in Sources */,
 				BB4C6E2629229131001C901A /* ColorExtension.swift in Sources */,
+				269DA09928E23D37007D51D6 /* MessageRowView.swift in Sources */,
 				648AF76D24ED7CA90004D727 /* UITextView+Helpers.swift in Sources */,
 				0E3697A8203243D3009A68CA /* BannedContactCell.swift in Sources */,
 				1A2D18F71F292D7200B2C785 /* MessageCellSent.swift in Sources */,
 				26069B6724C9FCE8002361A3 /* ObjCHandler.m in Sources */,
+				269DA09D28E23F57007D51D6 /* MessagesListView.swift in Sources */,
 				446FAF1B2373425E00519C4F /* SendFileViewController.swift in Sources */,
 				04399AAC1D1C304300E99CD9 /* AccountAdapter.mm in Sources */,
 				1D4EE84D29252AD000ED2010 /* SwarmCreationUIModel.swift in Sources */,
@@ -2668,6 +2745,7 @@
 				0E438A9A204F47E700402900 /* SettingsTableView.swift in Sources */,
 				0E49096A1FEAB156005CAA50 /* CallsAdapter.mm in Sources */,
 				1A2D18A61F27F7A400B2C785 /* UIViewController+Rx.swift in Sources */,
+				26BC15EE29302B00003FC8D1 /* ContactMessageVM.swift in Sources */,
 				64F8127724B8AA5200A7DE6A /* MessageCellLocationSharingReceived.swift in Sources */,
 				66E6381221764C2C005EA2B0 /* GrowingTextView.swift in Sources */,
 				267AD77E252B979F00047593 /* ConferenceParticipant.swift in Sources */,
@@ -2683,11 +2761,14 @@
 				1A20418B1F1EA58A00C08435 /* ViewModelBased.swift in Sources */,
 				1A5DC01E1F355DA70075E8EF /* ContactsAdapterDelegate.swift in Sources */,
 				0E983E6E1FC77C3E0082103E /* ConversationModel.swift in Sources */,
+				26EF35EB28E38DA200D97E14 /* MessageContentVM.swift in Sources */,
 				26B37342263C4B3700E2EE28 /* CustomSearchBar.swift in Sources */,
 				0E3BD4362044B39F00A50DDF /* ProfileHeaderView.swift in Sources */,
 				1A20418D1F1EABCC00C08435 /* StateableResponsive.swift in Sources */,
 				1A0C4EE51F1D67DF00550433 /* WalkthroughCoordinator.swift in Sources */,
+				26EF35E728E3401800D97E14 /* ReplyHistory.swift in Sources */,
 				1A2D18DD1F29192D00B2C785 /* MessableBubble.swift in Sources */,
+				260C73F329196C6C005C513F /* MessageStackVM.swift in Sources */,
 				0E96ED77225D06380016C07D /* GeneralSettingsViewController.swift in Sources */,
 				0E99F1A022417A0400CF8BD6 /* JamiURI.swift in Sources */,
 				1A5DC02E1F3565640075E8EF /* ConversationViewModel.swift in Sources */,
@@ -2701,11 +2782,13 @@
 				1A2D18C11F29180700B2C785 /* AccountConfigModel.swift in Sources */,
 				1A3D28A71F0EB9DB00B524EE /* Bool+String.swift in Sources */,
 				5516C29F1E71CEFF009D3D2D /* AccountModelHelper.swift in Sources */,
+				26EF35E928E3847100D97E14 /* MessageContentView.swift in Sources */,
 				1ABE07D31F0D8FE800D36361 /* Storyboards.swift in Sources */,
 				62AA15CA1FFD3D7E0064A063 /* VideoService.swift in Sources */,
 				26D08AB9269628F400E37574 /* RequestsService.swift in Sources */,
 				0EDE34C71F868E1200FFA15C /* EditProfileViewController.swift in Sources */,
 				1DE93596291B119900E426CF /* SwarmCreationViewModel.swift in Sources */,
+				2679E42C29328352007E4639 /* ConcurrentDictionary.swift in Sources */,
 				62A88D3B1F6C3ACC00F8AB18 /* PresenceService.swift in Sources */,
 				0E0FF1B51FC3947B003898C2 /* DBManager.swift in Sources */,
 				1A2D18E51F29197100B2C785 /* MessageAccessoryView.swift in Sources */,
@@ -2735,6 +2818,7 @@
 				62B60AF92048A40D001BEACF /* DataTransferAdapter.mm in Sources */,
 				1A5DC0371F35675E0075E8EF /* ContactRequestCell.swift in Sources */,
 				1A20417C1F1E56FF00C08435 /* WelcomeViewModel.swift in Sources */,
+				265DFB09292FD25000834B97 /* DefaultTransferView.swift in Sources */,
 				1A5DC03D1F35678D0075E8EF /* RequestItem.swift in Sources */,
 				BB1E8C7729159E1F005AE1D6 /* SwarmInfoViewController.swift in Sources */,
 				0E403F811F7D797300C80BC2 /* MessageCellGenerated.swift in Sources */,
@@ -2754,6 +2838,8 @@
 				62AF685E201A61FF003AA9E8 /* AudioService.swift in Sources */,
 				043999F71D1C2D9D00E99CD9 /* AppDelegate.swift in Sources */,
 				1A2041861F1EA19600C08435 /* CreateAccountViewController.swift in Sources */,
+				265DFB1129302A8700834B97 /* ContactMessageView.swift in Sources */,
+				265DFB03292EB94B00834B97 /* MessageContainerModel.swift in Sources */,
 				0EDCC8601F98150500B121D7 /* UIView+Rx.swift in Sources */,
 				0E36979D20322D75009A68CA /* BlockListViewModel.swift in Sources */,
 				1A2D18C21F29180700B2C785 /* AccountCredentialsModel.swift in Sources */,
@@ -2769,10 +2855,12 @@
 				2662FC7F246B790400FA7782 /* IncognitoSmartListViewModel.swift in Sources */,
 				446FAF1D2373427100519C4F /* SendFileViewModel.swift in Sources */,
 				26CE5AB82523968500DE6F90 /* ConferenceActionsMenu.swift in Sources */,
+				260C73F129196B66005C513F /* MessageHistoryVM.swift in Sources */,
 				62AD584A2056DADF00AF0701 /* MessageCellDataTransferReceived.swift in Sources */,
 				2673D62E252624AB000C56CB /* ConferenceMenuItemsManager.swift in Sources */,
 				0E0FF1AF1FC38CBC003898C2 /* ProfileDataHelper.swift in Sources */,
 				563AEC771EA664C0003A5641 /* RegistrationResponse.m in Sources */,
+				265DFB0B292FD41100834B97 /* PlayerSwiftUI.swift in Sources */,
 				564C445B1E8EA44E000F92B1 /* Durations.swift in Sources */,
 				26D08AB72693481C00E37574 /* InvitationViewModel.swift in Sources */,
 				26074FD924F7FF9500374570 /* PreviewViewController.swift in Sources */,
@@ -2784,6 +2872,7 @@
 				56C715FF1F0D36C600770048 /* ContactsAdapter.mm in Sources */,
 				1A5DC0281F3564AA0075E8EF /* MessageModel.swift in Sources */,
 				0E3697A1203235EA009A68CA /* BannedContactItem.swift in Sources */,
+				265DFAF32929C01400834B97 /* MessageRowVM.swift in Sources */,
 				56BBC9DF1EDDC9D300CDAF8B /* LookupNameResponse.m in Sources */,
 				645BDD7B24B7415A009129B1 /* MessageCellLocationSharingSent.swift in Sources */,
 				BB1E8C7A29159E3E005AE1D6 /* SwarmInfoViewModel.swift in Sources */,
@@ -2805,8 +2894,10 @@
 				62AF6862201A66CF003AA9E8 /* AudioAdapter.mm in Sources */,
 				627F11F120348FBF006560B5 /* AvatarView.swift in Sources */,
 				1A20417A1F1E547F00C08435 /* Stateable.swift in Sources */,
+				269DA09F28E244F1007D51D6 /* MessagesListVM.swift in Sources */,
 				26D08ABE2696296300E37574 /* RequestsAdapter.mm in Sources */,
 				1A2D18F51F292D7200B2C785 /* MessageCellReceived.swift in Sources */,
+				265DFB07292FBC4200834B97 /* TransferHelper.swift in Sources */,
 				26B37339263C439B00E2EE28 /* CustomSearchController.swift in Sources */,
 				56BBC9BC1ED7161200CDAF8B /* Date+Helpers.swift in Sources */,
 				564C44621E943DE6000F92B1 /* NameService.swift in Sources */,
@@ -2818,6 +2909,7 @@
 				2673D63225267785000C56CB /* ConferenceLayoutHelper.swift in Sources */,
 				1D4EE844291EF9E000ED2010 /* SwarmCreation.swift in Sources */,
 				0E5A668322F0B1F100AA6820 /* ProgressView.swift in Sources */,
+				26EF35EF29075A5300D97E14 /* MessageStackView.swift in Sources */,
 				0E96ED79225D06480016C07D /* GeneralSettingsViewModel.swift in Sources */,
 				1A2041801F1E903B00C08435 /* CreateProfileViewController.swift in Sources */,
 				BB1E8C7329159DFC005AE1D6 /* ProfilePicturesList.swift in Sources */,
diff --git a/Ring/Ring/Bridging/Ring-Bridging-Header.h b/Ring/Ring/Bridging/Ring-Bridging-Header.h
index 3d72034..2fa398e 100644
--- a/Ring/Ring/Bridging/Ring-Bridging-Header.h
+++ b/Ring/Ring/Bridging/Ring-Bridging-Header.h
@@ -49,3 +49,4 @@
 #import "DataTransferAdapter.h"
 #import "../../WhirlyGlobeMaply/ios/library/WhirlyGlobe-MaplyComponent/include/MaplyBridge.h"
 #import "ObjCHandler.h"
+#import "LinkPresentation/LinkPresentation.h"
diff --git a/Ring/Ring/Constants/Generated/Images.swift b/Ring/Ring/Constants/Generated/Images.swift
index fbff4f1..f8337b9 100644
--- a/Ring/Ring/Constants/Generated/Images.swift
+++ b/Ring/Ring/Constants/Generated/Images.swift
@@ -8,6 +8,9 @@
 #elseif os(tvOS) || os(watchOS)
   import UIKit
 #endif
+#if canImport(SwiftUI)
+  import SwiftUI
+#endif
 
 // Deprecated typealiases
 @available(*, deprecated, renamed: "ColorAsset.Color", message: "This typealias will be removed in SwiftGen 7.0")
@@ -117,6 +120,13 @@
   }
   #endif
 
+  #if canImport(SwiftUI)
+  @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
+  internal private(set) lazy var swiftUIColor: SwiftUI.Color = {
+    SwiftUI.Color(asset: self)
+  }()
+  #endif
+
   fileprivate init(name: String) {
     self.name = name
   }
@@ -136,6 +146,16 @@
   }
 }
 
+#if canImport(SwiftUI)
+@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
+internal extension SwiftUI.Color {
+  init(asset: ColorAsset) {
+    let bundle = BundleToken.bundle
+    self.init(asset.name, bundle: bundle)
+  }
+}
+#endif
+
 internal struct ImageAsset {
   internal fileprivate(set) var name: String
 
@@ -172,6 +192,13 @@
     return result
   }
   #endif
+
+  #if canImport(SwiftUI)
+  @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
+  internal var swiftUIImage: SwiftUI.Image {
+    SwiftUI.Image(asset: self)
+  }
+  #endif
 }
 
 internal extension ImageAsset.Image {
@@ -190,6 +217,26 @@
   }
 }
 
+#if canImport(SwiftUI)
+@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
+internal extension SwiftUI.Image {
+  init(asset: ImageAsset) {
+    let bundle = BundleToken.bundle
+    self.init(asset.name, bundle: bundle)
+  }
+
+  init(asset: ImageAsset, label: Text) {
+    let bundle = BundleToken.bundle
+    self.init(asset.name, bundle: bundle, label: label)
+  }
+
+  init(decorative asset: ImageAsset) {
+    let bundle = BundleToken.bundle
+    self.init(decorative: asset.name, bundle: bundle)
+  }
+}
+#endif
+
 // swiftlint:disable convenience_type
 private final class BundleToken {
   static let bundle: Bundle = {
diff --git a/Ring/Ring/Constants/Generated/Strings.swift b/Ring/Ring/Constants/Generated/Strings.swift
index d2347a8..37cf7ba 100644
--- a/Ring/Ring/Constants/Generated/Strings.swift
+++ b/Ring/Ring/Constants/Generated/Strings.swift
@@ -3,660 +3,661 @@
 
 import Foundation
 
-// swiftlint:disable superfluous_disable_command file_length implicit_return
+// swiftlint:disable superfluous_disable_command file_length implicit_return prefer_self_in_static_references
 
 // MARK: - Strings
 
 // swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
 // swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
 internal enum L10n {
-
   internal enum Account {
     /// Account Status
-    internal static let accountStatus = L10n.tr("Localizable", "account.accountStatus")
+    internal static let accountStatus = L10n.tr("Localizable", "account.accountStatus", fallback: "Account Status")
     /// Create a SIP Account
-    internal static let createSipAccount = L10n.tr("Localizable", "account.createSipAccount")
+    internal static let createSipAccount = L10n.tr("Localizable", "account.createSipAccount", fallback: "Create a SIP Account")
     /// Enable Account
-    internal static let enableAccount = L10n.tr("Localizable", "account.enableAccount")
+    internal static let enableAccount = L10n.tr("Localizable", "account.enableAccount", fallback: "Enable Account")
     /// Me
-    internal static let me = L10n.tr("Localizable", "account.me")
+    internal static let me = L10n.tr("Localizable", "account.me", fallback: "Me")
     /// account need to be migrated
-    internal static let needMigration = L10n.tr("Localizable", "account.needMigration")
+    internal static let needMigration = L10n.tr("Localizable", "account.needMigration", fallback: "account need to be migrated")
     /// Enter Password
-    internal static let passwordLabel = L10n.tr("Localizable", "account.passwordLabel")
+    internal static let passwordLabel = L10n.tr("Localizable", "account.passwordLabel", fallback: "Enter Password")
     /// Port
-    internal static let port = L10n.tr("Localizable", "account.port")
+    internal static let port = L10n.tr("Localizable", "account.port", fallback: "Port")
     /// Enter Port Number
-    internal static let portLabel = L10n.tr("Localizable", "account.portLabel")
+    internal static let portLabel = L10n.tr("Localizable", "account.portLabel", fallback: "Enter Port Number")
     /// Proxy
-    internal static let proxyServer = L10n.tr("Localizable", "account.proxyServer")
+    internal static let proxyServer = L10n.tr("Localizable", "account.proxyServer", fallback: "Proxy")
     /// Enter Address
-    internal static let serverLabel = L10n.tr("Localizable", "account.serverLabel")
+    internal static let serverLabel = L10n.tr("Localizable", "account.serverLabel", fallback: "Enter Address")
     /// Password
-    internal static let sipPassword = L10n.tr("Localizable", "account.sipPassword")
+    internal static let sipPassword = L10n.tr("Localizable", "account.sipPassword", fallback: "Password")
     /// SIP Server
-    internal static let sipServer = L10n.tr("Localizable", "account.sipServer")
+    internal static let sipServer = L10n.tr("Localizable", "account.sipServer", fallback: "SIP Server")
     /// User Name
-    internal static let sipUsername = L10n.tr("Localizable", "account.sipUsername")
+    internal static let sipUsername = L10n.tr("Localizable", "account.sipUsername", fallback: "User Name")
     /// Connecting
-    internal static let statusConnecting = L10n.tr("Localizable", "account.statusConnecting")
+    internal static let statusConnecting = L10n.tr("Localizable", "account.statusConnecting", fallback: "Connecting")
     /// Connection Error
-    internal static let statusConnectionerror = L10n.tr("Localizable", "account.statusConnectionerror")
+    internal static let statusConnectionerror = L10n.tr("Localizable", "account.statusConnectionerror", fallback: "Connection Error")
     /// Offline
-    internal static let statusOffline = L10n.tr("Localizable", "account.statusOffline")
+    internal static let statusOffline = L10n.tr("Localizable", "account.statusOffline", fallback: "Offline")
     /// Online
-    internal static let statusOnline = L10n.tr("Localizable", "account.statusOnline")
+    internal static let statusOnline = L10n.tr("Localizable", "account.statusOnline", fallback: "Online")
     /// Unknown
-    internal static let statusUnknown = L10n.tr("Localizable", "account.statusUnknown")
+    internal static let statusUnknown = L10n.tr("Localizable", "account.statusUnknown", fallback: "Unknown")
     /// Enter Username
-    internal static let usernameLabel = L10n.tr("Localizable", "account.usernameLabel")
+    internal static let usernameLabel = L10n.tr("Localizable", "account.usernameLabel", fallback: "Enter Username")
   }
-  internal enum SwarmCreation {
-        /// Create a Swarm
-    internal static let title = L10n.tr("Localizable", "swarmcreation.title")
-      /// Search for contact
-    internal static let searchBar = L10n.tr("Localizable", "swarmcreation.searchBar")
-      /// Create the Swarm
-    internal static let createTheSwarm = L10n.tr("Localizable", "swarmcreation.createTheSwarm")
-      /// Add a description
-    internal static let addADescription = L10n.tr("Localizable", "swarmcreation.addADescription")
-    }
-
   internal enum AccountPage {
     /// Auto register after expiration
-    internal static let autoRegistration = L10n.tr("Localizable", "accountPage.autoRegistration")
+    internal static let autoRegistration = L10n.tr("Localizable", "accountPage.autoRegistration", fallback: "Auto register after expiration")
     /// Blocked contacts
-    internal static let blockedContacts = L10n.tr("Localizable", "accountPage.blockedContacts")
+    internal static let blockedContacts = L10n.tr("Localizable", "accountPage.blockedContacts", fallback: "Blocked contacts")
     /// After enabling booth mode all your conversations will be removed.
-    internal static let boothModeAlertMessage = L10n.tr("Localizable", "accountPage.boothModeAlertMessage")
+    internal static let boothModeAlertMessage = L10n.tr("Localizable", "accountPage.boothModeAlertMessage", fallback: "After enabling booth mode all your conversations will be removed.")
     /// In booth mode conversation history not saved and jami functionality limited by making outgoing calls. When you enable booth mode all your conversations will be removed.
-    internal static let boothModeExplanation = L10n.tr("Localizable", "accountPage.boothModeExplanation")
+    internal static let boothModeExplanation = L10n.tr("Localizable", "accountPage.boothModeExplanation", fallback: "In booth mode conversation history not saved and jami functionality limited by making outgoing calls. When you enable booth mode all your conversations will be removed.")
     /// Change password
-    internal static let changePassword = L10n.tr("Localizable", "accountPage.changePassword")
+    internal static let changePassword = L10n.tr("Localizable", "accountPage.changePassword", fallback: "Change password")
     /// Password incorrect
-    internal static let changePasswordError = L10n.tr("Localizable", "accountPage.changePasswordError")
+    internal static let changePasswordError = L10n.tr("Localizable", "accountPage.changePasswordError", fallback: "Password incorrect")
     /// Contact me using "%s" on the Jami distributed communication platform: https://jami.net
     internal static func contactMeOnJamiContant(_ p1: UnsafePointer<CChar>) -> String {
-      return L10n.tr("Localizable", "accountPage.contactMeOnJamiContant", p1)
+      return L10n.tr("Localizable", "accountPage.contactMeOnJamiContant", p1, fallback: "Contact me using \"%s\" on the Jami distributed communication platform: https://jami.net")
     }
     /// Contact me on Jami!
-    internal static let contactMeOnJamiTitle = L10n.tr("Localizable", "accountPage.contactMeOnJamiTitle")
+    internal static let contactMeOnJamiTitle = L10n.tr("Localizable", "accountPage.contactMeOnJamiTitle", fallback: "Contact me on Jami!")
     /// Create password
-    internal static let createPassword = L10n.tr("Localizable", "accountPage.createPassword")
+    internal static let createPassword = L10n.tr("Localizable", "accountPage.createPassword", fallback: "Create password")
     /// Account Details
-    internal static let credentialsHeader = L10n.tr("Localizable", "accountPage.credentialsHeader")
+    internal static let credentialsHeader = L10n.tr("Localizable", "accountPage.credentialsHeader", fallback: "Account Details")
     /// Device revocation error
-    internal static let deviceRevocationError = L10n.tr("Localizable", "accountPage.deviceRevocationError")
+    internal static let deviceRevocationError = L10n.tr("Localizable", "accountPage.deviceRevocationError", fallback: "Device revocation error")
     /// Revoking...
-    internal static let deviceRevocationProgress = L10n.tr("Localizable", "accountPage.deviceRevocationProgress")
+    internal static let deviceRevocationProgress = L10n.tr("Localizable", "accountPage.deviceRevocationProgress", fallback: "Revoking...")
     /// Device was revoked
-    internal static let deviceRevocationSuccess = L10n.tr("Localizable", "accountPage.deviceRevocationSuccess")
+    internal static let deviceRevocationSuccess = L10n.tr("Localizable", "accountPage.deviceRevocationSuccess", fallback: "Device was revoked")
     /// Try again
-    internal static let deviceRevocationTryAgain = L10n.tr("Localizable", "accountPage.deviceRevocationTryAgain")
+    internal static let deviceRevocationTryAgain = L10n.tr("Localizable", "accountPage.deviceRevocationTryAgain", fallback: "Try again")
     /// Unknown device
-    internal static let deviceRevocationUnknownDevice = L10n.tr("Localizable", "accountPage.deviceRevocationUnknownDevice")
+    internal static let deviceRevocationUnknownDevice = L10n.tr("Localizable", "accountPage.deviceRevocationUnknownDevice", fallback: "Unknown device")
     /// Incorrect password
-    internal static let deviceRevocationWrongPassword = L10n.tr("Localizable", "accountPage.deviceRevocationWrongPassword")
+    internal static let deviceRevocationWrongPassword = L10n.tr("Localizable", "accountPage.deviceRevocationWrongPassword", fallback: "Incorrect password")
     /// Device revocation completed
-    internal static let deviceRevoked = L10n.tr("Localizable", "accountPage.deviceRevoked")
+    internal static let deviceRevoked = L10n.tr("Localizable", "accountPage.deviceRevoked", fallback: "Device revocation completed")
     /// Devices
-    internal static let devicesListHeader = L10n.tr("Localizable", "accountPage.devicesListHeader")
+    internal static let devicesListHeader = L10n.tr("Localizable", "accountPage.devicesListHeader", fallback: "Devices")
     /// Disable Booth Mode
-    internal static let disableBoothMode = L10n.tr("Localizable", "accountPage.disableBoothMode")
+    internal static let disableBoothMode = L10n.tr("Localizable", "accountPage.disableBoothMode", fallback: "Disable Booth Mode")
     /// Pleace provide your account password
-    internal static let disableBoothModeExplanation = L10n.tr("Localizable", "accountPage.disableBoothModeExplanation")
+    internal static let disableBoothModeExplanation = L10n.tr("Localizable", "accountPage.disableBoothModeExplanation", fallback: "Pleace provide your account password")
     /// Enable Booth Mode
-    internal static let enableBoothMode = L10n.tr("Localizable", "accountPage.enableBoothMode")
+    internal static let enableBoothMode = L10n.tr("Localizable", "accountPage.enableBoothMode", fallback: "Enable Booth Mode")
     /// Enable Notifications
-    internal static let enableNotifications = L10n.tr("Localizable", "accountPage.enableNotifications")
+    internal static let enableNotifications = L10n.tr("Localizable", "accountPage.enableNotifications", fallback: "Enable Notifications")
     /// Enable Proxy
-    internal static let enableProxy = L10n.tr("Localizable", "accountPage.enableProxy")
+    internal static let enableProxy = L10n.tr("Localizable", "accountPage.enableProxy", fallback: "Enable Proxy")
     /// Link another device
-    internal static let linkDeviceTitle = L10n.tr("Localizable", "accountPage.linkDeviceTitle")
+    internal static let linkDeviceTitle = L10n.tr("Localizable", "accountPage.linkDeviceTitle", fallback: "Link another device")
     /// Confirm new password
-    internal static let newPasswordConfirmPlaceholder = L10n.tr("Localizable", "accountPage.newPasswordConfirmPlaceholder")
+    internal static let newPasswordConfirmPlaceholder = L10n.tr("Localizable", "accountPage.newPasswordConfirmPlaceholder", fallback: "Confirm new password")
     /// Enter new password
-    internal static let newPasswordPlaceholder = L10n.tr("Localizable", "accountPage.newPasswordPlaceholder")
+    internal static let newPasswordPlaceholder = L10n.tr("Localizable", "accountPage.newPasswordPlaceholder", fallback: "Enter new password")
     /// To enable Booth mode you need to create account password first.
-    internal static let noBoothMode = L10n.tr("Localizable", "accountPage.noBoothMode")
+    internal static let noBoothMode = L10n.tr("Localizable", "accountPage.noBoothMode", fallback: "To enable Booth mode you need to create account password first.")
     /// Your device won't receive notifications when proxy is disabled
-    internal static let noProxyExplanationLabel = L10n.tr("Localizable", "accountPage.noProxyExplanationLabel")
+    internal static let noProxyExplanationLabel = L10n.tr("Localizable", "accountPage.noProxyExplanationLabel", fallback: "Your device won't receive notifications when proxy is disabled")
     /// Enter old password
-    internal static let oldPasswordPlaceholder = L10n.tr("Localizable", "accountPage.oldPasswordPlaceholder")
+    internal static let oldPasswordPlaceholder = L10n.tr("Localizable", "accountPage.oldPasswordPlaceholder", fallback: "Enter old password")
     /// Other
-    internal static let other = L10n.tr("Localizable", "accountPage.other")
+    internal static let other = L10n.tr("Localizable", "accountPage.other", fallback: "Other")
     /// Enter account password
-    internal static let passwordPlaceholder = L10n.tr("Localizable", "accountPage.passwordPlaceholder")
+    internal static let passwordPlaceholder = L10n.tr("Localizable", "accountPage.passwordPlaceholder", fallback: "Enter account password")
     /// Auto connect on local network
-    internal static let peerDiscovery = L10n.tr("Localizable", "accountPage.peerDiscovery")
+    internal static let peerDiscovery = L10n.tr("Localizable", "accountPage.peerDiscovery", fallback: "Auto connect on local network")
     /// Provide proxy address
-    internal static let proxyAddressAlert = L10n.tr("Localizable", "accountPage.proxyAddressAlert")
+    internal static let proxyAddressAlert = L10n.tr("Localizable", "accountPage.proxyAddressAlert", fallback: "Provide proxy address")
     /// In order to receive notifications, please enable proxy
-    internal static let proxyDisabledAlertBody = L10n.tr("Localizable", "accountPage.proxyDisabledAlertBody")
+    internal static let proxyDisabledAlertBody = L10n.tr("Localizable", "accountPage.proxyDisabledAlertBody", fallback: "In order to receive notifications, please enable proxy")
     /// Proxy Server Disabled
-    internal static let proxyDisabledAlertTitle = L10n.tr("Localizable", "accountPage.proxyDisabledAlertTitle")
+    internal static let proxyDisabledAlertTitle = L10n.tr("Localizable", "accountPage.proxyDisabledAlertTitle", fallback: "Proxy Server Disabled")
     /// Proxy address
-    internal static let proxyPaceholder = L10n.tr("Localizable", "accountPage.proxyPaceholder")
+    internal static let proxyPaceholder = L10n.tr("Localizable", "accountPage.proxyPaceholder", fallback: "Proxy address")
     /// Choosen username is not available
-    internal static let registerNameErrorMessage = L10n.tr("Localizable", "accountPage.registerNameErrorMessage")
+    internal static let registerNameErrorMessage = L10n.tr("Localizable", "accountPage.registerNameErrorMessage", fallback: "Choosen username is not available")
     /// Register a username
-    internal static let registerNameTitle = L10n.tr("Localizable", "accountPage.registerNameTitle")
+    internal static let registerNameTitle = L10n.tr("Localizable", "accountPage.registerNameTitle", fallback: "Register a username")
     /// Remove
-    internal static let removeAccountButton = L10n.tr("Localizable", "accountPage.removeAccountButton")
+    internal static let removeAccountButton = L10n.tr("Localizable", "accountPage.removeAccountButton", fallback: "Remove")
     /// By clicking "Remove" you will remove this account on this device! This action can not be undone. Also, your registered name can be lost.
-    internal static let removeAccountMessage = L10n.tr("Localizable", "accountPage.removeAccountMessage")
+    internal static let removeAccountMessage = L10n.tr("Localizable", "accountPage.removeAccountMessage", fallback: "By clicking \"Remove\" you will remove this account on this device! This action can not be undone. Also, your registered name can be lost.")
     /// Remove account
-    internal static let removeAccountTitle = L10n.tr("Localizable", "accountPage.removeAccountTitle")
+    internal static let removeAccountTitle = L10n.tr("Localizable", "accountPage.removeAccountTitle", fallback: "Remove account")
     /// Revoke
-    internal static let revokeDeviceButton = L10n.tr("Localizable", "accountPage.revokeDeviceButton")
+    internal static let revokeDeviceButton = L10n.tr("Localizable", "accountPage.revokeDeviceButton", fallback: "Revoke")
     /// Are you sure you want to revoke this device? This action could not be undone.
-    internal static let revokeDeviceMessage = L10n.tr("Localizable", "accountPage.revokeDeviceMessage")
+    internal static let revokeDeviceMessage = L10n.tr("Localizable", "accountPage.revokeDeviceMessage", fallback: "Are you sure you want to revoke this device? This action could not be undone.")
     /// Enter your passord
-    internal static let revokeDevicePlaceholder = L10n.tr("Localizable", "accountPage.revokeDevicePlaceholder")
+    internal static let revokeDevicePlaceholder = L10n.tr("Localizable", "accountPage.revokeDevicePlaceholder", fallback: "Enter your passord")
     /// Revoke device
-    internal static let revokeDeviceTitle = L10n.tr("Localizable", "accountPage.revokeDeviceTitle")
+    internal static let revokeDeviceTitle = L10n.tr("Localizable", "accountPage.revokeDeviceTitle", fallback: "Revoke device")
     /// Save
-    internal static let saveProxyAddress = L10n.tr("Localizable", "accountPage.saveProxyAddress")
+    internal static let saveProxyAddress = L10n.tr("Localizable", "accountPage.saveProxyAddress", fallback: "Save")
     /// Settings
-    internal static let settingsHeader = L10n.tr("Localizable", "accountPage.settingsHeader")
+    internal static let settingsHeader = L10n.tr("Localizable", "accountPage.settingsHeader", fallback: "Settings")
     /// Share Account Details
-    internal static let shareAccountDetails = L10n.tr("Localizable", "accountPage.shareAccountDetails")
+    internal static let shareAccountDetails = L10n.tr("Localizable", "accountPage.shareAccountDetails", fallback: "Share Account Details")
     /// UNBLOCK
-    internal static let unblockContact = L10n.tr("Localizable", "accountPage.unblockContact")
+    internal static let unblockContact = L10n.tr("Localizable", "accountPage.unblockContact", fallback: "UNBLOCK")
     /// Username
-    internal static let username = L10n.tr("Localizable", "accountPage.username")
+    internal static let username = L10n.tr("Localizable", "accountPage.username", fallback: "Username")
     /// username: not registered
-    internal static let usernameNotRegistered = L10n.tr("Localizable", "accountPage.usernameNotRegistered")
+    internal static let usernameNotRegistered = L10n.tr("Localizable", "accountPage.usernameNotRegistered", fallback: "username: not registered")
     /// Enter desired username
-    internal static let usernamePlaceholder = L10n.tr("Localizable", "accountPage.usernamePlaceholder")
+    internal static let usernamePlaceholder = L10n.tr("Localizable", "accountPage.usernamePlaceholder", fallback: "Enter desired username")
     /// Register
-    internal static let usernameRegisterAction = L10n.tr("Localizable", "accountPage.usernameRegisterAction")
+    internal static let usernameRegisterAction = L10n.tr("Localizable", "accountPage.usernameRegisterAction", fallback: "Register")
     /// Registering
-    internal static let usernameRegistering = L10n.tr("Localizable", "accountPage.usernameRegistering")
+    internal static let usernameRegistering = L10n.tr("Localizable", "accountPage.usernameRegistering", fallback: "Registering")
     /// Registration failed. Please check password.
-    internal static let usernameRegistrationFailed = L10n.tr("Localizable", "accountPage.usernameRegistrationFailed")
+    internal static let usernameRegistrationFailed = L10n.tr("Localizable", "accountPage.usernameRegistrationFailed", fallback: "Registration failed. Please check password.")
   }
-
   internal enum Actions {
     /// Back
-    internal static let backAction = L10n.tr("Localizable", "actions.backAction")
+    internal static let backAction = L10n.tr("Localizable", "actions.backAction", fallback: "Back")
     /// Cancel
-    internal static let cancelAction = L10n.tr("Localizable", "actions.cancelAction")
+    internal static let cancelAction = L10n.tr("Localizable", "actions.cancelAction", fallback: "Cancel")
     /// Clear
-    internal static let clearAction = L10n.tr("Localizable", "actions.clearAction")
+    internal static let clearAction = L10n.tr("Localizable", "actions.clearAction", fallback: "Clear")
     /// Delete
-    internal static let deleteAction = L10n.tr("Localizable", "actions.deleteAction")
+    internal static let deleteAction = L10n.tr("Localizable", "actions.deleteAction", fallback: "Delete")
     /// Done
-    internal static let doneAction = L10n.tr("Localizable", "actions.doneAction")
+    internal static let doneAction = L10n.tr("Localizable", "actions.doneAction", fallback: "Done")
     /// Go to Settings
-    internal static let goToSettings = L10n.tr("Localizable", "actions.goToSettings")
+    internal static let goToSettings = L10n.tr("Localizable", "actions.goToSettings", fallback: "Go to Settings")
     ///   Audio Call
-    internal static let startAudioCall = L10n.tr("Localizable", "actions.startAudioCall")
+    internal static let startAudioCall = L10n.tr("Localizable", "actions.startAudioCall", fallback: "  Audio Call")
     ///   Video Call
-    internal static let startVideoCall = L10n.tr("Localizable", "actions.startVideoCall")
+    internal static let startVideoCall = L10n.tr("Localizable", "actions.startVideoCall", fallback: "  Video Call")
     /// Stop sharing
-    internal static let stopLocationSharing = L10n.tr("Localizable", "actions.stopLocationSharing")
+    internal static let stopLocationSharing = L10n.tr("Localizable", "actions.stopLocationSharing", fallback: "Stop sharing")
   }
-
   internal enum Alerts {
     /// Account Added
-    internal static let accountAddedTitle = L10n.tr("Localizable", "alerts.accountAddedTitle")
+    internal static let accountAddedTitle = L10n.tr("Localizable", "alerts.accountAddedTitle", fallback: "Account Added")
     /// Account couldn't be found on the Jami network. Make sure it was exported on Jami from an existing device, and that provided credentials are correct.
-    internal static let accountCannotBeFoundMessage = L10n.tr("Localizable", "alerts.accountCannotBeFoundMessage")
+    internal static let accountCannotBeFoundMessage = L10n.tr("Localizable", "alerts.accountCannotBeFoundMessage", fallback: "Account couldn't be found on the Jami network. Make sure it was exported on Jami from an existing device, and that provided credentials are correct.")
     /// Can't find account
-    internal static let accountCannotBeFoundTitle = L10n.tr("Localizable", "alerts.accountCannotBeFoundTitle")
+    internal static let accountCannotBeFoundTitle = L10n.tr("Localizable", "alerts.accountCannotBeFoundTitle", fallback: "Can't find account")
     /// The account couldn't be created.
-    internal static let accountDefaultErrorMessage = L10n.tr("Localizable", "alerts.accountDefaultErrorMessage")
+    internal static let accountDefaultErrorMessage = L10n.tr("Localizable", "alerts.accountDefaultErrorMessage", fallback: "The account couldn't be created.")
     /// Unknown error
-    internal static let accountDefaultErrorTitle = L10n.tr("Localizable", "alerts.accountDefaultErrorTitle")
+    internal static let accountDefaultErrorTitle = L10n.tr("Localizable", "alerts.accountDefaultErrorTitle", fallback: "Unknown error")
     /// Linking account
-    internal static let accountLinkedTitle = L10n.tr("Localizable", "alerts.accountLinkedTitle")
+    internal static let accountLinkedTitle = L10n.tr("Localizable", "alerts.accountLinkedTitle", fallback: "Linking account")
     /// Could not add account because Jami couldn't connect to the distributed network. Check your device connectivity.
-    internal static let accountNoNetworkMessage = L10n.tr("Localizable", "alerts.accountNoNetworkMessage")
+    internal static let accountNoNetworkMessage = L10n.tr("Localizable", "alerts.accountNoNetworkMessage", fallback: "Could not add account because Jami couldn't connect to the distributed network. Check your device connectivity.")
     /// Can't connect to the network
-    internal static let accountNoNetworkTitle = L10n.tr("Localizable", "alerts.accountNoNetworkTitle")
+    internal static let accountNoNetworkTitle = L10n.tr("Localizable", "alerts.accountNoNetworkTitle", fallback: "Can't connect to the network")
     /// Already sharing location with this user
-    internal static let alreadylocationSharing = L10n.tr("Localizable", "alerts.alreadylocationSharing")
+    internal static let alreadylocationSharing = L10n.tr("Localizable", "alerts.alreadylocationSharing", fallback: "Already sharing location with this user")
     /// Are you sure you want to block this contact? The conversation history with this contact will also be deleted permanently.
-    internal static let confirmBlockContact = L10n.tr("Localizable", "alerts.confirmBlockContact")
+    internal static let confirmBlockContact = L10n.tr("Localizable", "alerts.confirmBlockContact", fallback: "Are you sure you want to block this contact? The conversation history with this contact will also be deleted permanently.")
     /// Block Contact
-    internal static let confirmBlockContactTitle = L10n.tr("Localizable", "alerts.confirmBlockContactTitle")
+    internal static let confirmBlockContactTitle = L10n.tr("Localizable", "alerts.confirmBlockContactTitle", fallback: "Block Contact")
     /// Are you sure you want to clear the conversation with this contact?
-    internal static let confirmClearConversation = L10n.tr("Localizable", "alerts.confirmClearConversation")
+    internal static let confirmClearConversation = L10n.tr("Localizable", "alerts.confirmClearConversation", fallback: "Are you sure you want to clear the conversation with this contact?")
     /// Clear Conversation
-    internal static let confirmClearConversationTitle = L10n.tr("Localizable", "alerts.confirmClearConversationTitle")
+    internal static let confirmClearConversationTitle = L10n.tr("Localizable", "alerts.confirmClearConversationTitle", fallback: "Clear Conversation")
     /// Are you sure you want to delete this conversation permanently?
-    internal static let confirmDeleteConversation = L10n.tr("Localizable", "alerts.confirmDeleteConversation")
+    internal static let confirmDeleteConversation = L10n.tr("Localizable", "alerts.confirmDeleteConversation", fallback: "Are you sure you want to delete this conversation permanently?")
     /// Are you sure you want to delete the conversation with this contact?
-    internal static let confirmDeleteConversationFromContact = L10n.tr("Localizable", "alerts.confirmDeleteConversationFromContact")
+    internal static let confirmDeleteConversationFromContact = L10n.tr("Localizable", "alerts.confirmDeleteConversationFromContact", fallback: "Are you sure you want to delete the conversation with this contact?")
     /// Delete Conversation
-    internal static let confirmDeleteConversationTitle = L10n.tr("Localizable", "alerts.confirmDeleteConversationTitle")
+    internal static let confirmDeleteConversationTitle = L10n.tr("Localizable", "alerts.confirmDeleteConversationTitle", fallback: "Delete Conversation")
     /// Please close application and try to open it again
-    internal static let dbFailedMessage = L10n.tr("Localizable", "alerts.dbFailedMessage")
+    internal static let dbFailedMessage = L10n.tr("Localizable", "alerts.dbFailedMessage", fallback: "Please close application and try to open it again")
     /// An error happned when launching Jami
-    internal static let dbFailedTitle = L10n.tr("Localizable", "alerts.dbFailedTitle")
+    internal static let dbFailedTitle = L10n.tr("Localizable", "alerts.dbFailedTitle", fallback: "An error happned when launching Jami")
     /// Cannot connect to provided account manager. Please check your credentials
-    internal static let errorWrongCredentials = L10n.tr("Localizable", "alerts.errorWrongCredentials")
+    internal static let errorWrongCredentials = L10n.tr("Localizable", "alerts.errorWrongCredentials", fallback: "Cannot connect to provided account manager. Please check your credentials")
     /// Incoming call from 
-    internal static let incomingCallAllertTitle = L10n.tr("Localizable", "alerts.incomingCallAllertTitle")
+    internal static let incomingCallAllertTitle = L10n.tr("Localizable", "alerts.incomingCallAllertTitle", fallback: "Incoming call from ")
     /// Ignore
-    internal static let incomingCallButtonIgnore = L10n.tr("Localizable", "alerts.incomingCallButtonIgnore")
+    internal static let incomingCallButtonIgnore = L10n.tr("Localizable", "alerts.incomingCallButtonIgnore", fallback: "Ignore")
     /// Turn on "Location Services" to allow "Jami" to determine your location.
-    internal static let locationServiceIsDisabled = L10n.tr("Localizable", "alerts.locationServiceIsDisabled")
+    internal static let locationServiceIsDisabled = L10n.tr("Localizable", "alerts.locationServiceIsDisabled", fallback: "Turn on \"Location Services\" to allow \"Jami\" to determine your location.")
     /// Share my location
-    internal static let locationSharing = L10n.tr("Localizable", "alerts.locationSharing")
+    internal static let locationSharing = L10n.tr("Localizable", "alerts.locationSharing", fallback: "Share my location")
     /// 10 min
-    internal static let locationSharingDuration10min = L10n.tr("Localizable", "alerts.locationSharingDuration10min")
+    internal static let locationSharingDuration10min = L10n.tr("Localizable", "alerts.locationSharingDuration10min", fallback: "10 min")
     /// 1 hour
-    internal static let locationSharingDuration1hour = L10n.tr("Localizable", "alerts.locationSharingDuration1hour")
+    internal static let locationSharingDuration1hour = L10n.tr("Localizable", "alerts.locationSharingDuration1hour", fallback: "1 hour")
     /// How long should the location sharing be?
-    internal static let locationSharingDurationTitle = L10n.tr("Localizable", "alerts.locationSharingDurationTitle")
+    internal static let locationSharingDurationTitle = L10n.tr("Localizable", "alerts.locationSharingDurationTitle", fallback: "How long should the location sharing be?")
     /// Map information
-    internal static let mapInformation = L10n.tr("Localizable", "alerts.mapInformation")
+    internal static let mapInformation = L10n.tr("Localizable", "alerts.mapInformation", fallback: "Map information")
     /// Access to photo library not granted
-    internal static let noLibraryPermissionsTitle = L10n.tr("Localizable", "alerts.noLibraryPermissionsTitle")
+    internal static let noLibraryPermissionsTitle = L10n.tr("Localizable", "alerts.noLibraryPermissionsTitle", fallback: "Access to photo library not granted")
     /// Access to location not granted
-    internal static let noLocationPermissionsTitle = L10n.tr("Localizable", "alerts.noLocationPermissionsTitle")
+    internal static let noLocationPermissionsTitle = L10n.tr("Localizable", "alerts.noLocationPermissionsTitle", fallback: "Access to location not granted")
     /// Media permission not granted
-    internal static let noMediaPermissionsTitle = L10n.tr("Localizable", "alerts.noMediaPermissionsTitle")
+    internal static let noMediaPermissionsTitle = L10n.tr("Localizable", "alerts.noMediaPermissionsTitle", fallback: "Media permission not granted")
     /// © OpenStreetMap contributors
-    internal static let openStreetMapCopyright = L10n.tr("Localizable", "alerts.openStreetMapCopyright")
+    internal static let openStreetMapCopyright = L10n.tr("Localizable", "alerts.openStreetMapCopyright", fallback: "© OpenStreetMap contributors")
     /// Learn more
-    internal static let openStreetMapCopyrightMoreInfo = L10n.tr("Localizable", "alerts.openStreetMapCopyrightMoreInfo")
+    internal static let openStreetMapCopyrightMoreInfo = L10n.tr("Localizable", "alerts.openStreetMapCopyrightMoreInfo", fallback: "Learn more")
     /// Cancel
-    internal static let profileCancelPhoto = L10n.tr("Localizable", "alerts.profileCancelPhoto")
+    internal static let profileCancelPhoto = L10n.tr("Localizable", "alerts.profileCancelPhoto", fallback: "Cancel")
     /// Take photo
-    internal static let profileTakePhoto = L10n.tr("Localizable", "alerts.profileTakePhoto")
+    internal static let profileTakePhoto = L10n.tr("Localizable", "alerts.profileTakePhoto", fallback: "Take photo")
     /// Upload photo
-    internal static let profileUploadPhoto = L10n.tr("Localizable", "alerts.profileUploadPhoto")
+    internal static let profileUploadPhoto = L10n.tr("Localizable", "alerts.profileUploadPhoto", fallback: "Upload photo")
     /// Record an audio message
-    internal static let recordAudioMessage = L10n.tr("Localizable", "alerts.recordAudioMessage")
+    internal static let recordAudioMessage = L10n.tr("Localizable", "alerts.recordAudioMessage", fallback: "Record an audio message")
     /// Record a video message
-    internal static let recordVideoMessage = L10n.tr("Localizable", "alerts.recordVideoMessage")
+    internal static let recordVideoMessage = L10n.tr("Localizable", "alerts.recordVideoMessage", fallback: "Record a video message")
     /// Upload file
-    internal static let uploadFile = L10n.tr("Localizable", "alerts.uploadFile")
+    internal static let uploadFile = L10n.tr("Localizable", "alerts.uploadFile", fallback: "Upload file")
     /// Upload photo or movie
-    internal static let uploadPhoto = L10n.tr("Localizable", "alerts.uploadPhoto")
+    internal static let uploadPhoto = L10n.tr("Localizable", "alerts.uploadPhoto", fallback: "Upload photo or movie")
   }
-
   internal enum BlockListPage {
     /// No blocked contacts
-    internal static let noBlockedContacts = L10n.tr("Localizable", "blockListPage.noBlockedContacts")
+    internal static let noBlockedContacts = L10n.tr("Localizable", "blockListPage.noBlockedContacts", fallback: "No blocked contacts")
   }
-
   internal enum Calls {
     /// Call finished
-    internal static let callFinished = L10n.tr("Localizable", "calls.callFinished")
+    internal static let callFinished = L10n.tr("Localizable", "calls.callFinished", fallback: "Call finished")
     /// Call
-    internal static let callItemTitle = L10n.tr("Localizable", "calls.callItemTitle")
+    internal static let callItemTitle = L10n.tr("Localizable", "calls.callItemTitle", fallback: "Call")
     /// Connecting…
-    internal static let connecting = L10n.tr("Localizable", "calls.connecting")
+    internal static let connecting = L10n.tr("Localizable", "calls.connecting", fallback: "Connecting…")
     /// Call with 
-    internal static let currentCallWith = L10n.tr("Localizable", "calls.currentCallWith")
+    internal static let currentCallWith = L10n.tr("Localizable", "calls.currentCallWith", fallback: "Call with ")
     /// hang up
-    internal static let haghUp = L10n.tr("Localizable", "calls.haghUp")
+    internal static let haghUp = L10n.tr("Localizable", "calls.haghUp", fallback: "hang up")
     /// wants to talk to you
-    internal static let incomingCallInfo = L10n.tr("Localizable", "calls.incomingCallInfo")
+    internal static let incomingCallInfo = L10n.tr("Localizable", "calls.incomingCallInfo", fallback: "wants to talk to you")
     /// lower hand
-    internal static let lowerHand = L10n.tr("Localizable", "calls.lowerHand")
+    internal static let lowerHand = L10n.tr("Localizable", "calls.lowerHand", fallback: "lower hand")
     /// maximize
-    internal static let maximize = L10n.tr("Localizable", "calls.maximize")
+    internal static let maximize = L10n.tr("Localizable", "calls.maximize", fallback: "maximize")
     /// minimize
-    internal static let minimize = L10n.tr("Localizable", "calls.minimize")
+    internal static let minimize = L10n.tr("Localizable", "calls.minimize", fallback: "minimize")
     /// mute audio
-    internal static let muteAudio = L10n.tr("Localizable", "calls.muteAudio")
+    internal static let muteAudio = L10n.tr("Localizable", "calls.muteAudio", fallback: "mute audio")
     /// unset moderator
-    internal static let removeModerator = L10n.tr("Localizable", "calls.removeModerator")
+    internal static let removeModerator = L10n.tr("Localizable", "calls.removeModerator", fallback: "unset moderator")
     /// Ringing…
-    internal static let ringing = L10n.tr("Localizable", "calls.ringing")
+    internal static let ringing = L10n.tr("Localizable", "calls.ringing", fallback: "Ringing…")
     /// Searching…
-    internal static let searching = L10n.tr("Localizable", "calls.searching")
+    internal static let searching = L10n.tr("Localizable", "calls.searching", fallback: "Searching…")
     /// set moderator
-    internal static let setModerator = L10n.tr("Localizable", "calls.setModerator")
+    internal static let setModerator = L10n.tr("Localizable", "calls.setModerator", fallback: "set moderator")
     /// Unknown
-    internal static let unknown = L10n.tr("Localizable", "calls.unknown")
+    internal static let unknown = L10n.tr("Localizable", "calls.unknown", fallback: "Unknown")
     /// unmute audio
-    internal static let unmuteAudio = L10n.tr("Localizable", "calls.unmuteAudio")
+    internal static let unmuteAudio = L10n.tr("Localizable", "calls.unmuteAudio", fallback: "unmute audio")
   }
-
   internal enum ContactPage {
     /// Block Contact
-    internal static let blockContact = L10n.tr("Localizable", "contactPage.blockContact")
+    internal static let blockContact = L10n.tr("Localizable", "contactPage.blockContact", fallback: "Block Contact")
     /// Clear Chat
-    internal static let clearConversation = L10n.tr("Localizable", "contactPage.clearConversation")
+    internal static let clearConversation = L10n.tr("Localizable", "contactPage.clearConversation", fallback: "Clear Chat")
     /// Remove Conversation
-    internal static let removeConversation = L10n.tr("Localizable", "contactPage.removeConversation")
+    internal static let removeConversation = L10n.tr("Localizable", "contactPage.removeConversation", fallback: "Remove Conversation")
     /// Send Message
-    internal static let sendMessage = L10n.tr("Localizable", "contactPage.sendMessage")
+    internal static let sendMessage = L10n.tr("Localizable", "contactPage.sendMessage", fallback: "Send Message")
     /// Start Audio Call
-    internal static let startAudioCall = L10n.tr("Localizable", "contactPage.startAudioCall")
+    internal static let startAudioCall = L10n.tr("Localizable", "contactPage.startAudioCall", fallback: "Start Audio Call")
     /// Start Video Call
-    internal static let startVideoCall = L10n.tr("Localizable", "contactPage.startVideoCall")
+    internal static let startVideoCall = L10n.tr("Localizable", "contactPage.startVideoCall", fallback: "Start Video Call")
   }
-
   internal enum Conversation {
     /// Failed to save image to galery
-    internal static let errorSavingImage = L10n.tr("Localizable", "conversation.errorSavingImage")
+    internal static let errorSavingImage = L10n.tr("Localizable", "conversation.errorSavingImage", fallback: "Failed to save image to galery")
     /// You are currently receiving a live location from 
-    internal static let explanationReceivingLocationFrom = L10n.tr("Localizable", "conversation.explanationReceivingLocationFrom")
+    internal static let explanationReceivingLocationFrom = L10n.tr("Localizable", "conversation.explanationReceivingLocationFrom", fallback: "You are currently receiving a live location from ")
     /// You are currently sharing your location with 
-    internal static let explanationSendingLocationTo = L10n.tr("Localizable", "conversation.explanationSendingLocationTo")
+    internal static let explanationSendingLocationTo = L10n.tr("Localizable", "conversation.explanationSendingLocationTo", fallback: "You are currently sharing your location with ")
     /// Write message to 
-    internal static let messagePlaceholder = L10n.tr("Localizable", "conversation.messagePlaceholder")
+    internal static let messagePlaceholder = L10n.tr("Localizable", "conversation.messagePlaceholder", fallback: "Write message to ")
     /// %s is not in your contact list.
     internal static func notContact(_ p1: UnsafePointer<CChar>) -> String {
-      return L10n.tr("Localizable", "conversation.notContact", p1)
+      return L10n.tr("Localizable", "conversation.notContact", p1, fallback: "%s is not in your contact list.")
     }
     /// %s sent you a request for a conversation.
     internal static func receivedRequest(_ p1: UnsafePointer<CChar>) -> String {
-      return L10n.tr("Localizable", "conversation.receivedRequest", p1)
+      return L10n.tr("Localizable", "conversation.receivedRequest", p1, fallback: "%s sent you a request for a conversation.")
     }
     /// Hello,
     /// Would you like to join the conversation?
-    internal static let requestMessage = L10n.tr("Localizable", "conversation.requestMessage")
+    internal static let requestMessage = L10n.tr("Localizable", "conversation.requestMessage", fallback: "Hello,\nWould you like to join the conversation?")
     /// Send him/her a contact request to be able to exchange together
-    internal static let sendRequest = L10n.tr("Localizable", "conversation.sendRequest")
+    internal static let sendRequest = L10n.tr("Localizable", "conversation.sendRequest", fallback: "Send him/her a contact request to be able to exchange together")
     /// Send Contact Request
-    internal static let sendRequestTitle = L10n.tr("Localizable", "conversation.sendRequestTitle")
+    internal static let sendRequestTitle = L10n.tr("Localizable", "conversation.sendRequestTitle", fallback: "Send Contact Request")
     /// We are waiting for %s connects to synchronize the conversation.
     internal static func synchronizationMessage(_ p1: UnsafePointer<CChar>) -> String {
-      return L10n.tr("Localizable", "conversation.synchronizationMessage", p1)
+      return L10n.tr("Localizable", "conversation.synchronizationMessage", p1, fallback: "We are waiting for %s connects to synchronize the conversation.")
     }
     /// You have accepted the conversation request.
-    internal static let synchronizationTitle = L10n.tr("Localizable", "conversation.synchronizationTitle")
+    internal static let synchronizationTitle = L10n.tr("Localizable", "conversation.synchronizationTitle", fallback: "You have accepted the conversation request.")
   }
-
   internal enum CreateAccount {
     /// Encrypt my account
-    internal static let chooseAPassword = L10n.tr("Localizable", "createAccount.ChooseAPassword")
+    internal static let chooseAPassword = L10n.tr("Localizable", "createAccount.ChooseAPassword", fallback: "Encrypt my account")
     /// Choose strong password you will remember to protect your Jami account.
-    internal static let chooseStrongPassword = L10n.tr("Localizable", "createAccount.chooseStrongPassword")
+    internal static let chooseStrongPassword = L10n.tr("Localizable", "createAccount.chooseStrongPassword", fallback: "Choose strong password you will remember to protect your Jami account.")
     /// Create your account
-    internal static let createAccountFormTitle = L10n.tr("Localizable", "createAccount.createAccountFormTitle")
+    internal static let createAccountFormTitle = L10n.tr("Localizable", "createAccount.createAccountFormTitle", fallback: "Create your account")
     /// Notifications
-    internal static let enableNotifications = L10n.tr("Localizable", "createAccount.EnableNotifications")
+    internal static let enableNotifications = L10n.tr("Localizable", "createAccount.EnableNotifications", fallback: "Notifications")
     /// Username
-    internal static let enterNewUsernamePlaceholder = L10n.tr("Localizable", "createAccount.enterNewUsernamePlaceholder")
+    internal static let enterNewUsernamePlaceholder = L10n.tr("Localizable", "createAccount.enterNewUsernamePlaceholder", fallback: "Username")
     /// invalid username
-    internal static let invalidUsername = L10n.tr("Localizable", "createAccount.invalidUsername")
+    internal static let invalidUsername = L10n.tr("Localizable", "createAccount.invalidUsername", fallback: "invalid username")
     /// Loading
-    internal static let loading = L10n.tr("Localizable", "createAccount.loading")
+    internal static let loading = L10n.tr("Localizable", "createAccount.loading", fallback: "Loading")
     /// looking for availability…
-    internal static let lookingForUsernameAvailability = L10n.tr("Localizable", "createAccount.lookingForUsernameAvailability")
+    internal static let lookingForUsernameAvailability = L10n.tr("Localizable", "createAccount.lookingForUsernameAvailability", fallback: "looking for availability…")
     /// Password
-    internal static let newPasswordPlaceholder = L10n.tr("Localizable", "createAccount.newPasswordPlaceholder")
+    internal static let newPasswordPlaceholder = L10n.tr("Localizable", "createAccount.newPasswordPlaceholder", fallback: "Password")
     /// 6 characters minimum
-    internal static let passwordCharactersNumberError = L10n.tr("Localizable", "createAccount.passwordCharactersNumberError")
+    internal static let passwordCharactersNumberError = L10n.tr("Localizable", "createAccount.passwordCharactersNumberError", fallback: "6 characters minimum")
     /// Choose a password to encrypt your local account. Don’t forget it or you will not be able to recover your account
-    internal static let passwordInformation = L10n.tr("Localizable", "createAccount.PasswordInformation")
+    internal static let passwordInformation = L10n.tr("Localizable", "createAccount.PasswordInformation", fallback: "Choose a password to encrypt your local account. Don’t forget it or you will not be able to recover your account")
     /// passwords do not match
-    internal static let passwordNotMatchingError = L10n.tr("Localizable", "createAccount.passwordNotMatchingError")
+    internal static let passwordNotMatchingError = L10n.tr("Localizable", "createAccount.passwordNotMatchingError", fallback: "passwords do not match")
     /// (Recommended)
-    internal static let recommended = L10n.tr("Localizable", "createAccount.Recommended")
+    internal static let recommended = L10n.tr("Localizable", "createAccount.Recommended", fallback: "(Recommended)")
     /// Register a username
-    internal static let registerAUsername = L10n.tr("Localizable", "createAccount.RegisterAUsername")
+    internal static let registerAUsername = L10n.tr("Localizable", "createAccount.RegisterAUsername", fallback: "Register a username")
     /// Confirm password
-    internal static let repeatPasswordPlaceholder = L10n.tr("Localizable", "createAccount.repeatPasswordPlaceholder")
+    internal static let repeatPasswordPlaceholder = L10n.tr("Localizable", "createAccount.repeatPasswordPlaceholder", fallback: "Confirm password")
     /// Username registration in progress... It could take a few moments.
-    internal static let timeoutMessage = L10n.tr("Localizable", "createAccount.timeoutMessage")
+    internal static let timeoutMessage = L10n.tr("Localizable", "createAccount.timeoutMessage", fallback: "Username registration in progress... It could take a few moments.")
     /// Account Created
-    internal static let timeoutTitle = L10n.tr("Localizable", "createAccount.timeoutTitle")
+    internal static let timeoutTitle = L10n.tr("Localizable", "createAccount.timeoutTitle", fallback: "Account Created")
     /// username already taken
-    internal static let usernameAlreadyTaken = L10n.tr("Localizable", "createAccount.usernameAlreadyTaken")
+    internal static let usernameAlreadyTaken = L10n.tr("Localizable", "createAccount.usernameAlreadyTaken", fallback: "username already taken")
     /// Account was created but username was not registered
-    internal static let usernameNotRegisteredMessage = L10n.tr("Localizable", "createAccount.UsernameNotRegisteredMessage")
+    internal static let usernameNotRegisteredMessage = L10n.tr("Localizable", "createAccount.UsernameNotRegisteredMessage", fallback: "Account was created but username was not registered")
     /// Network error
-    internal static let usernameNotRegisteredTitle = L10n.tr("Localizable", "createAccount.UsernameNotRegisteredTitle")
+    internal static let usernameNotRegisteredTitle = L10n.tr("Localizable", "createAccount.UsernameNotRegisteredTitle", fallback: "Network error")
     /// username is available
-    internal static let usernameValid = L10n.tr("Localizable", "createAccount.usernameValid")
+    internal static let usernameValid = L10n.tr("Localizable", "createAccount.usernameValid", fallback: "username is available")
     /// Adding account
-    internal static let waitCreateAccountTitle = L10n.tr("Localizable", "createAccount.waitCreateAccountTitle")
+    internal static let waitCreateAccountTitle = L10n.tr("Localizable", "createAccount.waitCreateAccountTitle", fallback: "Adding account")
   }
-
   internal enum CreateProfile {
     /// Create your avatar
-    internal static let createYourAvatar = L10n.tr("Localizable", "createProfile.createYourAvatar")
+    internal static let createYourAvatar = L10n.tr("Localizable", "createProfile.createYourAvatar", fallback: "Create your avatar")
     /// Enter a display name
-    internal static let enterNameLabel = L10n.tr("Localizable", "createProfile.enterNameLabel")
+    internal static let enterNameLabel = L10n.tr("Localizable", "createProfile.enterNameLabel", fallback: "Enter a display name")
     /// Enter name
-    internal static let enterNamePlaceholder = L10n.tr("Localizable", "createProfile.enterNamePlaceholder")
+    internal static let enterNamePlaceholder = L10n.tr("Localizable", "createProfile.enterNamePlaceholder", fallback: "Enter name")
     /// Next
-    internal static let profileCreated = L10n.tr("Localizable", "createProfile.profileCreated")
+    internal static let profileCreated = L10n.tr("Localizable", "createProfile.profileCreated", fallback: "Next")
     /// Skip
-    internal static let skipCreateProfile = L10n.tr("Localizable", "createProfile.skipCreateProfile")
+    internal static let skipCreateProfile = L10n.tr("Localizable", "createProfile.skipCreateProfile", fallback: "Skip")
     /// Your profile will be shared with your contacts. You can change it at any time.
-    internal static let subtitle = L10n.tr("Localizable", "createProfile.subtitle")
+    internal static let subtitle = L10n.tr("Localizable", "createProfile.subtitle", fallback: "Your profile will be shared with your contacts. You can change it at any time.")
     /// Personalise your profile
-    internal static let title = L10n.tr("Localizable", "createProfile.title")
+    internal static let title = L10n.tr("Localizable", "createProfile.title", fallback: "Personalise your profile")
   }
-
   internal enum DataTransfer {
     /// Press to start recording
-    internal static let infoMessage = L10n.tr("Localizable", "dataTransfer.infoMessage")
+    internal static let infoMessage = L10n.tr("Localizable", "dataTransfer.infoMessage", fallback: "Press to start recording")
     /// Pending…
-    internal static let readableStatusAwaiting = L10n.tr("Localizable", "dataTransfer.readableStatusAwaiting")
+    internal static let readableStatusAwaiting = L10n.tr("Localizable", "dataTransfer.readableStatusAwaiting", fallback: "Pending…")
     /// Cancel
-    internal static let readableStatusCancel = L10n.tr("Localizable", "dataTransfer.readableStatusCancel")
+    internal static let readableStatusCancel = L10n.tr("Localizable", "dataTransfer.readableStatusCancel", fallback: "Cancel")
     /// Canceled
-    internal static let readableStatusCanceled = L10n.tr("Localizable", "dataTransfer.readableStatusCanceled")
+    internal static let readableStatusCanceled = L10n.tr("Localizable", "dataTransfer.readableStatusCanceled", fallback: "Canceled")
     /// Initializing…
-    internal static let readableStatusCreated = L10n.tr("Localizable", "dataTransfer.readableStatusCreated")
+    internal static let readableStatusCreated = L10n.tr("Localizable", "dataTransfer.readableStatusCreated", fallback: "Initializing…")
     /// Error
-    internal static let readableStatusError = L10n.tr("Localizable", "dataTransfer.readableStatusError")
+    internal static let readableStatusError = L10n.tr("Localizable", "dataTransfer.readableStatusError", fallback: "Error")
     /// Transferring
-    internal static let readableStatusOngoing = L10n.tr("Localizable", "dataTransfer.readableStatusOngoing")
+    internal static let readableStatusOngoing = L10n.tr("Localizable", "dataTransfer.readableStatusOngoing", fallback: "Transferring")
     /// Complete
-    internal static let readableStatusSuccess = L10n.tr("Localizable", "dataTransfer.readableStatusSuccess")
+    internal static let readableStatusSuccess = L10n.tr("Localizable", "dataTransfer.readableStatusSuccess", fallback: "Complete")
     /// Failed to send
-    internal static let sendingFailed = L10n.tr("Localizable", "dataTransfer.sendingFailed")
+    internal static let sendingFailed = L10n.tr("Localizable", "dataTransfer.sendingFailed", fallback: "Failed to send")
     /// Send
-    internal static let sendMessage = L10n.tr("Localizable", "dataTransfer.sendMessage")
+    internal static let sendMessage = L10n.tr("Localizable", "dataTransfer.sendMessage", fallback: "Send")
   }
-
   internal enum GeneralSettings {
     /// Advanced settings
-    internal static let title = L10n.tr("Localizable", "generalSettings.title")
+    internal static let title = L10n.tr("Localizable", "generalSettings.title", fallback: "Advanced settings")
     /// Enable video acceleration
-    internal static let videoAcceleration = L10n.tr("Localizable", "generalSettings.videoAcceleration")
+    internal static let videoAcceleration = L10n.tr("Localizable", "generalSettings.videoAcceleration", fallback: "Enable video acceleration")
   }
-
   internal enum GeneratedMessage {
-    /// Contact added
-    internal static let contactAdded = L10n.tr("Localizable", "generatedMessage.contactAdded")
-    /// Contact left conversation
-    internal static let contactLeftConversation = L10n.tr("Localizable", "generatedMessage.contactLeftConversation")
+    /// You received inviation
+    internal static let contactAdded = L10n.tr("Localizable", "generatedMessage.contactAdded", fallback: "You received inviation")
+    /// was kicked
+    internal static let contactBanned = L10n.tr("Localizable", "generatedMessage.contactBanned", fallback: "was kicked")
+    /// left
+    internal static let contactLeftConversation = L10n.tr("Localizable", "generatedMessage.contactLeftConversation", fallback: "left")
+    /// was re-added
+    internal static let contactReAdded = L10n.tr("Localizable", "generatedMessage.contactReAdded", fallback: "was re-added")
     /// Incoming call
-    internal static let incomingCall = L10n.tr("Localizable", "generatedMessage.incomingCall")
-    /// Invitation accepted
-    internal static let invitationAccepted = L10n.tr("Localizable", "generatedMessage.invitationAccepted")
-    /// Invitation received
-    internal static let invitationReceived = L10n.tr("Localizable", "generatedMessage.invitationReceived")
+    internal static let incomingCall = L10n.tr("Localizable", "generatedMessage.incomingCall", fallback: "Incoming call")
+    /// joined the conversation
+    internal static let invitationAccepted = L10n.tr("Localizable", "generatedMessage.invitationAccepted", fallback: "joined the conversation")
+    /// was invited to join
+    internal static let invitationReceived = L10n.tr("Localizable", "generatedMessage.invitationReceived", fallback: "was invited to join")
     /// Live location sharing
-    internal static let liveLocationSharing = L10n.tr("Localizable", "generatedMessage.liveLocationSharing")
+    internal static let liveLocationSharing = L10n.tr("Localizable", "generatedMessage.liveLocationSharing", fallback: "Live location sharing")
     /// Missed incoming call
-    internal static let missedIncomingCall = L10n.tr("Localizable", "generatedMessage.missedIncomingCall")
+    internal static let missedIncomingCall = L10n.tr("Localizable", "generatedMessage.missedIncomingCall", fallback: "Missed incoming call")
     /// Missed outgoing call
-    internal static let missedOutgoingCall = L10n.tr("Localizable", "generatedMessage.missedOutgoingCall")
+    internal static let missedOutgoingCall = L10n.tr("Localizable", "generatedMessage.missedOutgoingCall", fallback: "Missed outgoing call")
     /// Outgoing call
-    internal static let outgoingCall = L10n.tr("Localizable", "generatedMessage.outgoingCall")
+    internal static let outgoingCall = L10n.tr("Localizable", "generatedMessage.outgoingCall", fallback: "Outgoing call")
+    /// Swarm created
+    internal static let swarmCreated = L10n.tr("Localizable", "generatedMessage.swarmCreated", fallback: "Swarm created")
+    /// You joined the conversation
+    internal static let youJoined = L10n.tr("Localizable", "generatedMessage.youJoined", fallback: "You joined the conversation")
   }
-
   internal enum Global {
     /// Accept
-    internal static let accept = L10n.tr("Localizable", "global.accept")
+    internal static let accept = L10n.tr("Localizable", "global.accept", fallback: "Accept")
     /// Block
-    internal static let block = L10n.tr("Localizable", "global.block")
+    internal static let block = L10n.tr("Localizable", "global.block", fallback: "Block")
     /// Close
-    internal static let close = L10n.tr("Localizable", "global.close")
+    internal static let close = L10n.tr("Localizable", "global.close", fallback: "Close")
     /// Forward
-    internal static let forward = L10n.tr("Localizable", "global.forward")
-    /// Conversations
-    internal static let homeTabBarTitle = L10n.tr("Localizable", "global.homeTabBarTitle")
+    internal static let forward = L10n.tr("Localizable", "global.forward", fallback: "Forward")
+    /// *  Copyright (C) 2017-2019 Savoir-faire Linux Inc.
+    ///  *
+    ///  *  Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
+    ///  *
+    ///  *  This program is free software; you can redistribute it and/or modify
+    ///  *  it under the terms of the GNU General Public License as published by
+    ///  *  the Free Software Foundation; either version 3 of the License, or
+    ///  *  (at your option) any later version.
+    ///  *
+    ///  *  This program is distributed in the hope that it will be useful,
+    ///  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+    ///  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    ///  *  GNU General Public License for more details.
+    ///  *
+    ///  *  You should have received a copy of the GNU General Public License
+    ///  *  along with this program; if not, write to the Free Software
+    ///  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+    internal static let homeTabBarTitle = L10n.tr("Localizable", "global.homeTabBarTitle", fallback: "Conversations")
     /// Account Settings
-    internal static let meTabBarTitle = L10n.tr("Localizable", "global.meTabBarTitle")
-    /// Ok
-    internal static let ok = L10n.tr("Localizable", "global.ok")
-    /// Preview
-    internal static let preview = L10n.tr("Localizable", "global.preview")
-    /// Refuse
-    internal static let refuse = L10n.tr("Localizable", "global.refuse")
-    /// Resend
-    internal static let resend = L10n.tr("Localizable", "global.resend")
-    /// Save
-    internal static let save = L10n.tr("Localizable", "global.save")
-    /// Share
-    internal static let share = L10n.tr("Localizable", "global.share")
+    internal static let meTabBarTitle = L10n.tr("Localizable", "global.meTabBarTitle", fallback: "Account Settings")
     /// Name
-    internal static let name = L10n.tr("Localizable", "global.name")
+    internal static let name = L10n.tr("Localizable", "global.name", fallback: "Name")
+    /// Ok
+    internal static let ok = L10n.tr("Localizable", "global.ok", fallback: "Ok")
+    /// Preview
+    internal static let preview = L10n.tr("Localizable", "global.preview", fallback: "Preview")
+    /// Refuse
+    internal static let refuse = L10n.tr("Localizable", "global.refuse", fallback: "Refuse")
+    /// Resend
+    internal static let resend = L10n.tr("Localizable", "global.resend", fallback: "Resend")
+    /// Save
+    internal static let save = L10n.tr("Localizable", "global.save", fallback: "Save")
+    /// Share
+    internal static let share = L10n.tr("Localizable", "global.share", fallback: "Share")
   }
-
   internal enum Invitations {
     /// No invitations
-    internal static let noInvitations = L10n.tr("Localizable", "invitations.noInvitations")
+    internal static let noInvitations = L10n.tr("Localizable", "invitations.noInvitations", fallback: "No invitations")
   }
-
   internal enum LinkDevice {
     /// An error occured during the export
-    internal static let defaultError = L10n.tr("Localizable", "linkDevice.defaultError")
+    internal static let defaultError = L10n.tr("Localizable", "linkDevice.defaultError", fallback: "An error occured during the export")
     /// To complete the process, you need to open Jami on the new device and choose the option "Link this device to an account." Your pin is valid for 10 minutes
-    internal static let explanationMessage = L10n.tr("Localizable", "linkDevice.explanationMessage")
+    internal static let explanationMessage = L10n.tr("Localizable", "linkDevice.explanationMessage", fallback: "To complete the process, you need to open Jami on the new device and choose the option \"Link this device to an account.\" Your pin is valid for 10 minutes")
     /// Verifying
-    internal static let hudMessage = L10n.tr("Localizable", "linkDevice.hudMessage")
+    internal static let hudMessage = L10n.tr("Localizable", "linkDevice.hudMessage", fallback: "Verifying")
     /// A network error occured during the export
-    internal static let networkError = L10n.tr("Localizable", "linkDevice.networkError")
+    internal static let networkError = L10n.tr("Localizable", "linkDevice.networkError", fallback: "A network error occured during the export")
     /// The password you entered does not unlock this account
-    internal static let passwordError = L10n.tr("Localizable", "linkDevice.passwordError")
+    internal static let passwordError = L10n.tr("Localizable", "linkDevice.passwordError", fallback: "The password you entered does not unlock this account")
     /// Link a new device
-    internal static let title = L10n.tr("Localizable", "linkDevice.title")
+    internal static let title = L10n.tr("Localizable", "linkDevice.title", fallback: "Link a new device")
   }
-
   internal enum LinkToAccount {
     /// To generate the PIN code, go to the account managment settings on device that contain account you want to use. In devices settings Select "Link another device to this account". You will get the necessary PIN to complete this form. The PIN is only valid for 10 minutes.
-    internal static let explanationPinMessage = L10n.tr("Localizable", "linkToAccount.explanationPinMessage")
+    internal static let explanationPinMessage = L10n.tr("Localizable", "linkToAccount.explanationPinMessage", fallback: "To generate the PIN code, go to the account managment settings on device that contain account you want to use. In devices settings Select \"Link another device to this account\". You will get the necessary PIN to complete this form. The PIN is only valid for 10 minutes.")
     /// Link device
-    internal static let linkButtonTitle = L10n.tr("Localizable", "linkToAccount.linkButtonTitle")
+    internal static let linkButtonTitle = L10n.tr("Localizable", "linkToAccount.linkButtonTitle", fallback: "Link device")
     /// Enter Password
-    internal static let passwordLabel = L10n.tr("Localizable", "linkToAccount.passwordLabel")
+    internal static let passwordLabel = L10n.tr("Localizable", "linkToAccount.passwordLabel", fallback: "Enter Password")
     /// Password
-    internal static let passwordPlaceholder = L10n.tr("Localizable", "linkToAccount.passwordPlaceholder")
+    internal static let passwordPlaceholder = L10n.tr("Localizable", "linkToAccount.passwordPlaceholder", fallback: "Password")
     /// Enter PIN
-    internal static let pinLabel = L10n.tr("Localizable", "linkToAccount.pinLabel")
+    internal static let pinLabel = L10n.tr("Localizable", "linkToAccount.pinLabel", fallback: "Enter PIN")
     /// PIN
-    internal static let pinPlaceholder = L10n.tr("Localizable", "linkToAccount.pinPlaceholder")
+    internal static let pinPlaceholder = L10n.tr("Localizable", "linkToAccount.pinPlaceholder", fallback: "PIN")
     /// Account linking
-    internal static let waitLinkToAccountTitle = L10n.tr("Localizable", "linkToAccount.waitLinkToAccountTitle")
+    internal static let waitLinkToAccountTitle = L10n.tr("Localizable", "linkToAccount.waitLinkToAccountTitle", fallback: "Account linking")
   }
-
   internal enum LinkToAccountManager {
     /// Enter JAMS URL
-    internal static let accountManagerLabel = L10n.tr("Localizable", "linkToAccountManager.accountManagerLabel")
+    internal static let accountManagerLabel = L10n.tr("Localizable", "linkToAccountManager.accountManagerLabel", fallback: "Enter JAMS URL")
     /// JAMS URL
-    internal static let accountManagerPlaceholder = L10n.tr("Localizable", "linkToAccountManager.accountManagerPlaceholder")
+    internal static let accountManagerPlaceholder = L10n.tr("Localizable", "linkToAccountManager.accountManagerPlaceholder", fallback: "JAMS URL")
     /// Enter Password
-    internal static let passwordLabel = L10n.tr("Localizable", "linkToAccountManager.passwordLabel")
+    internal static let passwordLabel = L10n.tr("Localizable", "linkToAccountManager.passwordLabel", fallback: "Enter Password")
     /// Password
-    internal static let passwordPlaceholder = L10n.tr("Localizable", "linkToAccountManager.passwordPlaceholder")
+    internal static let passwordPlaceholder = L10n.tr("Localizable", "linkToAccountManager.passwordPlaceholder", fallback: "Password")
     /// Sign In
-    internal static let signIn = L10n.tr("Localizable", "linkToAccountManager.signIn")
+    internal static let signIn = L10n.tr("Localizable", "linkToAccountManager.signIn", fallback: "Sign In")
     /// Enter Username
-    internal static let usernameLabel = L10n.tr("Localizable", "linkToAccountManager.usernameLabel")
+    internal static let usernameLabel = L10n.tr("Localizable", "linkToAccountManager.usernameLabel", fallback: "Enter Username")
     /// Username
-    internal static let usernamePlaceholder = L10n.tr("Localizable", "linkToAccountManager.usernamePlaceholder")
+    internal static let usernamePlaceholder = L10n.tr("Localizable", "linkToAccountManager.usernamePlaceholder", fallback: "Username")
   }
-
   internal enum MigrateAccount {
     /// Cancel
-    internal static let cancel = L10n.tr("Localizable", "migrateAccount.cancel")
+    internal static let cancel = L10n.tr("Localizable", "migrateAccount.cancel", fallback: "Cancel")
     /// Failed to migrate your account. You can retry or delete your account.
-    internal static let error = L10n.tr("Localizable", "migrateAccount.error")
+    internal static let error = L10n.tr("Localizable", "migrateAccount.error", fallback: "Failed to migrate your account. You can retry or delete your account.")
     /// This account needs to be migrated
-    internal static let explanation = L10n.tr("Localizable", "migrateAccount.explanation")
+    internal static let explanation = L10n.tr("Localizable", "migrateAccount.explanation", fallback: "This account needs to be migrated")
     /// Migrate Another Account
-    internal static let migrateAnother = L10n.tr("Localizable", "migrateAccount.migrateAnother")
+    internal static let migrateAnother = L10n.tr("Localizable", "migrateAccount.migrateAnother", fallback: "Migrate Another Account")
     /// Migrate Account
-    internal static let migrateButton = L10n.tr("Localizable", "migrateAccount.migrateButton")
+    internal static let migrateButton = L10n.tr("Localizable", "migrateAccount.migrateButton", fallback: "Migrate Account")
     /// Migrating...
-    internal static let migrating = L10n.tr("Localizable", "migrateAccount.migrating")
+    internal static let migrating = L10n.tr("Localizable", "migrateAccount.migrating", fallback: "Migrating...")
     /// To proceed with the migration, you need to enter a password that was used for this account
-    internal static let passwordExplanation = L10n.tr("Localizable", "migrateAccount.passwordExplanation")
+    internal static let passwordExplanation = L10n.tr("Localizable", "migrateAccount.passwordExplanation", fallback: "To proceed with the migration, you need to enter a password that was used for this account")
     /// Enter password
-    internal static let passwordPlaceholder = L10n.tr("Localizable", "migrateAccount.passwordPlaceholder")
+    internal static let passwordPlaceholder = L10n.tr("Localizable", "migrateAccount.passwordPlaceholder", fallback: "Enter password")
     /// Remove account
-    internal static let removeAccount = L10n.tr("Localizable", "migrateAccount.removeAccount")
+    internal static let removeAccount = L10n.tr("Localizable", "migrateAccount.removeAccount", fallback: "Remove account")
     /// Account migration
-    internal static let title = L10n.tr("Localizable", "migrateAccount.title")
+    internal static let title = L10n.tr("Localizable", "migrateAccount.title", fallback: "Account migration")
   }
-
   internal enum Notifications {
     /// Incoming Call
-    internal static let incomingCall = L10n.tr("Localizable", "notifications.incomingCall")
+    internal static let incomingCall = L10n.tr("Localizable", "notifications.incomingCall", fallback: "Incoming Call")
     /// Incoming location sharing started
-    internal static let locationSharingStarted = L10n.tr("Localizable", "notifications.locationSharingStarted")
+    internal static let locationSharingStarted = L10n.tr("Localizable", "notifications.locationSharingStarted", fallback: "Incoming location sharing started")
     /// Incoming location sharing stopped
-    internal static let locationSharingStopped = L10n.tr("Localizable", "notifications.locationSharingStopped")
+    internal static let locationSharingStopped = L10n.tr("Localizable", "notifications.locationSharingStopped", fallback: "Incoming location sharing stopped")
     /// Missed Call
-    internal static let missedCall = L10n.tr("Localizable", "notifications.missedCall")
+    internal static let missedCall = L10n.tr("Localizable", "notifications.missedCall", fallback: "Missed Call")
     /// New file
-    internal static let newFile = L10n.tr("Localizable", "notifications.newFile")
+    internal static let newFile = L10n.tr("Localizable", "notifications.newFile", fallback: "New file")
   }
-
   internal enum Scan {
     /// Bad QR code
-    internal static let badQrCode = L10n.tr("Localizable", "scan.badQrCode")
+    internal static let badQrCode = L10n.tr("Localizable", "scan.badQrCode", fallback: "Bad QR code")
     /// Searching…
-    internal static let search = L10n.tr("Localizable", "scan.search")
+    internal static let search = L10n.tr("Localizable", "scan.search", fallback: "Searching…")
   }
-
   internal enum Smartlist {
     /// About Jami
-    internal static let aboutJami = L10n.tr("Localizable", "smartlist.aboutJami")
+    internal static let aboutJami = L10n.tr("Localizable", "smartlist.aboutJami", fallback: "About Jami")
     /// Account Settings
-    internal static let accountSettings = L10n.tr("Localizable", "smartlist.accountSettings")
+    internal static let accountSettings = L10n.tr("Localizable", "smartlist.accountSettings", fallback: "Account Settings")
     /// Accounts
-    internal static let accountsTitle = L10n.tr("Localizable", "smartlist.accountsTitle")
+    internal static let accountsTitle = L10n.tr("Localizable", "smartlist.accountsTitle", fallback: "Accounts")
     /// + Add Account
-    internal static let addAccountButton = L10n.tr("Localizable", "smartlist.addAccountButton")
+    internal static let addAccountButton = L10n.tr("Localizable", "smartlist.addAccountButton", fallback: "+ Add Account")
     /// Advanced Settings
-    internal static let advancedSettings = L10n.tr("Localizable", "smartlist.advancedSettings")
+    internal static let advancedSettings = L10n.tr("Localizable", "smartlist.advancedSettings", fallback: "Advanced Settings")
     /// Be sure cellular access is granted in your settings
-    internal static let cellularAccess = L10n.tr("Localizable", "smartlist.cellularAccess")
+    internal static let cellularAccess = L10n.tr("Localizable", "smartlist.cellularAccess", fallback: "Be sure cellular access is granted in your settings")
     /// Conversations
-    internal static let conversations = L10n.tr("Localizable", "smartlist.conversations")
+    internal static let conversations = L10n.tr("Localizable", "smartlist.conversations", fallback: "Conversations")
     /// Invitations
-    internal static let invitations = L10n.tr("Localizable", "smartlist.invitations")
+    internal static let invitations = L10n.tr("Localizable", "smartlist.invitations", fallback: "Invitations")
     /// No conversations
-    internal static let noConversation = L10n.tr("Localizable", "smartlist.noConversation")
+    internal static let noConversation = L10n.tr("Localizable", "smartlist.noConversation", fallback: "No conversations")
     /// No network connectivity
-    internal static let noNetworkConnectivity = L10n.tr("Localizable", "smartlist.noNetworkConnectivity")
+    internal static let noNetworkConnectivity = L10n.tr("Localizable", "smartlist.noNetworkConnectivity", fallback: "No network connectivity")
     /// Selected contact does not have any number
-    internal static let noNumber = L10n.tr("Localizable", "smartlist.noNumber")
+    internal static let noNumber = L10n.tr("Localizable", "smartlist.noNumber", fallback: "Selected contact does not have any number")
     /// No results
-    internal static let noResults = L10n.tr("Localizable", "smartlist.noResults")
+    internal static let noResults = L10n.tr("Localizable", "smartlist.noResults", fallback: "No results")
     /// Public Directory
-    internal static let results = L10n.tr("Localizable", "smartlist.results")
+    internal static let results = L10n.tr("Localizable", "smartlist.results", fallback: "Public Directory")
     /// Search for new or existing contact...
-    internal static let searchBar = L10n.tr("Localizable", "smartlist.searchBar")
+    internal static let searchBar = L10n.tr("Localizable", "smartlist.searchBar", fallback: "Search for new or existing contact...")
     /// Enter name...
-    internal static let searchBarPlaceholder = L10n.tr("Localizable", "smartlist.searchBarPlaceholder")
+    internal static let searchBarPlaceholder = L10n.tr("Localizable", "smartlist.searchBarPlaceholder", fallback: "Enter name...")
     /// Searching...
-    internal static let searching = L10n.tr("Localizable", "smartlist.searching")
+    internal static let searching = L10n.tr("Localizable", "smartlist.searching", fallback: "Searching...")
     /// Select one of the numbers
-    internal static let selectOneNumber = L10n.tr("Localizable", "smartlist.selectOneNumber")
+    internal static let selectOneNumber = L10n.tr("Localizable", "smartlist.selectOneNumber", fallback: "Select one of the numbers")
     /// Yesterday
-    internal static let yesterday = L10n.tr("Localizable", "smartlist.yesterday")
+    internal static let yesterday = L10n.tr("Localizable", "smartlist.yesterday", fallback: "Yesterday")
   }
-
+  internal enum Swarmcreation {
+    /// Add a description
+    internal static let addADescription = L10n.tr("Localizable", "swarmcreation.addADescription", fallback: "Add a description")
+    /// Create the Swarm
+    internal static let createTheSwarm = L10n.tr("Localizable", "swarmcreation.createTheSwarm", fallback: "Create the Swarm")
+    /// Search for contact...
+    internal static let searchBar = L10n.tr("Localizable", "swarmcreation.searchBar", fallback: "Search for contact...")
+    /// Create the Swarm
+    internal static let title = L10n.tr("Localizable", "swarmcreation.title", fallback: "Create the Swarm")
+  }
   internal enum Welcome {
     /// Connect to a JAMS server
-    internal static let connectToManager = L10n.tr("Localizable", "welcome.connectToManager")
+    internal static let connectToManager = L10n.tr("Localizable", "welcome.connectToManager", fallback: "Connect to a JAMS server")
     /// Create a Jami account
-    internal static let createAccount = L10n.tr("Localizable", "welcome.createAccount")
+    internal static let createAccount = L10n.tr("Localizable", "welcome.createAccount", fallback: "Create a Jami account")
     /// Link this device to an account
-    internal static let linkDevice = L10n.tr("Localizable", "welcome.linkDevice")
+    internal static let linkDevice = L10n.tr("Localizable", "welcome.linkDevice", fallback: "Link this device to an account")
     /// Jami is a free and universal communication platform which preserves the users' privacy and freedoms
-    internal static let text = L10n.tr("Localizable", "welcome.text")
+    internal static let text = L10n.tr("Localizable", "welcome.text", fallback: "Jami is a free and universal communication platform which preserves the users' privacy and freedoms")
     /// Welcome to Jami !
-    internal static let title = L10n.tr("Localizable", "welcome.title")
+    internal static let title = L10n.tr("Localizable", "welcome.title", fallback: "Welcome to Jami !")
   }
 }
 // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
@@ -665,8 +666,8 @@
 // MARK: - Implementation Details
 
 extension L10n {
-  private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
-    let format = BundleToken.bundle.localizedString(forKey: key, value: nil, table: table)
+  private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) -> String {
+    let format = BundleToken.bundle.localizedString(forKey: key, value: value, table: table)
     return String(format: format, locale: Locale.current, arguments: args)
   }
 }
diff --git a/Ring/Ring/Database/DBManager.swift b/Ring/Ring/Database/DBManager.swift
index 88f47a7..d2423d7 100644
--- a/Ring/Ring/Database/DBManager.swift
+++ b/Ring/Ring/Database/DBManager.swift
@@ -547,6 +547,8 @@
                                                      accountId: accountId) {
                     observable.onNext(profile)
                     observable.on(.completed)
+                } else {
+                    observable.on(.error(DBBridgingError.getProfileFailed))
                 }
             } catch {
                 observable.on(.error(DBBridgingError.getProfileFailed))
@@ -643,25 +645,14 @@
                         dataBase: dataBase) else {
                 continue
             }
-            // let interaction = interactions[interactions.count - 1]
             for interaction in interactions {
                 let author = interaction.author == participant
                     ? participant : ""
                 if let message = self.convertToMessage(interaction: interaction, author: author) {
                     messages.append(message)
-                    let displayedMessage = author.isEmpty && message.status == .displayed
-                    let isLater = conversationModel
-                        .lastDisplayedMessage.id.isEmpty ||
-                        conversationModel
-                        .lastDisplayedMessage.timestamp < message.receivedDate
-                    if displayedMessage && isLater {
-                        conversationModel
-                            .lastDisplayedMessage = (message.id,
-                                                     message.receivedDate)
-                    }
                 }
             }
-            conversationModel.messages.accept(messages)
+            conversationModel.messages = messages
             conversationsToReturn.append(conversationModel)
         }
         return conversationsToReturn
diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift
index 327c722..2dbff16 100644
--- a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift
@@ -206,26 +206,10 @@
 
     @objc
     func updateProgressBar(timer: Timer) {
-        guard let userInfoDict = timer.userInfo as? NSDictionary else { return }
-        guard let transferId = userInfoDict["transferId"] as? String else { return }
-        guard let viewModel = userInfoDict["conversationViewModel"] as? ConversationViewModel else { return }
-        if let progress = viewModel.getTransferProgress(transferId: transferId, accountId: viewModel.conversation.value.accountId) {
-            DispatchQueue.main.async { [weak self] in
-                self?.progressBar.progress = progress
-            }
-        }
     }
 
     @objc
     func updateOutgoigTransfer(timer: Timer) {
-        guard let userInfoDict = timer.userInfo as? NSDictionary else { return }
-        guard let transferId = userInfoDict["transferId"] as? String else { return }
-        guard let viewModel = userInfoDict["conversationViewModel"] as? ConversationViewModel else { return }
-        if let progress = viewModel.getTransferProgress(transferId: transferId, accountId: viewModel.conversation.value.accountId) {
-            DispatchQueue.main.async { [weak self] in
-                self?.transferProgressView.progress = CGFloat(progress * 100)
-            }
-        }
     }
 
     // MARK: Configure
diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageContent.swift b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageContent.swift
new file mode 100644
index 0000000..1551e9a
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageContent.swift
@@ -0,0 +1,119 @@
+//
+//  MessageContent.swift
+//  Ring
+//
+//  Created by kateryna on 2022-09-27.
+//  Copyright © 2022 Savoir-faire Linux. All rights reserved.
+//
+
+import SwiftUI
+
+struct PlayerViewWrapper: UIViewRepresentable {
+    var viewModel: PlayerViewModel
+    var frame: CGRect
+    func makeUIView(context: Context) -> PlayerView {
+        let player = PlayerView(frame: frame)
+        player.viewModel = viewModel
+        return player
+    }
+    func updateUIView(_ uiView: PlayerView, context: Context) {
+
+    }
+
+    typealias UIViewType = PlayerView
+
+}
+
+struct MessageContent: View {
+    let messageModel: MessageViewModel
+    @StateObject var model: MessageContentModel
+    var body: some View {
+        VStack(alignment: .leading) {
+            if model.type == .fileTransfer {
+                if let player = model.player {
+                    PlayerViewWrapper.init(viewModel: player, frame: CGRect(x: 0, y: 0, width: 300, height: 300))
+                        .frame(minHeight: 200, maxHeight: 300)
+                        .frame(minWidth: 200, maxWidth: 300)
+                        .cornerRadius(20)
+
+                } else if let image = model.image {
+                    Image(uiImage: image)
+                        .resizable()
+                        .scaledToFit()
+                        .frame(minHeight: 50, maxHeight: 300)
+                        .cornerRadius(20)
+                } else {
+                    HStack(alignment: .top) {
+                        Spacer()
+                            .frame(width: 1)
+                        Image(systemName: "doc")
+                            .resizable()
+                            .foregroundColor(model.textColor)
+                            .scaledToFit()
+                            .frame(width: 20, height: 20)
+                        Spacer()
+                            .frame(width: 10)
+                        VStack(alignment: .leading) {
+                            Text(model.fileName)
+                                .lineLimit(1)
+                                .truncationMode(.middle)
+                                .foregroundColor(model.textColor)
+                                .background(model.backgroundColor)
+                                .font(.headline)
+                            Spacer()
+                                .frame(height: 10)
+                            Text(model.fileInfo)
+                                .foregroundColor(model.textColor)
+                                .background(model.backgroundColor)
+                                .font(.footnote)
+                            if model.showProgress {
+                                Spacer()
+                                    .frame(height: 15)
+                                ProgressView(value: model.fileProgress, total: 1)
+                                Spacer()
+                                    .frame(height: 10)
+                            }
+                            if !model.transferActions.isEmpty {
+                                HStack {
+                                    ForEach(model.transferActions) { action in
+                                        Button(action.toString()) {
+                                            model.transferAction(action: action)
+                                        }
+                                        Spacer()
+                                            .frame(width: 20)
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    .padding(model.textInset)
+                    .background(model.backgroundColor)
+                    .cornerRadius(radius: model.cornerRadius, corners: model.corners)
+                }
+            } else if model.type == .text {
+                Text(model.content)
+                    .padding(model.textInset)
+                    .foregroundColor(model.textColor)
+                    .lineLimit(nil)
+                    .background(model.backgroundColor)
+                    .font(model.textFont)
+                    .if(model.hasBorder) { view in
+                        view.overlay(
+                            CornerRadiusShape(radius: model.cornerRadius, corners: model.corners)
+                                .stroke(model.borderColor, lineWidth: 2))
+                    }
+                    .if(!model.hasBorder) { view in
+                        view.cornerRadius(radius: model.cornerRadius, corners: model.corners)
+                    }
+            }
+        }.onAppear {
+            self.model.onAppear()
+        }
+    }
+}
+
+struct MessageContent_Previews: PreviewProvider {
+    static var previews: some View {
+        MessageContent(messageModel: MessageViewModel(), model: MessageViewModel().messageContent)
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.storyboard b/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.storyboard
index 8515584..4c37052 100644
--- a/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.storyboard
+++ b/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.storyboard
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="O1m-sW-gim">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="O1m-sW-gim">
     <device id="retina4_7" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="System colors in document resources" minToolsVersion="11.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@@ -17,12 +17,12 @@
                         <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
-                            <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="yc2-Jn-6vm">
+                            <tableView hidden="YES" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="yc2-Jn-6vm">
                                 <rect key="frame" x="0.0" y="-100" width="375" height="767"/>
                                 <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                                 <prototypes>
                                     <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="MessageCell" id="Cd7-Fm-IM5">
-                                        <rect key="frame" x="0.0" y="24.5" width="375" height="44"/>
+                                        <rect key="frame" x="0.0" y="44.5" width="375" height="44"/>
                                         <autoresizingMask key="autoresizingMask"/>
                                         <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Cd7-Fm-IM5" id="TOb-Hu-RG9">
                                             <rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
diff --git a/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift b/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift
index 94dcca6..a153209 100644
--- a/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift
@@ -29,6 +29,14 @@
 import SwiftyBeaver
 import Photos
 import MobileCoreServices
+import SwiftUI
+
+enum ContextMenu: State {
+    case preview(message: MessageContentVM)
+    case forward(message: MessageContentVM)
+    case share(items: [Any])
+    case save(image: UIImage)
+}
 
 // swiftlint:disable file_length
 // swiftlint:disable type_body_length
@@ -38,15 +46,16 @@
                                   MessageAccessoryViewDelegate, ContactPickerDelegate,
                                   PHPickerViewControllerDelegate, UIDocumentInteractionControllerDelegate {
 
+    // MARK: StateableResponsive
+    let disposeBag = DisposeBag()
+
     let log = SwiftyBeaver.self
 
     @IBOutlet weak var tableView: UITableView!
     @IBOutlet weak var spinnerView: UIView!
 
-    let disposeBag = DisposeBag()
-
     var viewModel: ConversationViewModel!
-    var messageViewModels = [MessageViewModel]()
+    @Published var messageViewModels = [MessageViewModel]()
     var textFieldShouldEndEditing = false
     private let messageGroupingInterval = 10 * 60 // 10 minutes
     var bottomHeight: CGFloat = 0.00
@@ -57,6 +66,9 @@
     @IBOutlet weak var scanButtonLeadingConstraint: NSLayoutConstraint!
     @IBOutlet weak var callButtonHeightConstraint: NSLayoutConstraint!
 
+    var swiftUIModel: MessagesListVM?
+
+    var bottomAnchor: NSLayoutConstraint?
     var keyboardDismissTapRecognizer: UITapGestureRecognizer!
 
     private lazy var locationManager: CLLocationManager = { return CLLocationManager() }()
@@ -68,16 +80,14 @@
     override func viewDidLoad() {
         super.viewDidLoad()
         messageAccessoryView.delegate = self
-        tableView.delegate = self
         self.configureRingNavigationBar()
         self.setupUI()
-        self.setupTableView()
         self.setupBindings()
         /*
          Register to keyboard notifications to adjust tableView insets when the keybaord appears
          or disappears
          */
-        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(withNotification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow(withNotification:)), name: UIResponder.keyboardDidShowNotification, object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(withNotification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
         NotificationCenter.default.addObserver(self,
                                                selector: #selector(applicationWillResignActive),
@@ -85,22 +95,44 @@
                                                object: nil)
 
         keyboardDismissTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
+        self.addSwiftUIView()
     }
 
-    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
-        // Waiting for screen size change
-        DispatchQueue.global(qos: .background).async {
-            sleep(UInt32(0.5))
-            DispatchQueue.main.async { [weak self] in
-                guard let self = self,
-                      UIDevice.current.portraitOrLandscape else { return }
-                self.setupNavTitle(profileImageData: self.viewModel.profileImageData.value,
-                                   displayName: self.viewModel.displayName.value,
-                                   username: self.viewModel.userName.value)
-                self.tableView.reloadData()
-            }
-        }
-        super.viewWillTransition(to: size, with: coordinator)
+    private func addSwiftUIView() {
+        let transferHelper = TransferHelper(dataTransferService: self.viewModel.dataTransferService,
+                                            conversationViewModel: self.viewModel)
+        swiftUIModel = MessagesListVM(injectionBag: self.viewModel.injectionBag,
+                                      conversation: self.viewModel.conversation.value,
+                                      transferHelper: transferHelper)
+        swiftUIModel?.contextMenuState
+            .subscribe(onNext: { [weak self] (state) in
+                guard let self = self, let state = state as? ContextMenu else { return }
+                switch state {
+                case .preview(let message):
+                    if message.image == nil && message.player == nil { return }
+                    self.viewModel.openFullScreenPreview(parentView: self, viewModel: message.player, image: message.image, initialFrame: CGRect.zero, delegate: message)
+                case .forward(let message):
+                    self.viewModel.slectContactsToShareMessage(message: message)
+                case .share(let items):
+                    self.presentActivityControllerWithItems(items: items)
+                case .save(let image):
+                    self.saveImageToGalery(image: image)
+                }
+            })
+            .disposed(by: self.disposeBag)
+        let messageListView = MessagesListView(list: swiftUIModel!)
+        let swiftUIView = UIHostingController(rootView: messageListView)
+        addChild(swiftUIView)
+        swiftUIView.view.frame = self.view.frame
+        self.view.addSubview(swiftUIView.view)
+        swiftUIView.view.translatesAutoresizingMaskIntoConstraints = false
+        swiftUIView.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0).isActive = true
+        bottomAnchor = swiftUIView.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0)
+        bottomAnchor?.isActive = true
+        swiftUIView.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true
+        swiftUIView.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true
+        swiftUIView.didMove(toParent: self)
+        self.view.sendSubviewToBack(swiftUIView.view)
     }
 
     @objc
@@ -369,7 +401,7 @@
     }
 
     @objc
-    func keyboardWillShow(withNotification notification: Notification) {
+    func keyboardDidShow(withNotification notification: Notification) {
         guard let userInfo: Dictionary = notification.userInfo else {
             return
         }
@@ -378,27 +410,21 @@
         let keyboardRectangle = keyboardFrame.cgRectValue
         let keyboardHeight = keyboardRectangle.height
 
-        var heightOffset = CGFloat(0.0)
         if keyboardHeight != self.messageAccessoryView.frame.height {
-            if UIDevice.current.hasNotch {
-                heightOffset = -35
-            }
             self.view.addGestureRecognizer(keyboardDismissTapRecognizer)
         }
+        self.updateMessagesOffset()
+    }
 
-        self.tableView.contentInset.bottom = keyboardHeight + heightOffset
-        self.tableView.scrollIndicatorInsets.bottom = keyboardHeight + heightOffset
-        self.bottomHeight = keyboardHeight + heightOffset
-
-        if keyboardHeight > self.messageAccessoryView.frame.height {
-            self.scrollToBottomIfNeed()
-        }
+    func updateMessagesOffset() {
+        self.bottomHeight = self.messageAccessoryView.frame.height + 10
+        self.bottomAnchor?.constant = -self.bottomHeight
+        swiftUIModel?.scrollIfNeed()
     }
 
     @objc
     func keyboardWillHide(withNotification notification: Notification) {
-        self.tableView.contentInset.bottom = self.messageAccessoryView.frame.height
-        self.tableView.scrollIndicatorInsets.bottom = self.messageAccessoryView.frame.height
+        self.updateMessagesOffset()
     }
 
     func setupNavTitle(profileImageData: Data?, displayName: String? = nil, username: String?) {
@@ -520,7 +546,6 @@
         self.messageAccessoryView.sendButton.contentVerticalAlignment = .fill
         self.messageAccessoryView.sendButton.contentHorizontalAlignment = .fill
         spinnerView.backgroundColor = UIColor.jamiMsgBackground
-        self.tableView.backgroundColor = UIColor.jamiMsgBackground
         self.view.backgroundColor = UIColor.jamiMsgTextFieldBackground
 
         if self.viewModel.isAccountSip {
@@ -554,9 +579,6 @@
         })
         .disposed(by: self.disposeBag)
 
-        self.tableView.contentInset.bottom = messageAccessoryView.frame.size.height
-        self.tableView.scrollIndicatorInsets.bottom = messageAccessoryView.frame.size.height
-
         self.setRightNavigationButtons()
         self.viewModel.showCallButton
             .observe(on: MainScheduler.instance)
@@ -611,144 +633,21 @@
 
     override func viewDidAppear(_ animated: Bool) {
         super.viewDidAppear(animated)
-
-        self.scrollToBottom(animated: false)
         self.textFieldShouldEndEditing = false
         self.messagesLoadingFinished()
-        // self.viewModel.setMessagesAsRead()
     }
 
     override func viewWillDisappear(_ animated: Bool) {
         super.viewWillDisappear(animated)
         self.viewModel.setIsComposingMsg(isComposing: false)
         self.textFieldShouldEndEditing = true
-        // self.viewModel.setMessagesAsRead()
-    }
-
-    func subscribeMessages() {
-        self.viewModel.conversation.value.messages.asObservable()
-            .observe(on: MainScheduler.instance)
-            .startWith(self.viewModel.conversation.value.messages.value)
-            .subscribe(onNext: { [weak self] messages in
-                guard let self = self else {
-                    return
-                }
-                self.viewModel.setMessagesAsRead()
-                let oldNumber = self.messageViewModels.count
-                self.messageViewModels.removeAll()
-                for message in messages {
-                    let injBag = self.viewModel.injectionBag
-                    if let jamiId = self.viewModel.conversation.value.getParticipants().first?.jamiId {
-                        let isLastDisplayed = self.viewModel.isLastDisplayed(messageId: message.id, peerJamiId: jamiId)
-                        self.messageViewModels.append(MessageViewModel(withInjectionBag: injBag, withMessage: message, isLastDisplayed: isLastDisplayed))
-                    } else {
-                        self.messageViewModels.append(MessageViewModel(withInjectionBag: injBag, withMessage: message, isLastDisplayed: false))
-                    }
-                    //                    if self.viewModel.peerComposingMessage {
-                    //                        let msgModel = MessageModel(withId: "",
-                    //                                                    receivedDate: Date(),
-                    //                                                    content: "       ",
-                    //                                                    authorURI: self.viewModel.conversation.value.participants[0].uri,
-                    //                                                    incoming: true)
-                    //                        let composingIndicator = MessageViewModel(withInjectionBag: injBag, withMessage: msgModel, isLastDisplayed: false)
-                    //                        composingIndicator.isComposingIndicator = true
-                    //                        self.messageViewModels.append(composingIndicator)
-                    //                    }
-                }
-                let newNumber = self.messageViewModels.count
-                self.computeSequencing()
-                if oldNumber == newNumber {
-                    self.loadingMessages = false
-                    return
-                }
-                let numberOfRowsAdded = abs(newNumber - oldNumber)
-                if numberOfRowsAdded > 1 {
-                    let initialOffset = self.tableView.contentOffset.y
-                    self.tableView.alwaysBounceVertical = false
-                    self.tableView.isScrollEnabled = false
-                    self.tableView.reloadData()
-                    if numberOfRowsAdded < self.tableView.numberOfRows(inSection: 0) {
-                        self.tableView.scrollToRow(at: NSIndexPath(row: numberOfRowsAdded, section: 0) as IndexPath, at: .top, animated: false)
-                    }
-                    self.tableView.alwaysBounceVertical = true
-                    self.tableView.isScrollEnabled = true
-                    self.tableView.contentOffset.y += initialOffset
-                } else {
-                    self.tableView.reloadData()
-                }
-                self.loadingMessages = false
-            })
-            .disposed(by: self.disposeBag)
-    }
-
-    var cellHeights: [IndexPath: CGFloat] = [:]
-
-    func setupTableView() {
-        self.tableView.dataSource = self
-
-        self.tableView.estimatedRowHeight = 1000
-        self.tableView.rowHeight = UITableView.automaticDimension
-        self.tableView.separatorStyle = .none
-
-        // Register cell
-        self.tableView.register(cellType: MessageCellSent.self)
-        self.tableView.register(cellType: MessageCellReceived.self)
-        self.tableView.register(cellType: MessageCellDataTransferSent.self)
-        self.tableView.register(cellType: MessageCellDataTransferReceived.self)
-        self.tableView.register(cellType: MessageCellGenerated.self)
-        self.tableView.register(cellType: MessageCellLocationSharingSent.self)
-        self.tableView.register(cellType: MessageCellLocationSharingReceived.self)
-        self.subscribeMessages()
-        self.viewModel.conversation
-            .observe(on: MainScheduler.instance)
-            .subscribe { [weak self] _ in
-                self?.subscribeMessages()
-            } onError: { _ in
-
-            }
-            .disposed(by: self.disposeBag)
-
-        // Scroll to bottom when reloaded
-        self.tableView.rx.methodInvoked(#selector(UITableView.reloadData))
-            .subscribe(onNext: { [weak self] _ in
-                self?.scrollToBottomIfNeed()
-            })
-            .disposed(by: disposeBag)
+        self.viewModel.setMessagesAsRead()
     }
 
     private func messagesLoadingFinished() {
         self.spinnerView.isHidden = true
     }
 
-    private func scrollToBottomIfNeed() {
-        // if user scrolls to top we do not force scroll to bottom
-        if isScrollingToTop {
-            return
-        }
-        if self.isExecutingDeleteMessage {
-            self.isExecutingDeleteMessage = false
-            return
-        }
-        self.scrollToBottom(animated: false)
-    }
-
-    private func scrollToBottom(animated: Bool) {
-        let numberOfRows = self.tableView.numberOfRows(inSection: 0)
-        if  numberOfRows > 0 {
-            let last = IndexPath(row: numberOfRows - 1, section: 0)
-            self.tableView.isScrollEnabled = true
-            self.tableView.scrollToRow(at: last, at: .bottom, animated: animated)
-        }
-    }
-
-    private var isScrollingToTop: Bool {
-        let numberOfRows = self.tableView.numberOfRows(inSection: 0)
-        let visibleIndexes = self.tableView.indexPathsForVisibleRows
-        guard let number = self.tableView.indexPathsForVisibleRows?.count,
-              let last = visibleIndexes?.first?.row else { return false }
-        return last < numberOfRows - number * 2
-    }
-
     override var inputAccessoryView: UIView {
         return self.messageAccessoryView
     }
@@ -786,10 +685,9 @@
             .disposed(by: self.disposeBag)
 
         self.messageAccessoryView.messageTextViewHeight.asObservable()
-            .subscribe(onNext: { [weak self] height in
-                self?.tableView.contentInset.bottom = (self?.bottomHeight ?? 0) + height - 35
-                self?.tableView.scrollIndicatorInsets.bottom = (self?.bottomHeight ?? 0) + height - 35
-                self?.scrollToBottomIfNeed()
+            .subscribe(onNext: { [weak self] _ in
+                guard let self = self else { return }
+                self.updateMessagesOffset()
             })
             .disposed(by: self.disposeBag)
 
@@ -831,166 +729,6 @@
         return textFieldShouldEndEditing
     }
 
-    // MARK: - message formatting
-    private func computeSequencing() {
-        var lastMessageTime: Date?
-        for (index, messageViewModel) in self.messageViewModels.enumerated() {
-            // time labels
-            let currentMessageTime = messageViewModel.receivedDate
-            if index == 0 || messageViewModel.bubblePosition() == .generated || messageViewModel.isTransfer {
-                // always show first message's time
-                messageViewModel.shouldShowTimeString = true
-            } else {
-                // only show time for new messages if beyond an arbitrary time frame from the previously shown time
-                let timeDifference = currentMessageTime.timeIntervalSinceReferenceDate - lastMessageTime!.timeIntervalSinceReferenceDate
-                if Int(timeDifference) < messageGroupingInterval || messageViewModel.isComposingIndicator {
-                    messageViewModel.shouldShowTimeString = false
-                } else {
-                    messageViewModel.shouldShowTimeString = true
-                }
-            }
-            lastMessageTime = currentMessageTime
-            // sequencing
-            messageViewModel.sequencing = getMessageSequencing(forIndex: index)
-        }
-    }
-
-    private func getMessageSequencing(forIndex index: Int) -> MessageSequencing {
-        let messageItem = self.messageViewModels[index]
-        let msgOwner = messageItem.bubblePosition()
-        if self.messageViewModels.count == 1 || index == 0 {
-            if self.messageViewModels.count == index + 1 {
-                return MessageSequencing.singleMessage
-            }
-            let nextMessageItem = index + 1 <= self.messageViewModels.count
-                ? self.messageViewModels[index + 1] : nil
-            if nextMessageItem != nil {
-                return msgOwner != nextMessageItem?.bubblePosition()
-                    ? MessageSequencing.singleMessage : MessageSequencing.firstOfSequence
-            }
-        } else if self.messageViewModels.count == index + 1 {
-            let lastMessageItem = index - 1 >= 0 && index - 1 < self.messageViewModels.count
-                ? self.messageViewModels[index - 1] : nil
-            if lastMessageItem != nil {
-                return msgOwner != lastMessageItem?.bubblePosition()
-                    ? MessageSequencing.singleMessage : MessageSequencing.lastOfSequence
-            }
-        }
-        let lastMessageItem = index - 1 >= 0 && index - 1 < self.messageViewModels.count
-            ? self.messageViewModels[index - 1] : nil
-        let nextMessageItem = index + 1 <= self.messageViewModels.count
-            ? self.messageViewModels[index + 1] : nil
-        var sequencing = MessageSequencing.singleMessage
-        if (lastMessageItem != nil) && (nextMessageItem != nil) {
-            if msgOwner != lastMessageItem?.bubblePosition() && msgOwner == nextMessageItem?.bubblePosition() {
-                sequencing = MessageSequencing.firstOfSequence
-            } else if msgOwner != nextMessageItem?.bubblePosition() && msgOwner == lastMessageItem?.bubblePosition() {
-                sequencing = MessageSequencing.lastOfSequence
-            } else if msgOwner == nextMessageItem?.bubblePosition() && msgOwner == lastMessageItem?.bubblePosition() {
-                sequencing = MessageSequencing.middleOfSequence
-            }
-        }
-        return sequencing
-    }
-
-    func changeTransferStatus(_ cell: MessageCell,
-                              _ indexPath: IndexPath?,
-                              _ status: DataTransferStatus,
-                              _ item: MessageViewModel,
-                              _ conversationViewModel: ConversationViewModel) {
-        switch status {
-        case .created:
-            if item.bubblePosition() == .sent {
-                cell.statusLabel.isHidden = false
-                cell.statusLabel.text = L10n.DataTransfer.readableStatusCreated
-                cell.statusLabel.textColor = UIColor.darkGray
-                cell.progressBar.isHidden = true
-                cell.cancelButton.isHidden = false
-                cell.cancelButton.setTitle(L10n.DataTransfer.readableStatusCancel, for: .normal)
-                cell.buttonsHeightConstraint?.constant = 24.0
-            }
-        case .error:
-            // show status
-            cell.statusLabel.isHidden = false
-            cell.statusLabel.text = L10n.DataTransfer.readableStatusError
-            cell.statusLabel.textColor = UIColor.jamiFailure
-            // hide everything and shrink cell
-            cell.progressBar.isHidden = true
-            cell.acceptButton?.isHidden = true
-            cell.cancelButton.isHidden = true
-            cell.buttonsHeightConstraint?.constant = 0.0
-        case .awaiting:
-            cell.progressBar.isHidden = true
-            cell.cancelButton.isHidden = false
-            cell.buttonsHeightConstraint?.constant = 24.0
-            if item.bubblePosition() == .sent {
-                // status
-                cell.statusLabel.isHidden = false
-                cell.statusLabel.text = L10n.DataTransfer.readableStatusAwaiting
-                cell.statusLabel.textColor = UIColor.jamiSuccess
-                cell.cancelButton.setTitle(L10n.DataTransfer.readableStatusCancel, for: .normal)
-            } else if item.bubblePosition() == .received {
-                // hide status
-                cell.statusLabel.isHidden = true
-                cell.acceptButton?.isHidden = false
-                cell.cancelButton.setTitle(L10n.Global.refuse, for: .normal)
-            }
-        case .ongoing:
-            // status
-            cell.statusLabel.isHidden = false
-            cell.statusLabel.text = L10n.DataTransfer.readableStatusOngoing
-            cell.statusLabel.textColor = UIColor.darkGray
-            // start update progress timer process bar here
-            let transferId = item.message.daemonId
-            let progress = viewModel.getTransferProgress(transferId: transferId, accountId: viewModel.conversation.value.accountId) ?? 0.0
-            cell.progressBar.progress = progress
-            cell.progressBar.isHidden = false
-            cell.startProgressMonitor(item, viewModel)
-            // hide accept button only
-            cell.acceptButton?.isHidden = true
-            cell.cancelButton.isHidden = false
-            cell.cancelButton.setTitle(L10n.DataTransfer.readableStatusCancel, for: .normal)
-            cell.buttonsHeightConstraint?.constant = 24.0
-        case .canceled:
-            // status
-            cell.stopProgressMonitor()
-            cell.statusLabel.isHidden = false
-            cell.statusLabel.text = L10n.DataTransfer.readableStatusCanceled
-            cell.statusLabel.textColor = UIColor.jamiWarning
-            // hide everything and shrink cell
-            cell.progressBar.isHidden = true
-            cell.acceptButton?.isHidden = true
-            cell.cancelButton.isHidden = true
-            cell.buttonsHeightConstraint?.constant = 0.0
-        case .success:
-            // status
-            cell.displayTransferedImage(message: item, conversationID: self.viewModel.conversation.value.id, accountId: self.viewModel.conversation.value.accountId, isSwarm: self.viewModel.conversation.value.isSwarm())
-            cell.stopProgressMonitor()
-            cell.statusLabel.isHidden = false
-            cell.statusLabel.text = L10n.DataTransfer.readableStatusSuccess
-            cell.statusLabel.textColor = UIColor.jamiSuccess
-            // hide everything and shrink cell
-            cell.progressBar.isHidden = true
-            cell.acceptButton?.isHidden = true
-            cell.cancelButton.isHidden = true
-            cell.buttonsHeightConstraint?.constant = 0.0
-        default: break
-        }
-    }
-
-    func addShareAction(cell: MessageCell, item: MessageViewModel) {
-        cell.doubleTapGestureRecognizer = UITapGestureRecognizer()
-        cell.doubleTapGestureRecognizer?.numberOfTapsRequired = 2
-        cell.isUserInteractionEnabled = true
-        cell.addGestureRecognizer(cell.doubleTapGestureRecognizer!)
-        cell.doubleTapGestureRecognizer?.rx.event
-            .bind(onNext: { [weak self, weak item] _ in
-                guard let item = item else { return }
-                self?.showShareMenu(messageModel: item)
-            })
-            .disposed(by: cell.disposeBag)
-    }
-
     // MARK: open file
 
     func openDocument(messageModel: MessageViewModel) {
@@ -1010,37 +748,6 @@
         return self
     }
 
-    // MARK: open share menu
-    func showShareMenu(messageModel: MessageViewModel) {
-        let conversation = self.viewModel.conversation.value
-        let accountId = self.viewModel.conversation.value.accountId
-        if let file = messageModel.transferedFile(conversationID: conversation.id, accountId: accountId, isSwarm: conversation.isSwarm()) {
-            self.presentActivityControllerWithItems(items: [file])
-            return
-        }
-        if messageModel
-            .getURLFromPhotoLibrary(conversationID: conversation.id,
-                                    completionHandler: { [weak self, weak messageModel] url in
-                                        if let url = url {
-                                            self?.presentActivityControllerWithItems(items: [url])
-                                            return
-                                        }
-                                        guard let messageModel = messageModel else { return }
-                                        self?.shareImage(messageModel: messageModel)
-                                    }) { return }
-        self.shareImage(messageModel: messageModel)
-    }
-
-    func shareImage(messageModel: MessageViewModel) {
-        let conversation = self.viewModel.conversation.value
-        let accountId = conversation.accountId
-        if let image = messageModel.getTransferedImage(maxSize: 250,
-                                                       conversationID: conversation.id,
-                                                       accountId: accountId, isSwarm: conversation.isSwarm()) {
-            self.presentActivityControllerWithItems(items: [image])
-        }
-    }
-
     func presentActivityControllerWithItems(items: [Any]) {
         let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil)
         activityViewController.popoverPresentationController?.sourceView = self.view
@@ -1073,79 +780,9 @@
     func contactPickerDismissed() {
         self.inputAccessoryView.isHidden = false
     }
-
-    var loadingMessages = false
-
-    func scrollViewDidScroll(_ scrollView: UIScrollView) {
-        let visibleIndexes = self.tableView.indexPathsForVisibleRows
-        guard let first = visibleIndexes?.first?.row else { return }
-        if first < 1 && !loadingMessages {
-            loadingMessages = true
-            self.viewModel.loadMoreMessages()
-        }
-    }
 }
 
-// MARK: UITableViewDelegate
-extension ConversationViewController: UITableViewDelegate {
-    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
-        cellHeights[indexPath] = cell.frame.size.height
-    }
-
-    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
-        return cellHeights[indexPath] ?? 500.0
-    }
-}
-
-// MARK: TableDataSource
-extension ConversationViewController: UITableViewDataSource {
-    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
-        return self.messageViewModels.count
-    }
-
-    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-        let item = self.messageViewModels[indexPath.row]
-
-        if item.message.incoming &&
-            item.message.status != .displayed &&
-            item.message.type == .text {
-            self.viewModel.setMessageAsRead(daemonId: item.message.daemonId,
-                                            messageId: item.message.id)
-        }
-
-        let cellType = { (bubblePosition: BubblePosition, isTransfer: Bool, isLocationSharing: Bool) -> MessageCell.Type in
-            switch bubblePosition {
-            case .received:
-                if isLocationSharing {
-                    return MessageCellLocationSharingReceived.self
-                } else if isTransfer {
-                    return MessageCellDataTransferReceived.self
-                } else {
-                    return MessageCellReceived.self
-                }
-            case .sent:
-                if isLocationSharing {
-                    return MessageCellLocationSharingSent.self
-                } else if isTransfer {
-                    return MessageCellDataTransferSent.self
-                } else {
-                    return MessageCellSent.self
-                }
-            case .generated: return MessageCellGenerated.self
-            }
-        }(item.bubblePosition(), item.isTransfer, item.isLocationSharingBubble)
-
-        let cell = tableView.dequeueReusableCell(for: indexPath, cellType: cellType)
-        cell.configureFromItem(viewModel, self.messageViewModels, cellForRowAt: indexPath)
-
-        self.transferCellSetup(item, cell, tableView, indexPath)
-        self.locationCellSetup(item, cell)
-        self.deleteCellSetup(cell)
-        self.messageCellActionsSetUp(cell, item: item)
-        self.tapToShowTimeCellSetup(cell)
-
-        return cell
-    }
+extension ConversationViewController {
 
     private func deleteCellSetup(_ cell: MessageCell) {
         cell.deleteMessage
@@ -1165,51 +802,6 @@
             .disposed(by: cell.disposeBag)
     }
 
-    private func messageCellActionsSetUp(_ cell: MessageCell, item: MessageViewModel) {
-        cell.shareMessage
-            .observe(on: MainScheduler.instance)
-            .subscribe(onNext: { [weak self, weak item] (shouldShare) in
-                guard shouldShare, let item = item else { return }
-                self?.showShareMenu(messageModel: item)
-            })
-            .disposed(by: cell.disposeBag)
-        cell.forwardMessage
-            .observe(on: MainScheduler.instance)
-            .subscribe(onNext: { [weak self, weak item] (shouldForward) in
-                guard shouldForward, let item = item else { return }
-                self?.viewModel.slectContactsToShareMessage(message: item)
-            })
-            .disposed(by: cell.disposeBag)
-        cell.saveMessage
-            .observe(on: MainScheduler.instance)
-            .subscribe(onNext: { [weak self, weak cell] (shouldSave) in
-                guard shouldSave, let cell = cell, let image = cell.transferedImage else { return }
-                self?.saveImageToGalery(image: image)
-            })
-            .disposed(by: cell.disposeBag)
-        cell.resendMessage
-            .observe(on: MainScheduler.instance)
-            .subscribe(onNext: { [weak self, weak item] (shouldResend) in
-                guard shouldResend, let item = item else { return }
-                self?.viewModel.resendMessage(message: item)
-            })
-            .disposed(by: cell.disposeBag)
-        cell.previewMessage
-            .observe(on: MainScheduler.instance)
-            .subscribe(onNext: { [weak self, weak item, weak cell] (shouldPreview) in
-                guard shouldPreview, let item = item, let cell = cell, let self = self, let initialFrame = cell.getInitialFrame() else { return }
-                let player = item.getPlayer(conversationViewModel: self.viewModel)
-                let image = cell.transferedImage
-                if player == nil && image == nil {
-                    self.openDocument(messageModel: item)
-                    return
-                }
-                self.inputAccessoryView.isHidden = true
-                self.viewModel.openFullScreenPreview(parentView: self, viewModel: player, image: image, initialFrame: initialFrame, delegate: cell)
-            })
-            .disposed(by: cell.disposeBag)
-    }
-
     private func tapToShowTimeCellSetup(_ cell: MessageCell) {
         cell.tappedToShowTime
             .observe(on: MainScheduler.instance)
@@ -1263,105 +855,6 @@
             })
             .disposed(by: cell.disposeBag)
     }
-
-    // swiftlint:disable cyclomatic_complexity
-    private func transferCellSetup(_ item: MessageViewModel, _ cell: MessageCell, _ tableView: UITableView, _ indexPath: IndexPath) {
-        if item.isTransfer {
-            cell.acceptButton?.setTitle(L10n.Global.accept, for: .normal)
-            item.lastTransferStatus = .unknown
-            changeTransferStatus(cell, nil, item.message.transferStatus, item, viewModel)
-            item.transferStatus.asObservable()
-                .observe(on: MainScheduler.instance)
-                .filter {
-                    return $0 != DataTransferStatus.unknown && $0 != item.lastTransferStatus && $0 != item.initialTransferStatus
-                }
-                .subscribe(onNext: { [weak self, weak tableView, weak cell] status in
-                    guard let cell = cell else { return }
-                    guard let currentIndexPath = tableView?.indexPath(for: cell) else { return }
-                    let transferId = item.daemonId
-                    guard let model = self?.viewModel else { return }
-                    self?.log.info("Transfer status change from: \(item.lastTransferStatus.description) to: \(status.description) for transferId: \(transferId) cell row: \(currentIndexPath.row)")
-                    if item.bubblePosition() == .sent && item.shouldDisplayTransferedImage {
-                        cell.displayTransferedImage(message: item, conversationID: model.conversation.value.id, accountId: model.conversation.value.accountId, isSwarm: model.conversation.value.isSwarm())
-                    } else {
-                        self?.changeTransferStatus(cell, currentIndexPath, status, item, model)
-                    }
-                    item.lastTransferStatus = status
-                    item.initialTransferStatus = status
-                    tableView?.reloadData()
-                })
-                .disposed(by: cell.disposeBag)
-
-            cell.cancelButton.rx.tap
-                .subscribe(onNext: { [weak self, weak tableView, weak cell] _ in
-                    guard let cell = cell else { return }
-                    let transferId = item.daemonId
-                    self?.log.info("canceling transferId \(transferId)")
-                    // _ = self?.viewModel.cancelTransfer(transferId: transferId)
-                    item.initialTransferStatus = .canceled
-                    item.message.transferStatus = .canceled
-                    cell.stopProgressMonitor()
-                    tableView?.reloadData()
-                })
-                .disposed(by: cell.disposeBag)
-            cell.openPreview
-                .subscribe(onNext: { [weak self, weak item, weak cell] open in
-                    guard let self = self, open, let cell = cell, let item = item,
-                          let initialFrame = cell.getInitialFrame() else { return }
-                    let player = item.getPlayer(conversationViewModel: self.viewModel)
-                    let image = cell.transferedImage
-                    if player == nil && image == nil {
-                        self.openDocument(messageModel: item)
-                        return
-                    }
-                    self.inputAccessoryView.isHidden = true
-                    self.viewModel.openFullScreenPreview(parentView: self, viewModel: player, image: image, initialFrame: initialFrame, delegate: cell)
-                })
-                .disposed(by: cell.disposeBag)
-            cell.playerHeight
-                .asObservable()
-                .share()
-                .observe(on: MainScheduler.instance)
-                .subscribe(onNext: {[weak tableView] height in
-                    if height > 0 {
-                        UIView.performWithoutAnimation {
-                            guard let sectionNumber = tableView?.numberOfSections,
-                                  let rowNumber = tableView?.numberOfRows(inSection: indexPath.section) else { return }
-                            if indexPath.section < sectionNumber && indexPath.section >= 0 {
-                                if indexPath.row < rowNumber &&
-                                    indexPath.row >= 0 &&
-                                    indexPath.row != tableView?.numberOfRows(inSection: indexPath.section) {
-                                    tableView?
-                                        .reloadItemsAtIndexPaths([indexPath],
-                                                                 animationStyle: .top)
-                                }
-                            }
-                        }
-                    }
-                })
-                .disposed(by: cell.disposeBag)
-
-            if item.bubblePosition() == .received {
-                cell.acceptButton?.rx.tap
-                    .subscribe(onNext: { [weak self, weak tableView, weak cell] _ in
-                        guard let cell = cell else { return }
-                        let transferId = item.daemonId
-                        self?.log.info("accepting transferId \(transferId)")
-                        if self?.viewModel.acceptTransfer(transferId: item.message.daemonId, messageContent: &item.message.content, interactionId: item.message.id) != .success {
-                            _ = self?.viewModel.cancelTransfer(transferId: transferId)
-                            item.initialTransferStatus = .canceled
-                            item.message.transferStatus = .canceled
-                            cell.stopProgressMonitor()
-                            tableView?.reloadData()
-                        }
-                    })
-                    .disposed(by: cell.disposeBag)
-            }
-            if item.message.transferStatus == .success {
-                self.addShareAction(cell: cell, item: item)
-            }
-        }
-    }
 }
 
 // MARK: Location sharing
diff --git a/Ring/Ring/Features/Conversations/Conversation/ConversationViewModel.swift b/Ring/Ring/Features/Conversations/Conversation/ConversationViewModel.swift
index 8d5ec31..f7245ad 100644
--- a/Ring/Ring/Features/Conversations/Conversation/ConversationViewModel.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/ConversationViewModel.swift
@@ -40,13 +40,13 @@
     private let contactsService: ContactsService
     private let presenceService: PresenceService
     private let profileService: ProfilesService
-    private let dataTransferService: DataTransferService
     private let callService: CallsService
     private let locationSharingService: LocationSharingService
+    let dataTransferService: DataTransferService
 
     let injectionBag: InjectionBag
 
-    private let disposeBag = DisposeBag()
+    internal let disposeBag = DisposeBag()
 
     private var players = [String: PlayerViewModel]()
 
@@ -148,7 +148,6 @@
             self.subscribeUnreadMessages()
             self.subscribeLocationServiceLocationReceived()
             self.subscribeProfileServiceMyPhoto()
-            self.subscribeMessagesUpdate()
 
             guard let account = self.accountService.getAccount(fromAccountId: self.conversation.value.accountId) else { return }
             if account.type == AccountType.sip {
@@ -162,9 +161,22 @@
                 self.showInvitation.accept(showInv)
             }
 
-            let filterParicipants = conversation.value.getParticipants()
-            if conversation.value.isDialog() {
-                self.subscribePresenceServiceContactPresence()
+            self.subscribePresenceServiceContactPresence()
+            if conversation.value.isSwarm() {
+                self.swarmInfo!.finalAvatar.share()
+                    .subscribe { [weak self] image in
+                        self?.profileImageData.accept(image.pngData())
+                    } onError: { _ in
+                    }
+                    .disposed(by: self.disposeBag)
+                self.swarmInfo!.finalTitle.share()
+                    .subscribe { [weak self] name in
+                        self?.userName.accept(name)
+                    } onError: { _ in
+                    }
+                    .disposed(by: self.disposeBag)
+            } else {
+                let filterParicipants = conversation.value.getParticipants()
                 if let contact = self.contactsService.contact(withHash: filterParicipants.first?.jamiId ?? "") {
                     if let profile = self.contactsService.getProfile(uri: "ring:" + (filterParicipants.first?.jamiId ?? ""), accountId: self.conversation.value.accountId),
                        let alias = profile.alias, let photo = profile.photo {
@@ -176,33 +188,61 @@
                             self.profileImageData.accept(data)
                         }
                     }
-
                     if let contactUserName = contact.userName {
                         self.userName.accept(contactUserName)
                     } else if self.userName.value.isEmpty {
                         self.userName.accept(filterParicipants.first?.jamiId ?? "")
-
+                        self.subscribeUserServiceLookupStatus()
+                        self.nameService.lookupAddress(withAccount: self.conversation.value.accountId, nameserver: "", address: filterParicipants.first?.jamiId ?? "")
+                    } else if self.userName.value.isEmpty {
+                        self.userName.accept(filterParicipants.first?.jamiId ?? "")
                         self.subscribeUserServiceLookupStatus()
                         self.nameService.lookupAddress(withAccount: self.conversation.value.accountId, nameserver: "", address: filterParicipants.first?.jamiId ?? "")
                     }
-                } else if self.userName.value.isEmpty {
-                    self.userName.accept(filterParicipants.first?.jamiId ?? "")
-
-                    self.subscribeUserServiceLookupStatus()
-                    self.nameService.lookupAddress(withAccount: self.conversation.value.accountId, nameserver: "", address: filterParicipants.first?.jamiId ?? "")
                 }
             }
+            subscribeLastMessagesUpdate()
             // self.subscribeConversationServiceTypingIndicator()
         }
     }
 
+    private func subscribeLastMessagesUpdate() {
+        conversation.value.newMessages
+            .subscribe { [weak self] _ in
+                guard let self = self, let lastMessage = self.conversation.value.lastMessage else { return }
+                self.lastMessage.accept(lastMessage.content)
+                let lastMessageDate = lastMessage.receivedDate
+                let dateToday = Date()
+                var dateString = ""
+                let todayWeekOfYear = Calendar.current.component(.weekOfYear, from: dateToday)
+                let todayDay = Calendar.current.component(.day, from: dateToday)
+                let todayMonth = Calendar.current.component(.month, from: dateToday)
+                let todayYear = Calendar.current.component(.year, from: dateToday)
+                let weekOfYear = Calendar.current.component(.weekOfYear, from: lastMessageDate)
+                let day = Calendar.current.component(.day, from: lastMessageDate)
+                let month = Calendar.current.component(.month, from: lastMessageDate)
+                let year = Calendar.current.component(.year, from: lastMessageDate)
+                if todayDay == day && todayMonth == month && todayYear == year {
+                    dateString = self.hourFormatter.string(from: lastMessageDate)
+                } else if day == todayDay - 1 {
+                    dateString = L10n.Smartlist.yesterday
+                } else if todayYear == year && todayWeekOfYear == weekOfYear {
+                    dateString = lastMessageDate.dayOfWeek()
+                } else {
+                    dateString = self.dateFormatter.string(from: lastMessageDate)
+                }
+                self.lastMessageReceivedDate.accept(dateString)
+            } onError: { _ in
+            }
+            .disposed(by: self.disposeBag)
+    }
+
     /// Displays the entire date ( for messages received before the current week )
     private lazy var dateFormatter: DateFormatter = {
         let formatter = DateFormatter()
         formatter.dateStyle = .medium
         return formatter
     }()
-
     /// Displays the hour of the message reception ( for messages received today )
     private lazy var hourFormatter: DateFormatter = {
         let formatter = DateFormatter()
@@ -217,9 +257,9 @@
 
     var hideNewMessagesLabel = BehaviorRelay<Bool>(value: true)
 
-    var hideDate: Bool { self.conversation.value.messages.value.isEmpty }
+    var hideDate: Bool { self.conversation.value.messages.isEmpty }
 
-    func sendMessage(withContent content: String, contactURI: String? = nil) {
+    func sendMessage(withContent content: String, contactURI: String? = nil, parentId: String = "") {
         let conversation = self.conversation.value
         if !conversation.isSwarm() {
             /// send not swarm message
@@ -244,7 +284,7 @@
             return
         }
         /// send swarm message
-        self.conversationsService.sendSwarmMessage(conversationId: conversation.id, accountId: conversation.accountId, message: content, parentId: "")
+        self.conversationsService.sendSwarmMessage(conversationId: conversation.id, accountId: conversation.accountId, message: content, parentId: parentId)
     }
 
     func setMessagesAsRead() {
@@ -270,7 +310,7 @@
     }
 
     func deleteLocationMessage(messageId: String) {
-        guard let message = self.conversation.value.messages.value.filter({ $0.id == messageId }).first,
+        guard let message = self.conversation.value.messages.filter({ $0.id == messageId }).first,
               let jamiId = self.conversation.value.getParticipants().first?.jamiId else { return }
         self.conversationsService.deleteLocationUpdate(incoming: message.incoming, peerUri: jamiId, accountId: self.conversation.value.accountId, shouldRefreshConversations: true)
             .subscribe()
@@ -283,12 +323,11 @@
         self.stateSubject.onNext(ConversationState.startCall(contactRingId: jamiId, userName: self.displayName.value ?? self.userName.value))
     }
 
-    func loadMoreMessages() {
-        if self.conversation.value.allMessagesLoaded() { return }
+    func loadMoreMessages(messageId: String) {
         self.conversationsService
             .loadConversationMessages(conversationId: self.conversation.value.id,
                                       accountId: self.conversation.value.accountId,
-                                      from: self.conversation.value.messages.value.first?.id ?? "")
+                                      from: messageId)
     }
 
     func startAudioCall() {
@@ -415,14 +454,43 @@
         //        self.messages.accept(conversationsMsg)
     }
 
-    func isLastDisplayed(messageId: String, peerJamiId: String) -> Bool {
-        return self.conversation.value.isLastDisplayed(messageId: messageId, peerJamiId: peerJamiId)
-    }
-
     var myLocation: Observable<CLLocation?> { return self.locationSharingService.currentLocation.asObservable() }
 
     var myContactsLocation = BehaviorSubject<CLLocationCoordinate2D?>(value: nil)
     let shouldDismiss = BehaviorRelay<Bool>(value: false)
+    func openFullScreenPreview(parentView: UIViewController, viewModel: PlayerViewModel?, image: UIImage?, initialFrame: CGRect, delegate: PreviewViewControllerDelegate) {
+        self.stateSubject.onNext(ConversationState.openFullScreenPreview(parentView: parentView, viewModel: viewModel, image: image, initialFrame: initialFrame, delegate: delegate))
+    }
+
+    func openInvitationView(parentView: UIViewController) {
+        let name = self.displayName.value?.isEmpty ?? true ? self.userName.value : self.displayName.value ?? ""
+        let handler: ((String) -> Void) = { [weak self] conversationId in
+            guard let self = self else { return }
+            guard let conversation = self.conversationsService.getConversationForId(conversationId: conversationId, accountId: self.conversation.value.accountId),
+                  !conversationId.isEmpty else {
+                self.shouldDismiss.accept(true)
+                return
+            }
+            self.request = nil
+            self.conversation.accept(conversation)
+            if self.showInvitation.value {
+                self.showInvitation.accept(false)
+            }
+        }
+        if let request = self.request {
+            // show incoming request
+            self.stateSubject.onNext(ConversationState.openIncomingInvitationView(displayName: name, request: request, parentView: parentView, invitationHandeledCB: handler))
+        } else if self.conversation.value.id.isEmpty {
+            // send invitation for search result
+            let alias = (self.conversation.value.type == .jams ? self.displayName.value : "") ?? ""
+            self.stateSubject.onNext(ConversationState
+                                        .openOutgoingInvitationView(displayName: name, alias: alias, avatar: self.profileImageData.value,
+                                                                    contactJamiId: self.conversation.value.hash,
+                                                                    accountId: self.conversation.value.accountId,
+                                                                    parentView: parentView,
+                                                                    invitationHandeledCB: handler))
+        }
+    }
 }
 
 // MARK: Conversation didSet functions
@@ -481,45 +549,6 @@
         }
     }
 
-    private func subscribeMessagesUpdate() {
-        conversation.value.messages
-            .subscribe { [weak self] messages in
-                guard let self = self else { return }
-                // update last message
-                guard let lastMessage = messages.last else { return }
-                self.lastMessage.accept(lastMessage.content)
-                // update last message date
-                let lastMessageDate = lastMessage.receivedDate
-                let dateToday = Date()
-                var dateString = ""
-
-                // Get components from today date
-                let todayWeekOfYear = Calendar.current.component(.weekOfYear, from: dateToday)
-                let todayDay = Calendar.current.component(.day, from: dateToday)
-                let todayMonth = Calendar.current.component(.month, from: dateToday)
-                let todayYear = Calendar.current.component(.year, from: dateToday)
-
-                // Get components from last message date
-                let weekOfYear = Calendar.current.component(.weekOfYear, from: lastMessageDate)
-                let day = Calendar.current.component(.day, from: lastMessageDate)
-                let month = Calendar.current.component(.month, from: lastMessageDate)
-                let year = Calendar.current.component(.year, from: lastMessageDate)
-
-                if todayDay == day && todayMonth == month && todayYear == year {
-                    dateString = self.hourFormatter.string(from: lastMessageDate)
-                } else if day == todayDay - 1 {
-                    dateString = L10n.Smartlist.yesterday
-                } else if todayYear == year && todayWeekOfYear == weekOfYear {
-                    dateString = lastMessageDate.dayOfWeek()
-                } else {
-                    dateString = self.dateFormatter.string(from: lastMessageDate)
-                }
-                self.lastMessageReceivedDate.accept(dateString)
-            } onError: { _ in
-            }
-            .disposed(by: self.disposeBag)
-    }
-
     private func subscribeUnreadMessages() {
         self.conversation.value.numberOfUnreadMessages
             .subscribe { [weak self] unreadMessages in
@@ -600,40 +629,6 @@
         self.locationSharingService.stopSharingLocation(accountId: account.id,
                                                         contactUri: jamiId)
     }
-
-    func openFullScreenPreview(parentView: UIViewController, viewModel: PlayerViewModel?, image: UIImage?, initialFrame: CGRect, delegate: PreviewViewControllerDelegate) {
-        self.stateSubject.onNext(ConversationState.openFullScreenPreview(parentView: parentView, viewModel: viewModel, image: image, initialFrame: initialFrame, delegate: delegate))
-    }
-
-    func openInvitationView(parentView: UIViewController) {
-        let name = self.displayName.value?.isEmpty ?? true ? self.userName.value : self.displayName.value ?? ""
-        let handler: ((String) -> Void) = { [weak self] conversationId in
-            guard let self = self else { return }
-            guard let conversation = self.conversationsService.getConversationForId(conversationId: conversationId, accountId: self.conversation.value.accountId),
-                  !conversationId.isEmpty else {
-                self.shouldDismiss.accept(true)
-                return
-            }
-            self.request = nil
-            self.conversation.accept(conversation)
-            if self.showInvitation.value {
-                self.showInvitation.accept(false)
-            }
-        }
-        if let request = self.request {
-            // show incoming request
-            self.stateSubject.onNext(ConversationState.openIncomingInvitationView(displayName: name, request: request, parentView: parentView, invitationHandeledCB: handler))
-        } else if self.conversation.value.id.isEmpty {
-            // send invitation for search result
-            let alias = (self.conversation.value.type == .jams ? self.displayName.value : "") ?? ""
-            self.stateSubject.onNext(ConversationState
-                                        .openOutgoingInvitationView(displayName: name, alias: alias, avatar: self.profileImageData.value,
-                                                                    contactJamiId: self.conversation.value.hash,
-                                                                    accountId: self.conversation.value.accountId,
-                                                                    parentView: parentView,
-                                                                    invitationHandeledCB: handler))
-        }
-    }
 }
 
 // MARK: share message
@@ -650,8 +645,8 @@
         self.stateSubject.onNext(ConversationState.replaceCurrentWithConversationFor(participantUri: selectedItemURI))
     }
 
-    private func shareMessage(message: MessageViewModel, with contact: Contact, fileURL: URL?, image: UIImage?, fileName: String) {
-        if !message.isTransfer {
+    private func shareMessage(message: MessageContentVM, with contact: Contact, fileURL: URL?, image: UIImage?, fileName: String) {
+        if message.type != .fileTransfer {
             self.sendMessage(withContent: message.content, contactURI: contact.uri)
             return
         }
@@ -671,12 +666,10 @@
         self.sendAndSaveFile(displayName: fileName, imageData: data, conversationId: convId, accountId: contact.accountID)
     }
 
-    private func shareMessage(message: MessageViewModel, with selectedContacts: [ConferencableItem]) {
-        let conversationId = self.conversation.value.id
-        let accountId = self.conversation.value.accountId
+    private func shareMessage(message: MessageContentVM, with selectedContacts: [ConferencableItem]) {
         // to send file we need to have file url or image
-        let url = message.transferedFile(conversationID: conversationId, accountId: accountId, isSwarm: self.conversation.value.isSwarm())
-        let image = url == nil ? message.getTransferedImage(maxSize: 200, conversationID: conversationId, accountId: accountId, isSwarm: self.conversation.value.isSwarm()) : nil
+        let url = message.url
+        let image = url == nil ? message.image : nil
         var fileName = message.content
         if message.content.contains("\n") {
             guard let substring = message.content.split(separator: "\n").first else { return }
@@ -689,32 +682,7 @@
         self.changeConversationIfNeeded(items: selectedContacts)
     }
 
-    func resendMessage(message: MessageViewModel) {
-        guard message.message.type == .text || message.message.type == .fileTransfer else { return }
-        if message.message.type == .text {
-            self.sendMessage(withContent: message.content)
-            return
-        }
-        let conversationId = self.conversation.value.id
-        let accountId = self.conversation.value.accountId
-        var fileName = message.content
-        if message.content.contains("\n") {
-            guard let substring = message.content.split(separator: "\n").first else { return }
-            fileName = String(substring)
-        }
-        let isSwarm = self.conversation.value.isSwarm()
-        if let url = message.transferedFile(conversationID: conversationId, accountId: accountId, isSwarm: isSwarm) {
-            self.sendFile(filePath: url.path, displayName: fileName, contactHash: self.conversation.value.hash)
-            return
-        }
-        if let image = message.getTransferedImage(maxSize: 200, conversationID: conversationId, accountId: accountId, isSwarm: isSwarm) {
-            if let data = image.jpegData(compressionQuality: 100) {
-                self.sendAndSaveFile(displayName: fileName, imageData: data)
-            }
-        }
-    }
-
-    func slectContactsToShareMessage(message: MessageViewModel) {
+    func slectContactsToShareMessage(message: MessageContentVM) {
         guard message.message.type == .text || message.message.type == .fileTransfer else { return }
         self.stateSubject.onNext(ConversationState.showContactPicker(callID: "", contactSelectedCB: {[weak self] (selectedItems) in
             self?.shareMessage(message: message, with: selectedItems)
@@ -738,29 +706,4 @@
             self.dataTransferService.sendAndSaveFile(displayName: displayName, conversation: self.conversation.value, imageData: imageData)
         }
     }
-
-    func acceptTransfer(transferId: String, messageContent: inout String, interactionId: String) -> NSDataTransferError {
-        guard let accountId = accountService.currentAccount?.id else { return .unknown }
-        if !self.conversation.value.isSwarm() {
-            return self.dataTransferService.acceptTransfer(withId: transferId,
-                                                           fileName: &messageContent, accountID: accountId,
-                                                           conversationID: self.conversation.value.id, name: messageContent)
-        }
-        var fileName = ""
-        self.dataTransferService.downloadFile(withId: transferId, interactionID: interactionId, fileName: &fileName, accountID: accountId, conversationID: self.conversation.value.id)
-        return .success
-    }
-
-    func cancelTransfer(transferId: String) -> NSDataTransferError {
-        return self.dataTransferService.cancelTransfer(withId: transferId, accountId: self.conversation.value.accountId, conversationId: self.conversation.value.id)
-    }
-
-    func getTransferProgress(transferId: String, accountId: String) -> Float? {
-        return self.dataTransferService.getTransferProgress(withId: transferId, accountId: accountId, conversationId: self.conversation.value.id, isSwarm: self.conversation.value.isSwarm())
-    }
-
-    func getTransferSize(transferId: String, accountId: String) -> Int64? {
-        guard let info = self.dataTransferService.dataTransferInfo(withId: transferId, accountId: accountId, conversationId: self.conversation.value.id, isSwarm: self.conversation.value.isSwarm()) else { return nil }
-        return info.totalSize
-    }
 }
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageRowModel.swift b/Ring/Ring/Features/Conversations/Conversation/MessageRowModel.swift
new file mode 100644
index 0000000..b90b2c2
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageRowModel.swift
@@ -0,0 +1,41 @@
+//
+//  TestMessageModel.swift
+//  Ring
+//
+//  Created by kateryna on 2022-09-26.
+//  Copyright © 2022 Savoir-faire Linux. All rights reserved.
+//
+
+import Foundation
+
+class MessageRowModel: ObservableObject {
+    @Published var avatarImage: UIImage?
+    @Published var read: [UIImage]? {
+        didSet {
+            if read != nil {
+                print("********read indicator updated")
+            }
+        }
+    }
+    var incoming = false
+    var timeString: String?
+    var getAvatar: ((String) -> Void)?
+    var partisipantId: String = ""
+    var messageId: String = ""
+    var shouldDisplayAavatar = false {
+        didSet {
+            if let getAvatar = self.getAvatar, self.shouldDisplayAavatar, !partisipantId.isEmpty {
+                getAvatar(partisipantId)
+            }
+        }
+    }
+
+    func fetchLastRead() {
+        if let getlastRead = self.getlastRead, !messageId.isEmpty {
+            getlastRead(messageId)
+        }
+    }
+
+    var getlastRead: ((String) -> Void)?
+
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/ConcurrentDictionary.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/ConcurrentDictionary.swift
new file mode 100644
index 0000000..7c6d570
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/ConcurrentDictionary.swift
@@ -0,0 +1,48 @@
+/*
+ *  Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+import Foundation
+
+class ConcurentDictionary {
+    let queue: DispatchQueue
+    private var internalDictionary: [AnyHashable: Any]
+
+    init(name: String, dictionary: [AnyHashable: Any]) {
+        self.queue = DispatchQueue(label: name, qos: .background, attributes: .concurrent)
+        self.internalDictionary = dictionary
+    }
+
+    func get(key: AnyHashable) -> Any? {
+        var returnValue: Any?
+        queue.sync(flags: .barrier) {[weak self] in
+            returnValue = self?.internalDictionary[key]
+        }
+        return returnValue
+    }
+
+    func set(value: Any, for key: AnyHashable) {
+        queue.sync(flags: .barrier) {[weak self] in
+            self?.internalDictionary[key] = value
+        }
+    }
+
+    func filter(_ isIncluded: @escaping (Dictionary<AnyHashable, Any>.Element) throws -> Bool) rethrows -> [AnyHashable: Any]? {
+        return try? self.internalDictionary.filter(isIncluded)
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/ContactMessageVM.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/ContactMessageVM.swift
new file mode 100644
index 0000000..8578ee7
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/ContactMessageVM.swift
@@ -0,0 +1,60 @@
+/*
+ *  Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import Foundation
+import SwiftUI
+import RxSwift
+
+class ContactMessageVM: ObservableObject {
+    @Published var avatarImage: UIImage?
+    @Published var content: String
+    var borderColor: Color
+    var backgroundColor: Color
+    var textColor: Color
+    let cornerRadius: CGFloat = 20
+    var inset: CGFloat
+    var textFont: Font = .body
+
+    var message: MessageModel
+    var username = "" {
+        didSet {
+            DispatchQueue.main.async { [weak self] in
+                guard let self = self else { return }
+                self.content = self.username + " " + self.message.content
+            }
+        }
+    }
+    var infoState: PublishSubject<State>
+
+    init(message: MessageModel, infoState: PublishSubject<State>) {
+        self.message = message
+        self.infoState = infoState
+        self.textColor = Color(UIColor.label)
+        self.backgroundColor = Color(UIColor.clear)
+        self.inset = message.type == .initial ? 0 : 5
+        self.borderColor = message.type == .initial ? Color(UIColor.clear) : Color(UIColor.secondaryLabel)
+        self.content = message.content
+        if message.type == .contact && message.incoming {
+            let jamiId = message.uri.isEmpty ? message.authorId : message.uri
+            self.infoState.onNext(MessageInfo.updateAvatar(jamiId: jamiId))
+            self.infoState.onNext(MessageInfo.updateDisplayname(jamiId: jamiId))
+        }
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageContainerModel.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageContainerModel.swift
new file mode 100644
index 0000000..21dcaf1
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageContainerModel.swift
@@ -0,0 +1,121 @@
+/*
+ *  Copyright (C) 2017-2022 Savoir-faire Linux Inc.
+ *
+ *  Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *  Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *  Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import Foundation
+import RxSwift
+
+class MessageContainerModel: Identifiable {
+    let id: String
+    let messageContent: MessageContentVM
+    let messageRow: MessageRowVM
+    let historyModel: MessageHistoryVM
+    let stackViewModel: MessageStackVM
+    let contactViewModel: ContactMessageVM
+    let message: MessageModel
+    let disposeBag = DisposeBag()
+
+    // message info state
+    private let infoSubject = PublishSubject<State>()
+    lazy var messageInfoState: Observable<State> = {
+        return self.infoSubject.asObservable()
+    }()
+
+    // message transfer state
+    private let transferSubject = PublishSubject<State>()
+    lazy var messageTransferState: Observable<State> = {
+        return self.transferSubject.asObservable()
+    }()
+
+    // context menu state
+    private let contextMenuState: PublishSubject<State>
+
+    var shouldShowTimeString: Bool = false {
+        didSet {
+            self.messageRow.shouldShowTimeString = shouldShowTimeString
+        }
+    }
+
+    var shouldDisplayName: Bool = false {
+        didSet {
+            self.stackViewModel.shouldDisplayName = self.shouldDisplayName
+        }
+    }
+
+    var sequencing: MessageSequencing = .unknown {
+        didSet {
+            self.messageContent.setSequencing(sequencing: sequencing)
+            if sequencing == .lastOfSequence || sequencing == .singleMessage {
+                self.messageRow.shouldDisplayAavatar = true
+            }
+        }
+    }
+
+    init(message: MessageModel, contextMenuState: PublishSubject<State>) {
+        self.id = message.id
+        self.message = message
+        self.contextMenuState = contextMenuState
+        self.historyModel = MessageHistoryVM()
+        self.stackViewModel = MessageStackVM(message: message, infoState: self.infoSubject)
+        self.messageContent = MessageContentVM(message: message, contextMenuState: contextMenuState, transferState: self.transferSubject)
+        self.messageRow = MessageRowVM(message: message, infoState: self.infoSubject)
+        self.contactViewModel = ContactMessageVM(message: message, infoState: self.infoSubject)
+    }
+
+    func updateTransferStatus(status: DataTransferStatus) {
+        DispatchQueue.main.async { [weak self] in
+            guard let self = self else { return }
+            self.messageContent.setTransferStatus(transferStatus: status)
+        }
+    }
+
+    func updateRead(avatars: [UIImage]?) {
+        DispatchQueue.main.async { [weak self] in
+            guard let self = self else { return }
+            self.messageRow.read = avatars
+        }
+    }
+
+    func updateAvatar(image: UIImage) {
+        DispatchQueue.main.async { [weak self, weak image] in
+            guard let self = self, let image = image else { return }
+            if self.messageRow.shouldDisplayAavatar && self.message.incoming {
+                self.messageRow.avatarImage = image
+            }
+            if self.message.type == .contact && self.message.incoming {
+                self.contactViewModel.avatarImage = image
+            }
+        }
+    }
+
+    func updateUsername(name: String) {
+        DispatchQueue.main.async { [weak self] in
+            guard let self = self else { return }
+            if self.stackViewModel.shouldDisplayName && self.message.incoming {
+                self.stackViewModel.username = name
+            }
+            if self.message.type == .contact && self.message.incoming {
+                self.contactViewModel.username = name
+            }
+        }
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageContentVM.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageContentVM.swift
new file mode 100644
index 0000000..ca8392a
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageContentVM.swift
@@ -0,0 +1,465 @@
+/*
+ *  Copyright (C) 2017-2022 Savoir-faire Linux Inc.
+ *
+ *  Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *  Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *  Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import Foundation
+import SwiftUI
+import RxSwift
+import LinkPresentation
+
+enum TransferViewType: Equatable {
+    case playerView(player: PlayerViewModel)
+    case imageView(image: UIImage)
+    case defaultView
+
+    func toString() -> String {
+        switch self {
+        case .playerView:
+            return "playerView"
+        case .imageView:
+            return "imageView"
+        case .defaultView:
+            return "defaultView"
+        }
+    }
+
+    static func == (lhs: TransferViewType, rhs: TransferViewType) -> Bool {
+        return lhs.toString() == rhs.toString()
+    }
+}
+
+enum TransferAction: Identifiable {
+    var id: Self { self }
+
+    case accept
+    case cancel
+
+    func toString() -> String {
+        switch self {
+        case .accept:
+            return "Accept"
+        case .cancel:
+            return "Cancel"
+        }
+    }
+}
+
+enum ContextualMenuItem: Identifiable {
+    var id: Self { self }
+
+    case preview
+    case forward
+    case share
+    case save
+    case copy
+    case reply
+
+    func toString() -> String {
+        switch self {
+        case .preview:
+            return "Preview"
+        case .forward:
+            return "Forward"
+        case .share:
+            return "Share"
+        case .save:
+            return "Save"
+        case .copy:
+            return "Copy"
+        case .reply:
+            return "Reply"
+        }
+    }
+
+    func image() -> String {
+        switch self {
+        case .preview:
+            return "arrow.up.left.and.arrow.down.right"
+        case .forward:
+            return "arrowshape.turn.up.right"
+        case .share:
+            return "square.and.arrow.up"
+        case .save:
+            return "square.and.arrow.down"
+        case .copy:
+            return "doc.on.doc"
+        case .reply:
+            return "arrowshape.turn.up.left"
+        }
+    }
+}
+
+class MessageContentVM: ObservableObject, PreviewViewControllerDelegate {
+
+    @Published var content = ""
+    @Published var metadata: LPLinkMetadata?
+
+    // file transfer
+    @Published var fileName: String = ""
+    @Published var fileInfo: String = ""
+    @Published var fileProgress: CGFloat = 0
+    @Published var transferActions = [TransferAction]()
+    @Published var showProgress: Bool = true
+    var transferViewType: TransferViewType = .defaultView
+    var shouldUpdateTransferView = true
+    @Published var playerHeight: CGFloat = 100
+    @Published var playerWidth: CGFloat = 250
+    var image: UIImage? {
+        didSet {
+            updateTransferViewType()
+        }
+    }
+    var player: PlayerViewModel? {
+        didSet {
+            if player != nil {
+                self.player!.delegate = self
+                self.updateTransferViewType()
+            }
+        }
+    }
+    var url: URL?
+    var fileSize: Int64 = 0
+    var transferStatus: DataTransferStatus = .unknown
+    var dataTransferProgressUpdater: Timer?
+
+    @Published var menuItems = [ContextualMenuItem]()
+
+    // view parameters
+    var borderColor: Color
+    var backgroundColor: Color
+    var textColor: Color
+    var secondaryColor: Color
+    var hasBorder: Bool
+    var corners: UIRectCorner = .allCorners
+    let cornerRadius: CGFloat = 15
+    var textInset: CGFloat = 15
+    var textVerticalInset: CGFloat = 10
+    var textFont: Font = .body
+
+    var message: MessageModel
+    var isIncoming: Bool
+    var isHistory: Bool
+    var type: MessageType = .text
+
+    private var sequencing: MessageSequencing = .unknown {
+        didSet {
+            guard !isHistory else { return }
+            guard type == .text else { return }
+            switch sequencing {
+            case .firstOfSequence:
+                self.corners = isIncoming ? [.topLeft, .topRight, .bottomRight] : [.topLeft, .topRight, .bottomLeft]
+            case .lastOfSequence:
+                self.corners = isIncoming ? [.topRight, .bottomLeft, .bottomRight] : [.topLeft, .bottomLeft, .bottomRight]
+            case .middleOfSequence:
+                self.corners = isIncoming ? [.topRight, .bottomRight] : [.topLeft, .bottomLeft ]
+            case .singleMessage:
+                corners = [.allCorners]
+            case .unknown:
+                break
+            }
+            // text need to be updated to trigger view redrowing
+            let oldContent = self.content
+            self.content = oldContent
+        }
+    }
+
+    // state
+    var contextMenuState: PublishSubject<State>
+    var transferState: PublishSubject<State>
+
+    required init(message: MessageModel, contextMenuState: PublishSubject<State>, transferState: PublishSubject<State>) {
+        self.contextMenuState = contextMenuState
+        self.transferState = transferState
+        self.message = message
+        self.type = message.type
+        self.isIncoming = message.incoming
+        self.isHistory = false
+        self.content = message.content
+        self.transferStatus = message.transferStatus
+        self.secondaryColor = Color(UIColor.secondaryLabel)
+        if isHistory {
+            self.sequencing = .firstOfSequence
+            self.borderColor = Color(.secondaryLabel)
+            self.textColor = Color(.secondaryLabel)
+            self.hasBorder = true
+            self.backgroundColor = Color(.white)
+        } else {
+            self.textColor = isIncoming ? Color(UIColor.label) : Color(.white)
+            self.backgroundColor = isIncoming ? Color(.jamiMsgCellReceived) : Color(.jamiMsgCellSent)
+            self.hasBorder = false
+            self.borderColor = Color(.clear)
+        }
+        if self.content.containsOnlyEmoji {
+            self.backgroundColor = .clear
+            self.textFont = Font(UIFont.systemFont(ofSize: 40.0, weight: UIFont.Weight.medium))
+            self.textInset = 0
+        }
+        if self.type == .fileTransfer {
+            self.fileName = message.content
+            self.textColor = Color(UIColor.label)
+            self.updateTransferInfo()
+        }
+        if self.type == .contact {
+            self.sequencing = .firstOfSequence
+            self.hasBorder = true
+            self.textColor = Color(UIColor.label)
+            self.backgroundColor = Color(UIColor.clear)
+            self.borderColor = Color(UIColor.secondaryLabel)
+        }
+        self.fetchMetadata()
+    }
+
+    func setSequencing(sequencing: MessageSequencing) {
+        if self.sequencing != sequencing {
+            DispatchQueue.main.async {[weak self] in
+                guard let self = self else { return }
+                self.sequencing = sequencing
+            }
+        }
+    }
+
+    func setTransferStatus(transferStatus: DataTransferStatus) {
+        if self.transferStatus != transferStatus {
+            self.transferStatus = transferStatus
+            self.updateTransferInfo()
+        }
+    }
+
+    private func fetchMetadata() {
+        guard self.type == .text, self.content.isValidURL, let url = URL(string: self.content) else { return }
+        LPMetadataProvider().startFetchingMetadata(for: url) {(metaDataObj, error) in
+            DispatchQueue.main.async { [weak self, weak metaDataObj] in
+                guard let self = self else { return }
+                guard error == nil, let metaDataObj = metaDataObj else {
+                    return
+                }
+                self.metadata = metaDataObj
+            }
+        }
+    }
+
+    private func updateMenuitems() {
+        if self.type == .text {
+            self.menuItems = [.copy, .forward]
+        }
+        guard self.type == .fileTransfer else { return }
+        if self.url != nil {
+            if self.image != nil {
+                self.menuItems = [.save, .forward, .preview, .share]
+            } else {
+                self.menuItems = [.forward, .preview, .share]
+            }
+        }
+    }
+
+    // MARK: file transfer
+
+    private func updateTransferInfo() {
+        self.updateTransferActions()
+        self.updateFileDescription()
+        self.updateMenuitems()
+        if self.fileSize == 0 {
+            self.transferState.onNext(TransferState.getSize(viewModel: self))
+        }
+        if self.transferStatus == .ongoing {
+            self.startProgressMonitor()
+        } else {
+            self.stopProgressMonitor()
+        }
+        if (self.transferStatus == .success || !self.message.incoming), self.image == nil, self.player == nil {
+            self.transferState.onNext(TransferState.getImage(viewModel: self))
+            self.transferState.onNext(TransferState.getPlayer(viewModel: self))
+        }
+        if (self.transferStatus == .success || !self.message.incoming), self.url == nil {
+            self.transferState.onNext(TransferState.getURL(viewModel: self))
+        }
+        self.updateTransferViewType()
+    }
+
+    private func updateTransferViewType() {
+        guard self.message.type == .fileTransfer else { return }
+        var newType: TransferViewType = .defaultView
+        if let image = self.image {
+            newType = .imageView(image: image)
+        } else if let player = self.player {
+            newType = .playerView(player: player)
+        }
+        if newType != self.transferViewType {
+            DispatchQueue.main.async {[weak self] in
+                guard let self = self else { return }
+                self.transferViewType = newType
+                self.shouldUpdateTransferView = true
+            }
+        }
+    }
+
+    private func updateTransferActions() {
+        DispatchQueue.main.async {
+            switch self.transferStatus {
+            case .created, .awaiting:
+                self.transferActions = !self.isIncoming ? [.cancel] : [.accept, .cancel]
+            case .ongoing:
+                self.transferActions = [.cancel]
+            case .success, .error, .unknown, .canceled:
+                self.transferActions = [TransferAction]()
+            }
+        }
+    }
+
+    private func updateFileDescription() {
+        DispatchQueue.main.async {[weak self] in
+            guard let self = self else { return }
+            if self.fileSize == 0 {
+                self.fileInfo = self.transferStatus.description
+            } else {
+                self.fileInfo = self.getFileSizeString(bytes: self.fileSize) + " - " + self.transferStatus.description
+            }
+        }
+    }
+
+    private func getFileSizeString(bytes: Int64) -> String {
+        if bytes < 1024 {
+            return "\(bytes) bytes"
+        } else if bytes < 1024 * 1024 {
+            return "\(bytes / 1024) KB"
+        } else if bytes < 1024 * 1024 * 1024 {
+            return "\(bytes / (1024 * 1024)) MB"
+        }
+        return "\(bytes / (1024 * 1024 * 1024)) GB"
+    }
+
+    private func startProgressMonitor() {
+        self.showProgress = true
+        if self.dataTransferProgressUpdater != nil {
+            return
+        }
+        if self.message.incoming {
+            self.dataTransferProgressUpdater = Timer
+                .scheduledTimer(timeInterval: 0.5,
+                                target: self,
+                                selector: #selector(self.updateProgressBar),
+                                userInfo: nil,
+                                repeats: true)
+        }
+    }
+
+    private func stopProgressMonitor() {
+        self.showProgress = false
+        guard let updater = self.dataTransferProgressUpdater else { return }
+        updater.invalidate()
+        self.dataTransferProgressUpdater = nil
+    }
+
+    @objc
+    func updateProgressBar(timer: Timer) {
+        self.transferState.onNext(TransferState.getProgress(viewModel: self))
+    }
+
+    func extractedVideoFrame(with height: CGFloat) {
+        if let player = player, let firstImage = player.firstFrame,
+           let frameSize = firstImage.getNewSize(of: CGSize(width: maxDimension, height: maxDimension)) {
+            playerHeight = frameSize.height
+            playerWidth = frameSize.width
+        } else {
+            playerHeight = height
+        }
+    }
+
+    var maxDimension: CGFloat {
+        let screenWidth = UIScreen.main.bounds.width
+        // iPhone 5 width
+        if screenWidth <= 320 {
+            return 200
+            // iPhone 6, iPhone 6 Plus and iPhone XR width
+        } else if screenWidth > 320 && screenWidth <= 414 {
+            return 250
+            // iPad width
+        } else if screenWidth > 414 {
+            return 300
+        }
+        return 250
+    }
+
+    // MARK: actions received from UI
+
+    func onAppear() {
+        if self.type == .fileTransfer {
+            self.transferState.onNext(TransferState.getImage(viewModel: self))
+            self.transferState.onNext(TransferState.getPlayer(viewModel: self))
+            self.transferState.onNext(TransferState.getURL(viewModel: self))
+        }
+        updateMenuitems()
+    }
+
+    func transferAction(action: TransferAction) {
+        switch action {
+        case .accept:
+            self.transferState.onNext(TransferState.accept(viewModel: self))
+        case .cancel:
+            self.transferState.onNext(TransferState.cancel(viewModel: self))
+        }
+    }
+
+    func contextMenuSelect(item: ContextualMenuItem) {
+        switch item {
+        case .copy:
+            UIPasteboard.general.string = self.content
+        case .preview:
+            self.contextMenuState.onNext(ContextMenu.preview(message: self))
+        case .forward:
+            self.contextMenuState.onNext(ContextMenu.forward(message: self))
+        case .share:
+            let item: Any? = self.url != nil ? self.url : self.image
+            guard let item = item else {
+                return
+            }
+            self.contextMenuState.onNext(ContextMenu.share(items: [item]))
+        case .save:
+            guard let image = self.image else { return }
+            self.contextMenuState.onNext(ContextMenu.save(image: image))
+        case .reply:
+            break
+        // self.contextMenuState.onNext(ContextMenu.reply(messageId: self.message.id))
+        }
+    }
+}
+
+extension MessageContentVM: PlayerDelegate {
+    func deleteFile() {}
+
+    func shareFile() {
+        let item: Any? = self.url != nil ? self.url : self.image
+        guard let item = item else {
+            return
+        }
+        self.contextMenuState.onNext(ContextMenu.share(items: [item]))
+    }
+
+    func forwardFile() {
+        self.contextMenuState.onNext(ContextMenu.forward(message: self))
+    }
+
+    func saveFile() {
+        guard let image = self.image else { return }
+        self.contextMenuState.onNext(ContextMenu.save(image: image))
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageHistoryVM.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageHistoryVM.swift
new file mode 100644
index 0000000..681b217
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageHistoryVM.swift
@@ -0,0 +1,13 @@
+//
+//  MessageHistoryModel.swift
+//  Ring
+//
+//  Created by kateryna on 2022-11-07.
+//  Copyright © 2022 Savoir-faire Linux. All rights reserved.
+//
+
+import Foundation
+
+class MessageHistoryVM {
+
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageRowVM.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageRowVM.swift
new file mode 100644
index 0000000..4937295
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageRowVM.swift
@@ -0,0 +1,89 @@
+/*
+ *  Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ *  Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *  Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *  Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import Foundation
+import RxSwift
+
+class MessageRowVM: ObservableObject {
+    @Published var avatarImage: UIImage?
+    @Published var read: [UIImage]?
+    @Published var timeString: String = ""
+    var incoming: Bool
+    var infoState: PublishSubject<State>
+    var centeredMessage: Bool
+
+    var message: MessageModel
+
+    var shouldShowTimeString = false {
+        didSet {
+            self.timeString = self.shouldShowTimeString ? self.getTimeLabelString() : ""
+        }
+    }
+
+    var shouldDisplayAavatar = false {
+        didSet {
+            let jamiId = message.uri.isEmpty ? message.authorId : message.uri
+            if self.shouldDisplayAavatar {
+                self.infoState.onNext(MessageInfo.updateAvatar(jamiId: jamiId))
+            }
+        }
+    }
+
+    func fetchLastRead() {
+        self.infoState.onNext(MessageInfo.updateRead(messageId: self.message.id))
+    }
+
+    init(message: MessageModel, infoState: PublishSubject<State>) {
+        self.message = message
+        self.incoming = message.incoming
+        self.infoState = infoState
+        self.centeredMessage = message.type == .contact || message.type == .initial
+        self.timeString = getTimeLabelString()
+    }
+
+    func getTimeLabelString() -> String {
+        let time = self.message.receivedDate
+        // get the current time
+        let currentDateTime = Date()
+
+        // prepare formatter
+        let dateFormatter = DateFormatter()
+
+        if Calendar.current.compare(currentDateTime, to: time, toGranularity: .day) == .orderedSame {
+            // age: [0, received the previous day[
+            dateFormatter.dateFormat = "h:mma"
+        } else if Calendar.current.compare(currentDateTime, to: time, toGranularity: .weekOfYear) == .orderedSame {
+            // age: [received the previous day, received 7 days ago[
+            dateFormatter.dateFormat = "E h:mma"
+        } else if Calendar.current.compare(currentDateTime, to: time, toGranularity: .year) == .orderedSame {
+            // age: [received 7 days ago, received the previous year[
+            dateFormatter.dateFormat = "MMM d, h:mma"
+        } else {
+            // age: [received the previous year, inf[
+            dateFormatter.dateFormat = "MMM d, yyyy h:mma"
+        }
+
+        // generate the string containing the message time
+        return dateFormatter.string(from: time).uppercased()
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageStackVM.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageStackVM.swift
new file mode 100644
index 0000000..fe12a23
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessageStackVM.swift
@@ -0,0 +1,51 @@
+/*
+ *  Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import Foundation
+import SwiftUI
+import RxSwift
+
+class MessageStackVM {
+    @Published var username = ""
+    var horizontalAllignment: HorizontalAlignment {
+        self.message.incoming ? HorizontalAlignment.leading : HorizontalAlignment.trailing
+    }
+    var alignment: Alignment {
+        self.message.incoming ? Alignment.leading : Alignment.trailing
+    }
+    var message: MessageModel
+
+    var infoState: PublishSubject<State>
+
+    @Published var shouldDisplayName = false {
+        didSet {
+            let jamiId = message.uri.isEmpty ? message.authorId : message.uri
+            if shouldDisplayName {
+                self.infoState.onNext(MessageInfo.updateDisplayname(jamiId: jamiId))
+            }
+        }
+    }
+
+    init(message: MessageModel, infoState: PublishSubject<State>) {
+        self.message = message
+        self.infoState = infoState
+    }
+
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessagesListVM.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessagesListVM.swift
new file mode 100644
index 0000000..6024abb
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/MessagesListVM.swift
@@ -0,0 +1,593 @@
+/*
+ *  Copyright (C) 2017-2022 Savoir-faire Linux Inc.
+ *
+ *  Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *  Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *  Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import Foundation
+import RxSwift
+
+enum MessageInfo: State {
+    case updateAvatar(jamiId: String)
+    case updateRead(messageId: String)
+    case updateDisplayname(jamiId: String)
+}
+
+class MessagesListVM: ObservableObject {
+
+    private let contextStateSubject = PublishSubject<State>()
+    lazy var contextMenuState: Observable<State> = {
+        return self.contextStateSubject.asObservable()
+    }()
+
+    let disposeBag = DisposeBag()
+
+    @Published var messagesModels = [MessageContainerModel]()
+    @Published var needScroll = false
+    var lastMessageOnScreen = ""
+    var visibleRows: Set = [""]
+
+    var conversation: ConversationModel
+
+    var accountService: AccountsService
+    var profileService: ProfilesService
+    var dataTransferService: DataTransferService
+    var conversationService: ConversationsService
+    var contactsService: ContactsService
+    var nameService: NameService
+
+    var avatars = ConcurentDictionary(name: "com.AvatarsAccesDictionary", dictionary: [String: UIImage]())
+    var names = ConcurentDictionary(name: "com.NamesAccesDictionary", dictionary: [String: UIImage]())
+    // last read
+    // dictionary of participant id and last read message Id
+    var lastReadMessageForParticipant = ConcurentDictionary(name: "com.ReadMessageForParticipantAccesDictionary",
+                                                            dictionary: [String: String]())
+    // dictionary of message id and array of participants for whom the message is last read
+    var lastRead = ConcurentDictionary(name: "com.lastReadAccesDictionary",
+                                       dictionary: [String: [String: UIImage]]())
+
+    var transferHelper: TransferHelper
+
+    init (injectionBag: InjectionBag, conversation: ConversationModel, transferHelper: TransferHelper) {
+        self.conversation = conversation
+        self.accountService = injectionBag.accountService
+        self.profileService = injectionBag.profileService
+        self.dataTransferService = injectionBag.dataTransferService
+        self.conversationService = injectionBag.conversationsService
+        self.contactsService = injectionBag.contactsService
+        self.nameService = injectionBag.nameService
+        self.transferHelper = transferHelper
+        for message in conversation.messages {
+            _ = insert(message: message)
+        }
+        self.computeSequencing()
+        self.updateLastDisplayed()
+        self.lastMessageOnScreen = self.messagesModels.last?.message.id ?? ""
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
+            guard let self = self else { return }
+            self.loading = false
+            if self.messagesModels.count < 40 {
+                self.loadMore()
+            } else {
+                self.needScroll = true
+            }
+        }
+        conversation.newMessages.share()
+            .observe(on: MainScheduler.instance)
+            .subscribe { [weak self] messages in
+                guard let self = self else { return }
+                var insertionCount = 0
+                for newMessage in messages where self.insert(message: newMessage) == true {
+                    insertionCount += 1
+                }
+                if insertionCount == 0 {
+                    return
+                }
+                self.computeSequencing()
+                if self.shouldScroll() {
+                    if !self.loading {
+                        self.lastMessageOnScreen = self.messagesModels.last?.message.id ?? ""
+                    }
+                    self.needScroll = true
+                }
+                DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+                    self.loading = false
+                }
+            } onError: { _ in
+
+            }
+            .disposed(by: self.disposeBag)
+        self.subscribeMessagesStatus()
+    }
+
+    private func insert(message: MessageModel) -> Bool {
+        if self.messagesModels.contains(where: { messageModel in
+            messageModel.message.id == message.id
+        }) { return false}
+        let container = MessageContainerModel(message: message, contextMenuState: self.contextStateSubject)
+        self.subscribeMessage(container: container)
+        if let index = self.messagesModels.firstIndex(where: { message in
+            message.message.parentId == message.id
+        }) {
+            if index > 1 {
+                self.messagesModels.insert(container, at: index - 1)
+            } else {
+                self.messagesModels.insert(container, at: 0)
+            }
+        } else if let parentIndex = self.messagesModels.firstIndex(where: { messageModel in
+            messageModel.message.id == message.parentId
+        }) {
+            if parentIndex > self.messagesModels.count - 1 {
+                self.messagesModels.insert(container, at: parentIndex + 1)
+            } else {
+                self.messagesModels.append(container)
+            }
+        } else {
+            self.messagesModels.insert(container, at: 0)
+            conversation.unorderedInteractions.append(message.id)
+        }
+        return true
+    }
+
+    /**
+     move child interaction when found parent interaction
+     */
+    private func moveInteraction(interactionId: String, after parentId: String) {
+        if let index = messagesModels.firstIndex(where: { messge in
+            messge.id == interactionId
+        }), let parentIndex = messagesModels.firstIndex(where: { messge in
+            messge.id == parentId
+        }) {
+            if index == parentIndex + 1 {
+                /// alredy on right place
+                return
+            }
+            if parentIndex < messagesModels.count - 1 {
+                let interactionToMove = messagesModels[index]
+                if index < messagesModels.count - 1 {
+                    /// if interaction we are going to move is parent for next interaction we should move next interaction as well
+                    let nextInteraction = messagesModels[index + 1]
+                    let moveNextInteraction = interactionToMove.id == nextInteraction.message.parentId
+                    messagesModels.insert(messagesModels.remove(at: index), at: parentIndex + 1)
+                    if !moveNextInteraction {
+                        return
+                    }
+                    moveInteraction(interactionId: nextInteraction.id, after: interactionToMove.id)
+                } else {
+                    /// message we are going to move is last in the list, we do not need to check child interactions
+                    messagesModels.insert(messagesModels.remove(at: index), at: parentIndex + 1)
+                }
+            } else if parentIndex == messagesModels.count - 1 {
+                let interactionToMove = messagesModels[index]
+                let nextInteraction = messagesModels[index + 1]
+                let moveNextInteraction = interactionToMove.id == nextInteraction.message.parentId
+                messagesModels.append(messagesModels.remove(at: index))
+                if !moveNextInteraction {
+                    return
+                }
+                moveInteraction(interactionId: nextInteraction.id, after: interactionToMove.id)
+            }
+        }
+    }
+
+    // swiftlint:disable cyclomatic_complexity
+    private func subscribeMessage(container: MessageContainerModel) {
+        if container.message.type == .fileTransfer {
+            self.conversationService
+                .sharedResponseStream
+                .filter({ [weak container] (transferEvent) in
+                    guard let container = container,
+                          let transferId: String = transferEvent.getEventInput(ServiceEventInput.transferId) else { return false }
+                    return  transferEvent.eventType == ServiceEventType.dataTransferMessageUpdated &&
+                        container.message.daemonId == transferId
+                })
+                .subscribe(onNext: { [weak container] transferEvent in
+                    guard let container = container,
+                          let transferStatus: DataTransferStatus = transferEvent.getEventInput(ServiceEventInput.state) else {
+                        return
+                    }
+                    container.message.transferStatus = transferStatus
+                    container.messageContent.setTransferStatus(transferStatus: transferStatus)
+                })
+                .disposed(by: container.disposeBag)
+        }
+        container.messageInfoState.subscribe { [weak self, weak container] state in
+            guard let self = self, let container = container, let state = state as? MessageInfo else { return }
+            switch state {
+            case .updateAvatar(let jamiId):
+                if let avatar = self.avatars.get(key: jamiId) as? UIImage {
+                    container.updateAvatar(image: avatar)
+                } else {
+                    self.getInformationForContact(id: jamiId, message: container)
+                }
+            case .updateRead(let messageId):
+                if let lastReadAvatars = self.lastRead.get(key: messageId) as? [String: UIImage] {
+                    let values: [UIImage] = lastReadAvatars.map { value in
+                        return value.value
+                    }
+                    let newValue = values.isEmpty ? nil : values
+                    container.updateRead(avatars: newValue)
+                } else {
+                    self.updateLastRead(messageId: messageId, messageModel: container)
+                }
+            case .updateDisplayname(let jamiId):
+                if let name = self.names.get(key: jamiId) as? String {
+                    container.updateUsername(name: name)
+                } else {
+                    self.getInformationForContact(id: jamiId, message: container)
+                }
+            }
+        } onError: { _ in
+        }
+        .disposed(by: container.disposeBag)
+        container.messageTransferState.subscribe { [weak self] state in
+            guard let self = self, let state = state as? TransferState else { return }
+            switch state {
+            case .accept(let viewModel):
+                _ = self.transferHelper.acceptTransfer(conversation: self.conversation, message: viewModel.message)
+            case .cancel(let viewModel):
+                _ = self.transferHelper.cancelTransfer(conversation: self.conversation, message: viewModel.message)
+            case .getProgress(let viewModel):
+                if let progress = self.transferHelper.getTransferProgress(conversation: self.conversation, message: viewModel.message) {
+                    viewModel.fileProgress = CGFloat(progress)
+                }
+            case .getSize(let viewModel):
+                if let size = self.transferHelper.getTransferSize(conversation: self.conversation, message: viewModel.message) {
+                    viewModel.fileSize = size
+                }
+            case .getImage(let viewModel):
+                if viewModel.image != nil { return }
+                viewModel.image = self.transferHelper.getTransferedImage(maxSize: 450, conversation: self.conversation, message: viewModel.message)
+            case .getURL(let viewModel):
+                if viewModel.url != nil { return }
+                viewModel.url = self.transferHelper.getFileURL(conversation: self.conversation, message: viewModel.message)
+            case .getPlayer(let viewModel):
+                if viewModel.player != nil { return }
+                viewModel.player = self.transferHelper.getPlayer(conversation: self.conversation, message: viewModel.message)
+            }
+        } onError: { _ in
+        }
+        .disposed(by: container.disposeBag)
+    }
+
+    // MARK: last read message
+
+    private func subscribeMessagesStatus() {
+        self.conversationService
+            .sharedResponseStream
+            .filter({ messageUpdateEvent in
+                return messageUpdateEvent.eventType == ServiceEventType.messageStateChanged
+            })
+            .subscribe(onNext: { [weak self] messageUpdateEvent in
+                if let status: MessageStatus = messageUpdateEvent.getEventInput(.messageStatus) {
+                    if status == .displayed, let jamiId: String = messageUpdateEvent.getEventInput(.uri),
+                       let messageId: String = messageUpdateEvent.getEventInput(.messageId),
+                       let localParticipant = self?.conversation.getLocalParticipants(),
+                       localParticipant.jamiId != jamiId {
+                        var currentid: String?
+                        if let current = self?.lastReadMessageForParticipant.get(key: jamiId) as? String {
+                            currentid = current
+                        }
+                        self?.lastReadMessageForParticipant.set(value: messageId, for: jamiId)
+                        if let model = self?.messagesModels.filter({ message in
+                            message.id == messageId
+                        }).first, !model.message.incoming {
+                            self?.updateLastRead(messageId: messageId, messageModel: model)
+                        }
+                        if let currentid = currentid, let message1 = self?.messagesModels.filter({ message2 in
+                            message2.id == currentid
+                        }).first, !message1.message.incoming {
+                            self?.updateLastRead(messageId: message1.id, messageModel: message1)
+                        }
+                    }
+                }
+            })
+            .disposed(by: self.disposeBag)
+    }
+
+    private func updateLastDisplayed() {
+        for participant in self.conversation.getParticipants() {
+            self.lastReadMessageForParticipant.set(value: participant.lastDisplayed, for: participant.jamiId)
+        }
+    }
+
+    // MARK: view actions
+
+    func messagesAddedToScreen(messageId: String) {
+        self.visibleRows.insert(messageId)
+        if self.messagesModels.first?.id == messageId {
+            self.loadMore()
+        }
+    }
+    func messagesremovedFromScreen(messageId: String) {
+        if let index = visibleRows.firstIndex(of: messageId) {
+            visibleRows.remove(at: index)
+        }
+    }
+
+    // MARK: loading
+
+    func scrollIfNeed() {
+        if shouldScroll() {
+            self.needScroll = true
+        }
+    }
+
+    private func shouldScroll() -> Bool {
+
+        /*
+         scroll should be performed in two cases:
+         1. when loadin more messages
+         2. when a new message received while previous last message for conversation
+         was visible on the screen
+         */
+
+        if visibleRows.isEmpty || self.loading { return true }
+
+        // check if previous message was visible on screen
+        if self.messagesModels.count < 3 {
+            return true
+        }
+        let previousMessage = self.messagesModels[self.messagesModels.count - 2]
+        return visibleRows.contains(previousMessage.message.id)
+    }
+
+    var loading = true
+
+    private func sortVisibleRows() -> [String] {
+        var temporary = [String: Int]()
+        for row in visibleRows {
+            if row == "" {
+                continue
+            }
+            let index = messagesModels.firstIndex { message in
+                message.id == row
+            }!
+            temporary[row] = index
+        }
+        let sorted = temporary.sorted { firstRow, secondRow in
+            firstRow.value < secondRow.value
+        }
+        .map { element in
+            return element.key
+        }
+        return sorted
+    }
+
+    private func allLoaded() -> Bool {
+        guard let firstMessage = self.messagesModels.first else { return false }
+        return firstMessage.message.parentId.isEmpty
+    }
+
+    private func updateLastVisibleRow() {
+        let sortedRows = sortVisibleRows()
+        if sortedRows.count > 2 {
+            self.lastMessageOnScreen = sortedRows[sortedRows.count - 2]
+        } else  if sortedRows.count > 1 {
+            self.lastMessageOnScreen = sortedRows[sortedRows.count - 1]
+        } else if let lastRow = sortedRows.last {
+            self.lastMessageOnScreen = lastRow
+        }
+    }
+
+    private func loadMore() {
+        if self.loading || self.allLoaded() {
+            return
+        }
+        self.updateLastVisibleRow()
+        if let messageId = self.messagesModels.first?.id {
+            self.conversationService
+                .loadConversationMessages(conversationId: self.conversation.id,
+                                          accountId: self.conversation.accountId,
+                                          from: messageId)
+            self.loading = true
+        }
+    }
+
+    // MARK: sequencing
+
+    private func computeSequencing() {
+        var lastMessageTime: Date?
+        for (index, model) in self.messagesModels.enumerated() {
+            let currentMessageTime = model.message.receivedDate
+            if index == 0 || model.message.type != .text {
+                // always show first message's time
+                model.shouldShowTimeString = true
+            } else {
+                // only show time for new messages if beyond an arbitrary time frame from the previously shown time
+                let timeDifference = currentMessageTime.timeIntervalSinceReferenceDate - lastMessageTime!.timeIntervalSinceReferenceDate
+                model.shouldShowTimeString = Int(timeDifference) < messageGroupingInterval ? false : true
+            }
+            lastMessageTime = currentMessageTime
+        }
+        for (index, model) in self.messagesModels.enumerated() {
+            model.sequencing = getMessageSequencing(forIndex: index)
+            if model.sequencing == .firstOfSequence || model.sequencing == .singleMessage {
+                model.shouldDisplayName = true
+            } else {
+                model.shouldDisplayName = false
+            }
+        }
+    }
+
+    private let messageGroupingInterval = 10 * 60 // 10 minutes
+
+    // swiftlint:disable cyclomatic_complexity
+    private func getMessageSequencing(forIndex index: Int) -> MessageSequencing {
+        let messageItem = self.messagesModels[index]
+        let msgOwner = messageItem.message.incoming
+        if self.messagesModels.count == 1 || index == 0 {
+            if self.messagesModels.count == index + 1 {
+                return MessageSequencing.singleMessage
+            }
+            let nextMessageItem = index + 1 <= self.messagesModels.count
+                ? self.messagesModels[index + 1] : nil
+            if nextMessageItem != nil {
+                return msgOwner != nextMessageItem?.message.incoming
+                    ? MessageSequencing.singleMessage : MessageSequencing.firstOfSequence
+            }
+        } else if self.messagesModels.count == index + 1 {
+            let lastMessageItem = index - 1 >= 0 && index - 1 < self.messagesModels.count
+                ? self.messagesModels[index - 1] : nil
+            if lastMessageItem != nil {
+                return msgOwner != lastMessageItem?.message.incoming
+                    ? MessageSequencing.singleMessage : MessageSequencing.lastOfSequence
+            }
+        }
+        let lastMessageItem = index - 1 >= 0 && index - 1 < self.messagesModels.count
+            ? self.messagesModels[index - 1] : nil
+        let nextMessageItem = index + 1 <= self.messagesModels.count
+            ? self.messagesModels[index + 1] : nil
+        var sequencing = MessageSequencing.singleMessage
+        if (lastMessageItem != nil) && (nextMessageItem != nil) {
+            if msgOwner != lastMessageItem?.message.incoming && msgOwner == nextMessageItem?.message.incoming {
+                sequencing = MessageSequencing.firstOfSequence
+            } else if msgOwner != nextMessageItem?.message.incoming && msgOwner == lastMessageItem?.message.incoming {
+                sequencing = MessageSequencing.lastOfSequence
+            } else if msgOwner == nextMessageItem?.message.incoming && msgOwner == lastMessageItem?.message.incoming {
+                sequencing = MessageSequencing.middleOfSequence
+            }
+        }
+        if messageItem.shouldShowTimeString {
+            if index == messagesModels.count - 1 {
+                sequencing = .singleMessage
+            } else if sequencing != .singleMessage && sequencing != .lastOfSequence {
+                sequencing = .firstOfSequence
+            } else {
+                sequencing = .singleMessage
+            }
+        }
+
+        if index + 1 < messagesModels.count && messagesModels[index + 1].shouldShowTimeString {
+            switch sequencing {
+            case .firstOfSequence: sequencing = .singleMessage
+            case .middleOfSequence: sequencing = .lastOfSequence
+            default: break
+            }
+        }
+        return sequencing
+    }
+
+    // MARK: participant information
+
+    private func updateName(name: String, id: String, message: MessageContainerModel) {
+        self.names.set(value: name, for: id)
+        message.updateUsername(name: name)
+    }
+
+    private func updateAvatar(image: UIImage, id: String, message: MessageContainerModel) {
+        self.avatars.set(value: image, for: id)
+        message.updateAvatar(image: image)
+        if var lastReadAvatars = self.lastRead.get(key: message.id) as? [String: UIImage] {
+            if var _ = lastReadAvatars[id] {
+                lastReadAvatars[id] = image
+                self.lastRead.set(value: lastReadAvatars, for: message.id)
+                let values: [UIImage] = lastReadAvatars.map { value in
+                    return value.value
+                }
+                let newValue = values.isEmpty ? nil : values
+                message.updateRead(avatars: newValue)
+
+            }
+        }
+    }
+
+    private func nameLookup(id: String, message: MessageContainerModel) {
+        self.nameService.usernameLookupStatus
+            .filter({ lookupNameResponse in
+                return lookupNameResponse.address != nil &&
+                    lookupNameResponse.address == id
+            })
+            .asObservable()
+            .take(1)
+            .subscribe(onNext: { [weak self, weak message] lookupNameResponse in
+                guard let self = self, let message = message else { return }
+                // if we have a registered name then we should update the value for it
+                if let name = lookupNameResponse.name, !name.isEmpty {
+                    self.updateName(name: name, id: id, message: message)
+                } else {
+                    self.updateName(name: id, id: id, message: message)
+                }
+                if let username = self.names.get(key: id) as? String {
+                    let image = UIImage.createContactAvatar(username: username, size: CGSize(width: 30, height: 30))
+                    self.updateAvatar(image: image, id: id, message: message)
+                }
+            })
+            .disposed(by: message.disposeBag)
+        self.nameService.lookupAddress(withAccount: self.conversation.accountId, nameserver: "", address: id)
+    }
+
+    private func getInformationForContact(id: String, message: MessageContainerModel) {
+        guard let account = self.accountService.getAccount(fromAccountId: self.conversation.accountId) else { return }
+        if self.contactsService.contact(withHash: id) == nil {
+            self.updateName(name: id, id: id, message: message)
+            self.nameLookup(id: id, message: message)
+            return
+        }
+        let schema: URIType = account.type == .sip ? .sip : .ring
+        guard let contactURI = JamiURI(schema: schema, infoHach: id).uriString else { return }
+        self.profileService
+            .getProfile(uri: contactURI,
+                        createIfNotexists: false,
+                        accountId: account.id)
+            .subscribe(onNext: { [weak self, weak message] profile in
+                guard let self = self, let message = message else { return }
+                if let photo = profile.photo,
+                   let data = NSData(base64Encoded: photo, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters) as Data?,
+                   let image = UIImage(data: data) {
+                    self.updateAvatar(image: image, id: id, message: message)
+                } else if let username = self.names.get(key: id) as? String, (self.avatars.get(key: id) as? UIImage) == nil {
+                    let image = UIImage.createContactAvatar(username: username, size: CGSize(width: 30, height: 30))
+                    self.updateAvatar(image: image, id: id, message: message)
+                }
+                if let name = profile.alias, !name.isEmpty {
+                    self.updateName(name: name, id: id, message: message)
+                } else if (self.names.get(key: id) as? String) == nil {
+                    self.nameLookup(id: id, message: message)
+                }
+            })
+            .disposed(by: message.disposeBag)
+    }
+
+    private func updateLastRead(messageId: String, messageModel: MessageContainerModel) {
+        guard let participants = self.lastReadMessageForParticipant.filter({ participant in
+            if let id = participant.value as? String {
+                return id == messageId
+            }
+            return false
+        }) as? [String: String] else { return }
+        var images = [String: UIImage]()
+        lastRead.set(value: images, for: messageId)
+        for participant in participants {
+            if let avatar = self.avatars.get(key: participant.key) as? UIImage {
+                images[participant.key] = avatar
+            } else {
+                images[participant.key] = UIImage()
+                self.getInformationForContact(id: participant.key, message: messageModel)
+            }
+        }
+        lastRead.set(value: images, for: messageId)
+        let values: [UIImage] = images.map { value in
+            return value.value
+        }
+        let newValue = values.isEmpty ? nil : values
+        messageModel.updateRead(avatars: newValue)
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/TransferHelper.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/TransferHelper.swift
new file mode 100644
index 0000000..fd745ef
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/ViewModels/TransferHelper.swift
@@ -0,0 +1,188 @@
+/*
+ *  Copyright (C) 2017-2022 Savoir-faire Linux Inc.
+ *
+ *  Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *  Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
+ *  Author: Raphaël Brulé <raphael.brule@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import Foundation
+import RxSwift
+
+enum TransferState: State {
+    case accept(viewModel: MessageContentVM)
+    case cancel(viewModel: MessageContentVM)
+    case getProgress(viewModel: MessageContentVM)
+    case getSize(viewModel: MessageContentVM)
+    case getImage(viewModel: MessageContentVM)
+    case getURL(viewModel: MessageContentVM)
+    case getPlayer(viewModel: MessageContentVM)
+}
+
+class TransferHelper {
+    let dataTransferService: DataTransferService
+    let conversationViewModel: ConversationViewModel
+
+    init (dataTransferService: DataTransferService, conversationViewModel: ConversationViewModel) {
+        self.dataTransferService = dataTransferService
+        self.conversationViewModel = conversationViewModel
+    }
+
+    func acceptTransfer(conversation: ConversationModel, message: MessageModel) -> NSDataTransferError {
+        if !conversation.isSwarm() {
+            return self.dataTransferService.acceptTransfer(withId: message.daemonId,
+                                                           fileName: &message.content, accountID: conversation.accountId,
+                                                           conversationID: conversation.id, name: message.content)
+        }
+        var fileName = ""
+        self.dataTransferService.downloadFile(withId: message.daemonId, interactionID: message.id, fileName: &fileName, accountID: conversation.accountId, conversationID: conversation.id)
+        return .success
+    }
+
+    func cancelTransfer(conversation: ConversationModel, message: MessageModel) -> NSDataTransferError {
+        return self.dataTransferService.cancelTransfer(withId: message.daemonId, accountId: conversation.accountId, conversationId: conversation.id)
+    }
+
+    func getTransferProgress(conversation: ConversationModel, message: MessageModel) -> Float? {
+        return self.dataTransferService.getTransferProgress(withId: message.daemonId, accountId: conversation.accountId, conversationId: conversation.id, isSwarm: conversation.isSwarm())
+    }
+
+    func getTransferSize(conversation: ConversationModel, message: MessageModel) -> Int64? {
+        guard let info = self.dataTransferService.dataTransferInfo(withId: message.daemonId, accountId: conversation.accountId, conversationId: conversation.id, isSwarm: conversation.isSwarm()) else { return nil }
+        return info.totalSize
+    }
+
+    func getTransferedImage(maxSize: CGFloat,
+                            conversation: ConversationModel,
+                            message: MessageModel) -> UIImage? {
+        let transferInfo = self.getTransferFileData(content: message.content)
+        let name = conversation.isSwarm() ? message.daemonId : transferInfo.fileName
+        return self.dataTransferService
+            .getImage(for: name,
+                      maxSize: maxSize,
+                      identifier: transferInfo.identifier,
+                      accountID: conversation.accountId,
+                      conversationID: conversation.id,
+                      isSwarm: conversation.isSwarm())
+    }
+
+    func getFileURL(conversation: ConversationModel, message: MessageModel) -> URL? {
+        if message.transferStatus != .success {
+            return nil
+        }
+        let transferInfo = self.getTransferFileData(content: message.content)
+        if conversation.isSwarm() {
+            return self.dataTransferService.getFileUrlForSwarm(fileName: message.daemonId, accountID: conversation.accountId, conversationID: conversation.id)
+        }
+        if message.incoming {
+            return self.dataTransferService
+                .getFileUrlNonSwarm(fileName: transferInfo.fileName,
+                                    inFolder: Directories.downloads.rawValue,
+                                    accountID: conversation.accountId,
+                                    conversationID: conversation.id)
+        }
+
+        let recorded = self.dataTransferService
+            .getFileUrlNonSwarm(fileName: transferInfo.fileName,
+                                inFolder: Directories.recorded.rawValue,
+                                accountID: conversation.accountId,
+                                conversationID: conversation.id)
+        guard recorded == nil, recorded?.path.isEmpty ?? true else { return recorded }
+        return self.dataTransferService
+            .getFileUrlNonSwarm(fileName: transferInfo.fileName,
+                                inFolder: Directories.downloads.rawValue,
+                                accountID: conversation.accountId,
+                                conversationID: conversation.id)
+    }
+
+    func getPlayer(conversation: ConversationModel, message: MessageModel) -> PlayerViewModel? {
+        if message.transferStatus != .success {
+            return nil
+        }
+
+        if let playerModel = conversationViewModel.getPlayer(messageID: String(message.id)) {
+            return playerModel
+        }
+        let transferInfo = self.getTransferFileData(content: message.content)
+        let name = conversation.isSwarm() ? message.daemonId : transferInfo.fileName
+        guard let fileExtension = NSURL(fileURLWithPath: name).pathExtension else {
+            return nil
+        }
+        if fileExtension.isMediaExtension() {
+            if conversation.isSwarm() {
+                let path = self.dataTransferService
+                    .getFileUrlForSwarm(fileName: message.daemonId,
+                                        accountID: conversation.accountId,
+                                        conversationID: conversation.id)
+                let pathString = path?.path ?? ""
+                if pathString.isEmpty {
+                    return nil
+                }
+                let model = PlayerViewModel(injectionBag: conversationViewModel.injectionBag, path: pathString)
+                conversationViewModel.setPlayer(messageID: String(message.id), player: model)
+                return model
+            }
+            // first search for incoming video in downloads folder and for outgoing in recorded
+            let folderName = message.incoming ? Directories.downloads.rawValue : Directories.recorded.rawValue
+            var path = self.dataTransferService
+                .getFileUrlNonSwarm(fileName: name,
+                                    inFolder: folderName,
+                                    accountID: conversation.accountId,
+                                    conversationID: conversation.id)
+            var pathString = path?.path ?? ""
+            if pathString.isEmpty && message.incoming {
+                return nil
+            } else if pathString.isEmpty {
+                // try to search outgoing video in downloads folder
+                path = self.dataTransferService
+                    .getFileUrlNonSwarm(fileName: name,
+                                        inFolder: Directories.downloads.rawValue,
+                                        accountID: conversation.accountId,
+                                        conversationID: conversation.id)
+                pathString = path?.path ?? ""
+                if pathString.isEmpty {
+                    return nil
+                }
+            }
+            let model = PlayerViewModel(injectionBag: conversationViewModel.injectionBag, path: pathString)
+            conversationViewModel.setPlayer(messageID: String(message.id), player: model)
+            return model
+        }
+        return nil
+    }
+
+    typealias TransferParsingTuple = (fileName: String, fileSize: String?, identifier: String?)
+
+    private func getTransferFileData(content: String) -> TransferParsingTuple {
+        let contentArr = content.components(separatedBy: "\n")
+        var name: String
+        var identifier: String?
+        var size: String?
+        if contentArr.count > 2 {
+            name = contentArr[0]
+            size = contentArr[1]
+            identifier = contentArr[2]
+        } else if contentArr.count > 1 {
+            name = contentArr[0]
+            size = contentArr[1]
+        } else {
+            name = content
+        }
+        return (name, size, identifier)
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/ContactMessageView.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/ContactMessageView.swift
new file mode 100644
index 0000000..e59b784
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/ContactMessageView.swift
@@ -0,0 +1,48 @@
+/*
+ *  Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import SwiftUI
+
+struct ContactMessageView: View {
+    @StateObject var model: ContactMessageVM
+    var body: some View {
+        HStack(alignment: .center) {
+            if let avatar = model.avatarImage {
+                Image(uiImage: avatar)
+                    .resizable()
+                    .scaledToFill()
+                    .frame(width: 30, height: 30)
+                    .cornerRadius(15)
+            }
+            Spacer()
+                .frame(width: model.inset)
+            Text(model.content)
+                .foregroundColor(model.textColor)
+                .lineLimit(1)
+                .background(model.backgroundColor)
+                .font(model.textFont)
+                .truncationMode(.middle)
+        }
+        .padding(model.inset)
+        .overlay(
+            RoundedRectangle(cornerRadius: model.cornerRadius)
+                .stroke(model.borderColor, lineWidth: 1))
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/DefaultTransferView.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/DefaultTransferView.swift
new file mode 100644
index 0000000..9571d60
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/DefaultTransferView.swift
@@ -0,0 +1,73 @@
+/*
+ *  Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import SwiftUI
+
+struct DefaultTransferView: View {
+    @StateObject var model: MessageContentVM
+    var body: some View {
+        HStack(alignment: .top) {
+            Spacer()
+                .frame(width: 1)
+            Image(systemName: "doc")
+                .resizable()
+                .foregroundColor(model.textColor)
+                .scaledToFit()
+                .frame(width: 20, height: 20)
+            Spacer()
+                .frame(width: 10)
+            VStack(alignment: .leading) {
+                Text(model.fileName)
+                    .lineLimit(1)
+                    .truncationMode(.middle)
+                    .foregroundColor(model.textColor)
+                    .background(model.backgroundColor)
+                    .font(.headline)
+                Spacer()
+                    .frame(height: 10)
+                Text(model.fileInfo)
+                    .foregroundColor(model.textColor)
+                    .background(model.backgroundColor)
+                    .font(.footnote)
+                if model.showProgress {
+                    Spacer()
+                        .frame(height: 15)
+                    SwiftUI.ProgressView(value: model.fileProgress, total: 1)
+                    Spacer()
+                        .frame(height: 10)
+                }
+                if !model.transferActions.isEmpty {
+                    HStack {
+                        ForEach(model.transferActions) { action in
+                            Button(action.toString()) {
+                                model.transferAction(action: action)
+                            }
+                            Spacer()
+                                .frame(width: 20)
+                        }
+                    }
+                }
+            }
+        }
+        .padding(model.textInset)
+        .background(model.backgroundColor)
+        .cornerRadius(radius: model.cornerRadius, corners: model.corners)
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/MessageContentView.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/MessageContentView.swift
new file mode 100644
index 0000000..156a332
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/MessageContentView.swift
@@ -0,0 +1,116 @@
+/*
+ *  Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import SwiftUI
+import LinkPresentation
+
+class CustomLinkView: LPLinkView {
+    override var intrinsicContentSize: CGSize { CGSize(width: 0, height: super.intrinsicContentSize.height) }
+}
+
+struct URLPreview: UIViewRepresentable {
+    typealias UIViewType = CustomLinkView
+
+    var metadata: LPLinkMetadata
+    var maxDimension: CGFloat
+
+    func makeUIView(context: Context) -> CustomLinkView {
+        let view = CustomLinkView(metadata: metadata)
+        view.frame = CGRect(x: 0, y: 0, width: maxDimension, height: maxDimension)
+        view.contentMode = .scaleAspectFit
+        return view
+    }
+
+    func updateUIView(_ uiView: CustomLinkView, context: Context) {}
+}
+
+struct MessageContentView: View {
+    let messageModel: MessageContainerModel
+    @StateObject var model: MessageContentVM
+    var body: some View {
+        VStack(alignment: .leading) {
+            if model.type == .call {
+                Text(model.content)
+                    .padding(model.textInset)
+                    .foregroundColor(model.textColor)
+                    .lineLimit(1)
+                    .background(model.backgroundColor)
+                    .font(model.textFont)
+                    .cornerRadius(20)
+            } else if model.type == .fileTransfer {
+                if model.shouldUpdateTransferView {
+                    switch model.transferViewType {
+                    case .defaultView:
+                        DefaultTransferView(model: model)
+                    case .playerView(let player):
+                        PlayerSwiftUI(model: model, player: player)
+                    case .imageView(let image):
+                        Image(uiImage: image)
+                            .resizable()
+                            .scaledToFit()
+                            .frame(minHeight: 50, maxHeight: 300)
+                            .cornerRadius(20)
+                    }
+                }
+            } else if model.type == .text {
+                if let metadata = model.metadata {
+                    URLPreview(metadata: metadata, maxDimension: model.maxDimension)
+                } else if model.content.isValidURL, let url = URL(string: model.content) {
+                    Link(model.content, destination: url)
+                        .padding(model.textInset)
+                } else {
+                    Text(model.content)
+                        .padding(.top, model.textVerticalInset)
+                        .padding(.bottom, model.textVerticalInset)
+                        .padding(.leading, model.textInset)
+                        .padding(.trailing, model.textInset)
+                        .foregroundColor(model.textColor)
+                        .lineLimit(nil)
+                        .background(model.backgroundColor)
+                        .font(model.textFont)
+                        .if(model.hasBorder) { view in
+                            view.overlay(
+                                CornerRadiusShape(radius: model.cornerRadius, corners: model.corners)
+                                    .stroke(model.borderColor, lineWidth: 2))
+                        }
+                        .if(!model.hasBorder) { view in
+                            view.cornerRadius(radius: model.cornerRadius, corners: model.corners)
+                        }
+                }
+            }
+        }.contextMenu {
+            ForEach(model.menuItems) { item in
+                Button {
+                    model.contextMenuSelect(item: item)
+                } label: {
+                    Label(item.toString(), systemImage: item.image())
+                }
+            }
+        }
+        .onChange(of: model.shouldUpdateTransferView, perform: { update in
+            if update {
+                model.shouldUpdateTransferView = false
+            }
+        })
+        .onAppear {
+            self.model.onAppear()
+        }
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/MessageRowView.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/MessageRowView.swift
new file mode 100644
index 0000000..bfb060e
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/MessageRowView.swift
@@ -0,0 +1,132 @@
+/*
+ *  Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import SwiftUI
+
+// step 1 -- Create a shape view which can give shape
+struct CornerRadiusShape: Shape {
+    var radius = CGFloat.infinity
+    var corners = UIRectCorner.allCorners
+
+    func path(in rect: CGRect) -> Path {
+        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
+        return Path(path.cgPath)
+    }
+}
+
+// step 2 - embed shape in viewModifier to help use with ease
+struct CornerRadiusStyle: ViewModifier {
+    var radius: CGFloat
+    var corners: UIRectCorner
+
+    func body(content: Content) -> some View {
+        content
+            .clipShape(CornerRadiusShape(radius: radius, corners: corners))
+    }
+}
+
+// step 3 - crate a polymorphic view with same name as swiftUI's cornerRadius
+extension View {
+    func cornerRadius(radius: CGFloat, corners: UIRectCorner) -> some View {
+        ModifiedContent(content: self, modifier: CornerRadiusStyle(radius: radius, corners: corners))
+    }
+}
+
+extension View {
+    /// Applies the given transform if the given condition evaluates to `true`.
+    /// - Parameters:
+    ///   - condition: The condition to evaluate.
+    ///   - transform: The transform to apply to the source `View`.
+    /// - Returns: Either the original `View` or the modified `View` if the condition is `true`.
+    @ViewBuilder func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
+        if condition {
+            transform(self)
+        } else {
+            self
+        }
+    }
+}
+
+struct MessageRowView: View {
+    let messageModel: MessageContainerModel
+    @StateObject var model: MessageRowVM
+    var body: some View {
+        VStack(alignment: .leading) {
+            if model.shouldShowTimeString {
+                Spacer()
+                    .frame(height: 20)
+                Text(model.timeString)
+                    .font(.footnote)
+                    .foregroundColor(.secondary)
+                    .frame(maxWidth: .infinity, alignment: .center)
+                Spacer()
+                    .frame(height: 20)
+            }
+            if model.centeredMessage {
+                ContactMessageView(model: messageModel.contactViewModel)
+                    .frame(maxWidth: .infinity, alignment: .center)
+            } else if model.incoming {
+                HStack(alignment: .bottom) {
+                    if let avatar = model.avatarImage {
+                        Image(uiImage: avatar)
+                            .resizable()
+                            .scaledToFill()
+                            .frame(width: 30, height: 30)
+                            .cornerRadius(15)
+                    } else {
+                        Spacer()
+                            .frame(width: 30)
+                    }
+                    Spacer()
+                        .frame(width: 10)
+                    MessageStackView(messageModel: messageModel)
+                }.padding(.trailing, 50)
+            } else {
+                HStack(alignment: .bottom) {
+                    Spacer()
+                    MessageStackView(messageModel: messageModel)
+                }.padding(.leading, 50)
+            }
+            if let readImages = model.read {
+                Spacer()
+                    .frame(height: 10)
+                HStack(alignment: .top, spacing: -3) {
+                    Spacer()
+                    ForEach(0..<readImages.count, id: \.self) { index in
+                        Image(uiImage: readImages[index])
+                            .resizable()
+                            .frame(width: 15, height: 15)
+                            .clipShape(Circle())
+                            .overlay(Circle().stroke(Color.white, lineWidth: 2))
+                            .zIndex(Double(readImages.count - index))
+                    }
+                }
+                Spacer()
+                    .frame(height: 10)
+            }
+        }.onAppear(perform: {
+            model.fetchLastRead()
+        })
+        .padding(.top, -3)
+        .padding(.bottom, -3)
+        .padding(.leading, 15)
+        .padding(.trailing, 15)
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/MessageStackView.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/MessageStackView.swift
new file mode 100644
index 0000000..a2dcea6
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/MessageStackView.swift
@@ -0,0 +1,45 @@
+/*
+ *  Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import SwiftUI
+
+struct MessageStackView: View {
+    let messageModel: MessageContainerModel
+    var model: MessageStackVM {
+        return messageModel.stackViewModel
+    }
+    var body: some View {
+        VStack(alignment: model.horizontalAllignment) {
+            //            if messageModel.replyTo != nil {
+            //                ReplyHistory(messageModel: messageModel)
+            //            }
+            if model.shouldDisplayName {
+                Text(model.username)
+                    .font(.footnote)
+                    .foregroundColor(Color(UIColor.tertiaryLabel))
+                    .frame(maxWidth: .infinity, alignment: .leading)
+                Spacer()
+                    .frame(height: 4)
+            }
+            MessageContentView(messageModel: messageModel, model: messageModel.messageContent)
+                .frame(maxWidth: .infinity, alignment: model.alignment)
+        }
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/MessagesListView.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/MessagesListView.swift
new file mode 100644
index 0000000..5e0d018
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/MessagesListView.swift
@@ -0,0 +1,47 @@
+/*
+ *  Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import SwiftUI
+
+struct MessagesListView: View {
+    @ObservedObject var list: MessagesListVM
+    @SwiftUI.State private var username: String = ""
+    @SwiftUI.State private var keyboardHeight: CGFloat = 0
+    var body: some View {
+        ScrollViewReader { scrollView in
+            ScrollView {
+                LazyVStack {
+                    ForEach(list.messagesModels) { message in
+                        MessageRowView(messageModel: message, model: message.messageRow)
+                            .onAppear { self.list.messagesAddedToScreen(messageId: message.id) }
+                            .onDisappear { self.list.messagesremovedFromScreen(messageId: message.id) }
+                    }
+                }
+                .listRowBackground(Color.clear)
+                .onChange(of: list.needScroll, perform: { updated in
+                    if updated {
+                        scrollView.scrollTo(list.lastMessageOnScreen)
+                        list.needScroll = false
+                    }
+                })
+            }
+        }
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/PlayerSwiftUI.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/PlayerSwiftUI.swift
new file mode 100644
index 0000000..9d0be03
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/PlayerSwiftUI.swift
@@ -0,0 +1,49 @@
+/*
+ *  Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import SwiftUI
+
+struct PlayerViewWrapper: UIViewRepresentable {
+    typealias UIViewType = PlayerView
+
+    var viewModel: PlayerViewModel
+    var width: CGFloat
+    var height: CGFloat
+
+    func makeUIView(context: Context) -> PlayerView {
+        let frame = CGRect(x: 0, y: 0, width: width, height: height)
+        let player = PlayerView(frame: frame)
+        player.viewModel = viewModel
+        return player
+    }
+
+    func updateUIView(_ uiView: PlayerView, context: Context) {}
+}
+
+struct PlayerSwiftUI: View {
+    @StateObject var model: MessageContentVM
+    var player: PlayerViewModel
+    var body: some View {
+        PlayerViewWrapper.init(viewModel: player, width: model.playerWidth, height: model.playerHeight)
+            .frame(height: model.playerHeight)
+            .frame(width: model.playerWidth)
+            .cornerRadius(20)
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/ReplyHistory.swift b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/ReplyHistory.swift
new file mode 100644
index 0000000..97ef065
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessageSwiftUI/Views/ReplyHistory.swift
@@ -0,0 +1,81 @@
+/*
+ *  Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+ */
+
+import SwiftUI
+
+struct ReplyHistory: View {
+    let messageModel: MessageContainerModel
+    var model: MessageHistoryVM {
+        return messageModel.historyModel
+    }
+    var body: some View {
+        VStack {
+            HStack(alignment: .bottom) {
+                //                if let aimage = test.avatarImage {
+                //                    Image(uiImage: aimage)
+                //                        .resizable()
+                //                        .scaledToFit()
+                //                        .frame(width: 30, height: 30)
+                //                        .background(Color.blue)
+                //                        .cornerRadius(15)
+                //                }
+                //                VStack {
+                //                    Text(test.username)
+                //                        .font(.callout)
+                //                        .foregroundColor(.secondary)
+                //                        .frame(maxWidth: .infinity, alignment: .leading)
+                //                    Spacer()
+                //                        .frame(height: 5)
+                //                    HStack(alignment: .bottom) {
+                //                        if let image = test.image {
+                //                            Image(uiImage: image)
+                //                                .resizable()
+                //                                .scaledToFit()
+                //                                .frame(width: 100, height: 100)
+                //                                .background(Color.blue)
+                //                                .cornerRadius(20)
+                //                        } else {
+                //                            Text(test.content)
+                //                                .frame(maxWidth: .infinity, alignment: .center)
+                //                                .padding(EdgeInsets(top: 15, leading: 0, bottom: 15, trailing: 0))
+                //                                .foregroundColor(.secondary)
+                //                                .font(.body)
+                //                                .overlay(
+                //                                    CornerRadiusShape(radius: 15, corners: [.topLeft, .topRight, .bottomRight])
+                //                                        .stroke(.gray, lineWidth: 2)
+                //                                )
+                //                        }
+                //                    }
+                //                }
+            }
+            .padding(EdgeInsets(top: 15, leading: 15, bottom: 5, trailing: 15))
+
+            Button("2 Replies") {
+                print("Button tapped!")
+            }
+            .padding(EdgeInsets(top: 0, leading: 0, bottom: 15, trailing: 0))
+        }
+        .overlay(
+            RoundedRectangle(cornerRadius: 15)
+                .stroke(.green, lineWidth: 2)
+        )
+        .padding()
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/MessagesList.swift b/Ring/Ring/Features/Conversations/Conversation/MessagesList.swift
new file mode 100644
index 0000000..d8889bb
--- /dev/null
+++ b/Ring/Ring/Features/Conversations/Conversation/MessagesList.swift
@@ -0,0 +1,50 @@
+//
+//  MessagesList.swift
+//  Ring
+//
+//  Created by kateryna on 2022-09-26.
+//  Copyright © 2022 Savoir-faire Linux. All rights reserved.
+//
+
+import SwiftUI
+
+struct MessagesList: View {
+    @ObservedObject var list: MessagesListModel
+    var body: some View {
+        ScrollViewReader { scrollView in
+            ScrollView {
+                LazyVStack {
+                    ForEach(list.messagesModels) { message in
+                        MessageRow(messageModel: message, model: message.messageRow)
+                            .onAppear { self.list.messagesAddedToScreen(messageId: message.id) }
+                            .onDisappear { self.list.messagesremovedFromScreen(messageId: message.id) }
+                            .onTapGesture {
+                                self.list.messageTaped(message: message)
+                            }
+                    }
+                    Spacer()
+                        .frame(height: 40)
+                        .id("lastMessageOnScreen")
+
+                }
+                .listRowBackground(Color.clear)
+                .onChange(of: list.messagesCount, perform: { _ in
+                    scrollView.scrollTo(list.lastMessageOnScreen)
+                })
+                .onChange(of: list.scrolToLast, perform: { updated in
+                    if updated {
+                        print("########will scroll")
+                        scrollView.scrollTo("lastMessageOnScreen")
+                        list.scrolToLast = false
+                    }
+                })
+            }
+        }
+    }
+}
+
+struct MessagesList_Previews: PreviewProvider {
+    static var previews: some View {
+        MessagesList(list: MessagesListModel())
+    }
+}
diff --git a/Ring/Ring/Features/Conversations/Conversation/SwarmCreationModel/SwarmCreationUI.swift b/Ring/Ring/Features/Conversations/Conversation/SwarmCreationModel/SwarmCreationUI.swift
index e8b2b6a..7ee3ff4 100644
--- a/Ring/Ring/Features/Conversations/Conversation/SwarmCreationModel/SwarmCreationUI.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/SwarmCreationModel/SwarmCreationUI.swift
@@ -99,7 +99,7 @@
                 VStack {
                     TextField(L10n.Global.name, text: $list.swarmName)
                         .font(.system(size: 18.0, weight: .semibold, design: .default))
-                    TextField(L10n.SwarmCreation.addADescription, text: $list.swarmDescription)
+                    TextField(L10n.Swarmcreation.addADescription, text: $list.swarmDescription)
                         .font(.system(size: 17.0, weight: .regular, design: .default))
                 }
                 Spacer()
@@ -125,16 +125,14 @@
         .frame(width: nil, height: nil, alignment: .leading)
         .accentColor(Color.black)
         if !list.selections.isEmpty {
-
-            Button(L10n.SwarmCreation.createTheSwarm) {
+            Button(L10n.Swarmcreation.createTheSwarm) {
                 list.createTheSwarm()
             }
-            .frame(maxWidth: .infinity, maxHeight: 60.0)
+            .frame(width: 300, height: 60, alignment: .center)
             .background(Color(UIColor.jamiButtonDark))
             .foregroundColor(.white)
-
+            .cornerRadius(20)
         }
-
     }
 }
 struct ImagePicker: UIViewControllerRepresentable {
diff --git a/Ring/Ring/Features/Conversations/Conversation/SwarmCreationModel/SwarmCreationUIModel.swift b/Ring/Ring/Features/Conversations/Conversation/SwarmCreationModel/SwarmCreationUIModel.swift
index 7cda8fb..8b45d2f 100644
--- a/Ring/Ring/Features/Conversations/Conversation/SwarmCreationModel/SwarmCreationUIModel.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/SwarmCreationModel/SwarmCreationUIModel.swift
@@ -31,7 +31,7 @@
     private let accountId: String
     private let conversationService: ConversationsService
     private var swarmInfo: SwarmInfo
-    var swarmCreated: (() -> Void)
+    var swarmCreated: ((Bool) -> Void)
 
     @Published var swarmName: String = ""
     @Published var swarmDescription: String = ""
@@ -39,7 +39,7 @@
     @Published var selections: [String] = []
     @Published var maximumLimit: Int = 8
 
-    required init(with injectionBag: InjectionBag, accountId: String, swarmCreated: @escaping (() -> Void)) {
+    required init(with injectionBag: InjectionBag, accountId: String, swarmCreated: @escaping ((Bool) -> Void)) {
         self.swarmCreated = swarmCreated
         self.conversationService = injectionBag.conversationsService
         self.accountId = accountId
@@ -103,7 +103,7 @@
             self.conversationService.addConversationMember(accountId: accountId, conversationId: conversationId, memberId: participant)
 
         }
-        // self.swarmCreated()
+        _ = self.swarmCreated(true)
     }
 
 }
diff --git a/Ring/Ring/Features/Conversations/Conversation/SwarmCreationModel/SwarmCreationViewController.swift b/Ring/Ring/Features/Conversations/Conversation/SwarmCreationModel/SwarmCreationViewController.swift
index de6dac6..6f7a0c0 100644
--- a/Ring/Ring/Features/Conversations/Conversation/SwarmCreationModel/SwarmCreationViewController.swift
+++ b/Ring/Ring/Features/Conversations/Conversation/SwarmCreationModel/SwarmCreationViewController.swift
@@ -39,12 +39,13 @@
     }()
 
     override func viewDidLoad() {
-        self.navigationItem.title = L10n.SwarmCreation.title
+        self.navigationItem.title = L10n.Swarmcreation.title
         super.viewDidLoad()
         guard let accountId = self.viewModel.currentAccount?.id else { return }
 
-        model = SwarmCreationUIModel(with: self.viewModel.injectionBag, accountId: accountId, swarmCreated: {
-            self.dismiss(animated: false)
+        model = SwarmCreationUIModel(with: self.viewModel.injectionBag, accountId: accountId, swarmCreated: {_ in
+            self.navigationController?.popViewController(animated: true)
+            self.dismiss(animated: true, completion: nil)
         })
         let contentView = UIHostingController(rootView: SwarmCreationUI(list: model))
         addChild(contentView)
@@ -81,7 +82,7 @@
     }
     func setupSearchBar() {
         searchController.searchResultsUpdater = self
-        searchController.searchBar.placeholder = L10n.SwarmCreation.searchBar
+        searchController.searchBar.placeholder = L10n.Swarmcreation.searchBar
         navigationItem.searchController = searchController
         navigationItem.hidesSearchBarWhenScrolling = false
     }
diff --git a/Ring/Ring/Features/Conversations/SmartList/SmartlistViewModel.swift b/Ring/Ring/Features/Conversations/SmartList/SmartlistViewModel.swift
index 465b928..daf4d65 100644
--- a/Ring/Ring/Features/Conversations/SmartList/SmartlistViewModel.swift
+++ b/Ring/Ring/Features/Conversations/SmartList/SmartlistViewModel.swift
@@ -341,7 +341,7 @@
     }
 
     func closeAllPlayers() {
-        self.conversationViewModels.forEach { (conversationModel) in
+        self.conversationViewModels.forEach { conversationModel in
             conversationModel.closeAllPlayers()
         }
     }
diff --git a/Ring/Ring/Features/Conversations/views/PlayerViewModel.swift b/Ring/Ring/Features/Conversations/views/PlayerViewModel.swift
index c06b810..e202df9 100644
--- a/Ring/Ring/Features/Conversations/views/PlayerViewModel.swift
+++ b/Ring/Ring/Features/Conversations/views/PlayerViewModel.swift
@@ -26,6 +26,7 @@
 }
 
 class PlayerViewModel {
+
     var hasVideo = BehaviorRelay<Bool>(value: true)
     var playerDuration = BehaviorRelay<Float>(value: 0)
     var playerPosition = PublishSubject<Float>()
@@ -81,6 +82,7 @@
             self?.seekToTime(time: 0)
             self?.startTimer()
             self?.playerReady.accept(true)
+            self?.playBackFrame.onNext(self?.firstFrame)
             if let image = renderer?.data {
                 DispatchQueue.main.async {
                     self?.delegate?.extractedVideoFrame(with: image.size.height)
diff --git a/Ring/Ring/Models/ConversationModel.swift b/Ring/Ring/Models/ConversationModel.swift
index db8c9fa..e85d179 100644
--- a/Ring/Ring/Models/ConversationModel.swift
+++ b/Ring/Ring/Models/ConversationModel.swift
@@ -145,12 +145,13 @@
 }
 
 class ConversationModel: Equatable {
-    var messages = BehaviorRelay<[MessageModel]>(value: [MessageModel]())
+    var newMessages = BehaviorRelay<[MessageModel]>(value: [MessageModel]())
     private var participants = [ConversationParticipant]()
+    var messages = [MessageModel]()
     var hash = ""/// contact hash for dialog, conversation title for multiparticipants
     var accountId: String = ""
     var id: String = ""
-    var lastDisplayedMessage: (id: String, timestamp: Date) = ("", Date())
+    var lastMessage: MessageModel?
     var type: ConversationType = .nonSwarm
     var needsSyncing = false
     var unorderedInteractions = [String]()/// array ofr interaction id with child not currently present in messages
@@ -227,19 +228,19 @@
 
     private func subscribeUnreadMessages() {
         if self.isSwarm() { return }
-        self.messages.asObservable()
-            .subscribe { [weak self] messages in
+        self.newMessages.asObservable()
+            .share()
+            .subscribe { [weak self] _ in
                 guard let self = self else { return }
-                let number = messages.filter({ $0.status != .displayed && $0.type == .text && $0.incoming }).count
+                let number = self.messages.filter({ $0.status != .displayed && $0.type == .text && $0.incoming }).count
                 self.numberOfUnreadMessages.accept(number)
-
             } onError: { _ in
             }
             .disposed(by: self.disposeBag)
     }
 
     func getMessage(withDaemonID daemonID: String) -> MessageModel? {
-        return self.messages.value.filter({ message in
+        return self.messages.filter({ message in
             return message.daemonId == daemonID
         }).first
     }
@@ -254,13 +255,13 @@
         let last = self.participants.filter { participant in
             !participant.isLocal
         }.first?.lastDisplayed
-        if let message = self.messages.value.filter({ ($0.id == last) }).first {
+        if let message = self.messages.filter({ ($0.id == last) }).first {
             if !message.incoming {
                 return last
-            } else if let index = self.messages.value.firstIndex(where: { message in
+            } else if let index = self.messages.firstIndex(where: { message in
                 message.id == last
             }) {
-                if let newMessage = self.messages.value[0..<index].reversed().filter({ !$0.incoming }).first {
+                if let newMessage = self.messages[0..<index].reversed().filter({ !$0.incoming }).first {
                     return newMessage.id
                 }
             }
@@ -279,26 +280,26 @@
     }
 
     func setMessageAsRead(messageId: String, daemonId: String) {
-        if let message = self.messages.value.filter({ messageModel in
+        if let message = self.messages.filter({ messageModel in
             messageModel.id == messageId && messageModel.daemonId == daemonId
         }).first {
             message.status = .displayed
         }
         if !self.isSwarm() {
-            let number = self.messages.value.filter({ $0.status != .displayed && $0.type == .text && $0.incoming }).count
+            let number = self.messages.filter({ $0.status != .displayed && $0.type == .text && $0.incoming }).count
             self.numberOfUnreadMessages.accept(number)
         }
     }
 
     func setAllMessagesAsRead() {
-        let unreadMessages = self.messages.value.filter({ messages in
+        let unreadMessages = self.messages.filter({ messages in
             return messages.status != .displayed && messages.incoming && messages.type == .text
         })
         unreadMessages.forEach { message in
             message.status = .displayed
         }
         if !self.isSwarm() {
-            let number = self.messages.value.filter({ $0.status != .displayed && $0.type == .text && $0.incoming }).count
+            let number = self.messages.filter({ $0.status != .displayed && $0.type == .text && $0.incoming }).count
             self.numberOfUnreadMessages.accept(number)
         }
     }
@@ -353,24 +354,15 @@
     }
 
     func allMessagesLoaded() -> Bool {
-        guard let firstMessage = self.messages.value.first else { return false }
+        guard let firstMessage = self.messages.first else { return false }
         return firstMessage.parentId.isEmpty
     }
 
     func appendNonSwarm(message: MessageModel) {
-        var values = self.messages.value
-        values.append(message)
-        self.messages.accept(values)
+        self.messages.append(message)
     }
 
     func isSwarm() -> Bool {
         return self.type != .nonSwarm && self.type != .sip && self.type != .jams
     }
-
-    func isLastDisplayed(messageId: String, peerJamiId: String) -> Bool {
-        if self.isSwarm() {
-            return self.getLastDisplayedMessageForDialog() == messageId
-        }
-        return lastDisplayedMessage.id == messageId
-    }
 }
diff --git a/Ring/Ring/Models/MessageModel.swift b/Ring/Ring/Models/MessageModel.swift
index af79809..8b95454 100644
--- a/Ring/Ring/Models/MessageModel.swift
+++ b/Ring/Ring/Models/MessageModel.swift
@@ -27,10 +27,13 @@
     case displayName = "displayName"
     case body = "body"
     case author = "author"
+    case uri = "uri"
     case timestamp = "timestamp"
     case parent = "linearizedParent"
     case action = "action"
     case duration = "duration"
+    case reply = "reply-to"
+    case react = "react-to"
 }
 
 enum MessageType: String {
@@ -41,6 +44,7 @@
     case location = "location"
     case merge = "merge"
     case initial = "initial"
+    case profile = "application/update-profile"
 }
 
 enum ContactAction: String {
@@ -48,9 +52,10 @@
     case remove
     case join
     case banned
+    case unban
 }
 
-class MessageModel {
+public class MessageModel {
 
     var id: String = ""
     /// daemonId for dht messages, file transfer id for datatransfer
@@ -60,11 +65,14 @@
     var content: String = ""
     /// jamiId for sender. For outgoing message authorId is empty
     var authorId: String = ""
+    var uri: String = ""
     var status: MessageStatus = .unknown
     var transferStatus: DataTransferStatus = .unknown
     var incoming: Bool
     var parentId: String = ""
     var type: MessageType = .text
+    var reply: String = ""
+    var react: String = ""
 
     init(withId id: String, receivedDate: Date, content: String, authorURI: String, incoming: Bool) {
         self.daemonId = id
@@ -81,6 +89,9 @@
         if let author = info[MessageAttributes.author.rawValue], author != accountJamiId {
             self.authorId = author
         }
+        if let uri = info[MessageAttributes.uri.rawValue] {
+            self.uri = uri
+        }
         if let type = info[MessageAttributes.type.rawValue],
            let messageType = MessageType(rawValue: type) {
             self.type = messageType
@@ -88,7 +99,13 @@
         if let content = info[MessageAttributes.body.rawValue], self.type == .text {
             self.content = content
         }
-        incoming = !self.authorId.isEmpty
+        if let reply = info[MessageAttributes.reply.rawValue] {
+            self.reply = reply
+        }
+        if let react = info[MessageAttributes.react.rawValue] {
+            self.react = react
+        }
+        incoming = self.uri.isEmpty ? !self.authorId.isEmpty : self.uri != accountJamiId
         if let parent = info[MessageAttributes.parent.rawValue] {
             self.parentId = parent
         }
@@ -120,13 +137,16 @@
                let contactAction = ContactAction(rawValue: action) {
                 switch contactAction {
                 case .add:
-                    self.content = self.incoming ? L10n.GeneratedMessage.invitationReceived : L10n.GeneratedMessage.contactAdded
+                    self.content = self.incoming ? L10n.GeneratedMessage.invitationReceived :
+                        L10n.GeneratedMessage.contactAdded
                 case .join:
-                    self.content = L10n.GeneratedMessage.invitationAccepted
+                    self.content = self.incoming ? L10n.GeneratedMessage.invitationAccepted : L10n.GeneratedMessage.youJoined
                 case .remove:
                     self.content = L10n.GeneratedMessage.contactLeftConversation
-                default:
-                    break
+                case.banned:
+                    self.content = L10n.GeneratedMessage.contactBanned
+                case .unban:
+                    self.content = L10n.GeneratedMessage.contactReAdded
                 }
             }
         case .fileTransfer:
@@ -137,8 +157,8 @@
                 self.content = displayName
             }
         case .initial:
-            self.type = .contact
-            self.content = self.incoming ? "Invitation received" : "Contact added"
+            self.type = .initial
+            self.content = L10n.GeneratedMessage.swarmCreated
         default:
             break
         }
diff --git a/Ring/Ring/Resources/en.lproj/Localizable.strings b/Ring/Ring/Resources/en.lproj/Localizable.strings
index 9ba208b..3e550fd 100644
--- a/Ring/Ring/Resources/en.lproj/Localizable.strings
+++ b/Ring/Ring/Resources/en.lproj/Localizable.strings
@@ -332,15 +332,19 @@
 
 
 //Generated Message
-"generatedMessage.contactAdded" = "Contact added";
-"generatedMessage.invitationReceived" = "Invitation received";
-"generatedMessage.invitationAccepted" = "Invitation accepted";
+"generatedMessage.contactAdded" = "You received inviation";
+"generatedMessage.swarmCreated" = "Swarm created";
+"generatedMessage.invitationReceived" = "was invited to join";
+"generatedMessage.invitationAccepted" = "joined the conversation";
+"generatedMessage.youJoined" = "You joined the conversation";
+"generatedMessage.contactBanned" = "was kicked";
+"generatedMessage.contactReAdded" = "was re-added";
 "generatedMessage.outgoingCall" = "Outgoing call";
 "generatedMessage.incomingCall" = "Incoming call";
 "generatedMessage.missedOutgoingCall" = "Missed outgoing call";
 "generatedMessage.missedIncomingCall" = "Missed incoming call";
 "generatedMessage.liveLocationSharing" = "Live location sharing";
-"generatedMessage.contactLeftConversation" = "Contact left conversation";
+"generatedMessage.contactLeftConversation" = "left";
 
 //General Settings
 "generalSettings.title" = "Advanced settings";
diff --git a/Ring/Ring/Services/ConversationsService.swift b/Ring/Ring/Services/ConversationsService.swift
index 41473f2..72e7040 100644
--- a/Ring/Ring/Services/ConversationsService.swift
+++ b/Ring/Ring/Services/ConversationsService.swift
@@ -97,7 +97,7 @@
                 }
                 /// filter out contact requests
                 var conversationsFromDB = conversationsModels.filter { conversation in
-                    !(conversation.messages.value.count == 1 && conversation.messages.value.first!.content == L10n.GeneratedMessage.invitationReceived)
+                    !(conversation.messages.count == 1 && conversation.messages.first!.content == L10n.GeneratedMessage.invitationReceived)
                 }
                 /// After location sharing we could have both: swarm and non swarm conversation for same contact.
                 /// Filter out conversations that already added to swarm
@@ -154,9 +154,6 @@
             }
             let conversation = ConversationModel(withId: conversationId, accountId: accountId, info: info)
             conversation.addParticipantsFromArray(participantsInfo: participantsInfo, accountURI: accountURI)
-            if let lastDisplayed = conversation.getLastDisplayedMessageForDialog() {
-                conversation.lastDisplayedMessage = (lastDisplayed, Date())
-            }
             if let lastRead = conversation.getLastReadMessage() {
                 let unreadInteractions = conversationsAdapter.countInteractions(accountId, conversationId: conversationId, from: lastRead, to: "", authorUri: accountURI)
                 conversation.numberOfUnreadMessages.accept(Int(unreadInteractions))
@@ -170,9 +167,9 @@
     private func sortAndUpdate(conversations: inout [ConversationModel]) {
         /// sort conversaton by last message date
         let sorted = conversations.sorted(by: { conversation1, conversations2 in
-            guard let lastMessage1 = conversation1.messages.value.last,
-                  let lastMessage2 = conversations2.messages.value.last else {
-                return conversation1.messages.value.count > conversations2.messages.value.count
+            guard let lastMessage1 = conversation1.lastMessage,
+                  let lastMessage2 = conversations2.lastMessage else {
+                return conversation1.messages.count > conversations2.messages.count
             }
             return lastMessage1.receivedDate > lastMessage2.receivedDate
         })
@@ -199,54 +196,14 @@
      after adding new interactions for conversation we check if conversation order need to be changed
      */
     private func sortIfNeeded() {
-        if !self.conversations.value.map({ conv in
-            return conv.messages.value.last?.receivedDate ?? Date()
+        let receivedDates = self.conversations.value.map({ conv in
+            return conv.lastMessage?.receivedDate ?? Date()
         })
-        .isAscending() {
+        if !receivedDates.isDescending() {
             var currentConversations = self.conversations.value
             self.sortAndUpdate(conversations: &currentConversations)
         }
     }
-    /**
-     move child interaction when found parent interaction
-     */
-    private func moveInteraction(interactionId: String, after parentId: String, messages: inout [MessageModel]) {
-        if let index = messages.firstIndex(where: { messge in
-            messge.id == interactionId
-        }), let parentIndex = messages.firstIndex(where: { messge in
-            messge.id == parentId
-        }) {
-            if index == parentIndex + 1 {
-                /// alredy on right place
-                return
-            }
-            if parentIndex < messages.count - 1 {
-                let interactionToMove = messages[index]
-                if index < messages.count - 1 {
-                    /// if interaction we are going to move is parent for next interaction we should move next interaction as well
-                    let nextInteraction = messages[index + 1]
-                    let moveNextInteraction = interactionToMove.id == nextInteraction.parentId
-                    messages.insert(messages.remove(at: index), at: parentIndex + 1)
-                    if !moveNextInteraction {
-                        return
-                    }
-                    moveInteraction(interactionId: nextInteraction.id, after: interactionToMove.id, messages: &messages)
-                } else {
-                    /// message we are going to move is last in the list, we do not need to check child interactions
-                    messages.insert(messages.remove(at: index), at: parentIndex + 1)
-                }
-            } else if parentIndex == messages.count - 1 {
-                let interactionToMove = messages[index]
-                let nextInteraction = messages[index + 1]
-                let moveNextInteraction = interactionToMove.id == nextInteraction.parentId
-                messages.append(messages.remove(at: index))
-                if !moveNextInteraction {
-                    return
-                }
-                moveInteraction(interactionId: nextInteraction.id, after: interactionToMove.id, messages: &messages)
-            }
-        }
-    }
 
     // MARK: swarm interactions management
 
@@ -273,69 +230,34 @@
                     return conversation.id == conversationId && conversation.accountId == accountId
                 })
                 .first else { return false }
-        var currentInteractions = conversation.messages.value
-        var numberOfNewMessages = 0
         // if all loaded messages are of type .merge, we need to load next messages
-        let numberOfInteractions = messages.filter { $0.type != .merge }.count
+        let numberOfInteractions = messages.filter { $0.type != .merge && $0.type != .profile }.count
         if fromLoaded && numberOfInteractions == 0 {
-            self.loadConversationMessages(conversationId: conversationId, accountId: accountId, from: messages.first?.id ?? "")
+            let firstMessage = messages.first?.id ?? ""
+            self.loadConversationMessages(conversationId: conversationId, accountId: accountId, from: firstMessage)
             return false
         }
+        var newMessages = [MessageModel]()
         messages.forEach { newMessage in
             /// filter out merge interaction
-            if newMessage.type == .merge { return }
+            if newMessage.type == .merge || newMessage.type == .profile { return }
             /// filter out existing messages
-            if currentInteractions.contains(where: { message in
+            if conversation.messages.contains(where: { message in
                 message.id == newMessage.id
             }) { return }
             if fromLoaded {
                 newMessage.status = .displayed
             }
-            numberOfNewMessages += 1
-            /// find child mesage
-            if let index = currentInteractions.firstIndex(where: { message in
-                message.parentId == newMessage.id
-            }) {
-                if index > 1 {
-                    currentInteractions.insert(newMessage, at: index - 1)
-                } else {
-                    currentInteractions.insert(newMessage, at: 0)
-                }
-            } else if let parentIndex = currentInteractions.firstIndex(where: { message in
-                message.id == newMessage.parentId
-            }) {
-                if parentIndex > currentInteractions.count - 1 {
-                    currentInteractions.insert(newMessage, at: parentIndex + 1)
-                } else {
-                    currentInteractions.append(newMessage)
-                }
-            } else {
-                /// no child or parent found. Just add interaction to begining for loaded and to the end for new
-                if fromLoaded {
-                    currentInteractions.insert(newMessage, at: 0)
-                } else {
-                    currentInteractions.append(newMessage)
-                }
-                /// save message without parent to dictionary, so if we receive parent later we could move message
-                conversation.unorderedInteractions.append(newMessage.id)
-            }
-            /// if a new message is a parent for previously added message change messages order
-            if conversation.unorderedInteractions.contains(where: { parentId in
-                parentId == newMessage.parentId
-            }) {
-                moveInteraction(interactionId: newMessage.id, after: newMessage.parentId, messages: &currentInteractions)
-                if let ind = conversation.unorderedInteractions.firstIndex(of: newMessage.parentId) {
-                    conversation.unorderedInteractions.remove(at: ind)
-                }
+            newMessages.append(newMessage)
+            guard let lastMessage = conversation.lastMessage,
+                  lastMessage.receivedDate > newMessage.receivedDate else {
+                conversation.lastMessage = newMessage
+                return
             }
         }
-        if numberOfNewMessages == 0 {
-            return false
-        }
-        /// emit signal for conversation messages
-        conversation.messages.accept(currentInteractions)
-        /// check if conversation order changed. In this case we need emit new signal for conversation
+        conversation.messages.append(contentsOf: newMessages)
         sortIfNeeded()
+        conversation.newMessages.accept(newMessages)
         self.updateUnreadMessages(conversationId: conversationId, accountId: accountId)
         return true
     }
@@ -439,7 +361,7 @@
          */
         for conversation in conversations where !conversation.isSwarm() {
             var updatedMessages = 0
-            for message in (conversation.messages.value) {
+            for message in (conversation.messages) {
                 if !message.daemonId.isEmpty && (message.status == .unknown || message.status == .sending ) {
                     let updatedMessageStatus = self.status(forMessageId: message.daemonId)
                     if (updatedMessageStatus.rawValue > message.status.rawValue && updatedMessageStatus != .failure) ||
@@ -472,9 +394,6 @@
                         .disposed(by: self.disposeBag)
                 }
             }
-            if updatedMessages > 0 {
-                conversation.messages.accept(conversation.messages.value)
-            }
         }
     }
 
@@ -514,9 +433,8 @@
                             : message.content
                         /// for location sharing we should just update message if if exists
                         if message.type == .location,
-                           let existingMessage = conversation.messages.value.filter({ $0.type == .location && $0.incoming == message.incoming }).first {
+                           let existingMessage = conversation.messages.filter({ $0.type == .location && $0.incoming == message.incoming }).first {
                             existingMessage.content = content
-                            conversation.messages.accept(conversation.messages.value)
                         } else {
                             message.content = content
                             message.id = savedMessage.messageID
@@ -601,7 +519,7 @@
         if let hash = JamiURI(from: contactUri).hash,
            interactionType == .contact,
            let conversation = self.getConversationForParticipant(jamiId: hash, accontId: accountId),
-           conversation.messages.value.map({ ($0.content) }).contains(messageContent) {
+           conversation.messages.map({ ($0.content) }).contains(messageContent) {
             return
 
         }
@@ -835,13 +753,12 @@
             conversationUnwraped = self.getConversationForParticipant(jamiId: jamiId, accontId: accountId)
         }
         guard let conversation = conversationUnwraped else { return }
-        let messages = conversation.messages.value
+        let messages = conversation.messages
         if let message = messages.first(where: { messageModel in
             messageModel.id == interactionId
         }) {
             message.transferStatus = transferStatus
         }
-        conversation.messages.accept(messages)
         let serviceEventType: ServiceEventType = .dataTransferMessageUpdated
         var serviceEvent = ServiceEvent(withEventType: serviceEventType)
         serviceEvent.addEventInput(.transferId, value: transferId)
@@ -889,7 +806,7 @@
                   let conversationURI = conversation.getConversationURI() else { return Disposables.create { } }
 
             /// Filter out read, outgoing, and transfer messages
-            let unreadMessages = conversation.messages.value.filter({ messages in
+            let unreadMessages = conversation.messages.filter({ messages in
                 return messages.status != .displayed && messages.incoming && messages.type == .text
             })
 
@@ -920,7 +837,7 @@
                     .disposed(by: self.disposeBag)
             } else {
                 /// for swarm set last message displayed, to get right number of unread messages
-                if let lastId = conversation.messages.value.map({ $0.id }).filter({ !$0.isEmpty }).last {
+                if let lastId = conversation.lastMessage?.id {
                     self.conversationsAdapter
                         .setMessageDisplayedFrom(conversationURI,
                                                  byAccount: accountId,
@@ -946,24 +863,15 @@
         }).first else { return }
 
         /// Find message
-        if let message: MessageModel = conversation.messages.value.filter({ (message) -> Bool in
+        if let message: MessageModel = conversation.messages.filter({ (message) -> Bool in
             let messageIDSame = !conversation.isSwarm() ? !message.daemonId.isEmpty && message.daemonId == messageId : message.id == messageId
-            return messageIDSame &&
+            return messageIDSame
+                &&
                 ((status.rawValue > message.status.rawValue && status != .failure) ||
                     (status == .failure && message.status == .sending))
         }).first {
             message.status = status
             self.updateUnreadMessages(conversationId: conversationId, accountId: accountId)
-            let displayedMessage = status == .displayed && !message.incoming
-            let oldDisplayedMessage = conversation.lastDisplayedMessage.id
-            let isLater = conversation.lastDisplayedMessage.timestamp < message.receivedDate
-            if  displayedMessage && isLater {
-                conversation.lastDisplayedMessage = (message.id, message.receivedDate)
-                var event = ServiceEvent(withEventType: .lastDisplayedMessageUpdated)
-                event.addEventInput(.oldDisplayedMessage, value: oldDisplayedMessage)
-                event.addEventInput(.newDisplayedMessage, value: message.id)
-                self.responseStream.onNext(event)
-            }
             var event = ServiceEvent(withEventType: .messageStateChanged)
             event.addEventInput(.messageStatus, value: status)
             event.addEventInput(.messageId, value: messageId)
@@ -1123,12 +1031,12 @@
                                 conversation.accountId == accountId
                         })
                         .first {
-                        var messages = conversation.messages.value
+                        var messages = conversation.messages
                         if let index = messages.firstIndex(where: { message in
                             message.type == .location && message.incoming == incoming
                         }) {
                             messages.remove(at: index)
-                            conversation.messages.accept(messages)
+                            // conversation.messages.accept(messages)
                         }
                     }
                     completable(.completed)
diff --git a/Ring/Ring/Services/DataTransferService.swift b/Ring/Ring/Services/DataTransferService.swift
index c44efdd..6b0e702 100644
--- a/Ring/Ring/Services/DataTransferService.swift
+++ b/Ring/Ring/Services/DataTransferService.swift
@@ -41,13 +41,13 @@
 enum DataTransferStatus: CustomStringConvertible {
     var description: String {
         switch self {
-        case .created: return "created"
-        case .awaiting: return "awaiting"
-        case .canceled: return "canceled"
-        case .ongoing: return "ongoing"
-        case .success: return "success"
-        case .error: return "error"
-        case .unknown: return "unknown"
+        case .created: return ""
+        case .awaiting: return ""
+        case .canceled: return "Canceled"
+        case .ongoing: return "Transferring"
+        case .success: return "Completed"
+        case .error: return ""
+        case .unknown: return ""
         }
     }
 
@@ -229,7 +229,7 @@
                                                    accountId: conversation.accountId,
                                                    conversationId: conversation.id,
                                                    withFilePath: filePath,
-                                                   parent: conversation.messages.value.last?.id)
+                                                   parent: conversation.lastMessage?.id)
         }
     }
 
diff --git a/Ring/Ring/Services/GeneratedInteractionsManager.swift b/Ring/Ring/Services/GeneratedInteractionsManager.swift
index dc8cd9f..57e4f77 100644
--- a/Ring/Ring/Services/GeneratedInteractionsManager.swift
+++ b/Ring/Ring/Services/GeneratedInteractionsManager.swift
@@ -107,7 +107,7 @@
             return
         }
         // remove conversation if it contain only contact messages
-        let messages = conversation.messages.value.filter({ $0.type != .contact })
+        let messages = conversation.messages.filter({ $0.type != .contact })
 
         if !messages.isEmpty {
             return
diff --git a/Ring/Ring/Services/ProfilesService.swift b/Ring/Ring/Services/ProfilesService.swift
index ff827ce..35431cc 100644
--- a/Ring/Ring/Services/ProfilesService.swift
+++ b/Ring/Ring/Services/ProfilesService.swift
@@ -211,9 +211,11 @@
         }
         self.dbManager
             .profileObservable(for: uri, createIfNotExists: createIfNotexists, accountId: accountId)
-            .subscribe(onNext: {profile in
+            .subscribe { profile in
                 profileObservable.onNext(profile)
-            })
+            } onError: { error in
+                profileObservable.onError(error)
+            }
             .disposed(by: self.disposeBag)
     }
 
diff --git a/Ring/Ring/SwarmInfo.swift b/Ring/Ring/SwarmInfo.swift
index 8e36251..a379558 100644
--- a/Ring/Ring/SwarmInfo.swift
+++ b/Ring/Ring/SwarmInfo.swift
@@ -364,14 +364,17 @@
                 }
             }
         }
-
         self.participants.accept(currentValue)
     }
 
     private func buildAvatarFrom(avatars: [UIImage]) -> UIImage {
         let participantsCount = self.participants.value.count
-        if participantsCount == 1, let avater = self.participants.value.first?.avatar.value {
-            return avater
+        // for one to one conversation return contact avatar
+        if participantsCount == 2, let localJamiId = accountsService.getAccount(fromAccountId: accountId)?.jamiId,
+           let avatar = self.participants.value.filter({ info in
+            return info.jamiId != localJamiId
+           }).first?.avatar.value {
+            return avatar
         }
         switch avatars.count {
         case 0:
@@ -384,13 +387,17 @@
     }
 
     private func buildTitleFrom(names: [String]) -> String {
-        let names = Array(Set(names))
         // title format: "name1, name2, name3 + number of other participants"
         let participantsCount = self.participants.value.count
-        var finalTitle = ""
-        if participantsCount == 1, let name = self.participants.value.first?.name.value {
+        // for one to one conversation return contact name
+        if participantsCount == 2, let localJamiId = accountsService.getAccount(fromAccountId: accountId)?.jamiId,
+           let name = self.participants.value.filter({ info in
+            return info.jamiId != localJamiId
+           }).first?.name.value {
             return name
         }
+        let names = Array(Set(names))
+        var finalTitle = ""
         if names.isEmpty { return finalTitle }
         // maximum 3 names could be displayed
         let numberOfDisplayedNames: Int = names.count < 3 ? names.count : 3