Просмотр исходного кода

add message kit sources to project
(from https://github.com/MessageKit/MessageKit.git, commit 161acb786b67631450672c6651699151ae0c4b7b)

cyberta 5 лет назад
Родитель
Сommit
ad67faa604
86 измененных файлов с 7763 добавлено и 841 удалено
  1. 0 1
      Podfile
  2. 0 388
      Pods/Pods.xcodeproj/project.pbxproj
  3. 0 2
      Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios-frameworks.sh
  4. 4 4
      Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios.debug.xcconfig
  5. 4 4
      Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios.release.xcconfig
  6. 3 3
      Pods/Target Support Files/Pods-deltachat-iosTests/Pods-deltachat-iosTests.debug.xcconfig
  7. 3 3
      Pods/Target Support Files/Pods-deltachat-iosTests/Pods-deltachat-iosTests.release.xcconfig
  8. 344 6
      deltachat-ios.xcodeproj/project.pbxproj
  9. 3 1
      deltachat-ios/Controller/ChatViewController.swift
  10. 0 1
      deltachat-ios/Controller/MailboxViewController.swift
  11. 0 1
      deltachat-ios/DC/Wrapper.swift
  12. 43 0
      deltachat-ios/Extensions/Bundle+Extensions.swift
  13. 34 0
      deltachat-ios/Extensions/CGRect+Extensions.swift
  14. 51 0
      deltachat-ios/Extensions/DcContact+Extension.swift
  15. 58 0
      deltachat-ios/Extensions/Extensions.swift
  16. 37 0
      deltachat-ios/Extensions/NSAttributedString+Extensions.swift
  17. 76 0
      deltachat-ios/Extensions/String+Extension.swift
  18. 77 0
      deltachat-ios/Extensions/UIColor+Extensions.swift
  19. 40 0
      deltachat-ios/Extensions/UIEdgeInsets+Extensions.swift
  20. 111 0
      deltachat-ios/Extensions/UIImage+Extension.swift
  21. 235 0
      deltachat-ios/Extensions/UIView+Extensions.swift
  22. 0 273
      deltachat-ios/Helper/Extensions.swift
  23. 0 49
      deltachat-ios/Helper/UIImage+Extension.swift
  24. 0 101
      deltachat-ios/Helper/UIView+Extension.swift
  25. 141 0
      deltachat-ios/MessageKit/Controllers/MessagesViewController+Keyboard.swift
  26. 104 0
      deltachat-ios/MessageKit/Controllers/MessagesViewController+Menu.swift
  27. 419 0
      deltachat-ios/MessageKit/Controllers/MessagesViewController.swift
  28. 44 0
      deltachat-ios/MessageKit/Layout/AudioMessageSizeCalculator.swift
  29. 50 0
      deltachat-ios/MessageKit/Layout/CellSizeCalculator.swift
  30. 74 0
      deltachat-ios/MessageKit/Layout/ContactMessageSizeCalculator.swift
  31. 44 0
      deltachat-ios/MessageKit/Layout/LocationMessageSizeCalculator.swift
  32. 49 0
      deltachat-ios/MessageKit/Layout/MediaMessageSizeCalculator.swift
  33. 292 0
      deltachat-ios/MessageKit/Layout/MessageSizeCalculator.swift
  34. 328 0
      deltachat-ios/MessageKit/Layout/MessagesCollectionViewFlowLayout.swift
  35. 109 0
      deltachat-ios/MessageKit/Layout/MessagesCollectionViewLayoutAttributes.swift
  36. 91 0
      deltachat-ios/MessageKit/Layout/TextMessageSizeCalculator.swift
  37. 43 0
      deltachat-ios/MessageKit/Layout/TypingIndicatorCellSizeCalculator.swift
  38. 48 0
      deltachat-ios/MessageKit/Models/AccessoryPosition.swift
  39. 48 0
      deltachat-ios/MessageKit/Models/Avatar.swift
  40. 98 0
      deltachat-ios/MessageKit/Models/AvatarPosition.swift
  41. 77 0
      deltachat-ios/MessageKit/Models/DetectorType.swift
  42. 56 0
      deltachat-ios/MessageKit/Models/HorizontalEdgeInsets.swift
  43. 47 0
      deltachat-ios/MessageKit/Models/LabelAlignment.swift
  44. 64 0
      deltachat-ios/MessageKit/Models/LocationMessageSnapshotOptions.swift
  45. 72 0
      deltachat-ios/MessageKit/Models/MessageKind.swift
  46. 66 0
      deltachat-ios/MessageKit/Models/MessageKitDateFormatter.swift
  47. 38 0
      deltachat-ios/MessageKit/Models/MessageKitError.swift
  48. 151 0
      deltachat-ios/MessageKit/Models/MessageStyle.swift
  49. 81 0
      deltachat-ios/MessageKit/Models/NSConstraintLayoutSet.swift
  50. 57 0
      deltachat-ios/MessageKit/Models/Sender.swift
  51. 40 0
      deltachat-ios/MessageKit/Protocols/AudioItem.swift
  52. 41 0
      deltachat-ios/MessageKit/Protocols/ContactItem.swift
  53. 37 0
      deltachat-ios/MessageKit/Protocols/LocationItem.swift
  54. 43 0
      deltachat-ios/MessageKit/Protocols/MediaItem.swift
  55. 180 0
      deltachat-ios/MessageKit/Protocols/MessageCellDelegate.swift
  56. 99 0
      deltachat-ios/MessageKit/Protocols/MessageLabelDelegate.swift
  57. 43 0
      deltachat-ios/MessageKit/Protocols/MessageType.swift
  58. 159 0
      deltachat-ios/MessageKit/Protocols/MessagesDataSource.swift
  59. 315 0
      deltachat-ios/MessageKit/Protocols/MessagesDisplayDelegate.swift
  60. 165 0
      deltachat-ios/MessageKit/Protocols/MessagesLayoutDelegate.swift
  61. 38 0
      deltachat-ios/MessageKit/Protocols/SenderType.swift
  62. 55 0
      deltachat-ios/MessageKit/Supporting/MessageInputBar.swift
  63. 73 0
      deltachat-ios/MessageKit/Supporting/MessageKit+Availability.swift
  64. 29 0
      deltachat-ios/MessageKit/Supporting/MessageKit.h
  65. 203 0
      deltachat-ios/MessageKit/Views/AvatarView.swift
  66. 49 0
      deltachat-ios/MessageKit/Views/BubbleCircle.swift
  67. 130 0
      deltachat-ios/MessageKit/Views/Cells/AudioMessageCell.swift
  68. 142 0
      deltachat-ios/MessageKit/Views/Cells/ContactMessageCell.swift
  69. 111 0
      deltachat-ios/MessageKit/Views/Cells/LocationMessageCell.swift
  70. 84 0
      deltachat-ios/MessageKit/Views/Cells/MediaMessageCell.swift
  71. 45 0
      deltachat-ios/MessageKit/Views/Cells/MessageCollectionViewCell.swift
  72. 348 0
      deltachat-ios/MessageKit/Views/Cells/MessageContentCell.swift
  73. 101 0
      deltachat-ios/MessageKit/Views/Cells/TextMessageCell.swift
  74. 66 0
      deltachat-ios/MessageKit/Views/Cells/TypingIndicatorCell.swift
  75. 40 0
      deltachat-ios/MessageKit/Views/HeadersFooters/MessageReusableView.swift
  76. 38 0
      deltachat-ios/MessageKit/Views/InsetLabel.swift
  77. 88 0
      deltachat-ios/MessageKit/Views/MessageContainerView.swift
  78. 546 0
      deltachat-ios/MessageKit/Views/MessageLabel.swift
  79. 195 0
      deltachat-ios/MessageKit/Views/MessagesCollectionView.swift
  80. 122 0
      deltachat-ios/MessageKit/Views/PlayButtonView.swift
  81. 156 0
      deltachat-ios/MessageKit/Views/TypingBubble.swift
  82. 165 0
      deltachat-ios/MessageKit/Views/TypingIndicator.swift
  83. 1 1
      deltachat-ios/Model/Location.swift
  84. 1 1
      deltachat-ios/Model/Media.swift
  85. 1 1
      deltachat-ios/Model/Message.swift
  86. 0 1
      deltachat-ios/View/CustomMessageCell.swift

+ 0 - 1
Podfile

@@ -16,7 +16,6 @@ target 'deltachat-ios' do
   pod 'JGProgressHUD'
   pod 'SwiftyBeaver'
   pod 'DBDebugToolkit'
-  pod 'MessageKit'
 
   target 'deltachat-iosTests' do
     inherit! :search_paths

+ 0 - 388
Pods/Pods.xcodeproj/project.pbxproj

@@ -33,14 +33,11 @@
 		013040C71F5AB4BB46687BE9EB805D6E /* DBCustomActionsTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = DB21220116833E3C7B0E951A8267AA5B /* DBCustomActionsTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		0183741FDEAAD6CB32ADA51F8758DD84 /* DBColorPickerTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 786BAF94C838FAA33CF437199BCE468B /* DBColorPickerTableViewCell.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		02046FB86D6A8C3740A7993E8598945D /* DBResourcesTableViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A05DA6F8172228C606DB42F0C9672322 /* DBResourcesTableViewController.storyboard */; };
-		03F9B2A4C641C137A34FC30F961E6BF1 /* InputBarAccessoryView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC1446C8006126DB7E9E07054F774C1D /* InputBarAccessoryView.framework */; };
 		04920A0A9ACC8BF2EAFF02FA5E3903F0 /* DBManagedObjectsListTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 687A115DC0BFB9375F92FC857F87310D /* DBManagedObjectsListTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		04ED6F1BC5AD6A625FBA60829C0DF065 /* DBNetworkToolkit.m in Sources */ = {isa = PBXBuildFile; fileRef = EFF27D80746797BA12936774F3760EF6 /* DBNetworkToolkit.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		05F25790BC1C166E599A32F834DB9E46 /* DBMenuSwitchTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 058B422CC6EC3C748065047822A5037B /* DBMenuSwitchTableViewCell.xib */; };
-		06059FE7782E40948EDB31995DA743C4 /* MessagesViewController+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31760CB7F4305589A7EC118DF6B612B6 /* MessagesViewController+Menu.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		062DDB403AA135579935235336736041 /* DBOptionsListTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = A8D48A16E5E7AA5E93875DF923914726 /* DBOptionsListTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		06AA4EB078A4CCDA823F6C8A16FE0F1F /* DBShakeTrigger.h in Headers */ = {isa = PBXBuildFile; fileRef = CFC3F8AC60EF3069C9F5B1D7BB4A52A2 /* DBShakeTrigger.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		0754130AD1077ED7C60AB95A4C5075E8 /* MessageStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FAD38D43037FDD86E6EE549962853C /* MessageStyle.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		079406C0A26BCB97F83D51AF1D7C158E /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFEEF4676F9162A5894925E7F0F9C64 /* Extensions.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		080B82993832E95FA3E169BFD92BB56D /* DBCookiesTableViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E42F92CB607FBD6FCD2D39A704AE1D0 /* DBCookiesTableViewController.storyboard */; };
 		085B2F5C24A3CA76F8DDD6B161D90F87 /* DBPerformanceTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8263487297CAA15549226652F49985B9 /* DBPerformanceTableViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
@@ -49,13 +46,10 @@
 		0A30F010E65F935555E575A18606360A /* DBMenuSegmentedControlTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 455E95E95CC98BA5BA0BC173646122B7 /* DBMenuSegmentedControlTableViewCell.xib */; };
 		0A55FFD2C603CC70E537B3B97BCE4381 /* DBGridOverlayView.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B9B31CABD2FF1C890E4E44247A00345 /* DBGridOverlayView.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		0B2C18BD33B6FEBDDD4BAB798765E5DB /* DBMenuTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D93D23BBFB63833B55519A26782EF5C8 /* DBMenuTableViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		0B85D3695AA53CF3B5A9A94366A1C28A /* AudioMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718E44B94F4806CE7AC495863ECEB9A8 /* AudioMessageSizeCalculator.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		0C124523CE9363A8ADD9A7C8A7F97658 /* DBBuildInfoProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A163DDEC84B55B7D9CC5D9249217EF7 /* DBBuildInfoProvider.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		0DFA4E3B12319C0DF6883B78D7CD75FA /* DBManagedObjectTableViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8B34D16812BA6715134A21C24F372726 /* DBManagedObjectTableViewController.storyboard */; };
 		0EE9FE30E4B70FCE2FB35B3F0D86BDB7 /* JGProgressHUDIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A0CD002C4EB47E7E949EE92221C9998 /* JGProgressHUDIndicatorView.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		0F12700F0ADC563B2C6894E318FF1863 /* MediaMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 599685DF7351AA2F9C9B8BEF4AA1E1E7 /* MediaMessageCell.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		0F1A9F111B011A612CE2EE54486487B1 /* DBPresetLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F5B18DB078F751C68B47BE6EDE150DE /* DBPresetLocation.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		0F27A71605EF5D68738875EEE1701879 /* CellSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1465C95E03D9D31DBAF0947967902B /* CellSizeCalculator.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		0F5FB137CBC8670B42CF4682BFE22AD4 /* UITextView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F82FB624705444AFE6ED6A435268048 /* UITextView+Extensions.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		1047D9F2379027FF574FEDEAFE29B161 /* DBRequestDetailsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = DDF61DF344E148D163B13F12B825F12E /* DBRequestDetailsViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		1091819916640952FE5318305CA7A375 /* DBFontFamiliesTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = CC40E24295A387B30BFC6B07B1C081C9 /* DBFontFamiliesTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -63,7 +57,6 @@
 		1112FBFFB8CB0BFEEA7BB0D160F8808D /* JGProgressHUDAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 936C5ED8FE8A16FC99E07D9E155B1F7E /* JGProgressHUDAnimation.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		12EE97F1B3FF3967C9F356426FB624F8 /* DBKeychainToolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = C6E145B0900846F45E026508AF4F75C9 /* DBKeychainToolkit.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		1338A633F85849B4B1545C1CF92C84EA /* DBPerformanceTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 74E0D2D8584A77234211F80DBFDF41DF /* DBPerformanceTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		157554B665FC1CB33EB8BD8C530502FB /* InsetLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 126ACF3D1759186782F7251082DBE735 /* InsetLabel.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		161B18DBDF568FA81DBFF5DFDF8B8054 /* QuickTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3741BFD8C793DB441C71D01E7E68317 /* QuickTableViewController.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		17A35EB6CFE6677795A070DAB5FEABF4 /* UIView+AutoLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F76078B2946346397CE0A315F35439B /* UIView+AutoLayout.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		17C19B238EEFE7A233A24A313C9C8C14 /* DBMainQueueOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = D10DD9B156D929B0F80E400626B13BDA /* DBMainQueueOperation.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -76,11 +69,9 @@
 		1BA7F188B5E866619484694DBBB58D87 /* DBMenuSegmentedControlTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 8821E159418F653A5B44CAD3B9092FCE /* DBMenuSegmentedControlTableViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		1C15CD344BE872DF4B21662ACA4F0209 /* ImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A5F4B1D60F8B62756DBB19046FDF44 /* ImageCell.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		1DB172FEC3DE2B0310DAE964C1957DB1 /* UIView+DBUserInterfaceToolkit.m in Sources */ = {isa = PBXBuildFile; fileRef = E9076DB61ECEDEC4E3E9CDE409FA22E6 /* UIView+DBUserInterfaceToolkit.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		1DF055B6C6BE84147AF954953614F508 /* DetectorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DDB96FE6149AE1B25461BDAF33B5939 /* DetectorType.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		1DF18686FE92630D86AE0901107059A4 /* UIColor+DBDebugToolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = B24B4D729D2FDEF0A90A229D0EAD1240 /* UIColor+DBDebugToolkit.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		1E3FAE3449031B937C659554213993B5 /* DBTextViewTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = AF26DDAA681963B6F25005D2DE334593 /* DBTextViewTableViewCell.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		1F649318AB549FE371566B4E947E0C62 /* UICircularProgressRingDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095AABA5B1540F1180572CA4F619EFC1 /* UICircularProgressRingDelegate.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		1F95302BDB9904D36FB67A6631A9CECE /* NSConstraintLayoutSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D86E6705890CDDC974316E6C07306291 /* NSConstraintLayoutSet.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		1FED5646809EF034BD4EB85C17AFE165 /* UIViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536DEC9245DD267947FB441BCCA2FF4A /* UIViewExtensions.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		209BCDB8BA86839B4B284020BA730842 /* DBPerformanceWidgetView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BBC44EB0C6A8C6C82F66983DC316312 /* DBPerformanceWidgetView.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		2155AB18CF86F112040F843EBA1B5199 /* DBRequestOutcome.m in Sources */ = {isa = PBXBuildFile; fileRef = 96C798C865C658D4A13F0FEFECC6FAAA /* DBRequestOutcome.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
@@ -99,24 +90,17 @@
 		29C0B82B3CB4FC28412BD0E3C4A2C770 /* UIWindow+DBShakeTrigger.h in Headers */ = {isa = PBXBuildFile; fileRef = 44B71FEE3B0C399CC07D3CAE340EBDF8 /* UIWindow+DBShakeTrigger.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		29D8D7370924852BC318E5FB4E591D33 /* DBCustomActionsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 236CB68B8771B7F799118AD9B0EE564E /* DBCustomActionsTableViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		29E9FDCDE9192FDEA875045ABD2E2E98 /* DBCoreDataFilterTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 850469A80DDB7CD757BBCEB26DE2272E /* DBCoreDataFilterTableViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		2B195A3EE3A8D859F446BCCFA45F6AA3 /* AvatarPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FC5B9D9696390BDCF961FD1C9593967 /* AvatarPosition.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		2B6D2729D9AE57EBFA968F77C31DE9E1 /* Pods-deltachat-ios-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 632E47E8543CA5DA3ACD00787696B0BB /* Pods-deltachat-ios-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		2B7E7F1D96317A298BB3B29CA93B196A /* LocationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B647E47F1761C357521009B7F05884FC /* LocationItem.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		2B98AC50E93E974FD7CD62C2E18C9D64 /* DBColorPickerTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 007531F7696130C3E7AA26E0166285B7 /* DBColorPickerTableViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		2BD6A14261812A2D9E3CC9A490796AD7 /* DBCookieTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 96DD695C085610BCF95F6D8ACB51E198 /* DBCookieTableViewCell.xib */; };
 		2C117200552E8B2699A49702A4490E11 /* DBCookieDetailsTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A4F140EA8630022745973D64FF0E0F0 /* DBCookieDetailsTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		2C414BB249EA71323E41A5EE4AEA82BB /* InputBarSendButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EB6691BC6060FCC9BA47476C0B095B /* InputBarSendButton.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		2D92582DD68356DA40995D5019753EF2 /* DBRequestModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 12C8EEFF9D3006316B751C6EBFA0E371 /* DBRequestModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		2E0E72BA64798E7B88BD0FCE42A542E8 /* DBTapTrigger.m in Sources */ = {isa = PBXBuildFile; fileRef = F27883EA0EF80E20C2DE403115075B18 /* DBTapTrigger.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		301FCC982A72C8672531F345FBABD500 /* UIEdgeInsets+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91083CE184E8CAB5777DB9676CBD404E /* UIEdgeInsets+Extensions.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		3045D45B5AFA6258F9F1625424752588 /* UIApplication+DBDebugToolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = 3086D165B42063AD64FFE893457C886C /* UIApplication+DBDebugToolkit.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		309546D828A4DAB707F1C403AAFFB9A2 /* DBManagedObjectTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = F9590E62086F66D98D853C9309F95740 /* DBManagedObjectTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		31084A593108FFC2CB4E19C76B881ED8 /* MessagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72BB49C3A6B1D8C21B2347A1AAD9BAEF /* MessagesViewController.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		327131B133AA2E5B6FB752C02D18A357 /* MessagesCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9395868C34C6F8BD562F24C9A622CBDB /* MessagesCollectionView.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		32D79829D4D84FF94F3AAE423AC3BBA9 /* DBOptionsListTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CA5A66FCAFA6746EF112AE5E0E2554A8 /* DBOptionsListTableViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		339005E9972FB83D6865A934C20253F6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D478715E61EBAC282D7EF7088E185A6 /* Foundation.framework */; };
-		33B03C7C71BFF36A3BE204CB05449455 /* Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = CADD4971B504F91C29539452EEDB2B77 /* Avatar.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		35241910C42D12B7B9513183AA5ECBF1 /* BubbleCircle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8756002DE3D13281782F43AC2A775AFB /* BubbleCircle.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		361B3FB0819B8A79EA30177EEE9776FB /* DBCoreDataFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D6BDBDCEF41A6E8225380B00F09D35D /* DBCoreDataFilter.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		36576DB838772DB722F9B4B7FD9A66E2 /* RadioSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E3FA97D9E85C54A3EB067116C8EAD4 /* RadioSection.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		36D474F702E4A6EFBE0EFB1810316361 /* ALCameraViewController-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = CE8CF126B38247A026AA135604D0C423 /* ALCameraViewController-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -129,7 +113,6 @@
 		3952122148CEDB13C26E4FCFBC4D7A95 /* DBBodyPreviewViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3D59B360ED2B07EDE514F07861780B96 /* DBBodyPreviewViewController.storyboard */; };
 		3ABF08FB9D856F985D76D1A2E7EB801C /* DBCookieDetailsTableViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 60D92310125B7BBBBA810C542EC60E7A /* DBCookieDetailsTableViewController.storyboard */; };
 		3B73415D1D43D071D1889289F7101F5F /* NSOperationQueue+DBMainQueueOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E17656EF1809A9BC5CF71261A04384D /* NSOperationQueue+DBMainQueueOperation.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		3BB73C8DAA73214A6F281C2B78A5B599 /* MessageLabelDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9871488E7E21A4292C775CA11014E6A6 /* MessageLabelDelegate.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		3BE422FCBD82BFD461F21B55F4621EE7 /* JGProgressHUDIndeterminateIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 32BC4A22CBD11FCFC82054BCCB6ABCD8 /* JGProgressHUDIndeterminateIndicatorView.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		3C004F6D0F641D0AF81ADFE30AC5CA12 /* DBTopLevelViewsWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 2BA434959DFC2D0C9C304C7B7B5B9438 /* DBTopLevelViewsWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		3C799BE510D34955B833D46A4A992177 /* DBDebugToolkitUserDefaultsKeys.h in Headers */ = {isa = PBXBuildFile; fileRef = 5AD56143D62BA0174D9B66713266101F /* DBDebugToolkitUserDefaultsKeys.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -144,20 +127,16 @@
 		42A24D410B67E5D8D888CF52D86A10A2 /* DBConsoleViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = DB9B1F49DA0AA691C53E0CAF63A7E334 /* DBConsoleViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		42ACF4F1B937A52D55BF6B63677C70EA /* CameraGlobals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69797FC218400936E597858F211C2393 /* CameraGlobals.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		43A808DCAEBDFEE02457771A28DE1FB0 /* CameraViewAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1EFB9C0D094AD8C8092CA6D192B03E09 /* CameraViewAssets.xcassets */; };
-		448DA30B415182D8BBEAB3E2AB239D03 /* CGRect+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3759041888B11174063953214BB5B090 /* CGRect+Extensions.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		4497943507FDD9C40EB7A97F3C74C2B1 /* DBConsoleViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84F93D759AE6B6ABF7E4205C5FFA758A /* DBConsoleViewController.storyboard */; };
 		44C5E9380F53EB793CBFCB733D6714D9 /* InputBarAccessoryViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B298E61946104C3F15958AD936DFB2 /* InputBarAccessoryViewDelegate.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		45259492B14E1D00DA0E9D08959A9FFE /* MessageKit-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = BB1D8A1BC1190A68B9ADF3415FC4A2B9 /* MessageKit-dummy.m */; };
 		457F98D200A00500653CB68E1E4E048F /* DBFontPreviewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 535DD54DE78AFC0FB8BBBE0E73D2557B /* DBFontPreviewViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		46201056929EA4738D24BBA6C2C7F822 /* DBTitleValueTableViewCellDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = E62BB3E79D3C03FFC61450481DB44BE6 /* DBTitleValueTableViewCellDataSource.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		462C40B6C99A749802D4C921D04F1A02 /* DBCustomVariable.h in Headers */ = {isa = PBXBuildFile; fileRef = 4155AA751C31EF83BD22CCE3CF15CDFE /* DBCustomVariable.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		4663F6B6F86B6C13F66DBB941EB47515 /* DBRequestDetailsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 74890CF366C32BBB69DE0D470270877A /* DBRequestDetailsViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		473F2BAA8B8895EF7DAA827139E3CEF9 /* DBCrashReportsTableViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7968EB90DFCDD9FB4EFED3B37BB0966E /* DBCrashReportsTableViewController.storyboard */; };
-		4A5DA398F200B9407722CDCE68641E89 /* LocationMessageSnapshotOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5471B7C3040EB43402B339B90320277 /* LocationMessageSnapshotOptions.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		4A5FD6639EC541B6F371E4A316C30D7C /* UICircularProgressRing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23824B181216730312A775A961D880CD /* UICircularProgressRing.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		4B47D03EC7A2C3FBD31B735FA71C2C77 /* DBManagedObjectsListTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C5BB2454829198C2DE557985783353E9 /* DBManagedObjectsListTableViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		4B755B52E7686D4921B6CBBFEDBF2603 /* AutocompleteCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD61B011B5D2DCBBB36ED9EE9858F27C /* AutocompleteCompletion.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		4BB743D477A59721872E2387C60B4F28 /* MessagesCollectionViewFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD316C4331A5C3ACBA98B61C07891AD7 /* MessagesCollectionViewFlowLayout.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		4CE4AA83ECE764724C9D46F8EFAA04D1 /* SingleImageSaver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62171B4AEB9AADE3835B20A05B9C2164 /* SingleImageSaver.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		4D46C4A8896F4DA504DA9B883D66E259 /* CameraShot.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DB32E69DBEE567D2B41E4C51C70229 /* CameraShot.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		4EE027432EEE43C80D9428DE8ABB8546 /* InputPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56A92278CC7A0C05A78DB9684FE11CF /* InputPlugin.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
@@ -170,24 +149,19 @@
 		50376920D671F1E84EF6AF00C728A295 /* DBFilesTableViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C1A9D05452AF384C0825F8551C06914E /* DBFilesTableViewController.storyboard */; };
 		51384154BC720AEE4AFDA771431772D6 /* DBGridOverlaySettingsTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 1098EB1DE43BE2118AEAFD77D09207AC /* DBGridOverlaySettingsTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		518E4BF8A3985A43F6EABAAA9961A045 /* DBRequestModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CB35C74824B9DBE03AA52AAF95A3C66 /* DBRequestModel.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		51B415FD6314FA15F9661BBE8429A553 /* TypingBubble.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2BD95727E4BEC5E2CCB968CAF27B013 /* TypingBubble.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		51EF677948FF643D97B3C0A5D75CCBDC /* JGProgressHUDErrorIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FFC30A0970B48652CC3008689110954 /* JGProgressHUDErrorIndicatorView.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		535740F55835C9913C4C8B95EE9CCAB7 /* DBNetworkViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE84EC851A9A9BCA2D0A4F07BE93717 /* DBNetworkViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		5371BA4900525F7CCC8F12E9289B5DD0 /* TypingIndicatorCellSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CE8A398078AB3F4BCB9E5A786C34880 /* TypingIndicatorCellSizeCalculator.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		539DAA5468A8F4199BFAA0B6BE6C02B3 /* jg_hud_success.png in Resources */ = {isa = PBXBuildFile; fileRef = 9416569C5FD0E9A901130FCCFF729460 /* jg_hud_success.png */; };
 		53A46D1FDFF77AE98CE7B976A2EFE7AC /* DBTitleValueTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 54E6C51558358404D779EBB473460A3B /* DBTitleValueTableViewCell.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		53FB96193A76501F8274C7581269B918 /* DBTouchIndicatorView.h in Headers */ = {isa = PBXBuildFile; fileRef = C4F7E3195E6B9E543F2B6929E3A219B0 /* DBTouchIndicatorView.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		54A2D7ABC520AC4894801EC6CB1575C4 /* JGProgressHUDErrorIndicatorView.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A5E303ACC0521A408BF4146F03228D3 /* JGProgressHUDErrorIndicatorView.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		54F04BC2C487C53008F63F9BEA7D4060 /* DBSliderTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = B0774281609BF4091C35E17EBB11B95E /* DBSliderTableViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		54F7E8D907BAE7B8D3AFED656BAF4810 /* UILabel+DBDebugToolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = ABE2ABD4054299AF228EECBF2B216CDD /* UILabel+DBDebugToolkit.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		564C5D7925FB803C03C0B7741A31D84C /* MessageKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 408AEC021E18225342B0E25C32E572CF /* MessageKind.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		567FFDA2E5D52FFD518BDBBA3F8132BA /* GoogleCloudDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E3B6F633B1D1802C05D85A46298620C /* GoogleCloudDestination.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		5685DAFA34538572F52821CB6173FE4C /* DBPersistentStoreCoordinatorsTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3D5A7735878961CE33850135055D0224 /* DBPersistentStoreCoordinatorsTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		56878442FC79462DAB17962039045DE4 /* MessageInputBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB0A80DE0C75AA84DF02B1E51A09EB6 /* MessageInputBar.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		56EF88DCF8D54F4797BBF150D2C8D065 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D478715E61EBAC282D7EF7088E185A6 /* Foundation.framework */; };
 		5721F0F51DF992271B625FF5C6E32175 /* InputBarAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96A140B21CA00BA875F56DEE882AD7E9 /* InputBarAccessoryView.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		578DA9EE012835629AD72B6CE20ABF12 /* jg_hud_error@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5A86E6D70A3E9A0DFDAD577B0D1B69A8 /* jg_hud_error@2x.png */; };
-		58491D2DC6CAF6A78D2A6FD92F09B2C1 /* MessagesDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750C94CB910688E98051AE87324C85B0 /* MessagesDataSource.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		5930CED03C493D40EFBFC0A0935CDDBC /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 939156CC2B77910F390C88962507BA09 /* SystemConfiguration.framework */; };
 		59A73F32078379791453C81C69AEA3D3 /* Pods-deltachat-ios-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = F86AF286211889F467E38091E221BD5E /* Pods-deltachat-ios-dummy.m */; };
 		59B88B51FFC486A2B97DAB3AA84F3ADE /* ALCameraViewController-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 920554CC81BBE196BA7C1A214393BDBF /* ALCameraViewController-dummy.m */; };
@@ -195,9 +169,7 @@
 		59DB42D655B61C6DA4DAE6259B8FBD17 /* DBCrashReportDetailsTableViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 76D42A9FB5F11A8FB8C0150956BAE412 /* DBCrashReportDetailsTableViewController.storyboard */; };
 		5A2FFE464F7C8E291BCC0E56C1ED4A90 /* DBTapTrigger.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A369B59179C46D4560465DD7D545D16 /* DBTapTrigger.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		5AA065F93A4C7B35B9B9A8F147CF4E37 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D478715E61EBAC282D7EF7088E185A6 /* Foundation.framework */; };
-		5BEE33379221E006E5B857FED37FD955 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 921842F6F8810C00FFC8D96FCE0B7ABE /* Bundle+Extensions.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		5CB20BF36323F8F9E15E1B543A0E56EC /* DBFileTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C0A31F766FF4B4521183952093A3AEE /* DBFileTableViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		5D10DEF44E6CF4971FCE68FCA5866E33 /* PlayButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AFC861CB066A5536665FEEE2B47FAF2 /* PlayButtonView.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		5D76EC7CF2C5B0A245F9F84C8474787E /* DBManagedObjectTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2EE52A70701BA1DFB133E8FAB9149BCC /* DBManagedObjectTableViewCell.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		5DC52D0FAD89C1B36FD880443F45592D /* Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E21B7F185F32E75EC2DE28C39D312D /* Base64.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		5DED0DBA7D56A0548A76D813634706F7 /* DBManagedObjectTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4869B5EA7D36DBF402877060DEE9BA7F /* DBManagedObjectTableViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
@@ -206,27 +178,21 @@
 		602807FF5820D28D8667752628B9CA50 /* JGProgressHUDFadeZoomAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = E026DE5D5B92DFF74F6DB69E827567B7 /* JGProgressHUDFadeZoomAnimation.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		61CC16B73E5DCA050F105F0C9E9E1D84 /* UICircularRingStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6921A53BE2092332BE9D3B5CD30C351 /* UICircularRingStyle.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		6246967B808EDAF81E1D80DF8608DF81 /* DBFileTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 55D5A0719FA1A156565582516DD8C621 /* DBFileTableViewCell.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		644DA359877F436FFF4A47A72015E0C3 /* MessageContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A05A43FE6F8908C2B998CA083D67D7 /* MessageContentCell.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		64D578D60CDA3936E0943035C990EB18 /* AttachmentManagerDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5724DE0D21ADD342E7EC2DF49F941A0D /* AttachmentManagerDataSource.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		65CF4B760898645885FF32987455CC8D /* Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB05649400D1D9665E7394E8B0307FEC /* Icon.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		65D84F54C53AAFFCD84ECD6F949FD027 /* DBDebugToolkit.m in Sources */ = {isa = PBXBuildFile; fileRef = C190828306305706C07B2FC80AFA27E6 /* DBDebugToolkit.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		67EFC1D8439F69DB6DDCD91B988FEDD9 /* JGProgressHUD-Defines.h in Headers */ = {isa = PBXBuildFile; fileRef = E863F16470470580D498911FC3254CBB /* JGProgressHUD-Defines.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		6990F55E90A56FF7010765B1CD5C4385 /* Pods-deltachat-iosTests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 718BB0DC93E32F1DA5C0DDE40F677193 /* Pods-deltachat-iosTests-dummy.m */; };
 		6A8CED07C6307984268450FBB760DF51 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ACC28D74F7B8DD3D20822FF87631D977 /* QuartzCore.framework */; };
-		6AE1160F128270F3F962BCC8B21107F7 /* MessageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19B4B6AA4627279C23C3B95A70C72D1 /* MessageCollectionViewCell.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		6BFB19D442C05362302B95179CADBE34 /* UIWindow+DBUserInterfaceToolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = 62B407CB274B2F5DED89816A4575731D /* UIWindow+DBUserInterfaceToolkit.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		6D559CC30AD2CCCD2E6CC24B902C4990 /* DBImageViewViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 99B2FCC361B1061034D4EF279405C78A /* DBImageViewViewController.storyboard */; };
 		6DD6827FBB2CC1FA3D9049CF63D06C72 /* DBNetworkSettingsTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = FC1EBEC976B7A98420F713A2EF96021A /* DBNetworkSettingsTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		6DD8056CC37D0C7EDEEEF0504C6FDBF0 /* MessagesLayoutDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC3FCBBC7C2D118ABF831125BE2E760 /* MessagesLayoutDelegate.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		6DF7064C760DDF64E525D398FCF0D25D /* DBDeviceInfoProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DFC9DDFAF65E42472FF207AE7D85FAF /* DBDeviceInfoProvider.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		6E0B3656AFDC1D2AFC2889A88ACDF1F0 /* RxInputBarAccessoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20F16C2F4D3632FBFCE01066B32FA8EE /* RxInputBarAccessoryView.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		6E6C75AFB21861CA38392655E17B9B99 /* DBLocationToolkit.m in Sources */ = {isa = PBXBuildFile; fileRef = 08ECEBCA5F7F43918DF95C1ACB2B2C56 /* DBLocationToolkit.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		6F86A228F7798553027904AB730C65AF /* MessageKitAssets.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 3E09F64B3AB663C3D62E73D5A50D1175 /* MessageKitAssets.bundle */; };
 		709182C8EB79D714D8E9884A86FA8B3C /* UICircularProgressRing.h in Headers */ = {isa = PBXBuildFile; fileRef = 74842ECC9B036526DC8FA2B73B5A262C /* UICircularProgressRing.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		709C1A062F777E1D103BA371545EFC0C /* DBResourcesTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 10ECB4AAF25E0E0FB38EE4057DF84ACE /* DBResourcesTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		70AF5CAC03E5C644AFB422DE14767E65 /* DBKeychainToolkit.m in Sources */ = {isa = PBXBuildFile; fileRef = 239BD151AF0CDED2E29ECB9CC1B7A9FD /* DBKeychainToolkit.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		70FC13348A812E69AED5BA7C2591133A /* Images in Resources */ = {isa = PBXBuildFile; fileRef = D27882E74F13E970710FF59B770D294D /* Images */; };
-		721DC43C99943175F26976E71CD84BE6 /* MessageKit-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C35E54B80B39F147C29D45EE1836C82 /* MessageKit-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		72EE3A5EC1B07C6FBB9AD7A3252A6D8F /* DBCrashReport.m in Sources */ = {isa = PBXBuildFile; fileRef = D73B7064173766E236D6BADF4EE00386 /* DBCrashReport.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		734DBE3771CB6DD3AD0D0C9C3DA89F3D /* InputBarAccessoryView-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = F32CC41A607413FA9F665EC57D997A4C /* InputBarAccessoryView-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		735635D397E7E036F08479A1AC463774 /* AES256CBC.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2C3C292625FED77778DA1C695A2911 /* AES256CBC.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
@@ -236,10 +202,8 @@
 		77D237C1FDEDB0534C923EC13E75DE72 /* DBRequestDataHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 7F5C38227CDF98DE2BB468FB2969333C /* DBRequestDataHandler.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		78357DC6A35CD6CA0E044A416E71F242 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D478715E61EBAC282D7EF7088E185A6 /* Foundation.framework */; };
 		78C4D0CEB1021FBE8D4AA48EB90E611D /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC5B1E4DFA7581FFDA14AD7FD7ABB96 /* Filter.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		78F9C13D557AD3A4A5D81ADABE44AC4A /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF2CC289A97440C7EA6012F10F9888FC /* NSAttributedString+Extensions.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		793E9669228708968996EF641B4A6B4F /* DBShakeTrigger.m in Sources */ = {isa = PBXBuildFile; fileRef = A55F3CBCDC4CE375AE5DD5784BC28DD7 /* DBShakeTrigger.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		7A36BA703708359EDFB5654E318B01C6 /* DBBodyPreviewViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = B40142448CAE5A031C9E06456E9FAB1C /* DBBodyPreviewViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		7A8892328A8E3F44D0CFB33D2D382BBE /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D478715E61EBAC282D7EF7088E185A6 /* Foundation.framework */; };
 		7A97EFD493B5FADF8E7341C5DFDF29E1 /* JGProgressHUD.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 66A2FC4BA176D2891FE9363105FBEAC4 /* JGProgressHUD.bundle */; };
 		7D6491B8C3E369C848FF6FC0380050AF /* DBFPSCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D3E151ABDD917F4CC35113749913E40 /* DBFPSCalculator.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		7D7E6ED70D27CC1D9B85F0DBFB5F91E9 /* AutocompleteManagerDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 689AA86978807521D745F37C389B34A0 /* AutocompleteManagerDataSource.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
@@ -248,29 +212,24 @@
 		7E2AA9C5D4FDE1210C1509490594E83A /* UICircularRingValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335C055E405D7226FE294B8EBE72B40D /* UICircularRingValueFormatter.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		7ED45B36470265E44C63D6A2724FB110 /* UIWindow+DBUserInterfaceToolkit.m in Sources */ = {isa = PBXBuildFile; fileRef = 5ACBC73A2E0196847E84D35AB83FCC98 /* UIWindow+DBUserInterfaceToolkit.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		7F5EE468DA286364DCEB22B9A27592CB /* DBCrashReportDetailsTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 7EE3F81AF26E5662730726E3B788703A /* DBCrashReportDetailsTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		7FF5FEACAD83DCC8C2CFE646EA484C74 /* ContactMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E45F7C4F805948433D6FAAFA5FF6947 /* ContactMessageCell.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		801A1510A2172D5870A475FC3D7B6A1A /* DBPerformanceToolkit.m in Sources */ = {isa = PBXBuildFile; fileRef = 438DC134798CD1712474BFC141F7D6B2 /* DBPerformanceToolkit.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		801E788DFA5D7C5B06D7C6076CB657C0 /* InputTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9043748E4E5AFA63CB60886D30F3A5 /* InputTextView.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		809B19B0E139021AD4BFD54568E11E21 /* NSObject+DBDebugToolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = A95B23FA35ADA5554C9490B6E1573C73 /* NSObject+DBDebugToolkit.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		80CEB6A0221653EDCB81157AD30104D6 /* ContactMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFCA663ADCDE362C6D977E5C0B1CC811 /* ContactMessageSizeCalculator.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		810410214555C7D1C1CBEFB6AEBD1C5E /* DBImageViewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D0EF243D9E2CD16DD8BC388F9493C07 /* DBImageViewViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		8132418128C2135AB01C16D886FB5652 /* jg_hud_success@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1388809C20F05DE27ACCD58D62F27A41 /* jg_hud_success@3x.png */; };
 		8137584CD943249F909654BCE10C850D /* DBDeviceInfoProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = E248156D3051F5CEFC7A2EACC1EBEB49 /* DBDeviceInfoProvider.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		8190B5F022579C0136E317ECAE95D024 /* DBTouchIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4115C0821BB57E613797F65EBE400E77 /* DBTouchIndicatorView.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		81C6414CEC896F2C0749FF7B16BAF255 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D478715E61EBAC282D7EF7088E185A6 /* Foundation.framework */; };
-		82DDC5559965A4FC7C6E78C67085CA7F /* TextMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0306EE318D1CA8968F944C85796D486 /* TextMessageSizeCalculator.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		82E8D50A8889133A4285B08272F85D60 /* DBGridOverlaySettingsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E0C7060329A315B13D95E9A93D7105D /* DBGridOverlaySettingsTableViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		834548E14BD312E897EB78094A88A398 /* DBTextViewViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D81D8E1B68B176C5A974B4F8CF56F974 /* DBTextViewViewController.storyboard */; };
 		8382002DE92B745CB3EAEEEA35973EB2 /* DBTitleValueTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 4829B3FCBF3E9FE370DF5BE781340FF1 /* DBTitleValueTableViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		8388FCD73A96CF42F5AB47875866CDB4 /* DBTextViewTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = CB1C44D1D455AD76C3D67A284CB9FEC6 /* DBTextViewTableViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		85350D1FC3916408761778BFABD6E326 /* UIWindow+DBShakeTrigger.m in Sources */ = {isa = PBXBuildFile; fileRef = F91DF448ABE8AFBA4C6C6AB8D96E9681 /* UIWindow+DBShakeTrigger.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		86037C68710AE49A4064723C028E74F9 /* DetailText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D32D46D70CEFF35CBB26D2AAA5F097BA /* DetailText.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		868168BD985CA6937CA5C1E11364BFE9 /* AudioItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C67203ECC6CEB553F6439B1AD1BA162D /* AudioItem.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		869BD7746418150E6D852F4BAEF63B78 /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7047715E53AAE83838D6F0617640070E /* Reachability.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		86C6C9BA42B1AB928A364888733E126F /* DBMenuSegmentedControlTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 1DF13FD31D466C6D756D74BC52E894CF /* DBMenuSegmentedControlTableViewCell.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		86CA46D617871DE4B42B293C739D1B3C /* DBCustomAction.h in Headers */ = {isa = PBXBuildFile; fileRef = 96C976A0CDC7D1B1AC716CF2C47FCA9C /* DBCustomAction.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		86CA4C1543B3C0A0278D217F51AB7F6B /* DBCrashReport.h in Headers */ = {isa = PBXBuildFile; fileRef = AE6317E236612F9E009FABEC3C9EE60B /* DBCrashReport.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		880046882FE5B5F89E879A81DA21B84D /* HorizontalEdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E8158D68641C882EE01C38B615E478 /* HorizontalEdgeInsets.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		8A896269483695ABCD1CE6BDE78E2397 /* DBCoreDataFilterTableViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 61D298C32F9AED4296D25D50C468AEBF /* DBCoreDataFilterTableViewController.storyboard */; };
 		8AD7E52322FC518B83B75C1410612C44 /* DBPerformanceToolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = B1D68AC074343A5225F932BD3AC195D3 /* DBPerformanceToolkit.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		8B1E9101706D4212A3F6036E26A9831E /* UIView+Snapshot.h in Headers */ = {isa = PBXBuildFile; fileRef = FB1F206928DC684B6836620579FBDC15 /* UIView+Snapshot.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -289,13 +248,10 @@
 		94FC906782E14CF5C8B3A015F1BA065C /* UICircularProgressRing-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 5925D68557276CA4922FC1741B670685 /* UICircularProgressRing-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		9534FCE8C654FC5616DB8496B6B1A718 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9614B8D5D12387AA9697B4943CD3064 /* UIKit.framework */; };
 		979C1C3A853C537A81E912D71D41D7EF /* DBManagedObjectTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = ABD4157716E2CB530C2FE45A83C0AC46 /* DBManagedObjectTableViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		98156C246AAA204DFF21AE3193C250FC /* LocationMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E3E45FA433746A84DC4095945F1A439 /* LocationMessageSizeCalculator.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		9861A8F1B84D926E70D67BCF922A42C0 /* DBRequestTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = FBF6D613818A24C320A546D53871D00B /* DBRequestTableViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		98E89D705934D7AD37EE20BC7EEC61F9 /* MessageCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B865A892DEDEC0FF592ECA0F5AFBE56C /* MessageCellDelegate.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		992E832B5B7A82CC72526208768052BE /* KeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC86E378ECF2DB8F0AD747AB8823443 /* KeyboardManager.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		9AD141A0827B49B0FE3068EBF0562A92 /* DBCookiesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B006EB0AF5694C41B6EE9617A0AFA2AD /* DBCookiesTableViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		9ADFBFBD910E13910223F374A23D94C5 /* ConfirmViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 71752EF8DC5545602142129163282246 /* ConfirmViewController.xib */; };
-		9AEE08E536E7C8EAE8B79F6BE068A0CC /* TextMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D49EF94CC108AFDDC3D131A77B0C309 /* TextMessageCell.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		9BB3ED7F19ECF1E4E6D5C45CDBFFA071 /* DBCoreDataFilterSettingsTableViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 58DCCEA9C6AB19182DBBE5DA8A442134 /* DBCoreDataFilterSettingsTableViewController.storyboard */; };
 		9D20F88CAEF1E12787998A3F900D6403 /* CropOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A6F03C28D13402A11EF8E09F8B7E90 /* CropOverlay.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		9D8CFD728FBB7F9D9C61A89CF8EFE592 /* DBColorCheckbox.h in Headers */ = {isa = PBXBuildFile; fileRef = 25807FC06EA56FBA931C4F6671AEB4B1 /* DBColorCheckbox.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -303,7 +259,6 @@
 		9E9891F2AE20A67E6DDCC4661AAD6B79 /* DBChartView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F568A29B2243FA6F0D6B0555E7FB1F4 /* DBChartView.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		9F903A6F103A70BD3CCBBFCF7E8AFCE3 /* DBFontPreviewViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9F73841FA123F5745F1C7903F7562177 /* DBFontPreviewViewController.storyboard */; };
 		A0006FFB32C402DC42D1D498EE03962E /* Pods-deltachat-iosTests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 6D698220BB377C1788857C77A9469D67 /* Pods-deltachat-iosTests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		A05964C4FE95F16A9B24FF82F9ABCAEC /* MessagesCollectionViewLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63BE799B5DCB8AEBA9D0629E376EB30 /* MessagesCollectionViewLayoutAttributes.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		A0EB22AB158E26386B2E7D9FB22D10C9 /* QuickTableViewController-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E7D3B70C842472BB643B372D9417AD /* QuickTableViewController-dummy.m */; };
 		A1170241B2EF8FA1EA2B4A601684D2E0 /* DBTitleValueListTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DCE572DFA94E089BA6675E0E52ED678 /* DBTitleValueListTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		A150E72DC1688E3F817584EDC90EFB73 /* DBColorCheckbox.m in Sources */ = {isa = PBXBuildFile; fileRef = 69A19EE969E599B54B2D70A82C76191F /* DBColorCheckbox.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
@@ -318,15 +273,11 @@
 		A6889C31E7B5B19D02D2E498D6AA2EBE /* OptionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E9C12D626354BD3A42C726CF8556D5 /* OptionRow.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		A68C04A596C2C213578DE97464DE9EA2 /* DBCustomVariablesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 342D4E169770AA17E74EBA6678C7200D /* DBCustomVariablesTableViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		A736FF3590144A9F4AC8246F9D74F6E2 /* DBCustomAction.m in Sources */ = {isa = PBXBuildFile; fileRef = BF5799BDF86A35C255B252B753A10BA1 /* DBCustomAction.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		A7629BB2DD7853DF7296079A1C2330E4 /* AvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F35F10832C9285F3E0C53F54B59AC8D /* AvatarView.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		A7C24549DB6913AF6B06B36223B2E387 /* UIButtonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C26A357479E69FE8C29E26486B86B74E /* UIButtonExtensions.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		A91F657B71AE55F6B72C76FAF2F5B14D /* DBCoreDataFilterTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F8D7AB8CA6DC1BF1F118A09085BB1A6 /* DBCoreDataFilterTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		A9D1E89C3319FE1466B602B0B3EB31AB /* MessagesDisplayDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017633526904CC46C29E2665CF233038 /* MessagesDisplayDelegate.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		A9D6E4C09D1ABC015AF6402E8D4E1785 /* UIColor+DBUserInterfaceToolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = 2CBF3F470BA1ACCA74AA68AC3AFD2F28 /* UIColor+DBUserInterfaceToolkit.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		AA0F6A6FE7695DCBAEE404A0CD349BA8 /* DBMenuTableViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4838BA9CE55260A1B95B30E817F4EFF3 /* DBMenuTableViewController.storyboard */; };
 		AA557FA05CB8C50CE778590934C9783E /* AttachmentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B6AD77EF91D080EBBC6245A51186E39 /* AttachmentCell.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		AB1995BB6B526C9FC3D4D443D6BC7F02 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00E7C125EA4CFEC5B4830F32E3CD1EA /* UIView+Extensions.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		AB7A9EE10D300FB4D8463BA1B71D7557 /* Sender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2F8667063B2F9B52ABEED2A23A91A7 /* Sender.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		AB890C534404AC593EA1A1A4A440D4C1 /* DBCookieDetailsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CEDA762D15EA669583FCDD94A58BF25D /* DBCookieDetailsTableViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		AD3550F6F2DACC454141A05344A5627F /* NSURLSessionConfiguration+DBURLProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 163E43D44FB5ACF6F4F3461F300B4448 /* NSURLSessionConfiguration+DBURLProtocol.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		AE8580A589AE51FA258E3BDB672E7EAE /* DBCoreDataFilterOperator.m in Sources */ = {isa = PBXBuildFile; fileRef = 08BD67EB8C102519FDC55B170B80C688 /* DBCoreDataFilterOperator.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
@@ -334,10 +285,8 @@
 		AECC61C7865EAAC99685F88AAB9A44F7 /* SeparatorLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CB6899F96158517BEDD0DA93F742677 /* SeparatorLine.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		AF34110B35772AF57EE5FECE3336CFF0 /* JGProgressHUD-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = D4B28F04CF920FC5FD1207C9B1823FDF /* JGProgressHUD-dummy.m */; };
 		AF3CB1216E34C7E56E8363DE53739FB1 /* SBPlatformDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB020078FD1896608964BCD4A75A6DC /* SBPlatformDestination.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		AF42F1B9C5470E47460C4CF3D952E576 /* ContactItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 855A2C36C48EB5853A918C0677F43335 /* ContactItem.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		AF6ECC961A7B3235153EA1647C94C9F0 /* DBDebugToolkit-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 22795D06549B6FDEC4392E5F35BFD6F3 /* DBDebugToolkit-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		AF82C906410BEB1E4A1643C629A101BF /* DBFPSCalculator.h in Headers */ = {isa = PBXBuildFile; fileRef = 011B44AE4C33F4086F65FBF79E882A88 /* DBFPSCalculator.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		B07A8E68D7C7E3C9782A51EE176C8798 /* MessageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F385C082A51DF3FC02F1E0ED7E11AE97 /* MessageType.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		B0CCE8D84AB4F61958B57E19E9F24B53 /* UICircularTimerRing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3BDCB41CB3BB46889B2A32B306FDFD /* UICircularTimerRing.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		B15F7FC7B258305863DC05D62FE69323 /* NSMutableAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C75DE0A7F2EB40E65AE5D7DBDE83268 /* NSMutableAttributedString+Extensions.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		B2166FD2D52A0D78F83B304389F8C412 /* DBSliderTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 174D4D12A270084F0DF296790B2B30AF /* DBSliderTableViewCell.xib */; };
@@ -355,7 +304,6 @@
 		B7EE5112FEBCDC93BC459FA5C781AB8B /* DBOptionsListTableViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2660B31D480626FC3A1765AB0EE96EB8 /* DBOptionsListTableViewController.storyboard */; };
 		B7FC19A65067A2C8050364F49A1B6CEE /* UICircularRingLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC6EF0C91FD20E5C76890DA9C5209EB2 /* UICircularRingLayer.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		B83A3F1F6CD5FAAB59B102FD16FE5D66 /* DBUserInterfaceToolkit.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D38E83B1DA46146CF85F73889D0C6DA /* DBUserInterfaceToolkit.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		B90600771565AC980DF13202530790A9 /* MediaItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318528AE5509FF1088A777CB530FE416 /* MediaItem.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		B97937557E86C9057A8CF21552893B35 /* DBRequestTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 10C1034CBFCE32A405713024ABABE797 /* DBRequestTableViewCell.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		BA32190ABFD67FEBA7FF4EBFEA51EBB8 /* DBNetworkSettingsTableViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 09EB7BEC1B974099213ED08F4D3ACD96 /* DBNetworkSettingsTableViewController.storyboard */; };
 		BA6FFF9728A2A58B5BB5B502E9F59085 /* JGProgressHUDAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = B5C6093F682538412A2BDA91BB37C111 /* JGProgressHUDAnimation.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -365,7 +313,6 @@
 		BB8B83C3E59C9D920EE68720A762C07C /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A78F81E7F27D5AF56FEE794E22C55D /* Extensions.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		BC2348FD121917A936DB2467664DAD1C /* DBCookieTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 5558B007DE774907022A91DCEDA1F5C9 /* DBCookieTableViewCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		BC33689DA9C36A1744F3C1955E999B1F /* UIView+DBUserInterfaceToolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = 897C817A17C82E5577142C279FB198AE /* UIView+DBUserInterfaceToolkit.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		BCB1EC61C4E62E7A5375B0BFE35E88D7 /* MessageKitError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C170831175AEC979417598F83E3869D /* MessageKitError.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		BCDC53B9D9CA716DA077B072C5A5D8BF /* DBFilesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 420CED6DE5D723D85BC976684771C5FD /* DBFilesTableViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		BCFC9E940B738BAB12D463D06E2C0B24 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 36CC0D9A3AE2E92E9BA7D5C95493D00C /* CoreTelephony.framework */; };
 		BD050562FFB5D1DF482912CE473025C3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D478715E61EBAC282D7EF7088E185A6 /* Foundation.framework */; };
@@ -392,20 +339,16 @@
 		C5CD8E348285212D3F4DFD22F0453491 /* AttachmentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A71CC8216274C561321B5497C469174 /* AttachmentManager.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		C6605A278DCC913D16207DD0AB1ABC79 /* Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = E261591557C8CD94276992B43F0D6FE7 /* Deprecated.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		C74AF2EE6DB18BDECC4581D49A69EE68 /* JGProgressHUDIndicatorView.h in Headers */ = {isa = PBXBuildFile; fileRef = F0D242D411E2639915AE894931799DCE /* JGProgressHUDIndicatorView.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		C77DAACDE24F15ECA80560489366F260 /* MessageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24BA1E165733DA60DD6E2E116B6A6401 /* MessageContainerView.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		C7D43195F3DB47D90132D827F5ADFE1F /* CameraViewControllerConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B5D6C9B0E939F261001684091EDB029 /* CameraViewControllerConstraint.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		C833EA3AD242A48ED1ABCA0594EFA1E4 /* JGProgressHUDFadeAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = 7BDA8D9D526AD00A426B078832BE4757 /* JGProgressHUDFadeAnimation.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		C87E2376621ADFAC547E2247069D05CC /* jg_hud_error.png in Resources */ = {isa = PBXBuildFile; fileRef = 454817C30AFC14EA66E40AF941F6DC53 /* jg_hud_error.png */; };
 		C8816A615EBC03893309D144C4C2B02C /* DBConsoleOutputCaptor.m in Sources */ = {isa = PBXBuildFile; fileRef = D2F06ED627E5A8FED91D2F3142F703C3 /* DBConsoleOutputCaptor.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		C90E0112FA4E65995B882544D3E30496 /* DBChartView.h in Headers */ = {isa = PBXBuildFile; fileRef = 489C214BEF74EAC414094C9DC1A7D4A3 /* DBChartView.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		C9297968CF151263A986689AABA8B733 /* PermissionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0EC9DBC31038671A5C611F0629B67E7 /* PermissionsView.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		C95C45CDA793EADDAAAFE5A8A189E824 /* LocationMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A701BD2389B4674D759384692CAD58E5 /* LocationMessageCell.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		C98DD0A36D252554E6348DC5AB73E58F /* DBCookiesTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = B83A691C3D8069DCE4E965EF343BD373 /* DBCookiesTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		C9BE2A26A56F6DAF43DB0842113981A3 /* Configurable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 592D0F676C430046D6D8BC9C687FA496 /* Configurable.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		C9BE5222080C8D3DB30293E7EE2F09FA /* AudioMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1241FD3C295C98EEF3067849232354F6 /* AudioMessageCell.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		CAA578D82F017C24E461C53F1330B9FC /* NSURLSessionConfiguration+DBURLProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BAB3329DC99B7CB2DBEBE6EACA13F70 /* NSURLSessionConfiguration+DBURLProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		CB2816C889CEE97F16F6E197C2314B52 /* JGProgressHUDPieIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = BD1C7BB602E9876B22CBD935238D64B4 /* JGProgressHUDPieIndicatorView.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		CB4BA8AF81B9102326F3EBF474A9BA46 /* SenderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3171673FED3BEB0E7367A789AC306A2 /* SenderType.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		CBA9408252B32099C536690AEAAE9E3D /* DBURLProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = E2AE30F565B3FF55C5DC60A180370F12 /* DBURLProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		CC474792BC284B2072ACD059F0D0E7C7 /* SwiftyBeaver-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 992EE2B8626D32756903A4DCF2643D69 /* SwiftyBeaver-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		CD43DF81866CE56D8360CE37AC817F4D /* TapActionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F0171AB3D69D4B941364A17B5107C8 /* TapActionRow.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
@@ -414,15 +357,11 @@
 		D085B3B53A09F96298903F1B37FFBF6B /* DBPerformanceWidgetView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B56200B92B27AD3694E5CB6680BF9B18 /* DBPerformanceWidgetView.xib */; };
 		D118B8644CBF2E12CD6BFCB88C9DD4F9 /* DBNetworkViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C4D7A3291BFC561E0520596C79A14C17 /* DBNetworkViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		D2CCD1FD025D4456B1654F96B6816406 /* DBCustomVariablesTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 2ABD4F88F587647BBEB74714DFB65D9F /* DBCustomVariablesTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		D309E1909F407EEC9E0640051811EF7B /* MediaMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9D3E335982112FC693D271F47D7274 /* MediaMessageSizeCalculator.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		D3526B8EC4C42992CB3D98356080E895 /* DBPresetLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4D8648EA688E2456B444467989C555 /* DBPresetLocation.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		D3BC11D1B5828D6FF565D493B61CD95C /* InputItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80FC5DD2B2D619C6584313314333DCBF /* InputItem.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		D40BED5A84B78D7B2943AAE3B2453438 /* AccessoryPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5305F0AFDE521FFC745101E9A0C64F /* AccessoryPosition.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		D450C3397CD302E2442B36E380AF8398 /* CLLocationManager+DBLocationToolkit.m in Sources */ = {isa = PBXBuildFile; fileRef = 9055F08A29153193F31785F5DE30A0C5 /* CLLocationManager+DBLocationToolkit.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		D6B5EA0315E2CEE9A59FA38C4A0E3B3B /* JGProgressHUDIndeterminateIndicatorView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D6F5736C3E4696C6A0BE964BA9146C6 /* JGProgressHUDIndeterminateIndicatorView.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		D78B22B4B3EFCFD7D7861AFCF1772B6B /* LabelAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF13576C7A275756BBD7B845F75952D9 /* LabelAlignment.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		D7FE95ECA28B5394F61D2F4F57896602 /* DBCoreDataFilterSettingsTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F771AC14455BD2D4837C9BAFE1E3F06 /* DBCoreDataFilterSettingsTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		D81C57790B6FFE8F3D66ACB1C5A5328C /* MessageKitDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF513DE3C9D44492382B491527E2F64D /* MessageKitDateFormatter.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		D89CAF13E0AEBEF1966BA4B581859103 /* DBTextViewTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B6BD8685A7F10C4C4B0ECA99142C0932 /* DBTextViewTableViewCell.xib */; };
 		D8A0D7C54E8CF3B6AC105AB74719BF10 /* NSPersistentStoreCoordinator+DBCoreDataToolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = 7C421FF6F3AFA1D6C6F43AC5F1BF09C2 /* NSPersistentStoreCoordinator+DBCoreDataToolkit.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		D939C6E7C444CE1BA0522377FBA845BB /* DBRequestDetailsViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BB9876B7CEAA7A8EB6F914B98B38F682 /* DBRequestDetailsViewController.storyboard */; };
@@ -440,11 +379,9 @@
 		DDA97C518953FA05F762A468535AA2AE /* DBManagedObjectsListTableViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 869995D45F172998904D42B97962F6D8 /* DBManagedObjectsListTableViewController.storyboard */; };
 		DDE72BA96B09471C13F8F46C86A65085 /* DBSliderTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 250E2106C4F364EC2B99A3C39221D6B4 /* DBSliderTableViewCell.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		DECC83CE3BEC32FE519DD27E9C02E88D /* DBCustomVariable.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B26B1A5C986EF5BD055753511648F3E /* DBCustomVariable.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		DEDB867229C60EE4B29D30928D613710 /* MessageKit+Availability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F1CDBE8B75C96381468BBA4DBD365E8 /* MessageKit+Availability.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		DF97970EB34F00E3DC8D24A1A638F915 /* DBBodyPreviewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 75DC4EBF9A43A392384E4A5347E8D223 /* DBBodyPreviewViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		DFDF45D41C416C7C7637F383EDC2BE48 /* Reusable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80B379F5ACC66F5BCA53DE1F3464B529 /* Reusable.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		E0C5152F5C025AE6A4777E2D65715160 /* NSBundle+DBDebugToolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = E2E47FB75E22FDA26B1906BDF647EC62 /* NSBundle+DBDebugToolkit.h */; settings = {ATTRIBUTES = (Public, ); }; };
-		E1D6B8A45239CAAE0C81FF1C4F14F6E0 /* MessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7432BDC540EF0F97064324F42DC509 /* MessageSizeCalculator.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		E231591AE6CF396A93B96220424FCE32 /* DBCustomLocationViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = E90B55D881DD062AB2B39A95405EF536 /* DBCustomLocationViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		E236D0C1875A0EFA5F63DE84AEB23371 /* KeyboardEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20C9681F1B0FC0075ACC58333E102F3E /* KeyboardEvent.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		E2F40E5CE14BDAE1F9B5554598F2BF59 /* DBColorPickerTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1AE16C693B55D5BD06992212C3AB5572 /* DBColorPickerTableViewCell.xib */; };
@@ -452,14 +389,10 @@
 		E47D11701CB4BADEDA784B86D4C7EA5B /* DBCrashReportsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 298D62467A81F42ED39282E5D400A8D1 /* DBCrashReportsTableViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		E5CA2EF5E60684930F5BDE8CC8C15C1A /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04610EF8C16BAA90EB220ADB0DB4C3B7 /* CameraView.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		E68CC50E604B324583E8FC647C3480A9 /* BaseDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B4B6220B652DA1BE44DC0FECFFC2FA /* BaseDestination.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		E7004E0C83A305551164866EF1504169 /* TypingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 457AAC28308C59CF00DEE229EEAD753F /* TypingIndicator.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		E72359BA63946C2212D3EF408CCA6BDB /* MessageReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E7B7FE104774CA8A2BC5225013FCBCC /* MessageReusableView.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		E85D85E318F17AD17BDC27CDC895244E /* ReachabilitySwift-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 48D8EA96CC1EB0CA5380977CB8BE5FF8 /* ReachabilitySwift-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		E8837CA66F3DA13DF00045EC219524F7 /* DBUserInterfaceTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A3EB4FA955FDA6FA2184ADD610E2E2F7 /* DBUserInterfaceTableViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		E96008FA1BB56E340D76530FC6FE7511 /* MessageLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E00875C8776F9B3FB700D037808664 /* MessageLabel.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		E96A086D5590C2746835FA74A12E9ED7 /* DBMainQueueOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B8B63BB2AAAB3B28EF0EFE6D83D03A7 /* DBMainQueueOperation.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		EA53CEB3F91726F893209FF371465153 /* AutocompleteTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5B44A46C19900BC09CCB2C1AC138F9E /* AutocompleteTableView.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		EA55022B6235F4DA794417C07315074D /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7E0ACF23F8C5ABE0CC00EB3D3A1BE5A /* UIImage+Extension.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		EAC782B2907DB5C8CE72313E53B20ECC /* DBGridOverlayColorScheme.h in Headers */ = {isa = PBXBuildFile; fileRef = 079F4E258E37EEE6C35D0B711CAEAADA /* DBGridOverlayColorScheme.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		EAC90D8C66B87B71FD9924A3207DEDEF /* ImageAttachmentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D95417377B4BC47CF6CE68185B8BB1B6 /* ImageAttachmentCell.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		EB84A5E583E26C5A6CD30E66F06ABEE5 /* DBTitleValueListViewModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 1E2571DBE952CB79B6B135AAC9EB1290 /* DBTitleValueListViewModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -474,13 +407,11 @@
 		EE2BA3AEA59FB15036D19FB2FC7ED6F1 /* DBMenuTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 131AEC10BE2B9F44FCFBD3BCF580FC8D /* DBMenuTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		EEECC0D7D6731D8D8A3E6A126AF5BDF4 /* JGProgressHUDImageIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 530996B415AAEE2C1768D0FD003AC87F /* JGProgressHUDImageIndicatorView.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		EF12D5D59454925CF5F27390A0584D69 /* DBLocationTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A35D16EF5C0DFAD725AF5675487DEFE /* DBLocationTableViewController.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		EF83B164D86702D4CEA40ECC5CA94394 /* TypingIndicatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0D52DD19CBBFA5FEB9A0EEB416EE51 /* TypingIndicatorCell.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		EFD4E237E6F79DA2696F3089996ACDF2 /* DBCoreDataFilterSettings.h in Headers */ = {isa = PBXBuildFile; fileRef = 71B03CAB6A1ABA9F73CFF9262E766C46 /* DBCoreDataFilterSettings.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		F00F293B76C477E7997301A6F4E73D2C /* DBLocationToolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = A404EB7D2FDD3104D94E54D30A7534D1 /* DBLocationToolkit.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		F03E88F72B965905977AF8CBC896A46E /* DBCoreDataToolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = D918029F30CC0071E6899151F2013785 /* DBCoreDataToolkit.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		F133DA44BE394AFA86D12E80413C46F5 /* NSConstraintLayoutSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979E3F00D5CE0EB500E893EC07235F9A /* NSConstraintLayoutSet.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		F18A479179A7452A65474B4430AF0675 /* InputBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 558957013218186F75773ABC77D39AB7 /* InputBarViewController.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		F3992A1FE6E3B8B6003428D83A91D08D /* MessagesViewController+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 824482471B01329904858439CBE579B5 /* MessagesViewController+Keyboard.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		F55C79ECA55E806467CEC95BB1818424 /* UICircularProgressRing-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = E06861AD060908DE2DB8F7820D6C2231 /* UICircularProgressRing-dummy.m */; };
 		F5C1C3635870F1F00211616D06BC5259 /* DBNetworkToolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = 3501C8D09579C8E89BF85BFACEF7D1AB /* DBNetworkToolkit.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		F63E5AA887855E646B002C32A9E27F98 /* ReachabilitySwift-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = CEA807B8544780E1C9B5AB7835A88836 /* ReachabilitySwift-dummy.m */; };
@@ -488,7 +419,6 @@
 		F684A3545D032575EC8CBD825E71884D /* DBFontPreviewViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = EAB5B32563019025EC011C6D8E4812F5 /* DBFontPreviewViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		F784C996998BCB0AA872092FC9FF5BF2 /* DBGridOverlaySettingsTableViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3254A5636E50B35646D8F602A333493C /* DBGridOverlaySettingsTableViewController.storyboard */; };
 		F7C3354A2D610024847D293085F4B7BB /* UIColor+DBUserInterfaceToolkit.m in Sources */ = {isa = PBXBuildFile; fileRef = 10E97A1D43C7B1E89CC7A3F276DA912C /* UIColor+DBUserInterfaceToolkit.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
-		F7E01944EF088F1D25C844FC3AD8357D /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87835B19E9CCB3B569554B550774054C /* UIColor+Extensions.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
 		F864A58958364ADEA89E62031CAD322D /* DBEntitiesTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = FE4B79AF28B0D1758EB9CF5C590E0D5D /* DBEntitiesTableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		F99AF2A9791C890C7AB04B82A599C9F6 /* DBUserInterfaceToolkit.h in Headers */ = {isa = PBXBuildFile; fileRef = B7953F19CFEB5C600CE04FAB31D14408 /* DBUserInterfaceToolkit.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		FA66AB47377BF76E315DCE81B9F55D7B /* FileDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82C8996CFDA7362D17127CA9965D773C /* FileDestination.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; };
@@ -537,13 +467,6 @@
 			remoteGlobalIDString = D73CE4D20C1559CE5586041DB209DAB1;
 			remoteInfo = ALCameraViewController;
 		};
-		69ED6C59F7D7A3EB2401F7EDED60D9A2 /* PBXContainerItemProxy */ = {
-			isa = PBXContainerItemProxy;
-			containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
-			proxyType = 1;
-			remoteGlobalIDString = 8224EFD39BE7C470070127F0B77FBDC6;
-			remoteInfo = InputBarAccessoryView;
-		};
 		91566FD82D895827D180960F0C74DE0C /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
 			containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
@@ -558,13 +481,6 @@
 			remoteGlobalIDString = CEA048FA8FCAC4BCF974EAA46047A50C;
 			remoteInfo = QuickTableViewController;
 		};
-		9DF5A612F443CCCDBA4494717742A822 /* PBXContainerItemProxy */ = {
-			isa = PBXContainerItemProxy;
-			containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
-			proxyType = 1;
-			remoteGlobalIDString = 41146F268563DD78000D9447FE2F3B1F;
-			remoteInfo = "MessageKit-MessageKitAssets";
-		};
 		A28DA4A5B3CC4B9768E3F6143FD17110 /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
 			containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
@@ -579,13 +495,6 @@
 			remoteGlobalIDString = D07C2B94C9F7A82134ABAFD6EE2CAD11;
 			remoteInfo = UICircularProgressRing;
 		};
-		CE19B972B882E120AF553D62A7F67211 /* PBXContainerItemProxy */ = {
-			isa = PBXContainerItemProxy;
-			containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
-			proxyType = 1;
-			remoteGlobalIDString = 571D48138D13E2A712E3F4F8587AC655;
-			remoteInfo = MessageKit;
-		};
 		DBA962088ED6FC5343154BF82F517E04 /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
 			containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
@@ -742,7 +651,6 @@
 		3D2051913F72BD45984AEA1B60E03406 /* DBTextViewViewController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DBTextViewViewController.h; path = DBDebugToolkit/Classes/UserInterface/DBTextViewViewController.h; sourceTree = "<group>"; };
 		3D59B360ED2B07EDE514F07861780B96 /* DBBodyPreviewViewController.storyboard */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file.storyboard; name = DBBodyPreviewViewController.storyboard; path = DBDebugToolkit/Resources/DBBodyPreviewViewController.storyboard; sourceTree = "<group>"; };
 		3D5A7735878961CE33850135055D0224 /* DBPersistentStoreCoordinatorsTableViewController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DBPersistentStoreCoordinatorsTableViewController.h; path = DBDebugToolkit/Classes/Resources/CoreData/DBPersistentStoreCoordinatorsTableViewController.h; sourceTree = "<group>"; };
-		3E09F64B3AB663C3D62E73D5A50D1175 /* MessageKitAssets.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MessageKitAssets.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
 		3E9043748E4E5AFA63CB60886D30F3A5 /* InputTextView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = InputTextView.swift; path = InputBarAccessoryView/Views/InputTextView.swift; sourceTree = "<group>"; };
 		3F8D7AB8CA6DC1BF1F118A09085BB1A6 /* DBCoreDataFilterTableViewController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = DBCoreDataFilterTableViewController.h; path = DBDebugToolkit/Classes/Resources/CoreData/Filters/DBCoreDataFilterTableViewController.h; sourceTree = "<group>"; };
 		408AEC021E18225342B0E25C32E572CF /* MessageKind.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MessageKind.swift; path = Sources/Models/MessageKind.swift; sourceTree = "<group>"; };
@@ -1097,7 +1005,6 @@
 		E2C6CA7B51B48BFFADCCEE5AC177F382 /* SwiftLint.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftLint.xcconfig; sourceTree = "<group>"; };
 		E2E47FB75E22FDA26B1906BDF647EC62 /* NSBundle+DBDebugToolkit.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSBundle+DBDebugToolkit.h"; path = "DBDebugToolkit/Classes/Categories/NSBundle+DBDebugToolkit.h"; sourceTree = "<group>"; };
 		E3741BFD8C793DB441C71D01E7E68317 /* QuickTableViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = QuickTableViewController.swift; path = Source/QuickTableViewController.swift; sourceTree = "<group>"; };
-		E3C47BF2D1B6BF3E6BC6E608CDB9EDEF /* MessageKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MessageKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		E4DB32E69DBEE567D2B41E4C51C70229 /* CameraShot.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CameraShot.swift; path = ALCameraViewController/Utilities/CameraShot.swift; sourceTree = "<group>"; };
 		E4E77D726B20687EA9423A77BDA7F641 /* MessageKit.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = MessageKit.xcconfig; sourceTree = "<group>"; };
 		E62BB3E79D3C03FFC61450481DB44BE6 /* DBTitleValueTableViewCellDataSource.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DBTitleValueTableViewCellDataSource.m; path = DBDebugToolkit/Classes/Cells/TitleValue/DBTitleValueTableViewCellDataSource.m; sourceTree = "<group>"; };
@@ -1185,22 +1092,6 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		69D77737F9A3E4A48EC1030266EA21BB /* Frameworks */ = {
-			isa = PBXFrameworksBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
-		847F1F1736796C87AB948A9182BE362B /* Frameworks */ = {
-			isa = PBXFrameworksBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				7A8892328A8E3F44D0CFB33D2D382BBE /* Foundation.framework in Frameworks */,
-				03F9B2A4C641C137A34FC30F961E6BF1 /* InputBarAccessoryView.framework in Frameworks */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
 		96D0859E0AEEC032C083E60E3FF7AC38 /* Frameworks */ = {
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
@@ -1871,8 +1762,6 @@
 				0C4BAD4692F46B7F6F4A73637335C858 /* InputBarAccessoryView.framework */,
 				66A2FC4BA176D2891FE9363105FBEAC4 /* JGProgressHUD.bundle */,
 				DED8C189F56BE3A2EB836951880C1FEB /* JGProgressHUD.framework */,
-				E3C47BF2D1B6BF3E6BC6E608CDB9EDEF /* MessageKit.framework */,
-				3E09F64B3AB663C3D62E73D5A50D1175 /* MessageKitAssets.bundle */,
 				FB6D48D8F511504ED99F0271051361ED /* Pods_deltachat_ios.framework */,
 				7F9E725FBD32899034E2B54362D267BD /* Pods_deltachat_iosTests.framework */,
 				73C446E77FD95DDCE9E0C6281BDE4FBF /* QuickTableViewController.framework */,
@@ -2265,14 +2154,6 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		F280B74E11F5C16AABF84323BF6B29B7 /* Headers */ = {
-			isa = PBXHeadersBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				721DC43C99943175F26976E71CD84BE6 /* MessageKit-umbrella.h in Headers */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
 /* End PBXHeadersBuildPhase section */
 
 /* Begin PBXNativeTarget section */
@@ -2313,43 +2194,6 @@
 			productReference = 37EA9B3AFECA678C4E52475D77C05D79 /* Reachability.framework */;
 			productType = "com.apple.product-type.framework";
 		};
-		41146F268563DD78000D9447FE2F3B1F /* MessageKit-MessageKitAssets */ = {
-			isa = PBXNativeTarget;
-			buildConfigurationList = 5F4A5A6F1C6ABB9A72084A5A1843CDA3 /* Build configuration list for PBXNativeTarget "MessageKit-MessageKitAssets" */;
-			buildPhases = (
-				F23173105CC36261343100AB92709A99 /* Sources */,
-				69D77737F9A3E4A48EC1030266EA21BB /* Frameworks */,
-				EE413C9A967029D111A0333E258260D5 /* Resources */,
-			);
-			buildRules = (
-			);
-			dependencies = (
-			);
-			name = "MessageKit-MessageKitAssets";
-			productName = "MessageKit-MessageKitAssets";
-			productReference = 3E09F64B3AB663C3D62E73D5A50D1175 /* MessageKitAssets.bundle */;
-			productType = "com.apple.product-type.bundle";
-		};
-		571D48138D13E2A712E3F4F8587AC655 /* MessageKit */ = {
-			isa = PBXNativeTarget;
-			buildConfigurationList = 5A233AE6FDA1B16155807B0B8A8E766B /* Build configuration list for PBXNativeTarget "MessageKit" */;
-			buildPhases = (
-				F280B74E11F5C16AABF84323BF6B29B7 /* Headers */,
-				866F51BEC8A749B7178B71979F01AA53 /* Sources */,
-				847F1F1736796C87AB948A9182BE362B /* Frameworks */,
-				063BBDC88A2363C0632F128A1771D612 /* Resources */,
-			);
-			buildRules = (
-			);
-			dependencies = (
-				317AF5CE7D92C81DB7A475D52C0BDA7C /* PBXTargetDependency */,
-				8701096393D6E43F73D6E6820616BD4E /* PBXTargetDependency */,
-			);
-			name = MessageKit;
-			productName = MessageKit;
-			productReference = E3C47BF2D1B6BF3E6BC6E608CDB9EDEF /* MessageKit.framework */;
-			productType = "com.apple.product-type.framework";
-		};
 		8224EFD39BE7C470070127F0B77FBDC6 /* InputBarAccessoryView */ = {
 			isa = PBXNativeTarget;
 			buildConfigurationList = BC83AB43705B29821DB0F1707C70F20A /* Build configuration list for PBXNativeTarget "InputBarAccessoryView" */;
@@ -2475,7 +2319,6 @@
 				FB653C80A1C0BE74BCF6AD19BF50C487 /* PBXTargetDependency */,
 				A8FE7EEE744337EE6700D1C0CEF95363 /* PBXTargetDependency */,
 				740AA455FCAEE31BDC2BF6CDA54A947C /* PBXTargetDependency */,
-				15DD6A7E8C45D27B6A4DEFAA4FA1E3D7 /* PBXTargetDependency */,
 				B4C820D2A846E15F55956FCF20268D24 /* PBXTargetDependency */,
 				643E566B623B8DA9D0244C2D5A3EF425 /* PBXTargetDependency */,
 				4080143B9C1B05AF9E7065F8BC284FF3 /* PBXTargetDependency */,
@@ -2550,9 +2393,6 @@
 				LastSwiftUpdateCheck = 0930;
 				LastUpgradeCheck = 0930;
 				TargetAttributes = {
-					571D48138D13E2A712E3F4F8587AC655 = {
-						LastSwiftMigration = 1030;
-					};
 					8224EFD39BE7C470070127F0B77FBDC6 = {
 						LastSwiftMigration = 1030;
 					};
@@ -2589,8 +2429,6 @@
 				8224EFD39BE7C470070127F0B77FBDC6 /* InputBarAccessoryView */,
 				AFD0B69E499DF10B4BFED5AAB7E64179 /* JGProgressHUD */,
 				FEF806502BEE6769704661F5678595A0 /* JGProgressHUD-JGProgressHUD */,
-				571D48138D13E2A712E3F4F8587AC655 /* MessageKit */,
-				41146F268563DD78000D9447FE2F3B1F /* MessageKit-MessageKitAssets */,
 				D4FF6FE56E5C9CB57EC63B97E72CBE39 /* Pods-deltachat-ios */,
 				21507CDD773A731EA5F9432F9BAF49D3 /* Pods-deltachat-iosTests */,
 				CEA048FA8FCAC4BCF974EAA46047A50C /* QuickTableViewController */,
@@ -2604,14 +2442,6 @@
 /* End PBXProject section */
 
 /* Begin PBXResourcesBuildPhase section */
-		063BBDC88A2363C0632F128A1771D612 /* Resources */ = {
-			isa = PBXResourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				6F86A228F7798553027904AB730C65AF /* MessageKitAssets.bundle in Resources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
 		197470ED436C86A0F3E0F983E19424C7 /* Resources */ = {
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
@@ -2750,14 +2580,6 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		EE413C9A967029D111A0333E258260D5 /* Resources */ = {
-			isa = PBXResourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				70FC13348A812E69AED5BA7C2591133A /* Images in Resources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */
@@ -2855,78 +2677,6 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		866F51BEC8A749B7178B71979F01AA53 /* Sources */ = {
-			isa = PBXSourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-				D40BED5A84B78D7B2943AAE3B2453438 /* AccessoryPosition.swift in Sources */,
-				868168BD985CA6937CA5C1E11364BFE9 /* AudioItem.swift in Sources */,
-				C9BE5222080C8D3DB30293E7EE2F09FA /* AudioMessageCell.swift in Sources */,
-				0B85D3695AA53CF3B5A9A94366A1C28A /* AudioMessageSizeCalculator.swift in Sources */,
-				33B03C7C71BFF36A3BE204CB05449455 /* Avatar.swift in Sources */,
-				2B195A3EE3A8D859F446BCCFA45F6AA3 /* AvatarPosition.swift in Sources */,
-				A7629BB2DD7853DF7296079A1C2330E4 /* AvatarView.swift in Sources */,
-				35241910C42D12B7B9513183AA5ECBF1 /* BubbleCircle.swift in Sources */,
-				5BEE33379221E006E5B857FED37FD955 /* Bundle+Extensions.swift in Sources */,
-				0F27A71605EF5D68738875EEE1701879 /* CellSizeCalculator.swift in Sources */,
-				448DA30B415182D8BBEAB3E2AB239D03 /* CGRect+Extensions.swift in Sources */,
-				AF42F1B9C5470E47460C4CF3D952E576 /* ContactItem.swift in Sources */,
-				7FF5FEACAD83DCC8C2CFE646EA484C74 /* ContactMessageCell.swift in Sources */,
-				80CEB6A0221653EDCB81157AD30104D6 /* ContactMessageSizeCalculator.swift in Sources */,
-				1DF055B6C6BE84147AF954953614F508 /* DetectorType.swift in Sources */,
-				880046882FE5B5F89E879A81DA21B84D /* HorizontalEdgeInsets.swift in Sources */,
-				157554B665FC1CB33EB8BD8C530502FB /* InsetLabel.swift in Sources */,
-				D78B22B4B3EFCFD7D7861AFCF1772B6B /* LabelAlignment.swift in Sources */,
-				2B7E7F1D96317A298BB3B29CA93B196A /* LocationItem.swift in Sources */,
-				C95C45CDA793EADDAAAFE5A8A189E824 /* LocationMessageCell.swift in Sources */,
-				98156C246AAA204DFF21AE3193C250FC /* LocationMessageSizeCalculator.swift in Sources */,
-				4A5DA398F200B9407722CDCE68641E89 /* LocationMessageSnapshotOptions.swift in Sources */,
-				B90600771565AC980DF13202530790A9 /* MediaItem.swift in Sources */,
-				0F12700F0ADC563B2C6894E318FF1863 /* MediaMessageCell.swift in Sources */,
-				D309E1909F407EEC9E0640051811EF7B /* MediaMessageSizeCalculator.swift in Sources */,
-				98E89D705934D7AD37EE20BC7EEC61F9 /* MessageCellDelegate.swift in Sources */,
-				6AE1160F128270F3F962BCC8B21107F7 /* MessageCollectionViewCell.swift in Sources */,
-				C77DAACDE24F15ECA80560489366F260 /* MessageContainerView.swift in Sources */,
-				644DA359877F436FFF4A47A72015E0C3 /* MessageContentCell.swift in Sources */,
-				56878442FC79462DAB17962039045DE4 /* MessageInputBar.swift in Sources */,
-				564C5D7925FB803C03C0B7741A31D84C /* MessageKind.swift in Sources */,
-				DEDB867229C60EE4B29D30928D613710 /* MessageKit+Availability.swift in Sources */,
-				45259492B14E1D00DA0E9D08959A9FFE /* MessageKit-dummy.m in Sources */,
-				D81C57790B6FFE8F3D66ACB1C5A5328C /* MessageKitDateFormatter.swift in Sources */,
-				BCB1EC61C4E62E7A5375B0BFE35E88D7 /* MessageKitError.swift in Sources */,
-				E96008FA1BB56E340D76530FC6FE7511 /* MessageLabel.swift in Sources */,
-				3BB73C8DAA73214A6F281C2B78A5B599 /* MessageLabelDelegate.swift in Sources */,
-				E72359BA63946C2212D3EF408CCA6BDB /* MessageReusableView.swift in Sources */,
-				327131B133AA2E5B6FB752C02D18A357 /* MessagesCollectionView.swift in Sources */,
-				4BB743D477A59721872E2387C60B4F28 /* MessagesCollectionViewFlowLayout.swift in Sources */,
-				A05964C4FE95F16A9B24FF82F9ABCAEC /* MessagesCollectionViewLayoutAttributes.swift in Sources */,
-				58491D2DC6CAF6A78D2A6FD92F09B2C1 /* MessagesDataSource.swift in Sources */,
-				A9D1E89C3319FE1466B602B0B3EB31AB /* MessagesDisplayDelegate.swift in Sources */,
-				E1D6B8A45239CAAE0C81FF1C4F14F6E0 /* MessageSizeCalculator.swift in Sources */,
-				6DD8056CC37D0C7EDEEEF0504C6FDBF0 /* MessagesLayoutDelegate.swift in Sources */,
-				0754130AD1077ED7C60AB95A4C5075E8 /* MessageStyle.swift in Sources */,
-				F3992A1FE6E3B8B6003428D83A91D08D /* MessagesViewController+Keyboard.swift in Sources */,
-				06059FE7782E40948EDB31995DA743C4 /* MessagesViewController+Menu.swift in Sources */,
-				31084A593108FFC2CB4E19C76B881ED8 /* MessagesViewController.swift in Sources */,
-				B07A8E68D7C7E3C9782A51EE176C8798 /* MessageType.swift in Sources */,
-				78F9C13D557AD3A4A5D81ADABE44AC4A /* NSAttributedString+Extensions.swift in Sources */,
-				1F95302BDB9904D36FB67A6631A9CECE /* NSConstraintLayoutSet.swift in Sources */,
-				5D10DEF44E6CF4971FCE68FCA5866E33 /* PlayButtonView.swift in Sources */,
-				AB7A9EE10D300FB4D8463BA1B71D7557 /* Sender.swift in Sources */,
-				CB4BA8AF81B9102326F3EBF474A9BA46 /* SenderType.swift in Sources */,
-				9AEE08E536E7C8EAE8B79F6BE068A0CC /* TextMessageCell.swift in Sources */,
-				82DDC5559965A4FC7C6E78C67085CA7F /* TextMessageSizeCalculator.swift in Sources */,
-				51B415FD6314FA15F9661BBE8429A553 /* TypingBubble.swift in Sources */,
-				E7004E0C83A305551164866EF1504169 /* TypingIndicator.swift in Sources */,
-				EF83B164D86702D4CEA40ECC5CA94394 /* TypingIndicatorCell.swift in Sources */,
-				5371BA4900525F7CCC8F12E9289B5DD0 /* TypingIndicatorCellSizeCalculator.swift in Sources */,
-				F7E01944EF088F1D25C844FC3AD8357D /* UIColor+Extensions.swift in Sources */,
-				301FCC982A72C8672531F345FBABD500 /* UIEdgeInsets+Extensions.swift in Sources */,
-				EA55022B6235F4DA794417C07315074D /* UIImage+Extension.swift in Sources */,
-				AB1995BB6B526C9FC3D4D443D6BC7F02 /* UIView+Extensions.swift in Sources */,
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
 		903209E63C2745AF1FBFE972F3810118 /* Sources */ = {
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
@@ -3124,13 +2874,6 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		F23173105CC36261343100AB92709A99 /* Sources */ = {
-			isa = PBXSourcesBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-		};
 /* End PBXSourcesBuildPhase section */
 
 /* Begin PBXTargetDependency section */
@@ -3140,18 +2883,6 @@
 			target = C432EC4F06930E2F8FB529936B43AFC9 /* DBDebugToolkit-DBDebugToolkit */;
 			targetProxy = 1AE305EF31AC76B0A8384CDE73D7E4EA /* PBXContainerItemProxy */;
 		};
-		15DD6A7E8C45D27B6A4DEFAA4FA1E3D7 /* PBXTargetDependency */ = {
-			isa = PBXTargetDependency;
-			name = MessageKit;
-			target = 571D48138D13E2A712E3F4F8587AC655 /* MessageKit */;
-			targetProxy = CE19B972B882E120AF553D62A7F67211 /* PBXContainerItemProxy */;
-		};
-		317AF5CE7D92C81DB7A475D52C0BDA7C /* PBXTargetDependency */ = {
-			isa = PBXTargetDependency;
-			name = InputBarAccessoryView;
-			target = 8224EFD39BE7C470070127F0B77FBDC6 /* InputBarAccessoryView */;
-			targetProxy = 69ED6C59F7D7A3EB2401F7EDED60D9A2 /* PBXContainerItemProxy */;
-		};
 		4080143B9C1B05AF9E7065F8BC284FF3 /* PBXTargetDependency */ = {
 			isa = PBXTargetDependency;
 			name = SwiftFormat;
@@ -3188,12 +2919,6 @@
 			target = D73CE4D20C1559CE5586041DB209DAB1 /* ALCameraViewController */;
 			targetProxy = 642970BA153E927547AFD3DE8E96A9EB /* PBXContainerItemProxy */;
 		};
-		8701096393D6E43F73D6E6820616BD4E /* PBXTargetDependency */ = {
-			isa = PBXTargetDependency;
-			name = "MessageKit-MessageKitAssets";
-			target = 41146F268563DD78000D9447FE2F3B1F /* MessageKit-MessageKitAssets */;
-			targetProxy = 9DF5A612F443CCCDBA4494717742A822 /* PBXContainerItemProxy */;
-		};
 		A2A939CE5FCEE134709C9BF5F899E267 /* PBXTargetDependency */ = {
 			isa = PBXTargetDependency;
 			name = "Pods-deltachat-ios";
@@ -3265,22 +2990,6 @@
 			};
 			name = Release;
 		};
-		02FB72C80F8B07B41F8B8A7BC72E6084 /* Release */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = E4E77D726B20687EA9423A77BDA7F641 /* MessageKit.xcconfig */;
-			buildSettings = {
-				CODE_SIGN_IDENTITY = "iPhone Developer";
-				CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/MessageKit";
-				INFOPLIST_FILE = "Target Support Files/MessageKit/ResourceBundle-MessageKitAssets-MessageKit-Info.plist";
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
-				PRODUCT_NAME = MessageKitAssets;
-				SDKROOT = iphoneos;
-				SKIP_INSTALL = YES;
-				TARGETED_DEVICE_FAMILY = "1,2";
-				WRAPPER_EXTENSION = bundle;
-			};
-			name = Release;
-		};
 		04BD20247626B622F2F883F2E43AF599 /* Debug */ = {
 			isa = XCBuildConfiguration;
 			baseConfigurationReference = 425962AE793524247CEDA7296FC34866 /* JGProgressHUD.xcconfig */;
@@ -3811,69 +3520,6 @@
 			};
 			name = Debug;
 		};
-		921509D430AA93A6247ADFD5A409350F /* Debug */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = E4E77D726B20687EA9423A77BDA7F641 /* MessageKit.xcconfig */;
-			buildSettings = {
-				CODE_SIGN_IDENTITY = "";
-				"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
-				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
-				"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
-				CURRENT_PROJECT_VERSION = 1;
-				DEFINES_MODULE = YES;
-				DYLIB_COMPATIBILITY_VERSION = 1;
-				DYLIB_CURRENT_VERSION = 1;
-				DYLIB_INSTALL_NAME_BASE = "@rpath";
-				GCC_PREFIX_HEADER = "Target Support Files/MessageKit/MessageKit-prefix.pch";
-				INFOPLIST_FILE = "Target Support Files/MessageKit/MessageKit-Info.plist";
-				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
-				MODULEMAP_FILE = "Target Support Files/MessageKit/MessageKit.modulemap";
-				PRODUCT_MODULE_NAME = MessageKit;
-				PRODUCT_NAME = MessageKit;
-				SDKROOT = iphoneos;
-				SKIP_INSTALL = YES;
-				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
-				SWIFT_VERSION = 4.2;
-				TARGETED_DEVICE_FAMILY = "1,2";
-				VERSIONING_SYSTEM = "apple-generic";
-				VERSION_INFO_PREFIX = "";
-			};
-			name = Debug;
-		};
-		94730F73328F76F136F3BFCF99E19D40 /* Release */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = E4E77D726B20687EA9423A77BDA7F641 /* MessageKit.xcconfig */;
-			buildSettings = {
-				CODE_SIGN_IDENTITY = "";
-				"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
-				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
-				"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
-				CURRENT_PROJECT_VERSION = 1;
-				DEFINES_MODULE = YES;
-				DYLIB_COMPATIBILITY_VERSION = 1;
-				DYLIB_CURRENT_VERSION = 1;
-				DYLIB_INSTALL_NAME_BASE = "@rpath";
-				GCC_PREFIX_HEADER = "Target Support Files/MessageKit/MessageKit-prefix.pch";
-				INFOPLIST_FILE = "Target Support Files/MessageKit/MessageKit-Info.plist";
-				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
-				MODULEMAP_FILE = "Target Support Files/MessageKit/MessageKit.modulemap";
-				PRODUCT_MODULE_NAME = MessageKit;
-				PRODUCT_NAME = MessageKit;
-				SDKROOT = iphoneos;
-				SKIP_INSTALL = YES;
-				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
-				SWIFT_VERSION = 4.2;
-				TARGETED_DEVICE_FAMILY = "1,2";
-				VALIDATE_PRODUCT = YES;
-				VERSIONING_SYSTEM = "apple-generic";
-				VERSION_INFO_PREFIX = "";
-			};
-			name = Release;
-		};
 		A57944437D59688307CB69B9C0AB3FC4 /* Release */ = {
 			isa = XCBuildConfiguration;
 			baseConfigurationReference = E2C6CA7B51B48BFFADCCEE5AC177F382 /* SwiftLint.xcconfig */;
@@ -3888,22 +3534,6 @@
 			};
 			name = Release;
 		};
-		AD84C2F6C3AD7A4C42CAAB73095A0B43 /* Debug */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = E4E77D726B20687EA9423A77BDA7F641 /* MessageKit.xcconfig */;
-			buildSettings = {
-				CODE_SIGN_IDENTITY = "iPhone Developer";
-				CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/MessageKit";
-				INFOPLIST_FILE = "Target Support Files/MessageKit/ResourceBundle-MessageKitAssets-MessageKit-Info.plist";
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
-				PRODUCT_NAME = MessageKitAssets;
-				SDKROOT = iphoneos;
-				SKIP_INSTALL = YES;
-				TARGETED_DEVICE_FAMILY = "1,2";
-				WRAPPER_EXTENSION = bundle;
-			};
-			name = Debug;
-		};
 		B0376F23711D88D1F859D46BDC9165EF /* Release */ = {
 			isa = XCBuildConfiguration;
 			baseConfigurationReference = C1A8C8E4AA969A18444058CF5EE6DEBB /* ReachabilitySwift.xcconfig */;
@@ -4268,24 +3898,6 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
-		5A233AE6FDA1B16155807B0B8A8E766B /* Build configuration list for PBXNativeTarget "MessageKit" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				921509D430AA93A6247ADFD5A409350F /* Debug */,
-				94730F73328F76F136F3BFCF99E19D40 /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
-		5F4A5A6F1C6ABB9A72084A5A1843CDA3 /* Build configuration list for PBXNativeTarget "MessageKit-MessageKitAssets" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				AD84C2F6C3AD7A4C42CAAB73095A0B43 /* Debug */,
-				02FB72C80F8B07B41F8B8A7BC72E6084 /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
 		7B1CF58187711382F2078A22E41C9C5A /* Build configuration list for PBXAggregateTarget "SwiftLint" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (

+ 0 - 2
Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios-frameworks.sh

@@ -157,7 +157,6 @@ if [[ "$CONFIGURATION" == "Debug" ]]; then
   install_framework "${BUILT_PRODUCTS_DIR}/DBDebugToolkit/DBDebugToolkit.framework"
   install_framework "${BUILT_PRODUCTS_DIR}/InputBarAccessoryView/InputBarAccessoryView.framework"
   install_framework "${BUILT_PRODUCTS_DIR}/JGProgressHUD/JGProgressHUD.framework"
-  install_framework "${BUILT_PRODUCTS_DIR}/MessageKit/MessageKit.framework"
   install_framework "${BUILT_PRODUCTS_DIR}/QuickTableViewController/QuickTableViewController.framework"
   install_framework "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework"
   install_framework "${BUILT_PRODUCTS_DIR}/SwiftyBeaver/SwiftyBeaver.framework"
@@ -168,7 +167,6 @@ if [[ "$CONFIGURATION" == "Release" ]]; then
   install_framework "${BUILT_PRODUCTS_DIR}/DBDebugToolkit/DBDebugToolkit.framework"
   install_framework "${BUILT_PRODUCTS_DIR}/InputBarAccessoryView/InputBarAccessoryView.framework"
   install_framework "${BUILT_PRODUCTS_DIR}/JGProgressHUD/JGProgressHUD.framework"
-  install_framework "${BUILT_PRODUCTS_DIR}/MessageKit/MessageKit.framework"
   install_framework "${BUILT_PRODUCTS_DIR}/QuickTableViewController/QuickTableViewController.framework"
   install_framework "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework"
   install_framework "${BUILT_PRODUCTS_DIR}/SwiftyBeaver/SwiftyBeaver.framework"

+ 4 - 4
Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios.debug.xcconfig

@@ -1,10 +1,10 @@
 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
-FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView" "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MessageKit" "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing"
+FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView" "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing"
 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
-HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController/ALCameraViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView/InputBarAccessoryView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD/JGProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MessageKit/MessageKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController/QuickTableViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing/UICircularProgressRing.framework/Headers"
+HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController/ALCameraViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView/InputBarAccessoryView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD/JGProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController/QuickTableViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing/UICircularProgressRing.framework/Headers"
 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
-OTHER_CFLAGS = $(inherited) -isystem "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController/ALCameraViewController.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView/InputBarAccessoryView.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD/JGProgressHUD.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/MessageKit/MessageKit.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController/QuickTableViewController.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing/UICircularProgressRing.framework/Headers" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/MessageKit" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing"
-OTHER_LDFLAGS = $(inherited) -framework "ALCameraViewController" -framework "CoreTelephony" -framework "DBDebugToolkit" -framework "Foundation" -framework "InputBarAccessoryView" -framework "JGProgressHUD" -framework "MessageKit" -framework "QuartzCore" -framework "QuickTableViewController" -framework "Reachability" -framework "SwiftyBeaver" -framework "SystemConfiguration" -framework "UICircularProgressRing" -framework "UIKit"
+OTHER_CFLAGS = $(inherited) -isystem "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController/ALCameraViewController.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView/InputBarAccessoryView.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD/JGProgressHUD.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController/QuickTableViewController.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing/UICircularProgressRing.framework/Headers" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing"
+OTHER_LDFLAGS = $(inherited) -framework "ALCameraViewController" -framework "CoreTelephony" -framework "DBDebugToolkit" -framework "Foundation" -framework "InputBarAccessoryView" -framework "JGProgressHUD" -framework "QuartzCore" -framework "QuickTableViewController" -framework "Reachability" -framework "SwiftyBeaver" -framework "SystemConfiguration" -framework "UICircularProgressRing" -framework "UIKit"
 OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
 PODS_BUILD_DIR = ${BUILD_DIR}
 PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

+ 4 - 4
Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios.release.xcconfig

@@ -1,10 +1,10 @@
 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
-FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView" "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MessageKit" "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing"
+FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView" "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing"
 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
-HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController/ALCameraViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView/InputBarAccessoryView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD/JGProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MessageKit/MessageKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController/QuickTableViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing/UICircularProgressRing.framework/Headers"
+HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController/ALCameraViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView/InputBarAccessoryView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD/JGProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController/QuickTableViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing/UICircularProgressRing.framework/Headers"
 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
-OTHER_CFLAGS = $(inherited) -isystem "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController/ALCameraViewController.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView/InputBarAccessoryView.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD/JGProgressHUD.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/MessageKit/MessageKit.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController/QuickTableViewController.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing/UICircularProgressRing.framework/Headers" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/MessageKit" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing"
-OTHER_LDFLAGS = $(inherited) -framework "ALCameraViewController" -framework "CoreTelephony" -framework "DBDebugToolkit" -framework "Foundation" -framework "InputBarAccessoryView" -framework "JGProgressHUD" -framework "MessageKit" -framework "QuartzCore" -framework "QuickTableViewController" -framework "Reachability" -framework "SwiftyBeaver" -framework "SystemConfiguration" -framework "UICircularProgressRing" -framework "UIKit"
+OTHER_CFLAGS = $(inherited) -isystem "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController/ALCameraViewController.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView/InputBarAccessoryView.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD/JGProgressHUD.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController/QuickTableViewController.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" -isystem "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing/UICircularProgressRing.framework/Headers" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" -iframework "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing"
+OTHER_LDFLAGS = $(inherited) -framework "ALCameraViewController" -framework "CoreTelephony" -framework "DBDebugToolkit" -framework "Foundation" -framework "InputBarAccessoryView" -framework "JGProgressHUD" -framework "QuartzCore" -framework "QuickTableViewController" -framework "Reachability" -framework "SwiftyBeaver" -framework "SystemConfiguration" -framework "UICircularProgressRing" -framework "UIKit"
 OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
 PODS_BUILD_DIR = ${BUILD_DIR}
 PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

+ 3 - 3
Pods/Target Support Files/Pods-deltachat-iosTests/Pods-deltachat-iosTests.debug.xcconfig

@@ -1,8 +1,8 @@
-FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView" "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MessageKit" "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing"
+FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView" "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing"
 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
-HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController/ALCameraViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView/InputBarAccessoryView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD/JGProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MessageKit/MessageKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController/QuickTableViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing/UICircularProgressRing.framework/Headers"
+HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController/ALCameraViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView/InputBarAccessoryView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD/JGProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController/QuickTableViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing/UICircularProgressRing.framework/Headers"
 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
-OTHER_LDFLAGS = $(inherited) -framework "ALCameraViewController" -framework "CoreTelephony" -framework "DBDebugToolkit" -framework "Foundation" -framework "InputBarAccessoryView" -framework "JGProgressHUD" -framework "MessageKit" -framework "QuartzCore" -framework "QuickTableViewController" -framework "Reachability" -framework "SwiftyBeaver" -framework "SystemConfiguration" -framework "UICircularProgressRing" -framework "UIKit"
+OTHER_LDFLAGS = $(inherited) -framework "ALCameraViewController" -framework "CoreTelephony" -framework "DBDebugToolkit" -framework "Foundation" -framework "InputBarAccessoryView" -framework "JGProgressHUD" -framework "QuartzCore" -framework "QuickTableViewController" -framework "Reachability" -framework "SwiftyBeaver" -framework "SystemConfiguration" -framework "UICircularProgressRing" -framework "UIKit"
 PODS_BUILD_DIR = ${BUILD_DIR}
 PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
 PODS_PODFILE_DIR_PATH = ${SRCROOT}/.

+ 3 - 3
Pods/Target Support Files/Pods-deltachat-iosTests/Pods-deltachat-iosTests.release.xcconfig

@@ -1,8 +1,8 @@
-FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView" "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MessageKit" "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing"
+FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit" "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView" "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver" "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing"
 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
-HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController/ALCameraViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView/InputBarAccessoryView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD/JGProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MessageKit/MessageKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController/QuickTableViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing/UICircularProgressRing.framework/Headers"
+HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController/ALCameraViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DBDebugToolkit/DBDebugToolkit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/InputBarAccessoryView/InputBarAccessoryView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/JGProgressHUD/JGProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController/QuickTableViewController.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver/SwiftyBeaver.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UICircularProgressRing/UICircularProgressRing.framework/Headers"
 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
-OTHER_LDFLAGS = $(inherited) -framework "ALCameraViewController" -framework "CoreTelephony" -framework "DBDebugToolkit" -framework "Foundation" -framework "InputBarAccessoryView" -framework "JGProgressHUD" -framework "MessageKit" -framework "QuartzCore" -framework "QuickTableViewController" -framework "Reachability" -framework "SwiftyBeaver" -framework "SystemConfiguration" -framework "UICircularProgressRing" -framework "UIKit"
+OTHER_LDFLAGS = $(inherited) -framework "ALCameraViewController" -framework "CoreTelephony" -framework "DBDebugToolkit" -framework "Foundation" -framework "InputBarAccessoryView" -framework "JGProgressHUD" -framework "QuartzCore" -framework "QuickTableViewController" -framework "Reachability" -framework "SwiftyBeaver" -framework "SystemConfiguration" -framework "UICircularProgressRing" -framework "UIKit"
 PODS_BUILD_DIR = ${BUILD_DIR}
 PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
 PODS_PODFILE_DIR_PATH = ${SRCROOT}/.

+ 344 - 6
deltachat-ios.xcodeproj/project.pbxproj

@@ -9,10 +9,74 @@
 /* Begin PBXBuildFile section */
 		30149D9322F21129003C12B5 /* QrViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30149D9222F21129003C12B5 /* QrViewController.swift */; };
 		3022E6BE22E8768800763272 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3022E6C022E8768800763272 /* InfoPlist.strings */; };
+		305961CC2346125100C80F33 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961822346125000C80F33 /* UIView+Extensions.swift */; };
+		305961CD2346125100C80F33 /* UIEdgeInsets+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961832346125000C80F33 /* UIEdgeInsets+Extensions.swift */; };
+		305961CF2346125100C80F33 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961852346125000C80F33 /* UIColor+Extensions.swift */; };
+		305961D02346125100C80F33 /* NSAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961862346125000C80F33 /* NSAttributedString+Extensions.swift */; };
+		305961D12346125100C80F33 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961872346125000C80F33 /* Bundle+Extensions.swift */; };
+		305961D22346125100C80F33 /* CGRect+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961882346125000C80F33 /* CGRect+Extensions.swift */; };
+		305961D32346125100C80F33 /* MessagesViewController+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3059618A2346125000C80F33 /* MessagesViewController+Keyboard.swift */; };
+		305961D42346125100C80F33 /* MessagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3059618B2346125000C80F33 /* MessagesViewController.swift */; };
+		305961D52346125100C80F33 /* MessagesViewController+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3059618C2346125000C80F33 /* MessagesViewController+Menu.swift */; };
+		305961D62346125100C80F33 /* MessageInputBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3059618E2346125000C80F33 /* MessageInputBar.swift */; };
+		305961D72346125100C80F33 /* MessageKit+Availability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961902346125000C80F33 /* MessageKit+Availability.swift */; };
+		305961D92346125100C80F33 /* ContactItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961932346125000C80F33 /* ContactItem.swift */; };
+		305961DA2346125100C80F33 /* MediaItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961942346125000C80F33 /* MediaItem.swift */; };
+		305961DB2346125100C80F33 /* AudioItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961952346125000C80F33 /* AudioItem.swift */; };
+		305961DC2346125100C80F33 /* MessagesLayoutDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961962346125000C80F33 /* MessagesLayoutDelegate.swift */; };
+		305961DD2346125100C80F33 /* SenderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961972346125000C80F33 /* SenderType.swift */; };
+		305961DE2346125100C80F33 /* MessageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961982346125000C80F33 /* MessageType.swift */; };
+		305961DF2346125100C80F33 /* MessageCellDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961992346125000C80F33 /* MessageCellDelegate.swift */; };
+		305961E02346125100C80F33 /* MessageLabelDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3059619A2346125000C80F33 /* MessageLabelDelegate.swift */; };
+		305961E12346125100C80F33 /* LocationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3059619B2346125000C80F33 /* LocationItem.swift */; };
+		305961E22346125100C80F33 /* MessagesDisplayDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3059619C2346125000C80F33 /* MessagesDisplayDelegate.swift */; };
+		305961E32346125100C80F33 /* MessagesDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3059619D2346125000C80F33 /* MessagesDataSource.swift */; };
+		305961E42346125100C80F33 /* MessageKitDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3059619F2346125100C80F33 /* MessageKitDateFormatter.swift */; };
+		305961E52346125100C80F33 /* LabelAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961A02346125100C80F33 /* LabelAlignment.swift */; };
+		305961E62346125100C80F33 /* LocationMessageSnapshotOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961A12346125100C80F33 /* LocationMessageSnapshotOptions.swift */; };
+		305961E72346125100C80F33 /* AccessoryPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961A22346125100C80F33 /* AccessoryPosition.swift */; };
+		305961E82346125100C80F33 /* Sender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961A32346125100C80F33 /* Sender.swift */; };
+		305961E92346125100C80F33 /* MessageKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961A42346125100C80F33 /* MessageKind.swift */; };
+		305961EA2346125100C80F33 /* MessageStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961A52346125100C80F33 /* MessageStyle.swift */; };
+		305961EB2346125100C80F33 /* MessageKitError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961A62346125100C80F33 /* MessageKitError.swift */; };
+		305961EC2346125100C80F33 /* Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961A72346125100C80F33 /* Avatar.swift */; };
+		305961ED2346125100C80F33 /* DetectorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961A82346125100C80F33 /* DetectorType.swift */; };
+		305961EE2346125100C80F33 /* AvatarPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961A92346125100C80F33 /* AvatarPosition.swift */; };
+		305961EF2346125100C80F33 /* HorizontalEdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961AA2346125100C80F33 /* HorizontalEdgeInsets.swift */; };
+		305961F02346125100C80F33 /* NSConstraintLayoutSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961AB2346125100C80F33 /* NSConstraintLayoutSet.swift */; };
+		305961F12346125100C80F33 /* ContactMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961AE2346125100C80F33 /* ContactMessageCell.swift */; };
+		305961F22346125100C80F33 /* LocationMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961AF2346125100C80F33 /* LocationMessageCell.swift */; };
+		305961F32346125100C80F33 /* MediaMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961B02346125100C80F33 /* MediaMessageCell.swift */; };
+		305961F42346125100C80F33 /* TextMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961B12346125100C80F33 /* TextMessageCell.swift */; };
+		305961F52346125100C80F33 /* TypingIndicatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961B22346125100C80F33 /* TypingIndicatorCell.swift */; };
+		305961F62346125100C80F33 /* MessageContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961B32346125100C80F33 /* MessageContentCell.swift */; };
+		305961F72346125100C80F33 /* MessageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961B42346125100C80F33 /* MessageCollectionViewCell.swift */; };
+		305961F82346125100C80F33 /* AudioMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961B52346125100C80F33 /* AudioMessageCell.swift */; };
+		305961F92346125100C80F33 /* MessageLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961B62346125100C80F33 /* MessageLabel.swift */; };
+		305961FA2346125100C80F33 /* MessageReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961B82346125100C80F33 /* MessageReusableView.swift */; };
+		305961FB2346125100C80F33 /* TypingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961B92346125100C80F33 /* TypingIndicator.swift */; };
+		305961FC2346125100C80F33 /* MessageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961BA2346125100C80F33 /* MessageContainerView.swift */; };
+		305961FD2346125100C80F33 /* TypingBubble.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961BB2346125100C80F33 /* TypingBubble.swift */; };
+		305961FE2346125100C80F33 /* InsetLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961BC2346125100C80F33 /* InsetLabel.swift */; };
+		305961FF2346125100C80F33 /* AvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961BD2346125100C80F33 /* AvatarView.swift */; };
+		305962002346125100C80F33 /* MessagesCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961BE2346125100C80F33 /* MessagesCollectionView.swift */; };
+		305962012346125100C80F33 /* PlayButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961BF2346125100C80F33 /* PlayButtonView.swift */; };
+		305962022346125100C80F33 /* BubbleCircle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961C02346125100C80F33 /* BubbleCircle.swift */; };
+		305962032346125100C80F33 /* CellSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961C22346125100C80F33 /* CellSizeCalculator.swift */; };
+		305962042346125100C80F33 /* MessagesCollectionViewLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961C32346125100C80F33 /* MessagesCollectionViewLayoutAttributes.swift */; };
+		305962052346125100C80F33 /* ContactMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961C42346125100C80F33 /* ContactMessageSizeCalculator.swift */; };
+		305962062346125100C80F33 /* TypingIndicatorCellSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961C52346125100C80F33 /* TypingIndicatorCellSizeCalculator.swift */; };
+		305962072346125100C80F33 /* MessagesCollectionViewFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961C62346125100C80F33 /* MessagesCollectionViewFlowLayout.swift */; };
+		305962082346125100C80F33 /* MediaMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961C72346125100C80F33 /* MediaMessageSizeCalculator.swift */; };
+		305962092346125100C80F33 /* AudioMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961C82346125100C80F33 /* AudioMessageSizeCalculator.swift */; };
+		3059620A2346125100C80F33 /* TextMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961C92346125100C80F33 /* TextMessageSizeCalculator.swift */; };
+		3059620B2346125100C80F33 /* LocationMessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961CA2346125100C80F33 /* LocationMessageSizeCalculator.swift */; };
+		3059620C2346125100C80F33 /* MessageSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 305961CB2346125100C80F33 /* MessageSizeCalculator.swift */; };
+		3059620E234614E700C80F33 /* DcContact+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3059620D234614E700C80F33 /* DcContact+Extension.swift */; };
+		305962102346154D00C80F33 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3059620F2346154D00C80F33 /* String+Extension.swift */; };
 		3060119C22DDE24000C1CE6F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3060119E22DDE24000C1CE6F /* Localizable.strings */; };
 		306011B622E5E7FB00C1CE6F /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 306011B422E5E7FB00C1CE6F /* Localizable.stringsdict */; };
 		30A4D9AE2332672700544344 /* QrInviteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30A4D9AD2332672600544344 /* QrInviteViewController.swift */; };
-		30BD261622F8812700F399DF /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BD261522F8812700F399DF /* UIView+Extension.swift */; };
 		6795F63A82E94FF7CD2CEC0F /* Pods_deltachat_iosTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F7009234DB9408201A6CDCB /* Pods_deltachat_iosTests.framework */; };
 		7070FB9B2101ECBB000DC258 /* GroupNameController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7070FB9A2101ECBB000DC258 /* GroupNameController.swift */; };
 		7092474120B3869500AF8799 /* ContactDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7092474020B3869500AF8799 /* ContactDetailViewController.swift */; };
@@ -106,6 +170,72 @@
 		3022E6D122E8769E00763272 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		3022E6D222E8769F00763272 /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
 		3022E6D322E876A100763272 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+		305961822346125000C80F33 /* UIView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = "<group>"; };
+		305961832346125000C80F33 /* UIEdgeInsets+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIEdgeInsets+Extensions.swift"; sourceTree = "<group>"; };
+		305961852346125000C80F33 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = "<group>"; };
+		305961862346125000C80F33 /* NSAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Extensions.swift"; sourceTree = "<group>"; };
+		305961872346125000C80F33 /* Bundle+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = "<group>"; };
+		305961882346125000C80F33 /* CGRect+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGRect+Extensions.swift"; sourceTree = "<group>"; };
+		3059618A2346125000C80F33 /* MessagesViewController+Keyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessagesViewController+Keyboard.swift"; sourceTree = "<group>"; };
+		3059618B2346125000C80F33 /* MessagesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesViewController.swift; sourceTree = "<group>"; };
+		3059618C2346125000C80F33 /* MessagesViewController+Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessagesViewController+Menu.swift"; sourceTree = "<group>"; };
+		3059618E2346125000C80F33 /* MessageInputBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInputBar.swift; sourceTree = "<group>"; };
+		3059618F2346125000C80F33 /* MessageKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MessageKit.h; sourceTree = "<group>"; };
+		305961902346125000C80F33 /* MessageKit+Availability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageKit+Availability.swift"; sourceTree = "<group>"; };
+		305961932346125000C80F33 /* ContactItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactItem.swift; sourceTree = "<group>"; };
+		305961942346125000C80F33 /* MediaItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaItem.swift; sourceTree = "<group>"; };
+		305961952346125000C80F33 /* AudioItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioItem.swift; sourceTree = "<group>"; };
+		305961962346125000C80F33 /* MessagesLayoutDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesLayoutDelegate.swift; sourceTree = "<group>"; };
+		305961972346125000C80F33 /* SenderType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SenderType.swift; sourceTree = "<group>"; };
+		305961982346125000C80F33 /* MessageType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageType.swift; sourceTree = "<group>"; };
+		305961992346125000C80F33 /* MessageCellDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCellDelegate.swift; sourceTree = "<group>"; };
+		3059619A2346125000C80F33 /* MessageLabelDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageLabelDelegate.swift; sourceTree = "<group>"; };
+		3059619B2346125000C80F33 /* LocationItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationItem.swift; sourceTree = "<group>"; };
+		3059619C2346125000C80F33 /* MessagesDisplayDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesDisplayDelegate.swift; sourceTree = "<group>"; };
+		3059619D2346125000C80F33 /* MessagesDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesDataSource.swift; sourceTree = "<group>"; };
+		3059619F2346125100C80F33 /* MessageKitDateFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageKitDateFormatter.swift; sourceTree = "<group>"; };
+		305961A02346125100C80F33 /* LabelAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelAlignment.swift; sourceTree = "<group>"; };
+		305961A12346125100C80F33 /* LocationMessageSnapshotOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationMessageSnapshotOptions.swift; sourceTree = "<group>"; };
+		305961A22346125100C80F33 /* AccessoryPosition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessoryPosition.swift; sourceTree = "<group>"; };
+		305961A32346125100C80F33 /* Sender.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sender.swift; sourceTree = "<group>"; };
+		305961A42346125100C80F33 /* MessageKind.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageKind.swift; sourceTree = "<group>"; };
+		305961A52346125100C80F33 /* MessageStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageStyle.swift; sourceTree = "<group>"; };
+		305961A62346125100C80F33 /* MessageKitError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageKitError.swift; sourceTree = "<group>"; };
+		305961A72346125100C80F33 /* Avatar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Avatar.swift; sourceTree = "<group>"; };
+		305961A82346125100C80F33 /* DetectorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetectorType.swift; sourceTree = "<group>"; };
+		305961A92346125100C80F33 /* AvatarPosition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarPosition.swift; sourceTree = "<group>"; };
+		305961AA2346125100C80F33 /* HorizontalEdgeInsets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalEdgeInsets.swift; sourceTree = "<group>"; };
+		305961AB2346125100C80F33 /* NSConstraintLayoutSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSConstraintLayoutSet.swift; sourceTree = "<group>"; };
+		305961AE2346125100C80F33 /* ContactMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactMessageCell.swift; sourceTree = "<group>"; };
+		305961AF2346125100C80F33 /* LocationMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationMessageCell.swift; sourceTree = "<group>"; };
+		305961B02346125100C80F33 /* MediaMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaMessageCell.swift; sourceTree = "<group>"; };
+		305961B12346125100C80F33 /* TextMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextMessageCell.swift; sourceTree = "<group>"; };
+		305961B22346125100C80F33 /* TypingIndicatorCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicatorCell.swift; sourceTree = "<group>"; };
+		305961B32346125100C80F33 /* MessageContentCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageContentCell.swift; sourceTree = "<group>"; };
+		305961B42346125100C80F33 /* MessageCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCollectionViewCell.swift; sourceTree = "<group>"; };
+		305961B52346125100C80F33 /* AudioMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioMessageCell.swift; sourceTree = "<group>"; };
+		305961B62346125100C80F33 /* MessageLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageLabel.swift; sourceTree = "<group>"; };
+		305961B82346125100C80F33 /* MessageReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageReusableView.swift; sourceTree = "<group>"; };
+		305961B92346125100C80F33 /* TypingIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicator.swift; sourceTree = "<group>"; };
+		305961BA2346125100C80F33 /* MessageContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageContainerView.swift; sourceTree = "<group>"; };
+		305961BB2346125100C80F33 /* TypingBubble.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingBubble.swift; sourceTree = "<group>"; };
+		305961BC2346125100C80F33 /* InsetLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InsetLabel.swift; sourceTree = "<group>"; };
+		305961BD2346125100C80F33 /* AvatarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarView.swift; sourceTree = "<group>"; };
+		305961BE2346125100C80F33 /* MessagesCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionView.swift; sourceTree = "<group>"; };
+		305961BF2346125100C80F33 /* PlayButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayButtonView.swift; sourceTree = "<group>"; };
+		305961C02346125100C80F33 /* BubbleCircle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleCircle.swift; sourceTree = "<group>"; };
+		305961C22346125100C80F33 /* CellSizeCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellSizeCalculator.swift; sourceTree = "<group>"; };
+		305961C32346125100C80F33 /* MessagesCollectionViewLayoutAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionViewLayoutAttributes.swift; sourceTree = "<group>"; };
+		305961C42346125100C80F33 /* ContactMessageSizeCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactMessageSizeCalculator.swift; sourceTree = "<group>"; };
+		305961C52346125100C80F33 /* TypingIndicatorCellSizeCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicatorCellSizeCalculator.swift; sourceTree = "<group>"; };
+		305961C62346125100C80F33 /* MessagesCollectionViewFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessagesCollectionViewFlowLayout.swift; sourceTree = "<group>"; };
+		305961C72346125100C80F33 /* MediaMessageSizeCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaMessageSizeCalculator.swift; sourceTree = "<group>"; };
+		305961C82346125100C80F33 /* AudioMessageSizeCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioMessageSizeCalculator.swift; sourceTree = "<group>"; };
+		305961C92346125100C80F33 /* TextMessageSizeCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextMessageSizeCalculator.swift; sourceTree = "<group>"; };
+		305961CA2346125100C80F33 /* LocationMessageSizeCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationMessageSizeCalculator.swift; sourceTree = "<group>"; };
+		305961CB2346125100C80F33 /* MessageSizeCalculator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageSizeCalculator.swift; sourceTree = "<group>"; };
+		3059620D234614E700C80F33 /* DcContact+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DcContact+Extension.swift"; sourceTree = "<group>"; };
+		3059620F2346154D00C80F33 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = "<group>"; };
 		3060119D22DDE24000C1CE6F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
 		3060119F22DDE24500C1CE6F /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
 		306011A022DDE24700C1CE6F /* sq */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sq; path = sq.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -147,7 +277,6 @@
 		306011C822E5E83100C1CE6F /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
 		306011C922E5E83500C1CE6F /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
 		30A4D9AD2332672600544344 /* QrInviteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QrInviteViewController.swift; sourceTree = "<group>"; };
-		30BD261522F8812700F399DF /* UIView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = "<group>"; usesTabs = 0; };
 		6241BE1534A653E79AD5D01D /* Pods_deltachat_ios.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_deltachat_ios.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		7070FB9A2101ECBB000DC258 /* GroupNameController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupNameController.swift; sourceTree = "<group>"; };
 		7092474020B3869500AF8799 /* ContactDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailViewController.swift; sourceTree = "<group>"; };
@@ -244,6 +373,152 @@
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		3059617E234610A800C80F33 /* MessageKit */ = {
+			isa = PBXGroup;
+			children = (
+				305961892346125000C80F33 /* Controllers */,
+				305961C12346125100C80F33 /* Layout */,
+				3059619E2346125100C80F33 /* Models */,
+				305961922346125000C80F33 /* Protocols */,
+				3059618D2346125000C80F33 /* Supporting */,
+				305961AC2346125100C80F33 /* Views */,
+			);
+			path = MessageKit;
+			sourceTree = "<group>";
+		};
+		305961812346125000C80F33 /* Extensions */ = {
+			isa = PBXGroup;
+			children = (
+				AEE56D7F225504DB007DC082 /* Extensions.swift */,
+				305961822346125000C80F33 /* UIView+Extensions.swift */,
+				78E45E4321D3F14A00D4B15E /* UIImage+Extension.swift */,
+				305961832346125000C80F33 /* UIEdgeInsets+Extensions.swift */,
+				305961852346125000C80F33 /* UIColor+Extensions.swift */,
+				305961862346125000C80F33 /* NSAttributedString+Extensions.swift */,
+				305961872346125000C80F33 /* Bundle+Extensions.swift */,
+				305961882346125000C80F33 /* CGRect+Extensions.swift */,
+				3059620D234614E700C80F33 /* DcContact+Extension.swift */,
+				3059620F2346154D00C80F33 /* String+Extension.swift */,
+			);
+			path = Extensions;
+			sourceTree = "<group>";
+		};
+		305961892346125000C80F33 /* Controllers */ = {
+			isa = PBXGroup;
+			children = (
+				3059618A2346125000C80F33 /* MessagesViewController+Keyboard.swift */,
+				3059618B2346125000C80F33 /* MessagesViewController.swift */,
+				3059618C2346125000C80F33 /* MessagesViewController+Menu.swift */,
+			);
+			path = Controllers;
+			sourceTree = "<group>";
+		};
+		3059618D2346125000C80F33 /* Supporting */ = {
+			isa = PBXGroup;
+			children = (
+				3059618E2346125000C80F33 /* MessageInputBar.swift */,
+				3059618F2346125000C80F33 /* MessageKit.h */,
+				305961902346125000C80F33 /* MessageKit+Availability.swift */,
+			);
+			path = Supporting;
+			sourceTree = "<group>";
+		};
+		305961922346125000C80F33 /* Protocols */ = {
+			isa = PBXGroup;
+			children = (
+				305961932346125000C80F33 /* ContactItem.swift */,
+				305961942346125000C80F33 /* MediaItem.swift */,
+				305961952346125000C80F33 /* AudioItem.swift */,
+				305961962346125000C80F33 /* MessagesLayoutDelegate.swift */,
+				305961972346125000C80F33 /* SenderType.swift */,
+				305961982346125000C80F33 /* MessageType.swift */,
+				305961992346125000C80F33 /* MessageCellDelegate.swift */,
+				3059619A2346125000C80F33 /* MessageLabelDelegate.swift */,
+				3059619B2346125000C80F33 /* LocationItem.swift */,
+				3059619C2346125000C80F33 /* MessagesDisplayDelegate.swift */,
+				3059619D2346125000C80F33 /* MessagesDataSource.swift */,
+			);
+			path = Protocols;
+			sourceTree = "<group>";
+		};
+		3059619E2346125100C80F33 /* Models */ = {
+			isa = PBXGroup;
+			children = (
+				3059619F2346125100C80F33 /* MessageKitDateFormatter.swift */,
+				305961A02346125100C80F33 /* LabelAlignment.swift */,
+				305961A12346125100C80F33 /* LocationMessageSnapshotOptions.swift */,
+				305961A22346125100C80F33 /* AccessoryPosition.swift */,
+				305961A32346125100C80F33 /* Sender.swift */,
+				305961A42346125100C80F33 /* MessageKind.swift */,
+				305961A52346125100C80F33 /* MessageStyle.swift */,
+				305961A62346125100C80F33 /* MessageKitError.swift */,
+				305961A72346125100C80F33 /* Avatar.swift */,
+				305961A82346125100C80F33 /* DetectorType.swift */,
+				305961A92346125100C80F33 /* AvatarPosition.swift */,
+				305961AA2346125100C80F33 /* HorizontalEdgeInsets.swift */,
+				305961AB2346125100C80F33 /* NSConstraintLayoutSet.swift */,
+			);
+			path = Models;
+			sourceTree = "<group>";
+		};
+		305961AC2346125100C80F33 /* Views */ = {
+			isa = PBXGroup;
+			children = (
+				305961AD2346125100C80F33 /* Cells */,
+				305961B62346125100C80F33 /* MessageLabel.swift */,
+				305961B72346125100C80F33 /* HeadersFooters */,
+				305961B92346125100C80F33 /* TypingIndicator.swift */,
+				305961BA2346125100C80F33 /* MessageContainerView.swift */,
+				305961BB2346125100C80F33 /* TypingBubble.swift */,
+				305961BC2346125100C80F33 /* InsetLabel.swift */,
+				305961BD2346125100C80F33 /* AvatarView.swift */,
+				305961BE2346125100C80F33 /* MessagesCollectionView.swift */,
+				305961BF2346125100C80F33 /* PlayButtonView.swift */,
+				305961C02346125100C80F33 /* BubbleCircle.swift */,
+			);
+			path = Views;
+			sourceTree = "<group>";
+		};
+		305961AD2346125100C80F33 /* Cells */ = {
+			isa = PBXGroup;
+			children = (
+				305961AE2346125100C80F33 /* ContactMessageCell.swift */,
+				305961AF2346125100C80F33 /* LocationMessageCell.swift */,
+				305961B02346125100C80F33 /* MediaMessageCell.swift */,
+				305961B12346125100C80F33 /* TextMessageCell.swift */,
+				305961B22346125100C80F33 /* TypingIndicatorCell.swift */,
+				305961B32346125100C80F33 /* MessageContentCell.swift */,
+				305961B42346125100C80F33 /* MessageCollectionViewCell.swift */,
+				305961B52346125100C80F33 /* AudioMessageCell.swift */,
+			);
+			path = Cells;
+			sourceTree = "<group>";
+		};
+		305961B72346125100C80F33 /* HeadersFooters */ = {
+			isa = PBXGroup;
+			children = (
+				305961B82346125100C80F33 /* MessageReusableView.swift */,
+			);
+			path = HeadersFooters;
+			sourceTree = "<group>";
+		};
+		305961C12346125100C80F33 /* Layout */ = {
+			isa = PBXGroup;
+			children = (
+				305961C22346125100C80F33 /* CellSizeCalculator.swift */,
+				305961C32346125100C80F33 /* MessagesCollectionViewLayoutAttributes.swift */,
+				305961C42346125100C80F33 /* ContactMessageSizeCalculator.swift */,
+				305961C52346125100C80F33 /* TypingIndicatorCellSizeCalculator.swift */,
+				305961C62346125100C80F33 /* MessagesCollectionViewFlowLayout.swift */,
+				305961C72346125100C80F33 /* MediaMessageSizeCalculator.swift */,
+				305961C82346125100C80F33 /* AudioMessageSizeCalculator.swift */,
+				305961C92346125100C80F33 /* TextMessageSizeCalculator.swift */,
+				305961CA2346125100C80F33 /* LocationMessageSizeCalculator.swift */,
+				305961CB2346125100C80F33 /* MessageSizeCalculator.swift */,
+			);
+			path = Layout;
+			sourceTree = "<group>";
+		};
 		7A9FB1371FB061E2001FEA36 = {
 			isa = PBXGroup;
 			children = (
@@ -275,6 +550,8 @@
 		7A9FB1421FB061E2001FEA36 /* deltachat-ios */ = {
 			isa = PBXGroup;
 			children = (
+				305961812346125000C80F33 /* Extensions */,
+				3059617E234610A800C80F33 /* MessageKit */,
 				7A9FB1431FB061E2001FEA36 /* AppDelegate.swift */,
 				AE851ACA227C79CF00ED86F0 /* DC */,
 				AE851AC3227C695900ED86F0 /* View */,
@@ -391,12 +668,9 @@
 			isa = PBXGroup;
 			children = (
 				AEACE2E21FB32B5C00DCDD78 /* Constants.swift */,
-				78E45E4321D3F14A00D4B15E /* UIImage+Extension.swift */,
 				AEACE2E41FB32E1900DCDD78 /* Utils.swift */,
-				AEE56D7F225504DB007DC082 /* Extensions.swift */,
 				AE38B3192267328200EC37A1 /* Colors.swift */,
 				AE851AC4227C755A00ED86F0 /* Protocols.swift */,
-				30BD261522F8812700F399DF /* UIView+Extension.swift */,
 			);
 			path = Helper;
 			sourceTree = "<group>";
@@ -717,29 +991,56 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				305961DC2346125100C80F33 /* MessagesLayoutDelegate.swift in Sources */,
+				3059620A2346125100C80F33 /* TextMessageSizeCalculator.swift in Sources */,
 				78ED839421D5AF8A00243125 /* QrCodeView.swift in Sources */,
+				305961F02346125100C80F33 /* NSConstraintLayoutSet.swift in Sources */,
+				3059620E234614E700C80F33 /* DcContact+Extension.swift in Sources */,
+				305961F72346125100C80F33 /* MessageCollectionViewCell.swift in Sources */,
 				AE851AC9227C77CF00ED86F0 /* Media.swift in Sources */,
 				AEACE2DF1FB3246400DCDD78 /* Message.swift in Sources */,
 				AE9DAF0F22C278C6004C9591 /* ChatTitleView.swift in Sources */,
 				AE4AEE3522B1030D000AA495 /* PreviewController.swift in Sources */,
 				7070FB9B2101ECBB000DC258 /* GroupNameController.swift in Sources */,
+				305961EA2346125100C80F33 /* MessageStyle.swift in Sources */,
+				305961F92346125100C80F33 /* MessageLabel.swift in Sources */,
+				305961FA2346125100C80F33 /* MessageReusableView.swift in Sources */,
 				AE52EA19229EB53C00C586C9 /* ContactDetailHeader.swift in Sources */,
 				78E45E4421D3F14A00D4B15E /* UIImage+Extension.swift in Sources */,
+				305962082346125100C80F33 /* MediaMessageSizeCalculator.swift in Sources */,
 				AE52EA20229EB9F000C586C9 /* EditGroupViewController.swift in Sources */,
 				70B08FCD21073B910097D3EA /* NewGroupMemberChoiceController.swift in Sources */,
 				78E45E3E21D3D28C00D4B15E /* DcNavigationController.swift in Sources */,
 				AE18F294228C602A0007B1BE /* SecuritySettingsController.swift in Sources */,
 				78ED838D21D577D000243125 /* events.swift in Sources */,
+				305961FD2346125100C80F33 /* TypingBubble.swift in Sources */,
+				305961D72346125100C80F33 /* MessageKit+Availability.swift in Sources */,
+				305961FE2346125100C80F33 /* InsetLabel.swift in Sources */,
 				B21005DB23383664004C70C5 /* SettingsClassicViewController.swift in Sources */,
+				305961F62346125100C80F33 /* MessageContentCell.swift in Sources */,
+				305961E42346125100C80F33 /* MessageKitDateFormatter.swift in Sources */,
+				305961D32346125100C80F33 /* MessagesViewController+Keyboard.swift in Sources */,
+				305961EF2346125100C80F33 /* HorizontalEdgeInsets.swift in Sources */,
 				30A4D9AE2332672700544344 /* QrInviteViewController.swift in Sources */,
+				305961D62346125100C80F33 /* MessageInputBar.swift in Sources */,
+				305961ED2346125100C80F33 /* DetectorType.swift in Sources */,
+				305962062346125100C80F33 /* TypingIndicatorCellSizeCalculator.swift in Sources */,
 				AE851AC7227C776400ED86F0 /* Location.swift in Sources */,
 				7AE0A5491FC42F65005ECB4B /* NewChatViewController.swift in Sources */,
+				305961E52346125100C80F33 /* LabelAlignment.swift in Sources */,
+				305961E82346125100C80F33 /* Sender.swift in Sources */,
+				305961EE2346125100C80F33 /* AvatarPosition.swift in Sources */,
 				AE25F09022807AD800CDEA66 /* GroupNameCell.swift in Sources */,
+				305961E62346125100C80F33 /* LocationMessageSnapshotOptions.swift in Sources */,
 				AEE6EC3F2282C59C00EDC689 /* GroupMembersViewController.swift in Sources */,
 				78E45E3A21D3CFBC00D4B15E /* SettingsController.swift in Sources */,
 				AE8519EA2272FDCA00ED86F0 /* DeviceContactsHandler.swift in Sources */,
+				3059620B2346125100C80F33 /* LocationMessageSizeCalculator.swift in Sources */,
+				305962072346125100C80F33 /* MessagesCollectionViewFlowLayout.swift in Sources */,
 				78ED838321D5379000243125 /* TextFieldCell.swift in Sources */,
 				78E45E3C21D3D03700D4B15E /* TextFieldTableViewCell.swift in Sources */,
+				305961D52346125100C80F33 /* MessagesViewController+Menu.swift in Sources */,
+				305961F22346125100C80F33 /* LocationMessageCell.swift in Sources */,
 				AE0D26FD1FB1FE88002FAFCE /* ChatListController.swift in Sources */,
 				30149D9322F21129003C12B5 /* QrViewController.swift in Sources */,
 				AEE56D80225504DB007DC082 /* Extensions.swift in Sources */,
@@ -748,28 +1049,65 @@
 				AEACE2DD1FB323CA00DCDD78 /* ChatViewController.swift in Sources */,
 				AEE6EC412282DF5700EDC689 /* MailboxViewController.swift in Sources */,
 				AEE6EC482283045D00EDC689 /* EditSettingsController.swift in Sources */,
+				305961DF2346125100C80F33 /* MessageCellDelegate.swift in Sources */,
+				305961CC2346125100C80F33 /* UIView+Extensions.swift in Sources */,
 				7A9FB1441FB061E2001FEA36 /* AppDelegate.swift in Sources */,
+				305961F52346125100C80F33 /* TypingIndicatorCell.swift in Sources */,
 				AEE56D7D2253ADB4007DC082 /* HudHandler.swift in Sources */,
+				305961FF2346125100C80F33 /* AvatarView.swift in Sources */,
+				3059620C2346125100C80F33 /* MessageSizeCalculator.swift in Sources */,
+				305962042346125100C80F33 /* MessagesCollectionViewLayoutAttributes.swift in Sources */,
+				305961D02346125100C80F33 /* NSAttributedString+Extensions.swift in Sources */,
+				305961CF2346125100C80F33 /* UIColor+Extensions.swift in Sources */,
 				AEACE2E51FB32E1900DCDD78 /* Utils.swift in Sources */,
+				305961E92346125100C80F33 /* MessageKind.swift in Sources */,
+				305962022346125100C80F33 /* BubbleCircle.swift in Sources */,
+				305961DD2346125100C80F33 /* SenderType.swift in Sources */,
+				305961E32346125100C80F33 /* MessagesDataSource.swift in Sources */,
+				305961E22346125100C80F33 /* MessagesDisplayDelegate.swift in Sources */,
+				305962092346125100C80F33 /* AudioMessageSizeCalculator.swift in Sources */,
+				305961DB2346125100C80F33 /* AudioItem.swift in Sources */,
+				305962012346125100C80F33 /* PlayButtonView.swift in Sources */,
 				789E879D21D6DF86003ED1C5 /* ProgressHud.swift in Sources */,
+				305961F32346125100C80F33 /* MediaMessageCell.swift in Sources */,
+				305962102346154D00C80F33 /* String+Extension.swift in Sources */,
 				78E45E4C21D404AE00D4B15E /* CustomMessageCell.swift in Sources */,
 				AE38B31A2267328200EC37A1 /* Colors.swift in Sources */,
 				789E879621D6CB58003ED1C5 /* QrCodeReaderController.swift in Sources */,
+				305961D22346125100C80F33 /* CGRect+Extensions.swift in Sources */,
+				305961E12346125100C80F33 /* LocationItem.swift in Sources */,
+				305961E72346125100C80F33 /* AccessoryPosition.swift in Sources */,
 				7A451DBE1FB4AD0700177250 /* Wrapper.swift in Sources */,
+				305961DE2346125100C80F33 /* MessageType.swift in Sources */,
 				AE851ACE227CA54400ED86F0 /* InitialsBadge.swift in Sources */,
+				305961DA2346125100C80F33 /* MediaItem.swift in Sources */,
+				305961EB2346125100C80F33 /* MessageKitError.swift in Sources */,
 				70B8882E2091B8550074812E /* ContactCell.swift in Sources */,
+				305961F82346125100C80F33 /* AudioMessageCell.swift in Sources */,
 				7A451D941FB1B1DB00177250 /* wrapper.c in Sources */,
-				30BD261622F8812700F399DF /* UIView+Extension.swift in Sources */,
+				305961EC2346125100C80F33 /* Avatar.swift in Sources */,
+				305961CD2346125100C80F33 /* UIEdgeInsets+Extensions.swift in Sources */,
+				305962032346125100C80F33 /* CellSizeCalculator.swift in Sources */,
+				305961E02346125100C80F33 /* MessageLabelDelegate.swift in Sources */,
+				305961D92346125100C80F33 /* ContactItem.swift in Sources */,
+				305961FB2346125100C80F33 /* TypingIndicator.swift in Sources */,
 				7092474120B3869500AF8799 /* ContactDetailViewController.swift in Sources */,
 				AE18F292228C17BC0007B1BE /* PortSettingsController.swift in Sources */,
+				305961F12346125100C80F33 /* ContactMessageCell.swift in Sources */,
 				AE851AD0227DF50900ED86F0 /* GroupChatDetailViewController.swift in Sources */,
+				305961D12346125100C80F33 /* Bundle+Extensions.swift in Sources */,
+				305962002346125100C80F33 /* MessagesCollectionView.swift in Sources */,
 				7A451DB01FB1F84900177250 /* AppCoordinator.swift in Sources */,
 				AE38B31822672DFC00EC37A1 /* ActionCell.swift in Sources */,
 				AE9DAF0D22C1215D004C9591 /* EditContactController.swift in Sources */,
+				305961FC2346125100C80F33 /* MessageContainerView.swift in Sources */,
+				305961D42346125100C80F33 /* MessagesViewController.swift in Sources */,
 				785BE16821E247F1003BE98C /* MessageInfoViewController.swift in Sources */,
 				AE851AC5227C755A00ED86F0 /* Protocols.swift in Sources */,
 				AE728F15229D5C390047565B /* PhotoPickerAlertAction.swift in Sources */,
+				305961F42346125100C80F33 /* TextMessageCell.swift in Sources */,
 				AEACE2E31FB32B5C00DCDD78 /* Constants.swift in Sources */,
+				305962052346125100C80F33 /* ContactMessageSizeCalculator.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};

+ 3 - 1
deltachat-ios/Controller/ChatViewController.swift

@@ -1,5 +1,4 @@
 import MapKit
-import MessageKit
 import QuickLook
 import UIKit
 import InputBarAccessoryView
@@ -744,6 +743,9 @@ extension ChatViewController: MessagesDisplayDelegate {
 
 // MARK: - MessagesLayoutDelegate
 extension ChatViewController: MessagesLayoutDelegate {
+
+
+
     func cellTopLabelHeight(for _: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
         if isTimeLabelVisible(at: indexPath) {
             return 18

+ 0 - 1
deltachat-ios/Controller/MailboxViewController.swift

@@ -1,5 +1,4 @@
 import UIKit
-import MessageKit
 
 class MailboxViewController: ChatViewController {
 

+ 0 - 1
deltachat-ios/DC/Wrapper.swift

@@ -1,5 +1,4 @@
 import Foundation
-import MessageKit
 import UIKit
 
 class DcContext {

+ 43 - 0
deltachat-ios/Extensions/Bundle+Extensions.swift

@@ -0,0 +1,43 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+
+internal extension Bundle {
+
+    static func messageKitAssetBundle() -> Bundle { // swiftlint:disable:this explicit_acl
+        let podBundle = Bundle(for: MessagesViewController.self)
+        
+        guard let resourceBundleUrl = podBundle.url(forResource: "MessageKitAssets", withExtension: "bundle") else {
+            fatalError(MessageKitError.couldNotCreateAssetsPath)
+        }
+        
+        guard let resourceBundle = Bundle(url: resourceBundleUrl) else {
+            fatalError(MessageKitError.couldNotLoadAssetsBundle)
+        }
+        
+        return resourceBundle
+    }
+
+}

+ 34 - 0
deltachat-ios/Extensions/CGRect+Extensions.swift

@@ -0,0 +1,34 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+internal extension CGRect {
+    
+    init(_ x: CGFloat, _ y: CGFloat, _ w: CGFloat, _ h: CGFloat) { // swiftlint:disable:this explicit_acl
+        self.init(x: x, y: y, width: w, height: h)
+    }
+
+}

+ 51 - 0
deltachat-ios/Extensions/DcContact+Extension.swift

@@ -0,0 +1,51 @@
+import Foundation
+
+extension DcContact {
+    func contains(searchText text: String) -> [ContactHighlights] {
+        var nameIndexes = [Int]()
+        var emailIndexes = [Int]()
+
+        let contactString = name + email
+        let subsequenceIndexes = contactString.contains(subSequence: text)
+
+        if !subsequenceIndexes.isEmpty {
+            for index in subsequenceIndexes {
+                if index < name.count {
+                    nameIndexes.append(index)
+                } else {
+                    let emailIndex = index - name.count
+                    emailIndexes.append(emailIndex)
+                }
+            }
+            return [ContactHighlights(contactDetail: .NAME, indexes: nameIndexes), ContactHighlights(contactDetail: .EMAIL, indexes: emailIndexes)]
+        } else {
+            return []
+        }
+    }
+
+    func containsExact(searchText text: String) -> [ContactHighlights] {
+        var contactHighlights = [ContactHighlights]()
+
+        let nameString = name + ""
+        let emailString = email + ""
+        if let nameRange = nameString.range(of: text, options: .caseInsensitive) {
+            let index: Int = nameString.distance(from: nameString.startIndex, to: nameRange.lowerBound)
+            var nameIndexes = [Int]()
+            for i in index..<(index + text.count) {
+                nameIndexes.append(i)
+            }
+            contactHighlights.append(ContactHighlights(contactDetail: .NAME, indexes: nameIndexes))
+        }
+
+        if let emailRange = emailString.range(of: text, options: .caseInsensitive) {
+            let index: Int = emailString.distance(from: emailString.startIndex, to: emailRange.lowerBound)
+            var emailIndexes = [Int]()
+            for i in index..<(index + text.count) {
+                emailIndexes.append(i)
+            }
+            contactHighlights.append(ContactHighlights(contactDetail: .EMAIL, indexes: emailIndexes))
+        }
+
+        return contactHighlights
+    }
+}

+ 58 - 0
deltachat-ios/Extensions/Extensions.swift

@@ -0,0 +1,58 @@
+import UIKit
+import Foundation
+
+extension URL {
+    public var queryParameters: [String: String]? {
+        guard
+            let components = URLComponents(url: self, resolvingAgainstBaseURL: true),
+            let queryItems = components.queryItems else { return nil }
+        return queryItems.reduce(into: [String: String]()) { result, item in
+            result[item.name] = item.value
+        }
+    }
+}
+
+extension Dictionary {
+    func percentEscaped() -> String {
+        return map { key, value in
+            let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
+            let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
+            return escapedKey + "=" + escapedValue
+        }
+        .joined(separator: "&")
+    }
+}
+
+extension CharacterSet {
+    static let urlQueryValueAllowed: CharacterSet = {
+        let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
+        let subDelimitersToEncode = "!$&'()*+,;="
+
+        var allowed = CharacterSet.urlQueryAllowed
+        allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
+        return allowed
+    }()
+}
+
+extension URLSession {
+    func synchronousDataTask(request: URLRequest) -> (Data?, URLResponse?, Error?) {
+        var data: Data?
+        var response: URLResponse?
+        var error: Error?
+
+        let semaphore = DispatchSemaphore(value: 0)
+
+        let task = dataTask(with: request) {
+            data = $0
+            response = $1
+            error = $2
+
+            semaphore.signal()
+        }
+        task.resume()
+
+        _ = semaphore.wait(timeout: .distantFuture)
+
+        return (data, response, error)
+    }
+}

+ 37 - 0
deltachat-ios/Extensions/NSAttributedString+Extensions.swift

@@ -0,0 +1,37 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+internal extension NSAttributedString {
+
+    func width(considering height: CGFloat) -> CGFloat { // swiftlint:disable:this explicit_acl
+
+        let constraintBox = CGSize(width: .greatestFiniteMagnitude, height: height)
+        let rect = self.boundingRect(with: constraintBox, options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
+        return rect.width
+        
+    }
+}

+ 76 - 0
deltachat-ios/Extensions/String+Extension.swift

@@ -0,0 +1,76 @@
+import Foundation
+import UIKit
+
+extension String {
+
+    func substring(_ from: Int, _ to: Int) -> String {
+        let idx1 = index(startIndex, offsetBy: from)
+        let idx2 = index(startIndex, offsetBy: to)
+        return String(self[idx1..<idx2])
+    }
+
+    func containsCharacters() -> Bool {
+        return !trimmingCharacters(in: [" "]).isEmpty
+    }
+
+    // O(n) - returns indexes of subsequences -> can be used to highlight subsequence within string
+    func contains(subSequence: String) -> [Int] {
+        if subSequence.count > count {
+            return []
+        }
+
+        let str = lowercased()
+        let sub = subSequence.lowercased()
+
+        var j = 0
+
+        var foundIndexes: [Int] = []
+
+        for (index, char) in str.enumerated() {
+            if j == sub.count {
+                break
+            }
+
+            if char == sub.subScript(j) {
+                foundIndexes.append(index)
+                j += 1
+            }
+        }
+        return foundIndexes.count == sub.count ? foundIndexes : []
+    }
+
+    func subScript(_ i: Int) -> Character {
+        return self[index(startIndex, offsetBy: i)]
+    }
+
+    func boldAt(indexes: [Int], fontSize: CGFloat) -> NSAttributedString {
+        let attributedText = NSMutableAttributedString(string: self)
+
+        for index in indexes {
+            if index < 0 || count <= index {
+                break
+            }
+            attributedText.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: fontSize), range: NSRange(location: index, length: 1))
+        }
+        return attributedText
+    }
+
+    static func localized(_ stringID: String) -> String {
+        let value = NSLocalizedString(stringID, comment: "")
+        if value != stringID || NSLocale.preferredLanguages.first == "en" {
+            return value
+        }
+
+        guard
+            let path = Bundle.main.path(forResource: "en", ofType: "lproj"),
+            let bundle = Bundle(path: path)
+        else { return value }
+        return NSLocalizedString(stringID, bundle: bundle, comment: "")
+    }
+
+    static func localized(stringID: String, count: Int) -> String {
+        let formatString: String = localized(stringID)
+        let resultString: String = String.localizedStringWithFormat(formatString, count)
+        return resultString
+    }
+}

+ 77 - 0
deltachat-ios/Extensions/UIColor+Extensions.swift

@@ -0,0 +1,77 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+// swiftlint:disable explicit_acl
+
+internal extension UIColor {
+
+    static let incomingGray = UIColor(red: 230/255, green: 230/255, blue: 235/255, alpha: 1.0)
+
+    static let outgoingGreen = UIColor(red: 69/255, green: 214/255, blue: 93/255, alpha: 1.0)
+
+    static let inputBarGray = UIColor(red: 247/255, green: 247/255, blue: 247/255, alpha: 1.0)
+
+    static let playButtonLightGray = UIColor(red: 230/255, green: 230/255, blue: 230/255, alpha: 1.0)
+
+    static let sendButtonBlue = UIColor(red: 15/255, green: 135/255, blue: 255/255, alpha: 1.0)
+
+    convenience init(alpha: Int, red: Int, green: Int, blue: Int) {
+        assert(red >= 0 && red <= 255, "Invalid red component")
+        assert(green >= 0 && green <= 255, "Invalid green component")
+        assert(blue >= 0 && blue <= 255, "Invalid blue component")
+
+        self.init(red: CGFloat(red) / 255, green: CGFloat(green) / 255, blue: CGFloat(blue) / 255, alpha: CGFloat(alpha) / 255)
+    }
+
+    convenience init(netHex: Int) {
+        var alpha = (netHex >> 24) & 0xFF
+        if alpha == 0 {
+            alpha = 255
+        }
+
+        self.init(alpha: alpha, red: (netHex >> 16) & 0xFF, green: (netHex >> 8) & 0xFF, blue: netHex & 0xFF)
+    }
+
+    // see: https://stackoverflow.com/a/33397427
+    convenience init(hexString: String) {
+        let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
+        var int = UInt32()
+        Scanner(string: hex).scanHexInt32(&int)
+        let a, r, g, b: UInt32
+        switch hex.count {
+        case 3: // RGB (12-bit)
+            (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
+        case 6: // RGB (24-bit)
+            (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
+        case 8: // ARGB (32-bit)
+            (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
+        default:
+            (a, r, g, b) = (255, 0, 0, 0)
+        }
+        self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
+    }
+}

+ 40 - 0
deltachat-ios/Extensions/UIEdgeInsets+Extensions.swift

@@ -0,0 +1,40 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+// swiftlint:disable explicit_acl
+
+internal extension UIEdgeInsets {
+
+    var vertical: CGFloat {
+        return top + bottom
+    }
+
+    var horizontal: CGFloat {
+        return left + right
+    }
+
+}

+ 111 - 0
deltachat-ios/Extensions/UIImage+Extension.swift

@@ -0,0 +1,111 @@
+import UIKit
+
+extension UIImage {
+    func imageResize(sizeChange: CGSize) -> UIImage {
+        let hasAlpha = true
+        let scale: CGFloat = 0.0 // Use scale factor of main screen
+
+        UIGraphicsBeginImageContextWithOptions(sizeChange, !hasAlpha, scale)
+        draw(in: CGRect(origin: CGPoint.zero, size: sizeChange))
+
+        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
+        return scaledImage!
+    }
+
+    public convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
+        let rect = CGRect(origin: .zero, size: size)
+        UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
+        color.setFill()
+        UIRectFill(rect)
+        let image = UIGraphicsGetImageFromCurrentImageContext()
+        UIGraphicsEndImageContext()
+
+        guard let cgImage = image?.cgImage else { return nil }
+        self.init(cgImage: cgImage)
+    }
+
+    func resizeImage(targetSize: CGSize) -> UIImage? {
+        let size = self.size
+
+        let widthRatio  = targetSize.width  / size.width
+        let heightRatio = targetSize.height / size.height
+
+        var newSize: CGSize
+        if widthRatio > heightRatio {
+            newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
+        } else {
+            newSize = CGSize(width: size.width * widthRatio, height: size.height *      widthRatio)
+        }
+
+        let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
+
+        UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
+        draw(in: rect)
+        let newImage = UIGraphicsGetImageFromCurrentImageContext()
+        UIGraphicsEndImageContext()
+
+        return newImage
+    }
+
+    func dcCompress(toMax target: Float = 1280) -> UIImage? {
+        return resize(toMax: target)
+    }
+
+    func imageSizeInPixel() -> CGSize {
+        let heightInPoints = size.height
+        let heightInPixels = heightInPoints * scale
+        let widthInPoints = size.width
+        let widthInPixels = widthInPoints * scale
+        return CGSize(width: widthInPixels, height: heightInPixels)
+    }
+
+    // source: https://stackoverflow.com/questions/29137488/how-do-i-resize-the-uiimage-to-reduce-upload-image-size // slightly changed
+    func resize(toMax: Float) -> UIImage? {
+        var actualHeight = Float(size.height)
+        var actualWidth = Float(size.width)
+        let maxHeight: Float = toMax
+        let maxWidth: Float = toMax
+        var imgRatio: Float = actualWidth / actualHeight
+        let maxRatio: Float = maxWidth / maxHeight
+        let compressionQuality: Float = 0.5
+        //50 percent compression
+        if actualHeight > maxHeight || actualWidth > maxWidth {
+            if imgRatio < maxRatio {
+                //adjust width according to maxHeight
+                imgRatio = maxHeight / actualHeight
+                actualWidth = imgRatio * actualWidth
+                actualHeight = maxHeight
+            } else if imgRatio > maxRatio {
+                //adjust height according to maxWidth
+                imgRatio = maxWidth / actualWidth
+                actualHeight = imgRatio * actualHeight
+                actualWidth = maxWidth
+            } else {
+                actualHeight = maxHeight
+                actualWidth = maxWidth
+            }
+        }
+
+        let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(actualWidth), height: CGFloat(actualHeight))
+        UIGraphicsBeginImageContext(rect.size)
+        draw(in: rect)
+        let img = UIGraphicsGetImageFromCurrentImageContext()
+        let imageData = img?.jpegData(compressionQuality: CGFloat(compressionQuality))
+        UIGraphicsEndImageContext()
+        return UIImage(data: imageData!)
+    }
+
+    public class func messageKitImageWith(type: ImageType) -> UIImage? {
+        let assetBundle = Bundle.messageKitAssetBundle()
+        let imagePath = assetBundle.path(forResource: type.rawValue, ofType: "png", inDirectory: "Images")
+        let image = UIImage(contentsOfFile: imagePath ?? "")
+        return image
+    }
+
+}
+
+public enum ImageType: String {
+    case play
+    case pause
+    case disclouser
+}

+ 235 - 0
deltachat-ios/Extensions/UIView+Extensions.swift

@@ -0,0 +1,235 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+// swiftlint:disable explicit_acl
+
+internal extension UIView {
+
+    func makeBorder(color: UIColor = UIColor.red) {
+        self.layer.borderColor = color.cgColor
+        self.layer.borderWidth = 2
+        print("hello")
+    }
+
+    func constraintAlignTopTo(_ view: UIView) -> NSLayoutConstraint {
+        return constraintAlignTopTo(view, paddingTop: 0.0)
+    }
+
+    func constraintAlignTopTo(_ view: UIView, paddingTop: CGFloat = 0.0) -> NSLayoutConstraint {
+        return NSLayoutConstraint(
+            item: self,
+            attribute: .top,
+            relatedBy: .equal,
+            toItem: view,
+            attribute: .top,
+            multiplier: 1.0,
+            constant: paddingTop)
+    }
+
+    func constraintAlignBottomTo(_ view: UIView, paddingBottom: CGFloat = 0.0) -> NSLayoutConstraint {
+        return NSLayoutConstraint(
+            item: self,
+            attribute: .bottom,
+            relatedBy: .equal,
+            toItem: view,
+            attribute: .bottom,
+            multiplier: 1.0,
+            constant: -paddingBottom)
+    }
+
+    func constraintAlignLeadingTo(_ view: UIView, paddingLeading: CGFloat = 0.0) -> NSLayoutConstraint {
+        return NSLayoutConstraint(
+            item: self,
+            attribute: .leading,
+            relatedBy: .equal,
+            toItem: view,
+            attribute: .leading,
+            multiplier: 1.0,
+            constant: paddingLeading)
+    }
+
+    func constraintAlignTrailingTo(_ view: UIView, paddingTrailing: CGFloat = 0.0) -> NSLayoutConstraint {
+        return NSLayoutConstraint(
+            item: self,
+            attribute: .trailing,
+            relatedBy: .equal,
+            toItem: view,
+            attribute: .trailing,
+            multiplier: 1.0,
+            constant: -paddingTrailing)
+    }
+
+    func constraintToBottomOf(_ view: UIView, paddingTop: CGFloat = 0.0) -> NSLayoutConstraint {
+        return NSLayoutConstraint(
+            item: self,
+            attribute: .top,
+            relatedBy: .equal,
+            toItem: view,
+            attribute: .bottom,
+            multiplier: 1.0,
+            constant: paddingTop)
+    }
+
+    func constraintToTrailingOf(_ view: UIView, paddingLeading: CGFloat = 0.0) -> NSLayoutConstraint {
+        return NSLayoutConstraint(
+            item: self,
+            attribute: .leading,
+            relatedBy: .equal,
+            toItem: view,
+            attribute: .trailing,
+            multiplier: 1.0,
+            constant: paddingLeading)
+    }
+
+
+    func constraintCenterXTo(_ view: UIView, paddingX: CGFloat = 0.0) -> NSLayoutConstraint {
+        return NSLayoutConstraint(item: self,
+                                  attribute: .centerX,
+                                  relatedBy: .equal,
+                                  toItem: view,
+                                  attribute: .centerX,
+                                  multiplier: 1.0,
+                                  constant: paddingX)
+    }
+
+    func constraintCenterYTo(_ view: UIView, paddingY: CGFloat = 0.0) -> NSLayoutConstraint {
+        return NSLayoutConstraint(item: self,
+                                  attribute: .centerY,
+                                  relatedBy: .equal,
+                                  toItem: view,
+                                  attribute: .centerY,
+                                  multiplier: 1.0,
+                                  constant: paddingY)
+    }
+
+    func constraintHeightTo(_ height: CGFloat) -> NSLayoutConstraint {
+        return heightAnchor.constraint(equalToConstant: height)
+    }
+
+    func constraintWitdthTo(_ width: CGFloat) -> NSLayoutConstraint {
+       return  widthAnchor.constraint(equalToConstant: width)
+    }
+    
+    func fillSuperview() {
+        guard let superview = self.superview else {
+            return
+        }
+        translatesAutoresizingMaskIntoConstraints = false
+
+	    let constraints: [NSLayoutConstraint] = [
+    	    leftAnchor.constraint(equalTo: superview.leftAnchor),
+    	    rightAnchor.constraint(equalTo: superview.rightAnchor),
+    	    topAnchor.constraint(equalTo: superview.topAnchor),
+    	    bottomAnchor.constraint(equalTo: superview.bottomAnchor)
+    	    ]
+	    NSLayoutConstraint.activate(constraints)
+    }
+
+    func centerInSuperview() {
+        guard let superview = self.superview else {
+            return
+        }
+        translatesAutoresizingMaskIntoConstraints = false
+        let constraints: [NSLayoutConstraint] = [
+            centerXAnchor.constraint(equalTo: superview.centerXAnchor),
+            centerYAnchor.constraint(equalTo: superview.centerYAnchor)
+        ]
+        NSLayoutConstraint.activate(constraints)
+    }
+    
+    func constraint(equalTo size: CGSize) {
+        guard superview != nil else { return }
+        translatesAutoresizingMaskIntoConstraints = false
+        let constraints: [NSLayoutConstraint] = [
+            widthAnchor.constraint(equalToConstant: size.width),
+            heightAnchor.constraint(equalToConstant: size.height)
+        ]
+        NSLayoutConstraint.activate(constraints)
+        
+    }
+
+    @discardableResult
+    internal func addConstraints(_ top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil, centerY: NSLayoutYAxisAnchor? = nil, centerX: NSLayoutXAxisAnchor? = nil, topConstant: CGFloat = 0, leftConstant: CGFloat = 0, bottomConstant: CGFloat = 0, rightConstant: CGFloat = 0, centerYConstant: CGFloat = 0, centerXConstant: CGFloat = 0, widthConstant: CGFloat = 0, heightConstant: CGFloat = 0) -> [NSLayoutConstraint] {
+        
+        if self.superview == nil {
+            return []
+        }
+        translatesAutoresizingMaskIntoConstraints = false
+        
+        var constraints = [NSLayoutConstraint]()
+        
+        if let top = top {
+            let constraint = topAnchor.constraint(equalTo: top, constant: topConstant)
+            constraint.identifier = "top"
+            constraints.append(constraint)
+        }
+        
+        if let left = left {
+            let constraint = leftAnchor.constraint(equalTo: left, constant: leftConstant)
+            constraint.identifier = "left"
+            constraints.append(constraint)
+        }
+        
+        if let bottom = bottom {
+            let constraint = bottomAnchor.constraint(equalTo: bottom, constant: -bottomConstant)
+            constraint.identifier = "bottom"
+            constraints.append(constraint)
+        }
+        
+        if let right = right {
+            let constraint = rightAnchor.constraint(equalTo: right, constant: -rightConstant)
+            constraint.identifier = "right"
+            constraints.append(constraint)
+        }
+
+        if let centerY = centerY {
+            let constraint = centerYAnchor.constraint(equalTo: centerY, constant: centerYConstant)
+            constraint.identifier = "centerY"
+            constraints.append(constraint)
+        }
+
+        if let centerX = centerX {
+            let constraint = centerXAnchor.constraint(equalTo: centerX, constant: centerXConstant)
+            constraint.identifier = "centerX"
+            constraints.append(constraint)
+        }
+        
+        if widthConstant > 0 {
+            let constraint = widthAnchor.constraint(equalToConstant: widthConstant)
+            constraint.identifier = "width"
+            constraints.append(constraint)
+        }
+        
+        if heightConstant > 0 {
+            let constraint = heightAnchor.constraint(equalToConstant: heightConstant)
+            constraint.identifier = "height"
+            constraints.append(constraint)
+        }
+        
+        NSLayoutConstraint.activate(constraints)
+        return constraints
+    }
+}

+ 0 - 273
deltachat-ios/Helper/Extensions.swift

@@ -1,273 +0,0 @@
-import UIKit
-
-extension String {
-
-    func substring(_ from: Int, _ to: Int) -> String {
-        let idx1 = index(startIndex, offsetBy: from)
-        let idx2 = index(startIndex, offsetBy: to)
-        return String(self[idx1..<idx2])
-    }
-
-    func containsCharacters() -> Bool {
-        return !trimmingCharacters(in: [" "]).isEmpty
-    }
-
-    // O(n) - returns indexes of subsequences -> can be used to highlight subsequence within string
-    func contains(subSequence: String) -> [Int] {
-        if subSequence.count > count {
-            return []
-        }
-
-        let str = lowercased()
-        let sub = subSequence.lowercased()
-
-        var j = 0
-
-        var foundIndexes: [Int] = []
-
-        for (index, char) in str.enumerated() {
-            if j == sub.count {
-                break
-            }
-
-            if char == sub.subScript(j) {
-                foundIndexes.append(index)
-                j += 1
-            }
-        }
-        return foundIndexes.count == sub.count ? foundIndexes : []
-    }
-
-    func subScript(_ i: Int) -> Character {
-        return self[index(startIndex, offsetBy: i)]
-    }
-
-    func boldAt(indexes: [Int], fontSize: CGFloat) -> NSAttributedString {
-        let attributedText = NSMutableAttributedString(string: self)
-
-        for index in indexes {
-            if index < 0 || count <= index {
-                break
-            }
-            attributedText.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: fontSize), range: NSRange(location: index, length: 1))
-        }
-        return attributedText
-    }
-
-    static func localized(_ stringID: String) -> String {
-        let value = NSLocalizedString(stringID, comment: "")
-        if value != stringID || NSLocale.preferredLanguages.first == "en" {
-            return value
-        }
-
-        guard
-            let path = Bundle.main.path(forResource: "en", ofType: "lproj"),
-            let bundle = Bundle(path: path)
-        else { return value }
-        return NSLocalizedString(stringID, bundle: bundle, comment: "")
-    }
-
-    static func localized(stringID: String, count: Int) -> String {
-        let formatString: String = localized(stringID)
-        let resultString: String = String.localizedStringWithFormat(formatString, count)
-        return resultString
-    }
-}
-
-extension URL {
-    public var queryParameters: [String: String]? {
-        guard
-            let components = URLComponents(url: self, resolvingAgainstBaseURL: true),
-            let queryItems = components.queryItems else { return nil }
-        return queryItems.reduce(into: [String: String]()) { result, item in
-            result[item.name] = item.value
-        }
-    }
-}
-
-extension Dictionary {
-    func percentEscaped() -> String {
-        return map { key, value in
-            let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
-            let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
-            return escapedKey + "=" + escapedValue
-        }
-        .joined(separator: "&")
-    }
-}
-
-extension CharacterSet {
-    static let urlQueryValueAllowed: CharacterSet = {
-        let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
-        let subDelimitersToEncode = "!$&'()*+,;="
-
-        var allowed = CharacterSet.urlQueryAllowed
-        allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
-        return allowed
-    }()
-}
-
-extension URLSession {
-    func synchronousDataTask(request: URLRequest) -> (Data?, URLResponse?, Error?) {
-        var data: Data?
-        var response: URLResponse?
-        var error: Error?
-
-        let semaphore = DispatchSemaphore(value: 0)
-
-        let task = dataTask(with: request) {
-            data = $0
-            response = $1
-            error = $2
-
-            semaphore.signal()
-        }
-        task.resume()
-
-        _ = semaphore.wait(timeout: .distantFuture)
-
-        return (data, response, error)
-    }
-}
-
-extension DcContact {
-    func contains(searchText text: String) -> [ContactHighlights] {
-        var nameIndexes = [Int]()
-        var emailIndexes = [Int]()
-
-        let contactString = name + email
-        let subsequenceIndexes = contactString.contains(subSequence: text)
-
-        if !subsequenceIndexes.isEmpty {
-            for index in subsequenceIndexes {
-                if index < name.count {
-                    nameIndexes.append(index)
-                } else {
-                    let emailIndex = index - name.count
-                    emailIndexes.append(emailIndex)
-                }
-            }
-            return [ContactHighlights(contactDetail: .NAME, indexes: nameIndexes), ContactHighlights(contactDetail: .EMAIL, indexes: emailIndexes)]
-        } else {
-            return []
-        }
-    }
-
-    func containsExact(searchText text: String) -> [ContactHighlights] {
-        var contactHighlights = [ContactHighlights]()
-
-        let nameString = name + ""
-        let emailString = email + ""
-        if let nameRange = nameString.range(of: text, options: .caseInsensitive) {
-            let index: Int = nameString.distance(from: nameString.startIndex, to: nameRange.lowerBound)
-            var nameIndexes = [Int]()
-            for i in index..<(index + text.count) {
-                nameIndexes.append(i)
-            }
-            contactHighlights.append(ContactHighlights(contactDetail: .NAME, indexes: nameIndexes))
-        }
-
-        if let emailRange = emailString.range(of: text, options: .caseInsensitive) {
-            let index: Int = emailString.distance(from: emailString.startIndex, to: emailRange.lowerBound)
-            var emailIndexes = [Int]()
-            for i in index..<(index + text.count) {
-                emailIndexes.append(i)
-            }
-            contactHighlights.append(ContactHighlights(contactDetail: .EMAIL, indexes: emailIndexes))
-        }
-
-        return contactHighlights
-    }
-}
-
-extension UIImage {
-
-    func dcCompress(toMax target: Float = 1280) -> UIImage? {
-        return resize(toMax: target)
-    }
-
-    func imageSizeInPixel() -> CGSize {
-        let heightInPoints = size.height
-        let heightInPixels = heightInPoints * scale
-        let widthInPoints = size.width
-        let widthInPixels = widthInPoints * scale
-        return CGSize(width: widthInPixels, height: heightInPixels)
-    }
-
-    // source: https://stackoverflow.com/questions/29137488/how-do-i-resize-the-uiimage-to-reduce-upload-image-size // slightly changed
-    func resize(toMax: Float) -> UIImage? {
-        var actualHeight = Float(size.height)
-        var actualWidth = Float(size.width)
-        let maxHeight: Float = toMax
-        let maxWidth: Float = toMax
-        var imgRatio: Float = actualWidth / actualHeight
-        let maxRatio: Float = maxWidth / maxHeight
-        let compressionQuality: Float = 0.5
-        //50 percent compression
-        if actualHeight > maxHeight || actualWidth > maxWidth {
-            if imgRatio < maxRatio {
-                //adjust width according to maxHeight
-                imgRatio = maxHeight / actualHeight
-                actualWidth = imgRatio * actualWidth
-                actualHeight = maxHeight
-            } else if imgRatio > maxRatio {
-                //adjust height according to maxWidth
-                imgRatio = maxWidth / actualWidth
-                actualHeight = imgRatio * actualHeight
-                actualWidth = maxWidth
-            } else {
-                actualHeight = maxHeight
-                actualWidth = maxWidth
-            }
-        }
-
-        let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(actualWidth), height: CGFloat(actualHeight))
-        UIGraphicsBeginImageContext(rect.size)
-        draw(in: rect)
-        let img = UIGraphicsGetImageFromCurrentImageContext()
-        let imageData = img?.jpegData(compressionQuality: CGFloat(compressionQuality))
-        UIGraphicsEndImageContext()
-        return UIImage(data: imageData!)
-    }
-}
-
-
-extension UIColor {
-    convenience init(alpha: Int, red: Int, green: Int, blue: Int) {
-        assert(red >= 0 && red <= 255, "Invalid red component")
-        assert(green >= 0 && green <= 255, "Invalid green component")
-        assert(blue >= 0 && blue <= 255, "Invalid blue component")
-
-        self.init(red: CGFloat(red) / 255, green: CGFloat(green) / 255, blue: CGFloat(blue) / 255, alpha: CGFloat(alpha) / 255)
-    }
-
-    convenience init(netHex: Int) {
-        var alpha = (netHex >> 24) & 0xFF
-        if alpha == 0 {
-            alpha = 255
-        }
-
-        self.init(alpha: alpha, red: (netHex >> 16) & 0xFF, green: (netHex >> 8) & 0xFF, blue: netHex & 0xFF)
-    }
-
-    // see: https://stackoverflow.com/a/33397427
-    convenience init(hexString: String) {
-        let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
-        var int = UInt32()
-        Scanner(string: hex).scanHexInt32(&int)
-        let a, r, g, b: UInt32
-        switch hex.count {
-        case 3: // RGB (12-bit)
-            (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
-        case 6: // RGB (24-bit)
-            (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
-        case 8: // ARGB (32-bit)
-            (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
-        default:
-            (a, r, g, b) = (255, 0, 0, 0)
-        }
-        self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
-    }
-
-
-}

+ 0 - 49
deltachat-ios/Helper/UIImage+Extension.swift

@@ -1,49 +0,0 @@
-import UIKit
-
-extension UIImage {
-    func imageResize(sizeChange: CGSize) -> UIImage {
-        let hasAlpha = true
-        let scale: CGFloat = 0.0 // Use scale factor of main screen
-
-        UIGraphicsBeginImageContextWithOptions(sizeChange, !hasAlpha, scale)
-        draw(in: CGRect(origin: CGPoint.zero, size: sizeChange))
-
-        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
-        return scaledImage!
-    }
-
-    public convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
-        let rect = CGRect(origin: .zero, size: size)
-        UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
-        color.setFill()
-        UIRectFill(rect)
-        let image = UIGraphicsGetImageFromCurrentImageContext()
-        UIGraphicsEndImageContext()
-
-        guard let cgImage = image?.cgImage else { return nil }
-        self.init(cgImage: cgImage)
-    }
-
-    func resizeImage(targetSize: CGSize) -> UIImage? {
-        let size = self.size
-
-        let widthRatio  = targetSize.width  / size.width
-        let heightRatio = targetSize.height / size.height
-
-        var newSize: CGSize
-        if widthRatio > heightRatio {
-            newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
-        } else {
-            newSize = CGSize(width: size.width * widthRatio, height: size.height *      widthRatio)
-        }
-
-        let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
-
-        UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
-        draw(in: rect)
-        let newImage = UIGraphicsGetImageFromCurrentImageContext()
-        UIGraphicsEndImageContext()
-
-        return newImage
-    }
-}

+ 0 - 101
deltachat-ios/Helper/UIView+Extension.swift

@@ -1,101 +0,0 @@
-import UIKit
-
-extension UIView {
-    func makeBorder(color: UIColor = UIColor.red) {
-        self.layer.borderColor = color.cgColor
-        self.layer.borderWidth = 2
-        print("hello")
-    }
-
-    func constraintAlignTopTo(_ view: UIView) -> NSLayoutConstraint {
-        return constraintAlignTopTo(view, paddingTop: 0.0)
-    }
-
-    func constraintAlignTopTo(_ view: UIView, paddingTop: CGFloat = 0.0) -> NSLayoutConstraint {
-        return NSLayoutConstraint(
-            item: self,
-            attribute: .top,
-            relatedBy: .equal,
-            toItem: view,
-            attribute: .top,
-            multiplier: 1.0,
-            constant: paddingTop)
-    }
-
-    func constraintAlignBottomTo(_ view: UIView, paddingBottom: CGFloat = 0.0) -> NSLayoutConstraint {
-        return NSLayoutConstraint(
-            item: self,
-            attribute: .bottom,
-            relatedBy: .equal,
-            toItem: view,
-            attribute: .bottom,
-            multiplier: 1.0,
-            constant: -paddingBottom)
-    }
-
-    func constraintAlignLeadingTo(_ view: UIView, paddingLeading: CGFloat = 0.0) -> NSLayoutConstraint {
-        return NSLayoutConstraint(
-            item: self,
-            attribute: .leading,
-            relatedBy: .equal,
-            toItem: view,
-            attribute: .leading,
-            multiplier: 1.0,
-            constant: paddingLeading)
-    }
-
-    func constraintAlignTrailingTo(_ view: UIView, paddingTrailing: CGFloat = 0.0) -> NSLayoutConstraint {
-        return NSLayoutConstraint(
-            item: self,
-            attribute: .trailing,
-            relatedBy: .equal,
-            toItem: view,
-            attribute: .trailing,
-            multiplier: 1.0,
-            constant: -paddingTrailing)
-    }
-
-    func constraintToBottomOf(_ view: UIView, paddingTop: CGFloat = 0.0) -> NSLayoutConstraint {
-        return NSLayoutConstraint(
-            item: self,
-            attribute: .top,
-            relatedBy: .equal,
-            toItem: view,
-            attribute: .bottom,
-            multiplier: 1.0,
-            constant: paddingTop)
-    }
-
-    func constraintToTrailingOf(_ view: UIView, paddingLeading: CGFloat = 0.0) -> NSLayoutConstraint {
-        return NSLayoutConstraint(
-            item: self,
-            attribute: .leading,
-            relatedBy: .equal,
-            toItem: view,
-            attribute: .trailing,
-            multiplier: 1.0,
-            constant: paddingLeading)
-    }
-
-
-    func constraintCenterXTo(_ view: UIView, paddingX: CGFloat = 0.0) -> NSLayoutConstraint {
-        return NSLayoutConstraint(item: self,
-                                  attribute: .centerX,
-                                  relatedBy: .equal,
-                                  toItem: view,
-                                  attribute: .centerX,
-                                  multiplier: 1.0,
-                                  constant: paddingX)
-    }
-
-    func constraintCenterYTo(_ view: UIView, paddingY: CGFloat = 0.0) -> NSLayoutConstraint {
-        return NSLayoutConstraint(item: self,
-                                  attribute: .centerY,
-                                  relatedBy: .equal,
-                                  toItem: view,
-                                  attribute: .centerY,
-                                  multiplier: 1.0,
-                                  constant: paddingY)
-    }
-
-}

+ 141 - 0
deltachat-ios/MessageKit/Controllers/MessagesViewController+Keyboard.swift

@@ -0,0 +1,141 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import InputBarAccessoryView
+
+internal extension MessagesViewController {
+
+    // MARK: - Register / Unregister Observers
+
+    internal func addKeyboardObservers() {
+        NotificationCenter.default.addObserver(self, selector: #selector(MessagesViewController.handleKeyboardDidChangeState(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(MessagesViewController.handleTextViewDidBeginEditing(_:)), name: UITextView.textDidBeginEditingNotification, object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(MessagesViewController.adjustScrollViewTopInset), name: UIDevice.orientationDidChangeNotification, object: nil)
+    }
+
+    internal func removeKeyboardObservers() {
+        NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
+        NotificationCenter.default.removeObserver(self, name: UITextView.textDidBeginEditingNotification, object: nil)
+        NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
+    }
+
+    // MARK: - Notification Handlers
+
+    @objc
+    private func handleTextViewDidBeginEditing(_ notification: Notification) {
+        if scrollsToBottomOnKeyboardBeginsEditing {
+            guard let inputTextView = notification.object as? InputTextView, inputTextView === messageInputBar.inputTextView else { return }
+            messagesCollectionView.scrollToBottom(animated: true)
+        }
+    }
+
+    @objc
+    private func handleKeyboardDidChangeState(_ notification: Notification) {
+        guard !isMessagesControllerBeingDismissed else { return }
+
+        guard let keyboardStartFrameInScreenCoords = notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? CGRect else { return }
+        guard !keyboardStartFrameInScreenCoords.isEmpty || UIDevice.current.userInterfaceIdiom != .pad else {
+            // WORKAROUND for what seems to be a bug in iPad's keyboard handling in iOS 11: we receive an extra spurious frame change
+            // notification when undocking the keyboard, with a zero starting frame and an incorrect end frame. The workaround is to
+            // ignore this notification.
+            return
+        }
+        
+        // Note that the check above does not exclude all notifications from an undocked keyboard, only the weird ones.
+        //
+        // We've tried following Apple's recommended approach of tracking UIKeyboardWillShow / UIKeyboardDidHide and ignoring frame
+        // change notifications while the keyboard is hidden or undocked (undocked keyboard is considered hidden by those events).
+        // Unfortunately, we do care about the difference between hidden and undocked, because we have an input bar which is at the
+        // bottom when the keyboard is hidden, and is tied to the keyboard when it's undocked.
+        //
+        // If we follow what Apple recommends and ignore notifications while the keyboard is hidden/undocked, we get an extra inset
+        // at the bottom when the undocked keyboard is visible (the inset that tries to compensate for the missing input bar).
+        // (Alternatives like setting newBottomInset to 0 or to the height of the input bar don't work either.)
+        //
+        // We could make it work by adding extra checks for the state of the keyboard and compensating accordingly, but it seems easier
+        // to simply check whether the current keyboard frame, whatever it is (even when undocked), covers the bottom of the collection
+        // view.
+        
+        guard let keyboardEndFrameInScreenCoords = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
+        let keyboardEndFrame = view.convert(keyboardEndFrameInScreenCoords, from: view.window)
+        
+        let newBottomInset = requiredScrollViewBottomInset(forKeyboardFrame: keyboardEndFrame)
+        let differenceOfBottomInset = newBottomInset - messageCollectionViewBottomInset
+        
+        if maintainPositionOnKeyboardFrameChanged && differenceOfBottomInset != 0 {
+            let contentOffset = CGPoint(x: messagesCollectionView.contentOffset.x, y: messagesCollectionView.contentOffset.y + differenceOfBottomInset)
+            messagesCollectionView.setContentOffset(contentOffset, animated: false)
+        }
+        
+        messageCollectionViewBottomInset = newBottomInset
+    }
+
+    // MARK: - Inset Computation
+
+    @objc
+    internal func adjustScrollViewTopInset() {
+        if #available(iOS 11.0, *) {
+            // No need to add to the top contentInset
+        } else {
+            let navigationBarInset = navigationController?.navigationBar.frame.height ?? 0
+            let statusBarInset: CGFloat = UIApplication.shared.isStatusBarHidden ? 0 : 20
+            let topInset = navigationBarInset + statusBarInset
+            messagesCollectionView.contentInset.top = topInset
+            messagesCollectionView.scrollIndicatorInsets.top = topInset
+        }
+    }
+
+    private func requiredScrollViewBottomInset(forKeyboardFrame keyboardFrame: CGRect) -> CGFloat {
+        // we only need to adjust for the part of the keyboard that covers (i.e. intersects) our collection view;
+        // see https://developer.apple.com/videos/play/wwdc2017/242/ for more details
+        let intersection = messagesCollectionView.frame.intersection(keyboardFrame)
+        
+        if intersection.isNull || (messagesCollectionView.frame.maxY - intersection.maxY) > 0.001 {
+            // The keyboard is hidden, is a hardware one, or is undocked and does not cover the bottom of the collection view.
+            // Note: intersection.maxY may be less than messagesCollectionView.frame.maxY when dealing with undocked keyboards.
+            return max(0, additionalBottomInset - automaticallyAddedBottomInset)
+        } else {
+            return max(0, intersection.height + additionalBottomInset - automaticallyAddedBottomInset)
+        }
+    }
+
+    internal func requiredInitialScrollViewBottomInset() -> CGFloat {
+        guard let inputAccessoryView = inputAccessoryView else { return 0 }
+        return max(0, inputAccessoryView.frame.height + additionalBottomInset - automaticallyAddedBottomInset)
+    }
+
+    /// iOS 11's UIScrollView can automatically add safe area insets to its contentInset,
+    /// which needs to be accounted for when setting the contentInset based on screen coordinates.
+    ///
+    /// - Returns: The distance automatically added to contentInset.bottom, if any.
+    private var automaticallyAddedBottomInset: CGFloat {
+        if #available(iOS 11.0, *) {
+            return messagesCollectionView.adjustedContentInset.bottom - messagesCollectionView.contentInset.bottom
+        } else {
+            return 0
+        }
+    }
+
+}

+ 104 - 0
deltachat-ios/MessageKit/Controllers/MessagesViewController+Menu.swift

@@ -0,0 +1,104 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+internal extension MessagesViewController {
+
+    // MARK: - Register / Unregister Observers
+
+    internal func addMenuControllerObservers() {
+        NotificationCenter.default.addObserver(self, selector: #selector(MessagesViewController.menuControllerWillShow(_:)), name: UIMenuController.willShowMenuNotification, object: nil)
+    }
+
+    internal func removeMenuControllerObservers() {
+        NotificationCenter.default.removeObserver(self, name: UIMenuController.willShowMenuNotification, object: nil)
+    }
+
+    // MARK: - Notification Handlers
+
+    /// Show menuController and set target rect to selected bubble
+    @objc
+    private func menuControllerWillShow(_ notification: Notification) {
+
+        guard let currentMenuController = notification.object as? UIMenuController,
+            let selectedIndexPath = selectedIndexPathForMenu else { return }
+
+        NotificationCenter.default.removeObserver(self, name: UIMenuController.willShowMenuNotification, object: nil)
+        defer {
+            NotificationCenter.default.addObserver(self,
+                                                   selector: #selector(MessagesViewController.menuControllerWillShow(_:)),
+                                                   name: UIMenuController.willShowMenuNotification, object: nil)
+            selectedIndexPathForMenu = nil
+        }
+
+        currentMenuController.setMenuVisible(false, animated: false)
+
+        guard let selectedCell = messagesCollectionView.cellForItem(at: selectedIndexPath) as? MessageContentCell else { return }
+        let selectedCellMessageBubbleFrame = selectedCell.convert(selectedCell.messageContainerView.frame, to: view)
+
+        var messageInputBarFrame: CGRect = .zero
+        if let messageInputBarSuperview = messageInputBar.superview {
+            messageInputBarFrame = view.convert(messageInputBar.frame, from: messageInputBarSuperview)
+        }
+
+        var topNavigationBarFrame: CGRect = navigationBarFrame
+        if navigationBarFrame != .zero, let navigationBarSuperview = navigationController?.navigationBar.superview {
+            topNavigationBarFrame = view.convert(navigationController!.navigationBar.frame, from: navigationBarSuperview)
+        }
+
+        let menuHeight = currentMenuController.menuFrame.height
+
+        let selectedCellMessageBubblePlusMenuFrame =
+            CGRect(selectedCellMessageBubbleFrame.origin.x,
+                   selectedCellMessageBubbleFrame.origin.y - menuHeight,
+                   selectedCellMessageBubbleFrame.size.width,
+                   selectedCellMessageBubbleFrame.size.height + 2 * menuHeight)
+
+        var targetRect: CGRect = selectedCellMessageBubbleFrame
+        currentMenuController.arrowDirection = .default
+
+        /// Message bubble intersects with navigationBar and keyboard
+        if selectedCellMessageBubblePlusMenuFrame.intersects(topNavigationBarFrame) && selectedCellMessageBubblePlusMenuFrame.intersects(messageInputBarFrame) {
+            let centerY = (selectedCellMessageBubblePlusMenuFrame.intersection(messageInputBarFrame).minY + selectedCellMessageBubblePlusMenuFrame.intersection(topNavigationBarFrame).maxY) / 2
+            targetRect = CGRect(selectedCellMessageBubblePlusMenuFrame.midX, centerY, 1, 1)
+        } /// Message bubble only intersects with navigationBar
+        else if selectedCellMessageBubblePlusMenuFrame.intersects(topNavigationBarFrame) {
+            currentMenuController.arrowDirection = .up
+        }
+
+        currentMenuController.setTargetRect(targetRect, in: view)
+        currentMenuController.setMenuVisible(true, animated: true)
+    }
+
+    // MARK: - Helpers
+
+    private var navigationBarFrame: CGRect {
+        guard let navigationController = navigationController, !navigationController.navigationBar.isHidden else {
+            return .zero
+        }
+        return navigationController.navigationBar.frame
+    }
+}

+ 419 - 0
deltachat-ios/MessageKit/Controllers/MessagesViewController.swift

@@ -0,0 +1,419 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+import InputBarAccessoryView
+
+/// A subclass of `UIViewController` with a `MessagesCollectionView` object
+/// that is used to display conversation interfaces.
+open class MessagesViewController: UIViewController,
+UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
+
+    /// The `MessagesCollectionView` managed by the messages view controller object.
+    open var messagesCollectionView = MessagesCollectionView()
+
+    /// The `InputBarAccessoryView` used as the `inputAccessoryView` in the view controller.
+    open var messageInputBar = InputBarAccessoryView()
+
+    /// A Boolean value that determines whether the `MessagesCollectionView` scrolls to the
+    /// bottom whenever the `InputTextView` begins editing.
+    ///
+    /// The default value of this property is `false`.
+    open var scrollsToBottomOnKeyboardBeginsEditing: Bool = false
+    
+    /// A Boolean value that determines whether the `MessagesCollectionView`
+    /// maintains it's current position when the height of the `MessageInputBar` changes.
+    ///
+    /// The default value of this property is `false`.
+    open var maintainPositionOnKeyboardFrameChanged: Bool = false
+
+    open override var canBecomeFirstResponder: Bool {
+        return true
+    }
+
+    open override var inputAccessoryView: UIView? {
+        return messageInputBar
+    }
+
+    open override var shouldAutorotate: Bool {
+        return false
+    }
+
+    /// A CGFloat value that adds to (or, if negative, subtracts from) the automatically
+    /// computed value of `messagesCollectionView.contentInset.bottom`. Meant to be used
+    /// as a measure of last resort when the built-in algorithm does not produce the right
+    /// value for your app. Please let us know when you end up having to use this property.
+    open var additionalBottomInset: CGFloat = 0 {
+        didSet {
+            let delta = additionalBottomInset - oldValue
+            messageCollectionViewBottomInset += delta
+        }
+    }
+
+    public var isTypingIndicatorHidden: Bool {
+        return messagesCollectionView.isTypingIndicatorHidden
+    }
+
+    public var selectedIndexPathForMenu: IndexPath?
+
+    private var isFirstLayout: Bool = true
+    
+    internal var isMessagesControllerBeingDismissed: Bool = false
+
+    internal var messageCollectionViewBottomInset: CGFloat = 0 {
+        didSet {
+            messagesCollectionView.contentInset.bottom = messageCollectionViewBottomInset
+            messagesCollectionView.scrollIndicatorInsets.bottom = messageCollectionViewBottomInset
+        }
+    }
+
+    // MARK: - View Life Cycle
+
+    open override func viewDidLoad() {
+        super.viewDidLoad()
+        setupDefaults()
+        setupSubviews()
+        setupConstraints()
+        setupDelegates()
+        addMenuControllerObservers()
+        addObservers()
+    }
+    
+    open override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        isMessagesControllerBeingDismissed = false
+    }
+    
+    open override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        isMessagesControllerBeingDismissed = true
+    }
+    
+    open override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+        isMessagesControllerBeingDismissed = false
+    }
+    
+    open override func viewDidLayoutSubviews() {
+        // Hack to prevent animation of the contentInset after viewDidAppear
+        if isFirstLayout {
+            defer { isFirstLayout = false }
+            addKeyboardObservers()
+            messageCollectionViewBottomInset = requiredInitialScrollViewBottomInset()
+        }
+        adjustScrollViewTopInset()
+    }
+
+    open override func viewSafeAreaInsetsDidChange() {
+        if #available(iOS 11.0, *) {
+            super.viewSafeAreaInsetsDidChange()
+        }
+        messageCollectionViewBottomInset = requiredInitialScrollViewBottomInset()
+    }
+
+    // MARK: - Initializers
+
+    deinit {
+        removeKeyboardObservers()
+        removeMenuControllerObservers()
+        removeObservers()
+        clearMemoryCache()
+    }
+
+    // MARK: - Methods [Private]
+
+    private func setupDefaults() {
+        extendedLayoutIncludesOpaqueBars = true
+        automaticallyAdjustsScrollViewInsets = false
+        view.backgroundColor = .white
+        messagesCollectionView.keyboardDismissMode = .interactive
+        messagesCollectionView.alwaysBounceVertical = true
+    }
+
+    private func setupDelegates() {
+        messagesCollectionView.delegate = self
+        messagesCollectionView.dataSource = self
+    }
+
+    private func setupSubviews() {
+        view.addSubview(messagesCollectionView)
+    }
+
+    private func setupConstraints() {
+        messagesCollectionView.translatesAutoresizingMaskIntoConstraints = false
+        
+        let top = messagesCollectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: topLayoutGuide.length)
+        let bottom = messagesCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
+        if #available(iOS 11.0, *) {
+            let leading = messagesCollectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor)
+            let trailing = messagesCollectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
+            NSLayoutConstraint.activate([top, bottom, trailing, leading])
+        } else {
+            let leading = messagesCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
+            let trailing = messagesCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
+            NSLayoutConstraint.activate([top, bottom, trailing, leading])
+        }
+    }
+
+    // MARK: - Typing Indicator API
+
+    /// Sets the typing indicator sate by inserting/deleting the `TypingBubbleCell`
+    ///
+    /// - Parameters:
+    ///   - isHidden: A Boolean value that is to be the new state of the typing indicator
+    ///   - animated: A Boolean value determining if the insertion is to be animated
+    ///   - updates: A block of code that will be executed during `performBatchUpdates`
+    ///              when `animated` is `TRUE` or before the `completion` block executes
+    ///              when `animated` is `FALSE`
+    ///   - completion: A completion block to execute after the insertion/deletion
+    open func setTypingIndicatorViewHidden(_ isHidden: Bool, animated: Bool, whilePerforming updates: (() -> Void)? = nil, completion: ((Bool) -> Void)? = nil) {
+
+        guard isTypingIndicatorHidden != isHidden else {
+            completion?(false)
+            return
+        }
+
+        let section = messagesCollectionView.numberOfSections
+        messagesCollectionView.setTypingIndicatorViewHidden(isHidden)
+
+        if animated {
+            messagesCollectionView.performBatchUpdates({ [weak self] in
+                self?.performUpdatesForTypingIndicatorVisability(at: section)
+                updates?()
+                }, completion: completion)
+        } else {
+            performUpdatesForTypingIndicatorVisability(at: section)
+            updates?()
+            completion?(true)
+        }
+    }
+
+    /// Performs a delete or insert on the `MessagesCollectionView` on the provided section
+    ///
+    /// - Parameter section: The index to modify
+    private func performUpdatesForTypingIndicatorVisability(at section: Int) {
+        if isTypingIndicatorHidden {
+            messagesCollectionView.deleteSections([section - 1])
+        } else {
+            messagesCollectionView.insertSections([section])
+        }
+    }
+
+    /// A method that by default checks if the section is the last in the
+    /// `messagesCollectionView` and that `isTypingIndicatorViewHidden`
+    /// is FALSE
+    ///
+    /// - Parameter section
+    /// - Returns: A Boolean indicating if the TypingIndicator should be presented at the given section
+    public func isSectionReservedForTypingIndicator(_ section: Int) -> Bool {
+        return !messagesCollectionView.isTypingIndicatorHidden && section == self.numberOfSections(in: messagesCollectionView) - 1
+    }
+
+    // MARK: - UICollectionViewDataSource
+
+    open func numberOfSections(in collectionView: UICollectionView) -> Int {
+        guard let collectionView = collectionView as? MessagesCollectionView else {
+            fatalError(MessageKitError.notMessagesCollectionView)
+        }
+        let sections = collectionView.messagesDataSource?.numberOfSections(in: collectionView) ?? 0
+        return collectionView.isTypingIndicatorHidden ? sections : sections + 1
+    }
+
+    open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        guard let collectionView = collectionView as? MessagesCollectionView else {
+            fatalError(MessageKitError.notMessagesCollectionView)
+        }
+        if isSectionReservedForTypingIndicator(section) {
+            return 1
+        }
+        return collectionView.messagesDataSource?.numberOfItems(inSection: section, in: collectionView) ?? 0
+    }
+
+    /// Notes:
+    /// - If you override this method, remember to call MessagesDataSource's customCell(for:at:in:)
+    /// for MessageKind.custom messages, if necessary.
+    ///
+    /// - If you are using the typing indicator you will need to ensure that the section is not
+    /// reserved for it with `isSectionReservedForTypingIndicator` defined in
+    /// `MessagesCollectionViewFlowLayout`
+    open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+
+        guard let messagesCollectionView = collectionView as? MessagesCollectionView else {
+            fatalError(MessageKitError.notMessagesCollectionView)
+        }
+
+        guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
+            fatalError(MessageKitError.nilMessagesDataSource)
+        }
+
+        if isSectionReservedForTypingIndicator(indexPath.section) {
+            return messagesDataSource.typingIndicator(at: indexPath, in: messagesCollectionView)
+        }
+
+        let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
+
+        switch message.kind {
+        case .text, .attributedText, .emoji:
+            let cell = messagesCollectionView.dequeueReusableCell(TextMessageCell.self, for: indexPath)
+            cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+            return cell
+        case .photo, .video:
+            let cell = messagesCollectionView.dequeueReusableCell(MediaMessageCell.self, for: indexPath)
+            cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+            return cell
+        case .location:
+            let cell = messagesCollectionView.dequeueReusableCell(LocationMessageCell.self, for: indexPath)
+            cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+            return cell
+        case .audio:
+            let cell = messagesCollectionView.dequeueReusableCell(AudioMessageCell.self, for: indexPath)
+            cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+            return cell
+        case .contact:
+            let cell = messagesCollectionView.dequeueReusableCell(ContactMessageCell.self, for: indexPath)
+            cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+            return cell
+        case .custom:
+            return messagesDataSource.customCell(for: message, at: indexPath, in: messagesCollectionView)
+        }
+    }
+
+    open func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
+
+        guard let messagesCollectionView = collectionView as? MessagesCollectionView else {
+            fatalError(MessageKitError.notMessagesCollectionView)
+        }
+
+        guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else {
+            fatalError(MessageKitError.nilMessagesDisplayDelegate)
+        }
+
+        switch kind {
+        case UICollectionView.elementKindSectionHeader:
+            return displayDelegate.messageHeaderView(for: indexPath, in: messagesCollectionView)
+        case UICollectionView.elementKindSectionFooter:
+            return displayDelegate.messageFooterView(for: indexPath, in: messagesCollectionView)
+        default:
+            fatalError(MessageKitError.unrecognizedSectionKind)
+        }
+    }
+
+    // MARK: - UICollectionViewDelegateFlowLayout
+
+    open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
+        guard let messagesFlowLayout = collectionViewLayout as? MessagesCollectionViewFlowLayout else { return .zero }
+        return messagesFlowLayout.sizeForItem(at: indexPath)
+    }
+
+    open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
+
+        guard let messagesCollectionView = collectionView as? MessagesCollectionView else {
+            fatalError(MessageKitError.notMessagesCollectionView)
+        }
+        guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else {
+            fatalError(MessageKitError.nilMessagesLayoutDelegate)
+        }
+        if isSectionReservedForTypingIndicator(section) {
+            return .zero
+        }
+        return layoutDelegate.headerViewSize(for: section, in: messagesCollectionView)
+    }
+
+    open func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
+        guard let cell = cell as? TypingIndicatorCell else { return }
+        cell.typingBubble.startAnimating()
+    }
+
+    open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
+        guard let messagesCollectionView = collectionView as? MessagesCollectionView else {
+            fatalError(MessageKitError.notMessagesCollectionView)
+        }
+        guard let layoutDelegate = messagesCollectionView.messagesLayoutDelegate else {
+            fatalError(MessageKitError.nilMessagesLayoutDelegate)
+        }
+        if isSectionReservedForTypingIndicator(section) {
+            return .zero
+        }
+        return layoutDelegate.footerViewSize(for: section, in: messagesCollectionView)
+    }
+
+    open func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool {
+        guard let messagesDataSource = messagesCollectionView.messagesDataSource else { return false }
+
+        if isSectionReservedForTypingIndicator(indexPath.section) {
+            return false
+        }
+
+        let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
+
+        switch message.kind {
+        case .text, .attributedText, .emoji, .photo:
+            selectedIndexPathForMenu = indexPath
+            return true
+        default:
+            return false
+        }
+    }
+
+    open func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
+        if isSectionReservedForTypingIndicator(indexPath.section) {
+            return false
+        }
+        return (action == NSSelectorFromString("copy:"))
+    }
+
+    open func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
+        guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
+            fatalError(MessageKitError.nilMessagesDataSource)
+        }
+        let pasteBoard = UIPasteboard.general
+        let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
+
+        switch message.kind {
+        case .text(let text), .emoji(let text):
+            pasteBoard.string = text
+        case .attributedText(let attributedText):
+            pasteBoard.string = attributedText.string
+        case .photo(let mediaItem):
+            pasteBoard.image = mediaItem.image ?? mediaItem.placeholderImage
+        default:
+            break
+        }
+    }
+
+    // MARK: - Helpers
+    
+    private func addObservers() {
+        NotificationCenter.default.addObserver(
+            self, selector: #selector(clearMemoryCache), name: UIApplication.didReceiveMemoryWarningNotification, object: nil)
+    }
+    
+    private func removeObservers() {
+        NotificationCenter.default.removeObserver(self, name: UIApplication.didReceiveMemoryWarningNotification, object: nil)
+    }
+    
+    @objc private func clearMemoryCache() {
+        MessageStyle.bubbleImageCache.removeAllObjects()
+    }
+}

+ 44 - 0
deltachat-ios/MessageKit/Layout/AudioMessageSizeCalculator.swift

@@ -0,0 +1,44 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+open class AudioMessageSizeCalculator: MessageSizeCalculator {
+
+    open override func messageContainerSize(for message: MessageType) -> CGSize {
+        switch message.kind {
+        case .audio(let item):
+            let maxWidth = messageContainerMaxWidth(for: message)
+            if maxWidth < item.size.width {
+                // Maintain the ratio if width is too great
+                let height = maxWidth * item.size.height / item.size.width
+                return CGSize(width: maxWidth, height: height)
+            }
+            return item.size
+        default:
+            fatalError("messageContainerSize received unhandled MessageDataType: \(message.kind)")
+        }
+    }
+}

+ 50 - 0
deltachat-ios/MessageKit/Layout/CellSizeCalculator.swift

@@ -0,0 +1,50 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+/// An object is responsible for
+/// sizing and configuring cells for given `IndexPath`s.
+open class CellSizeCalculator {
+
+    /// The layout object for which the cell size calculator is used.
+    public weak var layout: UICollectionViewFlowLayout?
+
+    /// Used to configure the layout attributes for a given cell.
+    ///
+    /// - Parameters:
+    /// - attributes: The attributes of the cell.
+    /// The default does nothing
+    open func configure(attributes: UICollectionViewLayoutAttributes) {}
+
+    /// Used to size an item at a given `IndexPath`.
+    ///
+    /// - Parameters:
+    /// - indexPath: The `IndexPath` of the item to be displayed.
+    /// The default return .zero
+    open func sizeForItem(at indexPath: IndexPath) -> CGSize { return .zero }
+    
+    public init() {}
+
+}

+ 74 - 0
deltachat-ios/MessageKit/Layout/ContactMessageSizeCalculator.swift

@@ -0,0 +1,74 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2018 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+open class ContactMessageSizeCalculator: MessageSizeCalculator {
+    
+    public var incomingMessageNameLabelInsets = UIEdgeInsets(top: 7, left: 46, bottom: 7, right: 30)
+    public var outgoingMessageNameLabelInsets = UIEdgeInsets(top: 7, left: 41, bottom: 7, right: 35)
+    public var contactLabelFont = UIFont.preferredFont(forTextStyle: .body)
+    
+    internal func contactLabelInsets(for message: MessageType) -> UIEdgeInsets {
+        let dataSource = messagesLayout.messagesDataSource
+        let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
+        return isFromCurrentSender ? outgoingMessageNameLabelInsets : incomingMessageNameLabelInsets
+    }
+    
+    open override func messageContainerMaxWidth(for message: MessageType) -> CGFloat {
+        let maxWidth = super.messageContainerMaxWidth(for: message)
+        let textInsets = contactLabelInsets(for: message)
+        return maxWidth - textInsets.horizontal
+    }
+    
+    open override func messageContainerSize(for message: MessageType) -> CGSize {
+        let maxWidth = messageContainerMaxWidth(for: message)
+        
+        var messageContainerSize: CGSize
+        let attributedText: NSAttributedString
+        
+        switch message.kind {
+        case .contact(let item):
+            attributedText = NSAttributedString(string: item.displayName, attributes: [.font: contactLabelFont])
+        default:
+            fatalError("messageContainerSize received unhandled MessageDataType: \(message.kind)")
+        }
+        
+        messageContainerSize = labelSize(for: attributedText, considering: maxWidth)
+        
+        let messageInsets = contactLabelInsets(for: message)
+        messageContainerSize.width += messageInsets.horizontal
+        messageContainerSize.height += messageInsets.vertical
+        
+        return messageContainerSize
+    }
+    
+    open override func configure(attributes: UICollectionViewLayoutAttributes) {
+        super.configure(attributes: attributes)
+        guard let attributes = attributes as? MessagesCollectionViewLayoutAttributes else { return }
+        attributes.messageLabelFont = contactLabelFont
+    }
+
+}

+ 44 - 0
deltachat-ios/MessageKit/Layout/LocationMessageSizeCalculator.swift

@@ -0,0 +1,44 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+open class LocationMessageSizeCalculator: MessageSizeCalculator {
+
+    open override func messageContainerSize(for message: MessageType) -> CGSize {
+        switch message.kind {
+        case .location(let item):
+            let maxWidth = messageContainerMaxWidth(for: message)
+            if maxWidth < item.size.width {
+                // Maintain the ratio if width is too great
+                let height = maxWidth * item.size.height / item.size.width
+                return CGSize(width: maxWidth, height: height)
+            }
+            return item.size
+        default:
+            fatalError("messageContainerSize received unhandled MessageDataType: \(message.kind)")
+        }
+    }
+}

+ 49 - 0
deltachat-ios/MessageKit/Layout/MediaMessageSizeCalculator.swift

@@ -0,0 +1,49 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+open class MediaMessageSizeCalculator: MessageSizeCalculator {
+
+    open override func messageContainerSize(for message: MessageType) -> CGSize {
+        let maxWidth = messageContainerMaxWidth(for: message)
+        let sizeForMediaItem = { (maxWidth: CGFloat, item: MediaItem) -> CGSize in
+            if maxWidth < item.size.width {
+                // Maintain the ratio if width is too great
+                let height = maxWidth * item.size.height / item.size.width
+                return CGSize(width: maxWidth, height: height)
+            }
+            return item.size
+        }
+        switch message.kind {
+        case .photo(let item):
+            return sizeForMediaItem(maxWidth, item)
+        case .video(let item):
+            return sizeForMediaItem(maxWidth, item)
+        default:
+            fatalError("messageContainerSize received unhandled MessageDataType: \(message.kind)")
+        }
+    }
+}

+ 292 - 0
deltachat-ios/MessageKit/Layout/MessageSizeCalculator.swift

@@ -0,0 +1,292 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+open class MessageSizeCalculator: CellSizeCalculator {
+
+    public init(layout: MessagesCollectionViewFlowLayout? = nil) {
+        super.init()
+        
+        self.layout = layout
+    }
+
+    public var incomingAvatarSize = CGSize(width: 30, height: 30)
+    public var outgoingAvatarSize = CGSize(width: 30, height: 30)
+
+    public var incomingAvatarPosition = AvatarPosition(vertical: .cellBottom)
+    public var outgoingAvatarPosition = AvatarPosition(vertical: .cellBottom)
+
+    public var avatarLeadingTrailingPadding: CGFloat = 0
+
+    public var incomingMessagePadding = UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 30)
+    public var outgoingMessagePadding = UIEdgeInsets(top: 0, left: 30, bottom: 0, right: 4)
+
+    public var incomingCellTopLabelAlignment = LabelAlignment(textAlignment: .center, textInsets: .zero)
+    public var outgoingCellTopLabelAlignment = LabelAlignment(textAlignment: .center, textInsets: .zero)
+    
+    public var incomingCellBottomLabelAlignment = LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(left: 42))
+    public var outgoingCellBottomLabelAlignment = LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(right: 42))
+
+    public var incomingMessageTopLabelAlignment = LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(left: 42))
+    public var outgoingMessageTopLabelAlignment = LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(right: 42))
+
+    public var incomingMessageBottomLabelAlignment = LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(left: 42))
+    public var outgoingMessageBottomLabelAlignment = LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(right: 42))
+
+    public var incomingAccessoryViewSize = CGSize.zero
+    public var outgoingAccessoryViewSize = CGSize.zero
+
+    public var incomingAccessoryViewPadding = HorizontalEdgeInsets.zero
+    public var outgoingAccessoryViewPadding = HorizontalEdgeInsets.zero
+    
+    public var incomingAccessoryViewPosition: AccessoryPosition = .messageCenter
+    public var outgoingAccessoryViewPosition: AccessoryPosition = .messageCenter
+
+    open override func configure(attributes: UICollectionViewLayoutAttributes) {
+        guard let attributes = attributes as? MessagesCollectionViewLayoutAttributes else { return }
+
+        let dataSource = messagesLayout.messagesDataSource
+        let indexPath = attributes.indexPath
+        let message = dataSource.messageForItem(at: indexPath, in: messagesLayout.messagesCollectionView)
+
+        attributes.avatarSize = avatarSize(for: message)
+        attributes.avatarPosition = avatarPosition(for: message)
+        attributes.avatarLeadingTrailingPadding = avatarLeadingTrailingPadding
+
+        attributes.messageContainerPadding = messageContainerPadding(for: message)
+        attributes.messageContainerSize = messageContainerSize(for: message)
+        attributes.cellTopLabelSize = cellTopLabelSize(for: message, at: indexPath)
+        attributes.cellBottomLabelSize = cellBottomLabelSize(for: message, at: indexPath)
+        attributes.cellBottomLabelAlignment = cellBottomLabelAlignment(for: message)
+        attributes.messageTopLabelSize = messageTopLabelSize(for: message, at: indexPath)
+        attributes.messageTopLabelAlignment = messageTopLabelAlignment(for: message)
+
+        attributes.messageBottomLabelAlignment = messageBottomLabelAlignment(for: message)
+        attributes.messageBottomLabelSize = messageBottomLabelSize(for: message, at: indexPath)
+
+        attributes.accessoryViewSize = accessoryViewSize(for: message)
+        attributes.accessoryViewPadding = accessoryViewPadding(for: message)
+        attributes.accessoryViewPosition = accessoryViewPosition(for: message)
+    }
+
+    open override func sizeForItem(at indexPath: IndexPath) -> CGSize {
+        let dataSource = messagesLayout.messagesDataSource
+        let message = dataSource.messageForItem(at: indexPath, in: messagesLayout.messagesCollectionView)
+        let itemHeight = cellContentHeight(for: message, at: indexPath)
+        return CGSize(width: messagesLayout.itemWidth, height: itemHeight)
+    }
+
+    open func cellContentHeight(for message: MessageType, at indexPath: IndexPath) -> CGFloat {
+
+        let messageContainerHeight = messageContainerSize(for: message).height
+        let cellBottomLabelHeight = cellBottomLabelSize(for: message, at: indexPath).height
+        let messageBottomLabelHeight = messageBottomLabelSize(for: message, at: indexPath).height
+        let cellTopLabelHeight = cellTopLabelSize(for: message, at: indexPath).height
+        let messageTopLabelHeight = messageTopLabelSize(for: message, at: indexPath).height
+        let messageVerticalPadding = messageContainerPadding(for: message).vertical
+        let avatarHeight = avatarSize(for: message).height
+        let avatarVerticalPosition = avatarPosition(for: message).vertical
+        let accessoryViewHeight = accessoryViewSize(for: message).height
+
+        switch avatarVerticalPosition {
+        case .messageCenter:
+            let totalLabelHeight: CGFloat = cellTopLabelHeight + messageTopLabelHeight
+                + messageContainerHeight + messageVerticalPadding + messageBottomLabelHeight + cellBottomLabelHeight
+            let cellHeight = max(avatarHeight, totalLabelHeight)
+            return max(cellHeight, accessoryViewHeight)
+        case .messageBottom:
+            var cellHeight: CGFloat = 0
+            cellHeight += messageBottomLabelHeight
+            cellHeight += cellBottomLabelHeight
+            let labelsHeight = messageContainerHeight + messageVerticalPadding + cellTopLabelHeight + messageTopLabelHeight
+            cellHeight += max(labelsHeight, avatarHeight)
+            return max(cellHeight, accessoryViewHeight)
+        case .messageTop:
+            var cellHeight: CGFloat = 0
+            cellHeight += cellTopLabelHeight
+            cellHeight += messageTopLabelHeight
+            let labelsHeight = messageContainerHeight + messageVerticalPadding + messageBottomLabelHeight + cellBottomLabelHeight
+            cellHeight += max(labelsHeight, avatarHeight)
+            return max(cellHeight, accessoryViewHeight)
+        case .messageLabelTop:
+            var cellHeight: CGFloat = 0
+            cellHeight += cellTopLabelHeight
+            let messageLabelsHeight = messageContainerHeight + messageBottomLabelHeight + messageVerticalPadding + messageTopLabelHeight + cellBottomLabelHeight
+            cellHeight += max(messageLabelsHeight, avatarHeight)
+            return max(cellHeight, accessoryViewHeight)
+        case .cellTop, .cellBottom:
+            let totalLabelHeight: CGFloat = cellTopLabelHeight + messageTopLabelHeight
+                + messageContainerHeight + messageVerticalPadding + messageBottomLabelHeight + cellBottomLabelHeight
+            let cellHeight = max(avatarHeight, totalLabelHeight)
+            return max(cellHeight, accessoryViewHeight)
+        }
+    }
+
+    // MARK: - Avatar
+
+    open func avatarPosition(for message: MessageType) -> AvatarPosition {
+        let dataSource = messagesLayout.messagesDataSource
+        let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
+        var position = isFromCurrentSender ? outgoingAvatarPosition : incomingAvatarPosition
+
+        switch position.horizontal {
+        case .cellTrailing, .cellLeading:
+            break
+        case .natural:
+            position.horizontal = isFromCurrentSender ? .cellTrailing : .cellLeading
+        }
+        return position
+    }
+
+    open func avatarSize(for message: MessageType) -> CGSize {
+        let dataSource = messagesLayout.messagesDataSource
+        let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
+        return isFromCurrentSender ? outgoingAvatarSize : incomingAvatarSize
+    }
+
+    // MARK: - Top cell Label
+
+    open func cellTopLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
+        let layoutDelegate = messagesLayout.messagesLayoutDelegate
+        let collectionView = messagesLayout.messagesCollectionView
+        let height = layoutDelegate.cellTopLabelHeight(for: message, at: indexPath, in: collectionView)
+        return CGSize(width: messagesLayout.itemWidth, height: height)
+    }
+
+    open func cellTopLabelAlignment(for message: MessageType) -> LabelAlignment {
+        let dataSource = messagesLayout.messagesDataSource
+        let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
+        return isFromCurrentSender ? outgoingCellTopLabelAlignment : incomingCellTopLabelAlignment
+    }
+    
+    // MARK: - Top message Label
+    
+    open func messageTopLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
+        let layoutDelegate = messagesLayout.messagesLayoutDelegate
+        let collectionView = messagesLayout.messagesCollectionView
+        let height = layoutDelegate.messageTopLabelHeight(for: message, at: indexPath, in: collectionView)
+        return CGSize(width: messagesLayout.itemWidth, height: height)
+    }
+    
+    open func messageTopLabelAlignment(for message: MessageType) -> LabelAlignment {
+        let dataSource = messagesLayout.messagesDataSource
+        let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
+        return isFromCurrentSender ? outgoingMessageTopLabelAlignment : incomingMessageTopLabelAlignment
+    }
+    
+    // MARK: - Bottom cell Label
+    
+    open func cellBottomLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
+        let layoutDelegate = messagesLayout.messagesLayoutDelegate
+        let collectionView = messagesLayout.messagesCollectionView
+        let height = layoutDelegate.cellBottomLabelHeight(for: message, at: indexPath, in: collectionView)
+        return CGSize(width: messagesLayout.itemWidth, height: height)
+    }
+    
+    open func cellBottomLabelAlignment(for message: MessageType) -> LabelAlignment {
+        let dataSource = messagesLayout.messagesDataSource
+        let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
+        return isFromCurrentSender ? outgoingCellBottomLabelAlignment : incomingCellBottomLabelAlignment
+    }
+
+    // MARK: - Bottom Message Label
+
+    open func messageBottomLabelSize(for message: MessageType, at indexPath: IndexPath) -> CGSize {
+        let layoutDelegate = messagesLayout.messagesLayoutDelegate
+        let collectionView = messagesLayout.messagesCollectionView
+        let height = layoutDelegate.messageBottomLabelHeight(for: message, at: indexPath, in: collectionView)
+        return CGSize(width: messagesLayout.itemWidth, height: height)
+    }
+
+    open func messageBottomLabelAlignment(for message: MessageType) -> LabelAlignment {
+        let dataSource = messagesLayout.messagesDataSource
+        let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
+        return isFromCurrentSender ? outgoingMessageBottomLabelAlignment : incomingMessageBottomLabelAlignment
+    }
+
+    // MARK: - Accessory View
+
+    public func accessoryViewSize(for message: MessageType) -> CGSize {
+        let dataSource = messagesLayout.messagesDataSource
+        let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
+        return isFromCurrentSender ? outgoingAccessoryViewSize : incomingAccessoryViewSize
+    }
+
+    public func accessoryViewPadding(for message: MessageType) -> HorizontalEdgeInsets {
+        let dataSource = messagesLayout.messagesDataSource
+        let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
+        return isFromCurrentSender ? outgoingAccessoryViewPadding : incomingAccessoryViewPadding
+    }
+    
+    public func accessoryViewPosition(for message: MessageType) -> AccessoryPosition {
+        let dataSource = messagesLayout.messagesDataSource
+        let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
+        return isFromCurrentSender ? outgoingAccessoryViewPosition : incomingAccessoryViewPosition
+    }
+
+    // MARK: - MessageContainer
+
+    open func messageContainerPadding(for message: MessageType) -> UIEdgeInsets {
+        let dataSource = messagesLayout.messagesDataSource
+        let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
+        return isFromCurrentSender ? outgoingMessagePadding : incomingMessagePadding
+    }
+
+    open func messageContainerSize(for message: MessageType) -> CGSize {
+        // Returns .zero by default
+        return .zero
+    }
+
+    open func messageContainerMaxWidth(for message: MessageType) -> CGFloat {
+        let avatarWidth = avatarSize(for: message).width
+        let messagePadding = messageContainerPadding(for: message)
+        let accessoryWidth = accessoryViewSize(for: message).width
+        let accessoryPadding = accessoryViewPadding(for: message)
+        return messagesLayout.itemWidth - avatarWidth - messagePadding.horizontal - accessoryWidth - accessoryPadding.horizontal - avatarLeadingTrailingPadding
+    }
+
+    // MARK: - Helpers
+
+    public var messagesLayout: MessagesCollectionViewFlowLayout {
+        guard let layout = layout as? MessagesCollectionViewFlowLayout else {
+            fatalError("Layout object is missing or is not a MessagesCollectionViewFlowLayout")
+        }
+        return layout
+    }
+
+    internal func labelSize(for attributedText: NSAttributedString, considering maxWidth: CGFloat) -> CGSize {
+        let constraintBox = CGSize(width: maxWidth, height: .greatestFiniteMagnitude)
+        let rect = attributedText.boundingRect(with: constraintBox, options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil).integral
+
+        return rect.size
+    }
+}
+
+fileprivate extension UIEdgeInsets {
+    init(top: CGFloat = 0, bottom: CGFloat = 0, left: CGFloat = 0, right: CGFloat = 0) {
+        self.init(top: top, left: left, bottom: bottom, right: right)
+    }
+}

+ 328 - 0
deltachat-ios/MessageKit/Layout/MessagesCollectionViewFlowLayout.swift

@@ -0,0 +1,328 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+import AVFoundation
+
+/// The layout object used by `MessagesCollectionView` to determine the size of all
+/// framework provided `MessageCollectionViewCell` subclasses.
+open class MessagesCollectionViewFlowLayout: UICollectionViewFlowLayout {
+
+    open override class var layoutAttributesClass: AnyClass {
+        return MessagesCollectionViewLayoutAttributes.self
+    }
+    
+    /// The `MessagesCollectionView` that owns this layout object.
+    public var messagesCollectionView: MessagesCollectionView {
+        guard let messagesCollectionView = collectionView as? MessagesCollectionView else {
+            fatalError(MessageKitError.layoutUsedOnForeignType)
+        }
+        return messagesCollectionView
+    }
+    
+    /// The `MessagesDataSource` for the layout's collection view.
+    public var messagesDataSource: MessagesDataSource {
+        guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
+            fatalError(MessageKitError.nilMessagesDataSource)
+        }
+        return messagesDataSource
+    }
+    
+    /// The `MessagesLayoutDelegate` for the layout's collection view.
+    public var messagesLayoutDelegate: MessagesLayoutDelegate {
+        guard let messagesLayoutDelegate = messagesCollectionView.messagesLayoutDelegate else {
+            fatalError(MessageKitError.nilMessagesLayoutDelegate)
+        }
+        return messagesLayoutDelegate
+    }
+
+    public var itemWidth: CGFloat {
+        guard let collectionView = collectionView else { return 0 }
+        return collectionView.frame.width - sectionInset.left - sectionInset.right
+    }
+
+    public private(set) var isTypingIndicatorViewHidden: Bool = true
+
+    // MARK: - Initializers
+
+    public override init() {
+        super.init()
+        setupView()
+        setupObserver()
+    }
+
+    required public init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+        setupView()
+        setupObserver()
+    }
+
+    deinit {
+        NotificationCenter.default.removeObserver(self)
+    }
+    
+    // MARK: - Methods
+    
+    private func setupView() {
+        sectionInset = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8)
+    }
+    
+    private func setupObserver() {
+        NotificationCenter.default.addObserver(self, selector: #selector(MessagesCollectionViewFlowLayout.handleOrientationChange(_:)), name: UIDevice.orientationDidChangeNotification, object: nil)
+    }
+
+    // MARK: - Typing Indicator API
+
+    /// Notifies the layout that the typing indicator will change state
+    ///
+    /// - Parameters:
+    ///   - isHidden: A Boolean value that is to be the new state of the typing indicator
+    internal func setTypingIndicatorViewHidden(_ isHidden: Bool) {
+        isTypingIndicatorViewHidden = isHidden
+    }
+
+    /// A method that by default checks if the section is the last in the
+    /// `messagesCollectionView` and that `isTypingIndicatorViewHidden`
+    /// is FALSE
+    ///
+    /// - Parameter section
+    /// - Returns: A Boolean indicating if the TypingIndicator should be presented at the given section
+    open func isSectionReservedForTypingIndicator(_ section: Int) -> Bool {
+        return !isTypingIndicatorViewHidden && section == messagesCollectionView.numberOfSections - 1
+    }
+
+    // MARK: - Attributes
+
+    open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
+        guard let attributesArray = super.layoutAttributesForElements(in: rect) as? [MessagesCollectionViewLayoutAttributes] else {
+            return nil
+        }
+        for attributes in attributesArray where attributes.representedElementCategory == .cell {
+            let cellSizeCalculator = cellSizeCalculatorForItem(at: attributes.indexPath)
+            cellSizeCalculator.configure(attributes: attributes)
+        }
+        return attributesArray
+    }
+
+    open override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
+        guard let attributes = super.layoutAttributesForItem(at: indexPath) as? MessagesCollectionViewLayoutAttributes else {
+            return nil
+        }
+        if attributes.representedElementCategory == .cell {
+            let cellSizeCalculator = cellSizeCalculatorForItem(at: attributes.indexPath)
+            cellSizeCalculator.configure(attributes: attributes)
+        }
+        return attributes
+    }
+
+    // MARK: - Layout Invalidation
+
+    open override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
+        return collectionView?.bounds.width != newBounds.width
+    }
+
+    open override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
+        let context = super.invalidationContext(forBoundsChange: newBounds)
+        guard let flowLayoutContext = context as? UICollectionViewFlowLayoutInvalidationContext else { return context }
+        flowLayoutContext.invalidateFlowLayoutDelegateMetrics = shouldInvalidateLayout(forBoundsChange: newBounds)
+        return flowLayoutContext
+    }
+
+    @objc
+    private func handleOrientationChange(_ notification: Notification) {
+        invalidateLayout()
+    }
+
+    // MARK: - Cell Sizing
+
+    lazy open var textMessageSizeCalculator = TextMessageSizeCalculator(layout: self)
+    lazy open var attributedTextMessageSizeCalculator = TextMessageSizeCalculator(layout: self)
+    lazy open var emojiMessageSizeCalculator: TextMessageSizeCalculator = {
+        let sizeCalculator = TextMessageSizeCalculator(layout: self)
+        sizeCalculator.messageLabelFont = UIFont.systemFont(ofSize: sizeCalculator.messageLabelFont.pointSize * 2)
+        return sizeCalculator
+    }()
+    lazy open var photoMessageSizeCalculator = MediaMessageSizeCalculator(layout: self)
+    lazy open var videoMessageSizeCalculator = MediaMessageSizeCalculator(layout: self)
+    lazy open var locationMessageSizeCalculator = LocationMessageSizeCalculator(layout: self)
+    lazy open var audioMessageSizeCalculator = AudioMessageSizeCalculator(layout: self)
+    lazy open var contactMessageSizeCalculator = ContactMessageSizeCalculator(layout: self)
+    lazy open var typingIndicatorSizeCalculator = TypingCellSizeCalculator(layout: self)
+
+    /// Note:
+    /// - If you override this method, remember to call MessageLayoutDelegate's
+    /// customCellSizeCalculator(for:at:in:) method for MessageKind.custom messages, if necessary
+    /// - If you are using the typing indicator be sure to return the `typingIndicatorSizeCalculator`
+    /// when the section is reserved for it, indicated by `isSectionReservedForTypingIndicator`
+    open func cellSizeCalculatorForItem(at indexPath: IndexPath) -> CellSizeCalculator {
+        if isSectionReservedForTypingIndicator(indexPath.section) {
+            return typingIndicatorSizeCalculator
+        }
+        let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
+        switch message.kind {
+        case .text:
+            return textMessageSizeCalculator
+        case .attributedText:
+            return attributedTextMessageSizeCalculator
+        case .emoji:
+            return emojiMessageSizeCalculator
+        case .photo:
+            return photoMessageSizeCalculator
+        case .video:
+            return videoMessageSizeCalculator
+        case .location:
+            return locationMessageSizeCalculator
+        case .audio:
+            return audioMessageSizeCalculator
+        case .contact:
+            return contactMessageSizeCalculator
+        case .custom:
+            return messagesLayoutDelegate.customCellSizeCalculator(for: message, at: indexPath, in: messagesCollectionView)
+        }
+    }
+
+    open func sizeForItem(at indexPath: IndexPath) -> CGSize {
+        let calculator = cellSizeCalculatorForItem(at: indexPath)
+        return calculator.sizeForItem(at: indexPath)
+    }
+    
+    /// Set `incomingAvatarSize` of all `MessageSizeCalculator`s
+    public func setMessageIncomingAvatarSize(_ newSize: CGSize) {
+        messageSizeCalculators().forEach { $0.incomingAvatarSize = newSize }
+    }
+    
+    /// Set `outgoingAvatarSize` of all `MessageSizeCalculator`s
+    public func setMessageOutgoingAvatarSize(_ newSize: CGSize) {
+        messageSizeCalculators().forEach { $0.outgoingAvatarSize = newSize }
+    }
+    
+    /// Set `incomingAvatarPosition` of all `MessageSizeCalculator`s
+    public func setMessageIncomingAvatarPosition(_ newPosition: AvatarPosition) {
+        messageSizeCalculators().forEach { $0.incomingAvatarPosition = newPosition }
+    }
+    
+    /// Set `outgoingAvatarPosition` of all `MessageSizeCalculator`s
+    public func setMessageOutgoingAvatarPosition(_ newPosition: AvatarPosition) {
+        messageSizeCalculators().forEach { $0.outgoingAvatarPosition = newPosition }
+    }
+
+    /// Set `avatarLeadingTrailingPadding` of all `MessageSizeCalculator`s
+    public func setAvatarLeadingTrailingPadding(_ newPadding: CGFloat) {
+        messageSizeCalculators().forEach { $0.avatarLeadingTrailingPadding = newPadding }
+    }
+    
+    /// Set `incomingMessagePadding` of all `MessageSizeCalculator`s
+    public func setMessageIncomingMessagePadding(_ newPadding: UIEdgeInsets) {
+        messageSizeCalculators().forEach { $0.incomingMessagePadding = newPadding }
+    }
+    
+    /// Set `outgoingMessagePadding` of all `MessageSizeCalculator`s
+    public func setMessageOutgoingMessagePadding(_ newPadding: UIEdgeInsets) {
+        messageSizeCalculators().forEach { $0.outgoingMessagePadding = newPadding }
+    }
+    
+    /// Set `incomingCellTopLabelAlignment` of all `MessageSizeCalculator`s
+    public func setMessageIncomingCellTopLabelAlignment(_ newAlignment: LabelAlignment) {
+        messageSizeCalculators().forEach { $0.incomingCellTopLabelAlignment = newAlignment }
+    }
+    
+    /// Set `outgoingCellTopLabelAlignment` of all `MessageSizeCalculator`s
+    public func setMessageOutgoingCellTopLabelAlignment(_ newAlignment: LabelAlignment) {
+        messageSizeCalculators().forEach { $0.outgoingCellTopLabelAlignment = newAlignment }
+    }
+    
+    /// Set `incomingCellBottomLabelAlignment` of all `MessageSizeCalculator`s
+    public func setMessageIncomingCellBottomLabelAlignment(_ newAlignment: LabelAlignment) {
+        messageSizeCalculators().forEach { $0.incomingCellBottomLabelAlignment = newAlignment }
+    }
+    
+    /// Set `outgoingCellBottomLabelAlignment` of all `MessageSizeCalculator`s
+    public func setMessageOutgoingCellBottomLabelAlignment(_ newAlignment: LabelAlignment) {
+        messageSizeCalculators().forEach { $0.outgoingCellBottomLabelAlignment = newAlignment }
+    }
+    
+    /// Set `incomingMessageTopLabelAlignment` of all `MessageSizeCalculator`s
+    public func setMessageIncomingMessageTopLabelAlignment(_ newAlignment: LabelAlignment) {
+        messageSizeCalculators().forEach { $0.incomingMessageTopLabelAlignment = newAlignment }
+    }
+    
+    /// Set `outgoingMessageTopLabelAlignment` of all `MessageSizeCalculator`s
+    public func setMessageOutgoingMessageTopLabelAlignment(_ newAlignment: LabelAlignment) {
+        messageSizeCalculators().forEach { $0.outgoingMessageTopLabelAlignment = newAlignment }
+    }
+    
+    /// Set `incomingMessageBottomLabelAlignment` of all `MessageSizeCalculator`s
+    public func setMessageIncomingMessageBottomLabelAlignment(_ newAlignment: LabelAlignment) {
+        messageSizeCalculators().forEach { $0.incomingMessageBottomLabelAlignment = newAlignment }
+    }
+    
+    /// Set `outgoingMessageBottomLabelAlignment` of all `MessageSizeCalculator`s
+    public func setMessageOutgoingMessageBottomLabelAlignment(_ newAlignment: LabelAlignment) {
+        messageSizeCalculators().forEach { $0.outgoingMessageBottomLabelAlignment = newAlignment }
+    }
+
+    /// Set `incomingAccessoryViewSize` of all `MessageSizeCalculator`s
+    public func setMessageIncomingAccessoryViewSize(_ newSize: CGSize) {
+        messageSizeCalculators().forEach { $0.incomingAccessoryViewSize = newSize }
+    }
+
+    /// Set `outgoingAccessoryViewSize` of all `MessageSizeCalculator`s
+    public func setMessageOutgoingAccessoryViewSize(_ newSize: CGSize) {
+        messageSizeCalculators().forEach { $0.outgoingAccessoryViewSize = newSize }
+    }
+
+    /// Set `incomingAccessoryViewPadding` of all `MessageSizeCalculator`s
+    public func setMessageIncomingAccessoryViewPadding(_ newPadding: HorizontalEdgeInsets) {
+        messageSizeCalculators().forEach { $0.incomingAccessoryViewPadding = newPadding }
+    }
+
+    /// Set `outgoingAccessoryViewPadding` of all `MessageSizeCalculator`s
+    public func setMessageOutgoingAccessoryViewPadding(_ newPadding: HorizontalEdgeInsets) {
+        messageSizeCalculators().forEach { $0.outgoingAccessoryViewPadding = newPadding }
+    }
+    
+    /// Set `incomingAccessoryViewPosition` of all `MessageSizeCalculator`s
+    public func setMessageIncomingAccessoryViewPosition(_ newPosition: AccessoryPosition) {
+        messageSizeCalculators().forEach { $0.incomingAccessoryViewPosition = newPosition }
+    }
+    
+    /// Set `outgoingAccessoryViewPosition` of all `MessageSizeCalculator`s
+    public func setMessageOutgoingAccessoryViewPosition(_ newPosition: AccessoryPosition) {
+        messageSizeCalculators().forEach { $0.outgoingAccessoryViewPosition = newPosition }
+    }
+
+    /// Get all `MessageSizeCalculator`s
+    open func messageSizeCalculators() -> [MessageSizeCalculator] {
+        return [textMessageSizeCalculator,
+                attributedTextMessageSizeCalculator,
+                emojiMessageSizeCalculator,
+                photoMessageSizeCalculator,
+                videoMessageSizeCalculator,
+                locationMessageSizeCalculator,
+                audioMessageSizeCalculator,
+                contactMessageSizeCalculator
+        ]
+    }
+    
+}

+ 109 - 0
deltachat-ios/MessageKit/Layout/MessagesCollectionViewLayoutAttributes.swift

@@ -0,0 +1,109 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+/// The layout attributes used by a `MessageCollectionViewCell` to layout its subviews.
+open class MessagesCollectionViewLayoutAttributes: UICollectionViewLayoutAttributes {
+
+    // MARK: - Properties
+
+    public var avatarSize: CGSize = .zero
+    public var avatarPosition = AvatarPosition(vertical: .cellBottom)
+    public var avatarLeadingTrailingPadding: CGFloat = 0
+
+    public var messageContainerSize: CGSize = .zero
+    public var messageContainerPadding: UIEdgeInsets = .zero
+    public var messageLabelFont: UIFont = UIFont.preferredFont(forTextStyle: .body)
+    public var messageLabelInsets: UIEdgeInsets = .zero
+
+    public var cellTopLabelAlignment = LabelAlignment(textAlignment: .center, textInsets: .zero)
+    public var cellTopLabelSize: CGSize = .zero
+    
+    public var cellBottomLabelAlignment = LabelAlignment(textAlignment: .center, textInsets: .zero)
+    public var cellBottomLabelSize: CGSize = .zero
+    
+    public var messageTopLabelAlignment = LabelAlignment(textAlignment: .center, textInsets: .zero)
+    public var messageTopLabelSize: CGSize = .zero
+
+    public var messageBottomLabelAlignment = LabelAlignment(textAlignment: .center, textInsets: .zero)
+    public var messageBottomLabelSize: CGSize = .zero
+
+    public var accessoryViewSize: CGSize = .zero
+    public var accessoryViewPadding: HorizontalEdgeInsets = .zero
+    public var accessoryViewPosition: AccessoryPosition = .messageCenter
+    
+    // MARK: - Methods
+
+    open override func copy(with zone: NSZone? = nil) -> Any {
+        // swiftlint:disable force_cast
+        let copy = super.copy(with: zone) as! MessagesCollectionViewLayoutAttributes
+        copy.avatarSize = avatarSize
+        copy.avatarPosition = avatarPosition
+        copy.avatarLeadingTrailingPadding = avatarLeadingTrailingPadding
+        copy.messageContainerSize = messageContainerSize
+        copy.messageContainerPadding = messageContainerPadding
+        copy.messageLabelFont = messageLabelFont
+        copy.messageLabelInsets = messageLabelInsets
+        copy.cellTopLabelAlignment = cellTopLabelAlignment
+        copy.cellTopLabelSize = cellTopLabelSize
+        copy.cellBottomLabelAlignment = cellBottomLabelAlignment
+        copy.cellBottomLabelSize = cellBottomLabelSize
+        copy.messageTopLabelAlignment = messageTopLabelAlignment
+        copy.messageTopLabelSize = messageTopLabelSize
+        copy.messageBottomLabelAlignment = messageBottomLabelAlignment
+        copy.messageBottomLabelSize = messageBottomLabelSize
+        copy.accessoryViewSize = accessoryViewSize
+        copy.accessoryViewPadding = accessoryViewPadding
+        copy.accessoryViewPosition = accessoryViewPosition
+        return copy
+        // swiftlint:enable force_cast
+    }
+
+    open override func isEqual(_ object: Any?) -> Bool {
+        // MARK: - LEAVE this as is
+        if let attributes = object as? MessagesCollectionViewLayoutAttributes {
+            return super.isEqual(object) && attributes.avatarSize == avatarSize
+                && attributes.avatarPosition == avatarPosition
+                && attributes.avatarLeadingTrailingPadding == avatarLeadingTrailingPadding
+                && attributes.messageContainerSize == messageContainerSize
+                && attributes.messageContainerPadding == messageContainerPadding
+                && attributes.messageLabelFont == messageLabelFont
+                && attributes.messageLabelInsets == messageLabelInsets
+                && attributes.cellTopLabelAlignment == cellTopLabelAlignment
+                && attributes.cellTopLabelSize == cellTopLabelSize
+                && attributes.cellBottomLabelAlignment == cellBottomLabelAlignment
+                && attributes.cellBottomLabelSize == cellBottomLabelSize
+                && attributes.messageTopLabelAlignment == messageTopLabelAlignment
+                && attributes.messageTopLabelSize == messageTopLabelSize
+                && attributes.messageBottomLabelAlignment == messageBottomLabelAlignment
+                && attributes.messageBottomLabelSize == messageBottomLabelSize
+                && attributes.accessoryViewSize == accessoryViewSize
+                && attributes.accessoryViewPadding == accessoryViewPadding
+                && attributes.accessoryViewPosition == accessoryViewPosition
+        } else {
+            return false
+        }
+    }
+}

+ 91 - 0
deltachat-ios/MessageKit/Layout/TextMessageSizeCalculator.swift

@@ -0,0 +1,91 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+open class TextMessageSizeCalculator: MessageSizeCalculator {
+
+    public var incomingMessageLabelInsets = UIEdgeInsets(top: 7, left: 18, bottom: 7, right: 14)
+    public var outgoingMessageLabelInsets = UIEdgeInsets(top: 7, left: 14, bottom: 7, right: 18)
+
+    public var messageLabelFont = UIFont.preferredFont(forTextStyle: .body)
+
+    internal func messageLabelInsets(for message: MessageType) -> UIEdgeInsets {
+        let dataSource = messagesLayout.messagesDataSource
+        let isFromCurrentSender = dataSource.isFromCurrentSender(message: message)
+        return isFromCurrentSender ? outgoingMessageLabelInsets : incomingMessageLabelInsets
+    }
+
+    open override func messageContainerMaxWidth(for message: MessageType) -> CGFloat {
+        let maxWidth = super.messageContainerMaxWidth(for: message)
+        let textInsets = messageLabelInsets(for: message)
+        return maxWidth - textInsets.horizontal
+    }
+
+    open override func messageContainerSize(for message: MessageType) -> CGSize {
+        let maxWidth = messageContainerMaxWidth(for: message)
+
+        var messageContainerSize: CGSize
+        let attributedText: NSAttributedString
+
+        switch message.kind {
+        case .attributedText(let text):
+            attributedText = text
+        case .text(let text), .emoji(let text):
+            attributedText = NSAttributedString(string: text, attributes: [.font: messageLabelFont])
+        default:
+            fatalError("messageContainerSize received unhandled MessageDataType: \(message.kind)")
+        }
+
+        messageContainerSize = labelSize(for: attributedText, considering: maxWidth)
+
+        let messageInsets = messageLabelInsets(for: message)
+        messageContainerSize.width += messageInsets.horizontal
+        messageContainerSize.height += messageInsets.vertical
+
+        return messageContainerSize
+    }
+
+    open override func configure(attributes: UICollectionViewLayoutAttributes) {
+        super.configure(attributes: attributes)
+        guard let attributes = attributes as? MessagesCollectionViewLayoutAttributes else { return }
+
+        let dataSource = messagesLayout.messagesDataSource
+        let indexPath = attributes.indexPath
+        let message = dataSource.messageForItem(at: indexPath, in: messagesLayout.messagesCollectionView)
+
+        attributes.messageLabelInsets = messageLabelInsets(for: message)
+        attributes.messageLabelFont = messageLabelFont
+
+        switch message.kind {
+        case .attributedText(let text):
+            guard !text.string.isEmpty else { return }
+            guard let font = text.attribute(.font, at: 0, effectiveRange: nil) as? UIFont else { return }
+            attributes.messageLabelFont = font
+        default:
+            break
+        }
+    }
+}

+ 43 - 0
deltachat-ios/MessageKit/Layout/TypingIndicatorCellSizeCalculator.swift

@@ -0,0 +1,43 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+open class TypingCellSizeCalculator: CellSizeCalculator {
+
+    open var height: CGFloat = 62
+
+    public init(layout: MessagesCollectionViewFlowLayout? = nil) {
+        super.init()
+        self.layout = layout
+    }
+
+    open override func sizeForItem(at indexPath: IndexPath) -> CGSize {
+        guard let layout = layout else { return .zero }
+        let collectionViewWidth = layout.collectionView?.bounds.width ?? 0
+        let contentInset = layout.collectionView?.contentInset ?? .zero
+        let inset = layout.sectionInset.horizontal + contentInset.horizontal
+        return CGSize(width: collectionViewWidth - inset, height: height)
+    }
+}

+ 48 - 0
deltachat-ios/MessageKit/Models/AccessoryPosition.swift

@@ -0,0 +1,48 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+
+/// Used to determine the `Horizontal` and `Vertical` position of
+// an `AccessoryView` in a `MessageCollectionViewCell`.
+public enum AccessoryPosition {
+    
+    /// Aligns the `AccessoryView`'s top edge to the cell's top edge.
+    case cellTop
+    
+    /// Aligns the `AccessoryView`'s top edge to the `messageTopLabel`'s top edge.
+    case messageLabelTop
+    
+    /// Aligns the `AccessoryView`'s top edge to the `MessageContainerView`'s top edge.
+    case messageTop
+    
+    /// Aligns the `AccessoryView` center to the `MessageContainerView` center.
+    case messageCenter
+    
+    /// Aligns the `AccessoryView`'s bottom edge to the `MessageContainerView`s bottom edge.
+    case messageBottom
+    
+    /// Aligns the `AccessoryView`'s bottom edge to the cell's bottom edge.
+    case cellBottom
+}

+ 48 - 0
deltachat-ios/MessageKit/Models/Avatar.swift

@@ -0,0 +1,48 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+/// An object used to group the information to be used by an `AvatarView`.
+public struct Avatar {
+    
+    // MARK: - Properties
+    
+    /// The image to be used for an `AvatarView`.
+    public let image: UIImage?
+    
+    /// The placeholder initials to be used in the case where no image is provided.
+    ///
+    /// The default value of this property is "?".
+    public var initials: String = "?"
+    
+    // MARK: - Initializer
+    
+    public init(image: UIImage? = nil, initials: String = "?") {
+        self.image = image
+        self.initials = initials
+    }
+    
+}

+ 98 - 0
deltachat-ios/MessageKit/Models/AvatarPosition.swift

@@ -0,0 +1,98 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+
+/// Used to determine the `Horizontal` and `Vertical` position of
+// an `AvatarView` in a `MessageCollectionViewCell`.
+public struct AvatarPosition: Equatable {
+    
+    /// An enum representing the horizontal alignment of an `AvatarView`.
+    public enum Horizontal {
+        
+        /// Positions the `AvatarView` on the side closest to the cell's leading edge.
+        case cellLeading
+        
+        /// Positions the `AvatarView` on the side closest to the cell's trailing edge.
+        case cellTrailing
+        
+        /// Positions the `AvatarView` based on whether the message is from the current Sender.
+        /// The cell is positioned `.cellTrailling` if `isFromCurrentSender` is true
+        /// and `.cellLeading` if false.
+        case natural
+    }
+    
+    /// An enum representing the verical alignment for an `AvatarView`.
+    public enum Vertical {
+        
+        /// Aligns the `AvatarView`'s top edge to the cell's top edge.
+        case cellTop
+        
+        /// Aligns the `AvatarView`'s top edge to the `messageTopLabel`'s top edge.
+        case messageLabelTop
+        
+        /// Aligns the `AvatarView`'s top edge to the `MessageContainerView`'s top edge.
+        case messageTop
+        
+        /// Aligns the `AvatarView` center to the `MessageContainerView` center.
+        case messageCenter
+        
+        /// Aligns the `AvatarView`'s bottom edge to the `MessageContainerView`s bottom edge.
+        case messageBottom
+        
+        /// Aligns the `AvatarView`'s bottom edge to the cell's bottom edge.
+        case cellBottom
+        
+    }
+    
+    // MARK: - Properties
+    
+    // The vertical position
+    public var vertical: Vertical
+    
+    // The horizontal position
+    public var horizontal: Horizontal
+    
+    // MARK: - Initializers
+    
+    public init(horizontal: Horizontal, vertical: Vertical) {
+        self.horizontal = horizontal
+        self.vertical = vertical
+    }
+
+    public init(vertical: Vertical) {
+        self.init(horizontal: .natural, vertical: vertical)
+    }
+    
+}
+
+// MARK: - Equatable Conformance
+
+public extension AvatarPosition {
+
+    static func == (lhs: AvatarPosition, rhs: AvatarPosition) -> Bool {
+        return lhs.vertical == rhs.vertical && lhs.horizontal == rhs.horizontal
+    }
+
+}

+ 77 - 0
deltachat-ios/MessageKit/Models/DetectorType.swift

@@ -0,0 +1,77 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+
+public enum DetectorType: Hashable {
+
+    case address
+    case date
+    case phoneNumber
+    case url
+    case transitInformation
+    case custom(NSRegularExpression)
+
+    // swiftlint:disable force_try
+    public static var hashtag = DetectorType.custom(try! NSRegularExpression(pattern: "#[a-zA-Z0-9]{4,}", options: []))
+    public static var mention = DetectorType.custom(try! NSRegularExpression(pattern: "@[a-zA-Z0-9]{4,}", options: []))
+    // swiftlint:enable force_try
+
+    internal var textCheckingType: NSTextCheckingResult.CheckingType {
+        switch self {
+        case .address: return .address
+        case .date: return .date
+        case .phoneNumber: return .phoneNumber
+        case .url: return .link
+        case .transitInformation: return .transitInformation
+        case .custom: return .regularExpression
+        }
+    }
+
+    /// Simply check if the detector type is a .custom
+    public var isCustom: Bool {
+        switch self {
+        case .custom: return true
+        default: return false
+        }
+    }
+
+    ///The hashValue of the `DetectorType` so we can conform to `Hashable` and be sorted.
+    public func hash(into: inout Hasher) {
+        into.combine(toInt())
+    }
+
+    /// Return an 'Int' value for each `DetectorType` type so `DetectorType` can conform to `Hashable`
+    private func toInt() -> Int {
+        switch self {
+        case .address: return 0
+        case .date: return 1
+        case .phoneNumber: return 2
+        case .url: return 3
+        case .transitInformation: return 4
+        case .custom(let regex): return regex.hashValue
+        }
+    }
+
+}

+ 56 - 0
deltachat-ios/MessageKit/Models/HorizontalEdgeInsets.swift

@@ -0,0 +1,56 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+/// A varient of `UIEdgeInsets` that only has horizontal inset properties
+public struct HorizontalEdgeInsets: Equatable {
+
+    public var left: CGFloat
+    public var right: CGFloat
+
+    public init(left: CGFloat, right: CGFloat) {
+        self.left = left
+        self.right = right
+    }
+
+    public static var zero: HorizontalEdgeInsets {
+        return HorizontalEdgeInsets(left: 0, right: 0)
+    }
+}
+
+public extension HorizontalEdgeInsets {
+
+    static func == (lhs: HorizontalEdgeInsets, rhs: HorizontalEdgeInsets) -> Bool {
+        return lhs.left == rhs.left && lhs.right == rhs.right
+    }
+}
+
+internal extension HorizontalEdgeInsets {
+
+    internal var horizontal: CGFloat {
+        return left + right
+    }
+}

+ 47 - 0
deltachat-ios/MessageKit/Models/LabelAlignment.swift

@@ -0,0 +1,47 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+public struct LabelAlignment: Equatable {
+
+    public var textAlignment: NSTextAlignment
+    public var textInsets: UIEdgeInsets
+    
+    public init(textAlignment: NSTextAlignment, textInsets: UIEdgeInsets) {
+        self.textAlignment = textAlignment
+        self.textInsets = textInsets
+    }
+
+}
+
+// MARK: - Equatable Conformance
+
+public extension LabelAlignment {
+
+    static func == (lhs: LabelAlignment, rhs: LabelAlignment) -> Bool {
+        return lhs.textAlignment == rhs.textAlignment && lhs.textInsets == rhs.textInsets
+    }
+
+}

+ 64 - 0
deltachat-ios/MessageKit/Models/LocationMessageSnapshotOptions.swift

@@ -0,0 +1,64 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import MapKit
+
+/// An object grouping the settings used by the `MKMapSnapshotter` through the `LocationMessageDisplayDelegate`.
+public struct LocationMessageSnapshotOptions {
+
+    /// Initialize LocationMessageSnapshotOptions with given parameters
+    ///
+    /// - Parameters:
+    ///   - showsBuildings: A Boolean value indicating whether the snapshot image should display buildings.
+    ///   - showsPointsOfInterest: A Boolean value indicating whether the snapshot image should display points of interest.
+    ///   - span: The span of the snapshot.
+    ///   - scale: The scale of the snapshot.
+    public init(showsBuildings: Bool = false, showsPointsOfInterest: Bool = false, span: MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: 0, longitudeDelta: 0), scale: CGFloat = UIScreen.main.scale) {
+        self.showsBuildings = showsBuildings
+        self.showsPointsOfInterest = showsPointsOfInterest
+        self.span = span
+        self.scale = scale
+    }
+    
+    /// A Boolean value indicating whether the snapshot image should display buildings.
+    ///
+    /// The default value of this property is `false`.
+    public var showsBuildings: Bool
+    
+    /// A Boolean value indicating whether the snapshot image should display points of interest.
+    ///
+    /// The default value of this property is `false`.
+    public var showsPointsOfInterest: Bool
+    
+    /// The span of the snapshot.
+    ///
+    /// The default value of this property uses a width of `0` and height of `0`.
+    public var span: MKCoordinateSpan
+    
+    /// The scale of the snapshot.
+    ///
+    /// The default value of this property uses the `UIScreen.main.scale`.
+    public var scale: CGFloat
+    
+}

+ 72 - 0
deltachat-ios/MessageKit/Models/MessageKind.swift

@@ -0,0 +1,72 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+
+/// An enum representing the kind of message and its underlying kind.
+public enum MessageKind {
+
+    /// A standard text message.
+    ///
+    /// - Note: The font used for this message will be the value of the
+    /// `messageLabelFont` property in the `MessagesCollectionViewFlowLayout` object.
+    ///
+    /// Using `MessageKind.attributedText(NSAttributedString)` doesn't require you
+    /// to set this property and results in higher performance.
+    case text(String)
+    
+    /// A message with attributed text.
+    case attributedText(NSAttributedString)
+
+    /// A photo message.
+    case photo(MediaItem)
+
+    /// A video message.
+    case video(MediaItem)
+
+    /// A location message.
+    case location(LocationItem)
+
+    /// An emoji message.
+    case emoji(String)
+
+    /// An audio message.
+    case audio(AudioItem)
+    
+    /// A contact message.
+    case contact(ContactItem)
+
+    /// A custom message.
+    /// - Note: Using this case requires that you implement the following methods and handle this case:
+    ///   - MessagesDataSource: customCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell
+    ///   - MessagesLayoutDelegate: customCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator
+    case custom(Any?)
+
+    // MARK: - Not supported yet
+
+//    case system(String)
+//    
+//    case placeholder
+
+}

+ 66 - 0
deltachat-ios/MessageKit/Models/MessageKitDateFormatter.swift

@@ -0,0 +1,66 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+
+open class MessageKitDateFormatter {
+
+    // MARK: - Properties
+
+    public static let shared = MessageKitDateFormatter()
+
+    private let formatter = DateFormatter()
+
+    // MARK: - Initializer
+
+    private init() {}
+
+    // MARK: - Methods
+
+    public func string(from date: Date) -> String {
+        configureDateFormatter(for: date)
+        return formatter.string(from: date)
+    }
+
+    public func attributedString(from date: Date, with attributes: [NSAttributedString.Key: Any]) -> NSAttributedString {
+        let dateString = string(from: date)
+        return NSAttributedString(string: dateString, attributes: attributes)
+    }
+
+    open func configureDateFormatter(for date: Date) {
+        switch true {
+        case Calendar.current.isDateInToday(date) || Calendar.current.isDateInYesterday(date):
+            formatter.doesRelativeDateFormatting = true
+            formatter.dateStyle = .short
+            formatter.timeStyle = .short
+        case Calendar.current.isDate(date, equalTo: Date(), toGranularity: .weekOfYear):
+            formatter.dateFormat = "EEEE h:mm a"
+        case Calendar.current.isDate(date, equalTo: Date(), toGranularity: .year):
+            formatter.dateFormat = "E, d MMM, h:mm a"
+        default:
+            formatter.dateFormat = "MMM d, yyyy, h:mm a"
+        }
+    }
+    
+}

+ 38 - 0
deltachat-ios/MessageKit/Models/MessageKitError.swift

@@ -0,0 +1,38 @@
+/*
+ MIT License
+
+ Copyright (c) 2017 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+internal enum MessageKitError {
+    internal static let avatarPositionUnresolved = "AvatarPosition Horizontal.natural needs to be resolved."
+    internal static let nilMessagesDataSource = "MessagesDataSource has not been set."
+    internal static let nilMessagesDisplayDelegate = "MessagesDisplayDelegate has not been set."
+    internal static let nilMessagesLayoutDelegate = "MessagesLayoutDelegate has not been set."
+    internal static let notMessagesCollectionView = "The collectionView is not a MessagesCollectionView."
+    internal static let layoutUsedOnForeignType = "MessagesCollectionViewFlowLayout is being used on a foreign type."
+    internal static let unrecognizedSectionKind = "Received unrecognized element kind:"
+    internal static let unrecognizedCheckingResult = "Received an unrecognized NSTextCheckingResult.CheckingType"
+    internal static let couldNotLoadAssetsBundle = "MessageKit: Could not load the assets bundle"
+    internal static let couldNotCreateAssetsPath = "MessageKit: Could not create path to the assets bundle."
+    internal static let customDataUnresolvedCell = "Did not return a cell for MessageKind.custom(Any)."
+    internal static let customDataUnresolvedSize = "Did not return a size for MessageKind.custom(Any)."
+}

+ 151 - 0
deltachat-ios/MessageKit/Models/MessageStyle.swift

@@ -0,0 +1,151 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+public enum MessageStyle {
+
+    // MARK: - TailCorner
+
+    public enum TailCorner: String {
+
+        case topLeft
+        case bottomLeft
+        case topRight
+        case bottomRight
+
+        internal var imageOrientation: UIImage.Orientation {
+            switch self {
+            case .bottomRight: return .up
+            case .bottomLeft: return .upMirrored
+            case .topLeft: return .down
+            case .topRight: return .downMirrored
+            }
+        }
+    }
+
+    // MARK: - TailStyle
+
+    public enum TailStyle {
+
+        case curved
+        case pointedEdge
+
+        internal var imageNameSuffix: String {
+            switch self {
+            case .curved:
+                return "_tail_v2"
+            case .pointedEdge:
+                return "_tail_v1"
+            }
+        }
+    }
+
+    // MARK: - MessageStyle
+
+    case none
+    case bubble
+    case bubbleOutline(UIColor)
+    case bubbleTail(TailCorner, TailStyle)
+    case bubbleTailOutline(UIColor, TailCorner, TailStyle)
+    case custom((MessageContainerView) -> Void)
+
+    // MARK: - Public
+
+    public var image: UIImage? {
+        
+        guard let imageCacheKey = imageCacheKey, let path = imagePath else { return nil }
+
+        let cache = MessageStyle.bubbleImageCache
+
+        if let cachedImage = cache.object(forKey: imageCacheKey as NSString) {
+            return cachedImage
+        }
+        guard var image = UIImage(contentsOfFile: path) else { return nil }
+        
+        switch self {
+        case .none, .custom:
+            return nil
+        case .bubble, .bubbleOutline:
+            break
+        case .bubbleTail(let corner, _), .bubbleTailOutline(_, let corner, _):
+            guard let cgImage = image.cgImage else { return nil }
+            image = UIImage(cgImage: cgImage, scale: image.scale, orientation: corner.imageOrientation)
+        }
+        
+        let stretchedImage = stretch(image)
+        cache.setObject(stretchedImage, forKey: imageCacheKey as NSString)
+        return stretchedImage
+    }
+
+    // MARK: - Internal
+    
+    internal static let bubbleImageCache: NSCache<NSString, UIImage> = {
+        let cache = NSCache<NSString, UIImage>()
+        cache.name = "com.messagekit.MessageKit.bubbleImageCache"
+        return cache
+    }()
+    
+    // MARK: - Private
+    
+    private var imageCacheKey: String? {
+        guard let imageName = imageName else { return nil }
+        
+        switch self {
+        case .bubble, .bubbleOutline:
+            return imageName
+        case .bubbleTail(let corner, _), .bubbleTailOutline(_, let corner, _):
+            return imageName + "_" + corner.rawValue
+        default:
+            return nil
+        }
+    }
+
+    private var imageName: String? {
+        switch self {
+        case .bubble:
+            return "bubble_full"
+        case .bubbleOutline:
+            return "bubble_outlined"
+        case .bubbleTail(_, let tailStyle):
+            return "bubble_full" + tailStyle.imageNameSuffix
+        case .bubbleTailOutline(_, _, let tailStyle):
+            return "bubble_outlined" + tailStyle.imageNameSuffix
+        case .none, .custom:
+            return nil
+        }
+    }
+
+    private var imagePath: String? {
+        guard let imageName = imageName else { return nil }
+        let assetBundle = Bundle.messageKitAssetBundle()
+        return assetBundle.path(forResource: imageName, ofType: "png", inDirectory: "Images")
+    }
+
+    private func stretch(_ image: UIImage) -> UIImage {
+        let center = CGPoint(x: image.size.width / 2, y: image.size.height / 2)
+        let capInsets = UIEdgeInsets(top: center.y, left: center.x, bottom: center.y, right: center.x)
+        return image.resizableImage(withCapInsets: capInsets, resizingMode: .stretch)
+    }
+}

+ 81 - 0
deltachat-ios/MessageKit/Models/NSConstraintLayoutSet.swift

@@ -0,0 +1,81 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+internal class NSLayoutConstraintSet {
+    
+    internal var top: NSLayoutConstraint?
+    internal var bottom: NSLayoutConstraint?
+    internal var left: NSLayoutConstraint?
+    internal var right: NSLayoutConstraint?
+    internal var centerX: NSLayoutConstraint?
+    internal var centerY: NSLayoutConstraint?
+    internal var width: NSLayoutConstraint?
+    internal var height: NSLayoutConstraint?
+    
+    internal init(top: NSLayoutConstraint? = nil, bottom: NSLayoutConstraint? = nil,
+                  left: NSLayoutConstraint? = nil, right: NSLayoutConstraint? = nil,
+                  centerX: NSLayoutConstraint? = nil, centerY: NSLayoutConstraint? = nil,
+                  width: NSLayoutConstraint? = nil, height: NSLayoutConstraint? = nil) {
+        self.top = top
+        self.bottom = bottom
+        self.left = left
+        self.right = right
+        self.centerX = centerX
+        self.centerY = centerY
+        self.width = width
+        self.height = height
+    }
+
+    /// All of the currently configured constraints
+    private var availableConstraints: [NSLayoutConstraint] {
+        let constraints = [top, bottom, left, right, centerX, centerY, width, height]
+        var available: [NSLayoutConstraint] = []
+        for constraint in constraints {
+            if let value = constraint {
+                available.append(value)
+            }
+        }
+        return available
+    }
+    
+    /// Activates all of the non-nil constraints
+    ///
+    /// - Returns: Self
+    @discardableResult
+    internal func activate() -> Self {
+        NSLayoutConstraint.activate(availableConstraints)
+        return self
+    }
+    
+    /// Deactivates all of the non-nil constraints
+    ///
+    /// - Returns: Self
+    @discardableResult
+    internal func deactivate() -> Self {
+        NSLayoutConstraint.deactivate(availableConstraints)
+        return self
+    }
+}

+ 57 - 0
deltachat-ios/MessageKit/Models/Sender.swift

@@ -0,0 +1,57 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+
+/// An object that groups the metadata of a messages sender.
+@available(*, deprecated: 3.0.0, message: "`Sender` has been replaced with the `SenderType` protocol in 3.0.0")
+public struct Sender: SenderType {
+
+    /// MARK: - Properties
+
+    /// The unique String identifier for the sender.
+    ///
+    /// Note: This value must be unique across all senders.
+    public let senderId: String
+
+    @available(*, deprecated: 3.0.0, message: "`id` has been renamed `senderId` as defined in the `SenderType` protocol")
+    public var id: String {
+        return senderId
+    }
+
+    /// The display name of a sender.
+    public let displayName: String
+
+    // MARK: - Intializers
+
+    public init(senderId: String, displayName: String) {
+        self.senderId = senderId
+        self.displayName = displayName
+    }
+
+    @available(*, deprecated: 3.0.0, message: "`id` has been renamed `senderId` as defined in the `SenderType` protocol")
+    public init(id: String, displayName: String) {
+        self.init(senderId: id, displayName: displayName)
+    }
+}

+ 40 - 0
deltachat-ios/MessageKit/Protocols/AudioItem.swift

@@ -0,0 +1,40 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import class AVFoundation.AVAudioPlayer
+import UIKit
+
+/// A protocol used to represent the data for an audio message.
+public protocol AudioItem {
+
+    /// The url where the audio file is located.
+    var url: URL { get }
+
+    /// The audio file duration in seconds.
+    var duration: Float { get }
+
+    /// The size of the audio item.
+    var size: CGSize { get }
+
+}

+ 41 - 0
deltachat-ios/MessageKit/Protocols/ContactItem.swift

@@ -0,0 +1,41 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2018 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+
+/// A protocol used to represent the data for a contact message.
+public protocol ContactItem {
+    
+    /// contact displayed name
+    var displayName: String { get }
+    
+    /// initials from contact first and last name
+    var initials: String { get }
+    
+    /// contact phone numbers
+    var phoneNumbers: [String] { get }
+    
+    /// contact emails
+    var emails: [String] { get }
+}

+ 37 - 0
deltachat-ios/MessageKit/Protocols/LocationItem.swift

@@ -0,0 +1,37 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import class CoreLocation.CLLocation
+import UIKit
+
+/// A protocol used to represent the data for a location message.
+public protocol LocationItem {
+
+    /// The location.
+    var location: CLLocation { get }
+
+    /// The size of the location item.
+    var size: CGSize { get }
+
+}

+ 43 - 0
deltachat-ios/MessageKit/Protocols/MediaItem.swift

@@ -0,0 +1,43 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+/// A protocol used to represent the data for a media message.
+public protocol MediaItem {
+
+    /// The url where the media is located.
+    var url: URL? { get }
+
+    /// The image.
+    var image: UIImage? { get }
+
+    /// A placeholder image for when the image is obtained asychronously.
+    var placeholderImage: UIImage { get }
+
+    /// The size of the media item.
+    var size: CGSize { get }
+
+}

+ 180 - 0
deltachat-ios/MessageKit/Protocols/MessageCellDelegate.swift

@@ -0,0 +1,180 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+
+/// A protocol used by `MessageContentCell` subclasses to detect taps in the cell's subviews.
+public protocol MessageCellDelegate: MessageLabelDelegate {
+
+    /// Triggered when a tap occurs in the background of the cell.
+    ///
+    /// - Parameters:
+    ///   - cell: The cell where the tap occurred.
+    ///
+    /// - Note:
+    /// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
+    /// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
+    /// method `messageForItem(at:indexPath:messagesCollectionView)`.
+    func didTapBackground(in cell: MessageCollectionViewCell)
+
+    /// Triggered when a tap occurs in the `MessageContainerView`.
+    ///
+    /// - Parameters:
+    ///   - cell: The cell where the tap occurred.
+    ///
+    /// - Note:
+    /// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
+    /// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
+    /// method `messageForItem(at:indexPath:messagesCollectionView)`.
+    func didTapMessage(in cell: MessageCollectionViewCell)
+
+    /// Triggered when a tap occurs in the `AvatarView`.
+    ///
+    /// - Parameters:
+    ///   - cell: The cell where the tap occurred.
+    ///
+    /// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
+    /// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
+    /// method `messageForItem(at:indexPath:messagesCollectionView)`.
+    func didTapAvatar(in cell: MessageCollectionViewCell)
+
+    /// Triggered when a tap occurs in the cellTopLabel.
+    ///
+    /// - Parameters:
+    ///   - cell: The cell tap the touch occurred.
+    ///
+    /// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
+    /// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
+    /// method `messageForItem(at:indexPath:messagesCollectionView)`.
+    func didTapCellTopLabel(in cell: MessageCollectionViewCell)
+    
+    /// Triggered when a tap occurs in the cellBottomLabel.
+    ///
+    /// - Parameters:
+    ///   - cell: The cell tap the touch occurred.
+    ///
+    /// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
+    /// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
+    /// method `messageForItem(at:indexPath:messagesCollectionView)`.
+    func didTapCellBottomLabel(in cell: MessageCollectionViewCell)
+    
+    /// Triggered when a tap occurs in the messageTopLabel.
+    ///
+    /// - Parameters:
+    ///   - cell: The cell tap the touch occurred.
+    ///
+    /// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
+    /// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
+    /// method `messageForItem(at:indexPath:messagesCollectionView)`.
+    func didTapMessageTopLabel(in cell: MessageCollectionViewCell)
+
+    /// Triggered when a tap occurs in the messageBottomLabel.
+    ///
+    /// - Parameters:
+    ///   - cell: The cell where the tap occurred.
+    ///
+    /// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
+    /// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
+    /// method `messageForItem(at:indexPath:messagesCollectionView)`.
+    func didTapMessageBottomLabel(in cell: MessageCollectionViewCell)
+    
+    /// Triggered when a tap occurs in the accessoryView.
+    ///
+    /// - Parameters:
+    ///   - cell: The cell where the tap occurred.
+    ///
+    /// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
+    /// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
+    /// method `messageForItem(at:indexPath:messagesCollectionView)`.
+    func didTapAccessoryView(in cell: MessageCollectionViewCell)
+
+    /// Triggered when a tap occurs on the play button from audio cell.
+    ///
+    /// - Parameters:
+    ///   - cell: The audio cell where the touch occurred.
+    ///
+    /// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
+    /// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
+    /// method `messageForItem(at:indexPath:messagesCollectionView)`.
+    func didTapPlayButton(in cell: AudioMessageCell)
+
+    /// Triggered when audio player start playing audio.
+    ///
+    /// - Parameters:
+    ///   - cell: The cell where the audio sound is playing.
+    ///
+    /// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
+    /// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
+    /// method `messageForItem(at:indexPath:messagesCollectionView)`.
+    func didStartAudio(in cell: AudioMessageCell)
+
+    /// Triggered when audio player pause audio.
+    ///
+    /// - Parameters:
+    ///   - cell: The cell where the audio sound is paused.
+    ///
+    /// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
+    /// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
+    /// method `messageForItem(at:indexPath:messagesCollectionView)`.
+    func didPauseAudio(in cell: AudioMessageCell)
+
+    /// Triggered when audio player stoped audio.
+    ///
+    /// - Parameters:
+    ///   - cell: The cell where the audio sound is stoped.
+    ///
+    /// You can get a reference to the `MessageType` for the cell by using `UICollectionView`'s
+    /// `indexPath(for: cell)` method. Then using the returned `IndexPath` with the `MessagesDataSource`
+    /// method `messageForItem(at:indexPath:messagesCollectionView)`.
+    func didStopAudio(in cell: AudioMessageCell)
+
+}
+
+public extension MessageCellDelegate {
+
+    func didTapBackground(in cell: MessageCollectionViewCell) {}
+
+    func didTapMessage(in cell: MessageCollectionViewCell) {}
+
+    func didTapAvatar(in cell: MessageCollectionViewCell) {}
+
+    func didTapCellTopLabel(in cell: MessageCollectionViewCell) {}
+    
+    func didTapCellBottomLabel(in cell: MessageCollectionViewCell) {}
+
+    func didTapMessageTopLabel(in cell: MessageCollectionViewCell) {}
+
+    func didTapPlayButton(in cell: AudioMessageCell) {}
+
+    func didStartAudio(in cell: AudioMessageCell) {}
+
+    func didPauseAudio(in cell: AudioMessageCell) {}
+
+    func didStopAudio(in cell: AudioMessageCell) {}
+
+    func didTapMessageBottomLabel(in cell: MessageCollectionViewCell) {}
+    
+    func didTapAccessoryView(in cell: MessageCollectionViewCell) {}
+
+}

+ 99 - 0
deltachat-ios/MessageKit/Protocols/MessageLabelDelegate.swift

@@ -0,0 +1,99 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+
+/// A protocol used to handle tap events on detected text.
+public protocol MessageLabelDelegate: AnyObject {
+
+    /// Triggered when a tap occurs on a detected address.
+    ///
+    /// - Parameters:
+    ///   - addressComponents: The components of the selected address.
+    func didSelectAddress(_ addressComponents: [String: String])
+
+    /// Triggered when a tap occurs on a detected date.
+    ///
+    /// - Parameters:
+    ///   - date: The selected date.
+    func didSelectDate(_ date: Date)
+
+    /// Triggered when a tap occurs on a detected phone number.
+    ///
+    /// - Parameters:
+    ///   - phoneNumber: The selected phone number.
+    func didSelectPhoneNumber(_ phoneNumber: String)
+
+    /// Triggered when a tap occurs on a detected URL.
+    ///
+    /// - Parameters:
+    ///   - url: The selected URL.
+    func didSelectURL(_ url: URL)
+
+    /// Triggered when a tap occurs on detected transit information.
+    ///
+    /// - Parameters:
+    ///   - transitInformation: The selected transit information.
+    func didSelectTransitInformation(_ transitInformation: [String: String])
+    
+    /// Triggered when a tap occurs on a mention
+    ///
+    /// - Parameters:
+    ///   - mention: The selected mention
+    func didSelectMention(_ mention: String)
+    
+    /// Triggered when a tap occurs on a hashtag
+    ///
+    /// - Parameters:
+    ///   - mention: The selected hashtag
+    func didSelectHashtag(_ hashtag: String)
+
+    /// Triggered when a tap occurs on a custom regular expression
+    ///
+    /// - Parameters:
+    ///   - pattern: the pattern of the regular expression
+    ///   - match: part that match with the regular expression
+    func didSelectCustom(_ pattern: String, match: String?)
+
+}
+
+public extension MessageLabelDelegate {
+
+    func didSelectAddress(_ addressComponents: [String: String]) {}
+
+    func didSelectDate(_ date: Date) {}
+
+    func didSelectPhoneNumber(_ phoneNumber: String) {}
+
+    func didSelectURL(_ url: URL) {}
+    
+    func didSelectTransitInformation(_ transitInformation: [String: String]) {}
+
+    func didSelectMention(_ mention: String) {}
+
+    func didSelectHashtag(_ hashtag: String) {}
+
+    func didSelectCustom(_ pattern: String, match: String?) {}
+
+}

+ 43 - 0
deltachat-ios/MessageKit/Protocols/MessageType.swift

@@ -0,0 +1,43 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+
+/// A standard protocol representing a message.
+/// Use this protocol to create your own message object to be used by MessageKit.
+public protocol MessageType {
+
+    /// The sender of the message.
+    var sender: SenderType { get }
+
+    /// The unique identifier for the message.
+    var messageId: String { get }
+
+    /// The date the message was sent.
+    var sentDate: Date { get }
+
+    /// The kind of message and its underlying kind.
+    var kind: MessageKind { get }
+
+}

+ 159 - 0
deltachat-ios/MessageKit/Protocols/MessagesDataSource.swift

@@ -0,0 +1,159 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+/// An object that adopts the `MessagesDataSource` protocol is responsible for providing
+/// the data required by a `MessagesCollectionView`.
+public protocol MessagesDataSource: AnyObject {
+
+    /// The `SenderType` of new messages in the `MessagesCollectionView`.
+    func currentSender() -> SenderType
+
+    /// A helper method to determine if a given message is from the current `SenderType`.
+    ///
+    /// - Parameters:
+    ///   - message: The message to check if it was sent by the current `SenderType`.
+    ///
+    /// - Note:
+    ///   The default implementation of this method checks for equality between
+    ///   the message's `SenderType` and the current `SenderType`.
+    func isFromCurrentSender(message: MessageType) -> Bool
+
+    /// The message to be used for a `MessageCollectionViewCell` at the given `IndexPath`.
+    ///
+    /// - Parameters:
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which the message will be displayed.
+    func messageForItem(at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageType
+
+    /// The number of sections to be displayed in the `MessagesCollectionView`.
+    ///
+    /// - Parameters:
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which the messages will be displayed.
+    func numberOfSections(in messagesCollectionView: MessagesCollectionView) -> Int
+
+    /// The number of cells to be displayed in the `MessagesCollectionView`.
+    ///
+    /// - Parameters:
+    ///   - section: The number of the section in which the cells will be displayed.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which the messages will be displayed.
+    /// - Note:
+    ///   The default implementation of this method returns 1. Putting each message in its own section.
+    func numberOfItems(inSection section: Int, in messagesCollectionView: MessagesCollectionView) -> Int
+
+    /// The attributed text to be used for cell's top label.
+    ///
+    /// - Parameters:
+    ///   - message: The `MessageType` that will be displayed by this cell.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// The default value returned by this method is `nil`.
+    func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString?
+    
+    /// The attributed text to be used for cell's bottom label.
+    ///
+    /// - Parameters:
+    ///   - message: The `MessageType` that will be displayed by this cell.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// The default value returned by this method is `nil`.
+    func cellBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString?
+    
+    /// The attributed text to be used for message bubble's top label.
+    ///
+    /// - Parameters:
+    ///   - message: The `MessageType` that will be displayed by this cell.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// The default value returned by this method is `nil`.
+    func messageTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString?
+
+    /// The attributed text to be used for cell's bottom label.
+    ///
+    /// - Parameters:
+    ///   - message: The `MessageType` that will be displayed by this cell.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// The default value returned by this method is `nil`.
+    func messageBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString?
+    
+    /// Custom collectionView cell for message with `custom` message type.
+    ///
+    /// - Parameters:
+    ///   - message: The `custom` message type
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// - Note:
+    ///   This method will call fatalError() on default. You must override this method if you are using MessageKind.custom messages.
+    func customCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell
+
+    /// Typing indicator cell used when the indicator is set to be shown
+    ///
+    /// - Parameters:
+    ///   - indexPath: The index path to dequeue the cell at
+    ///   - messagesCollectionView: The `MessagesCollectionView` the cell is to be rendered in
+    /// - Returns: A `UICollectionViewCell` that indicates a user is typing
+    func typingIndicator(at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell
+}
+
+public extension MessagesDataSource {
+
+    func isFromCurrentSender(message: MessageType) -> Bool {
+        return message.sender.senderId == currentSender().senderId
+    }
+
+    func numberOfItems(inSection section: Int, in messagesCollectionView: MessagesCollectionView) -> Int {
+        return 1
+    }
+
+    func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
+        return nil
+    }
+    
+    func cellBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
+        return nil
+    }
+    
+    func messageTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
+        return nil
+    }
+
+    func messageBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
+        return nil
+    }
+    
+    func customCell(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell {
+        fatalError(MessageKitError.customDataUnresolvedCell)
+    }
+
+    func typingIndicator(at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UICollectionViewCell {
+        return messagesCollectionView.dequeueReusableCell(TypingIndicatorCell.self, for: indexPath)
+    }
+}

+ 315 - 0
deltachat-ios/MessageKit/Protocols/MessagesDisplayDelegate.swift

@@ -0,0 +1,315 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import MapKit
+import UIKit
+
+/// A protocol used by the `MessagesViewController` to customize the appearance of a `MessageContentCell`.
+public protocol MessagesDisplayDelegate: AnyObject {
+
+    // MARK: - All Messages
+
+    /// Specifies the `MessageStyle` to be used for a `MessageContainerView`.
+    ///
+    /// - Parameters:
+    ///   - message: The `MessageType` that will be displayed by this cell.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// - Note:
+    ///   The default value returned by this method is `MessageStyle.bubble`.
+    func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle
+
+    /// Specifies the background color of the `MessageContainerView`.
+    ///
+    /// - Parameters:
+    ///   - message: The `MessageType` that will be displayed by this cell.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// - Note:
+    ///   The default value is `UIColor.clear` for emoji messages.
+    ///   For all other `MessageKind` cases, the color depends on the `SenderType`.
+    ///
+    ///   Current sender: Green
+    ///
+    ///   All other senders: Gray
+    func backgroundColor(for message: MessageType, at  indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor
+
+    /// The section header to use for a given `IndexPath`.
+    ///
+    /// - Parameters:
+    ///   - message: The `MessageType` that will be displayed for this header.
+    ///   - indexPath: The `IndexPath` of the header.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this header will be displayed.
+    func messageHeaderView(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageReusableView
+
+    /// The section footer to use for a given `IndexPath`.
+    ///
+    /// - Parameters:
+    ///   - indexPath: The `IndexPath` of the footer.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this footer will be displayed.
+    func messageFooterView(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageReusableView
+    
+    /// Used to configure the `AvatarView`‘s image in a `MessageContentCell` class.
+    ///
+    /// - Parameters:
+    ///   - avatarView: The `AvatarView` of the cell.
+    ///   - message: The `MessageType` that will be displayed by this cell.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// - Note:
+    ///   The default image configured by this method is `?`.
+    func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView)
+
+    /// Used to configure the `AccessoryView` in a `MessageContentCell` class.
+    ///
+    /// - Parameters:
+    ///   - accessoryView: The `AccessoryView` of the cell.
+    ///   - message: The `MessageType` that will be displayed by this cell.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// - Note:
+    ///   The default image configured by this method is `?`.
+    func configureAccessoryView(_ accessoryView: UIView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView)
+
+    // MARK: - Text Messages
+
+    /// Specifies the color of the text for a `TextMessageCell`.
+    ///
+    /// - Parameters:
+    ///   - message: A `MessageType` with a `MessageKind` case of `.text` to which the color will apply.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// - Note:
+    ///   The default value returned by this method is determined by the messages `SenderType`.
+    ///
+    ///   Current sender: UIColor.white
+    ///
+    ///   All other senders: UIColor.darkText
+    func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor
+
+    /// Specifies the `DetectorType`s to check for the `MessageType`'s text against.
+    ///
+    /// - Parameters:
+    ///   - message: A `MessageType` with a `MessageKind` case of `.text` or `.attributedText` to which the detectors will apply.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// - Note:
+    ///   This method returns an empty array by default.
+    func enabledDetectors(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> [DetectorType]
+
+    /// Specifies the attributes for a given `DetectorType`
+    ///
+    /// - Parameters:
+    ///   - detector: The `DetectorType` for the applied attributes.
+    ///   - message: A `MessageType` with a `MessageKind` case of `.text` or `.attributedText` to which the detectors will apply.
+    ///   - indexPath: The `IndexPath` of the cell.
+    func detectorAttributes(for detector: DetectorType, and message: MessageType, at indexPath: IndexPath) -> [NSAttributedString.Key: Any]
+
+    // MARK: - Location Messages
+
+    /// Used to configure a `LocationMessageSnapshotOptions` instance to customize the map image on the given location message.
+    ///
+    /// - Parameters:
+    ///   - message: A `MessageType` with a `MessageKind` case of `.location`.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` requesting the information.
+    /// - Returns: The LocationMessageSnapshotOptions instance with the options to customize map style.
+    func snapshotOptionsForLocation(message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LocationMessageSnapshotOptions
+
+    /// Used to configure the annoation view of the map image on the given location message.
+    ///
+    /// - Parameters:
+    ///   - message: A `MessageType` with a `MessageKind` case of `.location`.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` requesting the information.
+    /// - Returns: The `MKAnnotationView` to use as the annotation view.
+    func annotationViewForLocation(message: MessageType, at indexPath: IndexPath, in messageCollectionView: MessagesCollectionView) -> MKAnnotationView?
+
+    /// Ask the delegate for a custom animation block to run when whe map screenshot is ready to be displaied in the given location message.
+    /// The animation block is called with the `UIImageView` to be animated.
+    ///
+    /// - Parameters:
+    ///   - message: A `MessageType` with a `MessageKind` case of `.location`.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` requesting the information.
+    /// - Returns: The animation block to use to apply the location image.
+    func animationBlockForLocation(message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> ((UIImageView) -> Void)?
+
+    // MARK: - Media Messages
+
+    /// Used to configure the `UIImageView` of a `MediaMessageCell.
+    ///
+    /// - Parameters:
+    ///   - imageView: The `UIImageView` of the cell.
+    ///   - message: The `MessageType` that will be displayed by this cell.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    func configureMediaMessageImageView(_ imageView: UIImageView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView)
+
+    // MARK: - Audio Message
+    
+    /// Used to configure the audio cell UI:
+    ///     1. play button selected state;
+    ///     2. progresssView progress;
+    ///     3. durationLabel text;
+    ///
+    /// - Parameters:
+    ///   - cell: The `AudioMessageCell` that needs to be configure.
+    ///   - message: The `MessageType` that configures the cell.
+    ///
+    /// - Note:
+    ///   This protocol method is called by MessageKit every time an audio cell needs to be configure
+    func configureAudioCell(_ cell: AudioMessageCell, message: MessageType)
+
+    /// Specifies the tint color of play button and progress bar for an `AudioMessageCell`.
+    ///
+    /// - Parameters:
+    ///   - message: A `MessageType` with a `MessageKind` case of `.audio` to which the color will apply.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// - Note:
+    ///   The default value returned by this method is UIColor.sendButtonBlue
+    func audioTintColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor
+
+    /// Used to format the audio sound duration in a readable string
+    ///
+    /// - Parameters:
+    ///   - duration: The audio sound duration is seconds.
+    ///   - audioCell: The `AudioMessageCell` that ask for formated duration.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// - Note:
+    ///   The default value is computed like fallow:
+    ///     1. return the time as 0:ss if duration is up to 59 seconds                         (e.g. 0:03     means 0 minutes and 3 seconds)
+    ///     2. return the time as m:ss if duration is greater than 59 and lower than 3600      (e.g. 12:23    means 12 mintues and 23 seconds)
+    ///     3. return the time as h:mm:ss for anything longer that 3600 seconds                (e.g. 1:19:08  means 1 hour 19 minutes and 8 seconds)
+    func audioProgressTextFormat(_ duration: Float, for audioCell: AudioMessageCell, in messageCollectionView: MessagesCollectionView) -> String
+
+}
+
+public extension MessagesDisplayDelegate {
+
+    // MARK: - All Messages Defaults
+
+    func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle {
+        return .bubble
+    }
+
+    func backgroundColor(for message: MessageType, at  indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
+
+        switch message.kind {
+        case .emoji:
+            return .clear
+        default:
+            guard let dataSource = messagesCollectionView.messagesDataSource else { return .white }
+            return dataSource.isFromCurrentSender(message: message) ? .outgoingGreen : .incomingGray
+        }
+    }
+    
+    func messageHeaderView(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageReusableView {
+        return messagesCollectionView.dequeueReusableHeaderView(MessageReusableView.self, for: indexPath)
+    }
+
+    func messageFooterView(for indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageReusableView {
+        return messagesCollectionView.dequeueReusableFooterView(MessageReusableView.self, for: indexPath)
+    }
+    
+    func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) {
+        avatarView.initials = "?"
+    }
+
+    func configureAccessoryView(_ accessoryView: UIView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) {}
+
+    // MARK: - Text Messages Defaults
+
+    func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
+        guard let dataSource = messagesCollectionView.messagesDataSource else {
+            fatalError(MessageKitError.nilMessagesDataSource)
+        }
+        return dataSource.isFromCurrentSender(message: message) ? .white : .darkText
+    }
+
+    func enabledDetectors(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> [DetectorType] {
+        return []
+    }
+
+    func detectorAttributes(for detector: DetectorType, and message: MessageType, at indexPath: IndexPath) -> [NSAttributedString.Key: Any] {
+        return MessageLabel.defaultAttributes
+    }
+
+    // MARK: - Location Messages Defaults
+
+    func snapshotOptionsForLocation(message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LocationMessageSnapshotOptions {
+        return LocationMessageSnapshotOptions()
+    }
+
+    func annotationViewForLocation(message: MessageType, at indexPath: IndexPath, in messageCollectionView: MessagesCollectionView) -> MKAnnotationView? {
+        return MKPinAnnotationView(annotation: nil, reuseIdentifier: nil)
+    }
+
+    func animationBlockForLocation(message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> ((UIImageView) -> Void)? {
+        return nil
+    }
+
+    // MARK: - Media Message Defaults
+
+    func configureMediaMessageImageView(_ imageView: UIImageView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) {
+    }
+
+    // MARK: - Audio Message Defaults
+    
+    func configureAudioCell(_ cell: AudioMessageCell, message: MessageType) {
+        
+    }
+
+    func audioTintColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
+        return UIColor.sendButtonBlue
+    }
+
+    func audioProgressTextFormat(_ duration: Float, for audioCell: AudioMessageCell, in messageCollectionView: MessagesCollectionView) -> String {
+        var retunValue = "0:00"
+        // print the time as 0:ss if duration is up to 59 seconds
+        // print the time as m:ss if duration is up to 59:59 seconds
+        // print the time as h:mm:ss for anything longer
+        if duration < 60 {
+            retunValue = String(format: "0:%.02d", Int(duration.rounded(.up)))
+        } else if duration < 3600 {
+            retunValue = String(format: "%.02d:%.02d", Int(duration/60), Int(duration) % 60)
+        } else {
+            let hours = Int(duration/3600)
+            let remainingMinutsInSeconds = Int(duration) - hours*3600
+            retunValue = String(format: "%.02d:%.02d:%.02d", hours, Int(remainingMinutsInSeconds/60), Int(remainingMinutsInSeconds) % 60)
+        }
+        return retunValue
+    }
+
+}

+ 165 - 0
deltachat-ios/MessageKit/Protocols/MessagesLayoutDelegate.swift

@@ -0,0 +1,165 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+/// A protocol used by the `MessagesCollectionViewFlowLayout` object to determine
+/// the size and layout of a `MessageCollectionViewCell` and its contents.
+public protocol MessagesLayoutDelegate: AnyObject {
+
+    /// Specifies the size to use for a header view.
+    ///
+    /// - Parameters:
+    ///   - section: The section number of the header.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this header will be displayed.
+    ///
+    /// - Note:
+    ///   The default value returned by this method is a size of `GGSize.zero`.
+    func headerViewSize(for section: Int, in messagesCollectionView: MessagesCollectionView) -> CGSize
+
+    /// Specifies the size to use for a footer view.
+    ///
+    /// - Parameters:
+    ///   - section: The section number of the footer.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this footer will be displayed.
+    ///
+    /// - Note:
+    ///   The default value returned by this method is a size of `GGSize.zero`.
+    func footerViewSize(for section: Int, in messagesCollectionView: MessagesCollectionView) -> CGSize
+
+    /// Specifies the size to use for a typing indicator view.
+    ///
+    /// - Parameters:
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this view will be displayed.
+    ///
+    /// - Note:
+    ///   The default value returned by this method is the width of the `messagesCollectionView` and
+    ///   a height of 52.
+    func typingIndicatorViewSize(in messagesCollectionView: MessagesCollectionView) -> CGSize
+
+    /// Specifies the top inset to use for a typing indicator view.
+    ///
+    /// - Parameters:
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this view will be displayed.
+    ///
+    /// - Note:
+    ///   The default value returned by this method is a top inset of 15.
+    func typingIndicatorViewTopInset(in messagesCollectionView: MessagesCollectionView) -> CGFloat
+
+    /// Specifies the height for the `MessageContentCell`'s top label.
+    ///
+    /// - Parameters:
+    ///   - message: The `MessageType` that will be displayed for this cell.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// - Note:
+    ///   The default value returned by this method is zero.
+    func cellTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat
+    
+    /// Specifies the height for the `MessageContentCell`'s bottom label.
+    ///
+    /// - Parameters:
+    ///   - message: The `MessageType` that will be displayed for this cell.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// - Note:
+    ///   The default value returned by this method is zero.
+    func cellBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat
+    
+    /// Specifies the height for the message bubble's top label.
+    ///
+    /// - Parameters:
+    ///   - message: The `MessageType` that will be displayed for this cell.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// - Note:
+    ///   The default value returned by this method is zero.
+    func messageTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat
+
+    /// Specifies the height for the `MessageContentCell`'s bottom label.
+    ///
+    /// - Parameters:
+    ///   - message: The `MessageType` that will be displayed for this cell.
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// - Note:
+    ///   The default value returned by this method is zero.
+    func messageBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat
+    
+    /// Custom cell size calculator for messages with MessageType.custom.
+    ///
+    /// - Parameters:
+    ///   - message: The custom message
+    ///   - indexPath: The `IndexPath` of the cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell will be displayed.
+    ///
+    /// - Note:
+    ///   The default implementation will throw fatalError(). You must override this method if you are using messages with MessageType.custom.
+    func customCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator
+}
+
+public extension MessagesLayoutDelegate {
+
+    func headerViewSize(for section: Int, in messagesCollectionView: MessagesCollectionView) -> CGSize {
+        return .zero
+    }
+
+    func footerViewSize(for section: Int, in messagesCollectionView: MessagesCollectionView) -> CGSize {
+        return .zero
+    }
+
+    func typingIndicatorViewSize(in messagesCollectionView: MessagesCollectionView) -> CGSize {
+        return CGSize(width: messagesCollectionView.bounds.width, height: 48)
+    }
+
+    func typingIndicatorViewTopInset(in messagesCollectionView: MessagesCollectionView) -> CGFloat {
+        return 15
+    }
+
+    func cellTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
+        return 0
+    }
+    
+    func cellBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
+        return 0
+    }
+
+    func messageTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
+        return 0
+    }
+
+    func messageBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
+        return 0
+    }
+
+    func customCellSizeCalculator(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CellSizeCalculator {
+        fatalError("Must return a CellSizeCalculator for MessageKind.custom(Any?)")
+    }
+}
+

+ 38 - 0
deltachat-ios/MessageKit/Protocols/SenderType.swift

@@ -0,0 +1,38 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+
+/// A standard protocol representing a sender.
+/// Use this protocol to adhere a object as the sender of a MessageType
+public protocol SenderType {
+
+    /// The unique String identifier for the sender.
+    ///
+    /// Note: This value must be unique across all senders.
+    var senderId: String { get }
+
+    /// The display name of a sender.
+    var displayName: String { get }
+}

+ 55 - 0
deltachat-ios/MessageKit/Supporting/MessageInputBar.swift

@@ -0,0 +1,55 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+import InputBarAccessoryView
+
+@available(*, obsoleted: 3.0.0, renamed: "InputBarAccessoryView")
+public typealias MessageInputBar = InputBarAccessoryView
+
+@available(*, obsoleted: 3.0.0, renamed: "InputBarAccessoryViewDelegate")
+public typealias MessageInputBarDelegate = InputBarAccessoryViewDelegate
+
+//public extension MessageInputBarDelegate {
+//
+//    @available(*, obsoleted: 3.0.0, message: "`MessageInputBar` has been replaced with `InputBarAccessoryView` in 3.0.0. Use `inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String)` instead.")
+//    func messageInputBar(_ inputBar: MessageInputBar, didPressSendButtonWith text: String) {
+//    }
+//
+//    @available(*, obsoleted: 3.0.0, message: "`MessageInputBar` has been replaced with `InputBarAccessoryView` in 3.0.0. Use `inputBar(_ inputBar: InputBarAccessoryView, textViewTextDidChangeTo text: String)` instead.")
+//    func messageInputBar(_ inputBar: MessageInputBar, textViewTextDidChangeTo text: String) {
+//    }
+//
+//    @available(*, obsoleted: 3.0.0, message: "`MessageInputBar` has been replaced with `InputBarAccessoryView` in 3.0.0. Use `inputBar(_ inputBar: InputBarAccessoryView, didChangeIntrinsicContentTo size: CGSize)` instead.")
+//    func messageInputBar(_ inputBar: MessageInputBar, didChangeIntrinsicContentTo size: CGSize) {
+//    }
+//}
+//
+//extension InputBarButtonItem {
+//
+//    @available(*, renamed: "inputBarAccessoryView")
+//    public var messageInputBar: MessageInputBar? {
+//        return inputBarAccessoryView
+//    }
+//}

+ 73 - 0
deltachat-ios/MessageKit/Supporting/MessageKit+Availability.swift

@@ -0,0 +1,73 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+public extension MessagesLayoutDelegate {
+
+    func avatarSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
+        fatalError("avatarSize(for:at:in) has been removed in MessageKit 1.0.")
+    }
+
+    func avatarPosition(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition {
+        fatalError("avatarPosition(for:at:in) has been removed in MessageKit 1.0.")
+    }
+
+    func messageLabelInset(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIEdgeInsets {
+        fatalError("messageLabelInset(for:at:in) has been removed in MessageKit 1.0")
+    }
+
+    func messagePadding(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIEdgeInsets {
+        fatalError("messagePadding(for:at:in) has been removed in MessageKit 1.0.")
+    }
+
+    func cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment {
+        fatalError("cellTopLabelAlignment(for:at:in) has been removed in MessageKit 1.0.")
+    }
+
+    func cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment {
+        fatalError("cellBottomLabelAlignment(for:at:in) has been removed in MessageKit 1.0.")
+    }
+
+    func widthForMedia(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
+        fatalError("widthForMedia(message:at:with:in) has been removed in MessageKit 1.0.")
+    }
+
+    func heightForMedia(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
+        fatalError("heightForMedia(message:at:with:in) has been removed in MessageKit 1.0.")
+    }
+
+    func widthForLocation(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
+        fatalError("widthForLocation(message:at:with:in) has been removed in MessageKit 1.0.")
+    }
+
+   func heightForLocation(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
+        fatalError("heightForLocation(message:at:with:in) has been removed in MessageKit 1.0.")
+    }
+
+    func shouldCacheLayoutAttributes(for message: MessageType) -> Bool {
+        fatalError("shouldCacheLayoutAttributes(for:) has been removed in MessageKit 1.0.")
+    }
+}

+ 29 - 0
deltachat-ios/MessageKit/Supporting/MessageKit.h

@@ -0,0 +1,29 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+#import <UIKit/UIKit.h>
+
+FOUNDATION_EXPORT double MessageKitVersionNumber;
+
+FOUNDATION_EXPORT const unsigned char MessageKitVersionString[];

+ 203 - 0
deltachat-ios/MessageKit/Views/AvatarView.swift

@@ -0,0 +1,203 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+open class AvatarView: UIImageView {
+
+    // MARK: - Properties
+    
+    open var initials: String? {
+        didSet {
+            setImageFrom(initials: initials)
+        }
+    }
+
+    open var placeholderFont: UIFont = UIFont.preferredFont(forTextStyle: .caption1) {
+        didSet {
+            setImageFrom(initials: initials)
+        }
+    }
+
+    open var placeholderTextColor: UIColor = .white {
+        didSet {
+            setImageFrom(initials: initials)
+        }
+    }
+
+    open var fontMinimumScaleFactor: CGFloat = 0.5
+
+    open var adjustsFontSizeToFitWidth = true
+
+    private var minimumFontSize: CGFloat {
+        return placeholderFont.pointSize * fontMinimumScaleFactor
+    }
+
+    private var radius: CGFloat?
+
+    // MARK: - Overridden Properties
+    open override var frame: CGRect {
+        didSet {
+            setCorner(radius: self.radius)
+        }
+    }
+
+    open override var bounds: CGRect {
+        didSet {
+            setCorner(radius: self.radius)
+        }
+    }
+
+    // MARK: - Initializers
+    public override init(frame: CGRect) {
+        super.init(frame: frame)
+        prepareView()
+    }
+    
+    required public init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+        prepareView()
+    }
+
+    convenience public init() {
+        self.init(frame: .zero)
+        prepareView()
+    }
+    
+    private func setImageFrom(initials: String?) {
+        guard let initials = initials else { return }
+        image = getImageFrom(initials: initials)
+    }
+
+    private func getImageFrom(initials: String) -> UIImage {
+        let width = frame.width
+        let height = frame.height
+        if width == 0 || height == 0 {return UIImage()}
+        var font = placeholderFont
+
+        _ = UIGraphicsBeginImageContextWithOptions(CGSize(width: width, height: height), false, UIScreen.main.scale)
+        defer { UIGraphicsEndImageContext() }
+        let context = UIGraphicsGetCurrentContext()!
+
+        //// Text Drawing
+        let textRect = calculateTextRect(outerViewWidth: width, outerViewHeight: height)
+        let initialsText = NSAttributedString(string: initials, attributes: [.font: font])
+        if adjustsFontSizeToFitWidth,
+            initialsText.width(considering: textRect.height) > textRect.width {
+            let newFontSize = calculateFontSize(text: initials, font: font, width: textRect.width, height: textRect.height)
+            font = placeholderFont.withSize(newFontSize)
+        }
+
+        let textStyle = NSMutableParagraphStyle()
+        textStyle.alignment = .center
+        let textFontAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: font,
+                                                                 NSAttributedString.Key.foregroundColor: placeholderTextColor,
+                                                                 NSAttributedString.Key.paragraphStyle: textStyle]
+
+        let textTextHeight: CGFloat = initials.boundingRect(with: CGSize(width: textRect.width, height: CGFloat.infinity),
+                                                            options: .usesLineFragmentOrigin,
+                                                            attributes: textFontAttributes,
+                                                            context: nil).height
+        context.saveGState()
+        context.clip(to: textRect)
+        initials.draw(in: CGRect(textRect.minX, textRect.minY + (textRect.height - textTextHeight) / 2, textRect.width, textTextHeight), withAttributes: textFontAttributes)
+        context.restoreGState()
+        guard let renderedImage = UIGraphicsGetImageFromCurrentImageContext() else { assertionFailure("Could not create image from context"); return UIImage()}
+        return renderedImage
+    }
+
+    /**
+     Recursively find the biggest size to fit the text with a given width and height
+     */
+    private func calculateFontSize(text: String, font: UIFont, width: CGFloat, height: CGFloat) -> CGFloat {
+        let attributedText = NSAttributedString(string: text, attributes: [.font: font])
+        if attributedText.width(considering: height) > width {
+            let newFont = font.withSize(font.pointSize - 1)
+            if newFont.pointSize > minimumFontSize {
+                return font.pointSize
+            } else {
+                return calculateFontSize(text: text, font: newFont, width: width, height: height)
+            }
+        }
+        return font.pointSize
+    }
+
+    /**
+     Calculates the inner circle's width.
+     Note: Assumes corner radius cannot be more than width/2 (this creates circle).
+     */
+    private func calculateTextRect(outerViewWidth: CGFloat, outerViewHeight: CGFloat) -> CGRect {
+        guard outerViewWidth > 0 else {
+            return CGRect.zero
+        }
+        let shortEdge = min(outerViewHeight, outerViewWidth)
+        // Converts degree to radian degree and calculate the
+        // Assumes, it is a perfect circle based on the shorter part of ellipsoid
+        // calculate a rectangle
+        let w = shortEdge * sin(CGFloat(45).degreesToRadians) * 2
+        let h = shortEdge * cos(CGFloat(45).degreesToRadians) * 2
+        let startX = (outerViewWidth - w)/2
+        let startY = (outerViewHeight - h)/2
+        // In case the font exactly fits to the region, put 2 pixel both left and right
+        return CGRect(startX+2, startY, w-4, h)
+    }
+
+    // MARK: - Internal methods
+
+    internal func prepareView() {
+        backgroundColor = .gray
+        contentMode = .scaleAspectFill
+        layer.masksToBounds = true
+        clipsToBounds = true
+        setCorner(radius: nil)
+    }
+
+    // MARK: - Open setters
+    
+    open func set(avatar: Avatar) {
+        if let image = avatar.image {
+            self.image = image
+        } else {
+            initials = avatar.initials
+        }
+    }
+
+    open func setCorner(radius: CGFloat?) {
+        guard let radius = radius else {
+            //if corner radius not set default to Circle
+            let cornerRadius = min(frame.width, frame.height)
+            layer.cornerRadius = cornerRadius/2
+            return
+        }
+        self.radius = radius
+        layer.cornerRadius = radius
+    }
+
+}
+
+fileprivate extension FloatingPoint {
+    var degreesToRadians: Self { return self * .pi / 180 }
+    var radiansToDegrees: Self { return self * 180 / .pi }
+}

+ 49 - 0
deltachat-ios/MessageKit/Views/BubbleCircle.swift

@@ -0,0 +1,49 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+/// A `UIView` subclass that maintains a mask to keep it fully circular
+open class BubbleCircle: UIView {
+    
+    /// Lays out subviews and applys a circular mask to the layer
+    open override func layoutSubviews() {
+        super.layoutSubviews()
+        layer.mask = roundedMask(corners: .allCorners, radius: bounds.height / 2)
+    }
+    
+    /// Returns a rounded mask of the view
+    ///
+    /// - Parameters:
+    ///   - corners: The corners to round
+    ///   - radius: The radius of curve
+    /// - Returns: A mask
+    open func roundedMask(corners: UIRectCorner, radius: CGFloat) -> CAShapeLayer {
+        let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
+        let mask = CAShapeLayer()
+        mask.path = path.cgPath
+        return mask
+    }
+    
+}

+ 130 - 0
deltachat-ios/MessageKit/Views/Cells/AudioMessageCell.swift

@@ -0,0 +1,130 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+import AVFoundation
+
+/// A subclass of `MessageContentCell` used to display video and audio messages.
+open class AudioMessageCell: MessageContentCell {
+
+    /// The play button view to display on audio messages.
+    public lazy var playButton: UIButton = {
+        let playButton = UIButton(type: .custom)
+        let playImage = UIImage.messageKitImageWith(type: .play)
+        let pauseImage = UIImage.messageKitImageWith(type: .pause)
+        playButton.setImage(playImage?.withRenderingMode(.alwaysTemplate), for: .normal)
+        playButton.setImage(pauseImage?.withRenderingMode(.alwaysTemplate), for: .selected)
+        return playButton
+    }()
+
+    /// The time duration lable to display on audio messages.
+    public lazy var durationLabel: UILabel = {
+        let durationLabel = UILabel(frame: CGRect.zero)
+        durationLabel.textAlignment = .right
+        durationLabel.font = UIFont.systemFont(ofSize: 14)
+        durationLabel.text = "0:00"
+        return durationLabel
+    }()
+
+    public lazy var progressView: UIProgressView = {
+        let progressView = UIProgressView(progressViewStyle: .default)
+        progressView.progress = 0.0
+        return progressView
+    }()
+
+    // MARK: - Methods
+
+    /// Responsible for setting up the constraints of the cell's subviews.
+    open func setupConstraints() {
+        playButton.constraint(equalTo: CGSize(width: 25, height: 25))
+        playButton.addConstraints(left: messageContainerView.leftAnchor, centerY: messageContainerView.centerYAnchor, leftConstant: 5)
+        durationLabel.addConstraints(right: messageContainerView.rightAnchor, centerY: messageContainerView.centerYAnchor, rightConstant: 15)
+        progressView.addConstraints(left: playButton.rightAnchor, right: durationLabel.leftAnchor, centerY: messageContainerView.centerYAnchor, leftConstant: 5, rightConstant: 5)
+    }
+
+    open override func setupSubviews() {
+        super.setupSubviews()
+        messageContainerView.addSubview(playButton)
+        messageContainerView.addSubview(durationLabel)
+        messageContainerView.addSubview(progressView)
+        setupConstraints()
+    }
+
+    open override func prepareForReuse() {
+        super.prepareForReuse()
+        progressView.progress = 0
+        playButton.isSelected = false
+        durationLabel.text = "0:00"
+    }
+
+    /// Handle tap gesture on contentView and its subviews.
+    open override func handleTapGesture(_ gesture: UIGestureRecognizer) {
+        let touchLocation = gesture.location(in: self)
+        // compute play button touch area, currently play button size is (25, 25) which is hardly touchable
+        // add 10 px around current button frame and test the touch against this new frame
+        let playButtonTouchArea = CGRect(playButton.frame.origin.x - 10.0, playButton.frame.origin.y - 10, playButton.frame.size.width + 20, playButton.frame.size.height + 20)
+        let translateTouchLocation = convert(touchLocation, to: messageContainerView)
+        if playButtonTouchArea.contains(translateTouchLocation) {
+            delegate?.didTapPlayButton(in: self)
+        } else {
+            super.handleTapGesture(gesture)
+        }
+    }
+
+    // MARK: - Configure Cell
+
+    open override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {
+        super.configure(with: message, at: indexPath, and: messagesCollectionView)
+
+        guard let dataSource = messagesCollectionView.messagesDataSource else {
+            fatalError(MessageKitError.nilMessagesDataSource)
+        }
+
+        let playButtonLeftConstraint = messageContainerView.constraints.filter { $0.identifier == "left" }.first
+        let durationLabelRightConstraint = messageContainerView.constraints.filter { $0.identifier == "right" }.first
+
+        if !dataSource.isFromCurrentSender(message: message) {
+            playButtonLeftConstraint?.constant = 12
+            durationLabelRightConstraint?.constant = -8
+        } else {
+            playButtonLeftConstraint?.constant = 5
+            durationLabelRightConstraint?.constant = -15
+        }
+
+        guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else {
+            fatalError(MessageKitError.nilMessagesDisplayDelegate)
+        }
+
+        let tintColor = displayDelegate.audioTintColor(for: message, at: indexPath, in: messagesCollectionView)
+        playButton.imageView?.tintColor = tintColor
+        durationLabel.textColor = tintColor
+        progressView.tintColor = tintColor
+
+        displayDelegate.configureAudioCell(self, message: message)
+
+        if case let .audio(audioItem) = message.kind {
+            durationLabel.text = displayDelegate.audioProgressTextFormat(audioItem.duration, for: self, in: messagesCollectionView)
+        }
+    }
+}

+ 142 - 0
deltachat-ios/MessageKit/Views/Cells/ContactMessageCell.swift

@@ -0,0 +1,142 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2018 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+open class ContactMessageCell: MessageContentCell {
+    
+    public enum ConstraintsID: String {
+        case initialsContainerLeftConstraint
+        case disclouserRigtConstraint
+    }
+    
+    /// The view container that holds contact initials
+    public lazy var initialsContainerView: UIView = {
+        let initialsContainer = UIView(frame: CGRect.zero)
+        initialsContainer.backgroundColor = .white
+        return initialsContainer
+    }()
+    
+    /// The label that display the contact initials
+    public lazy var initialsLabel: UILabel = {
+        let initialsLabel = UILabel(frame: CGRect.zero)
+        initialsLabel.textAlignment = .center
+        initialsLabel.textColor = .darkText
+        initialsLabel.font = UIFont.preferredFont(forTextStyle: .footnote)
+        return initialsLabel
+    }()
+    
+    /// The label that display contact name
+    public lazy var nameLabel: UILabel = {
+        let nameLabel = UILabel(frame: CGRect.zero)
+        nameLabel.numberOfLines = 0
+        return nameLabel
+    }()
+    
+    /// The disclouser image view
+    public lazy var disclosureImageView: UIImageView = {
+        let disclouserImage = UIImage.messageKitImageWith(type: .disclouser)?.withRenderingMode(.alwaysTemplate)
+        let disclouser = UIImageView(image: disclouserImage)
+        return disclouser
+    }()
+    
+    // MARK: - Methods
+    open override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
+        super.apply(layoutAttributes)
+        guard let attributes = layoutAttributes as? MessagesCollectionViewLayoutAttributes else {
+            return
+        }
+        nameLabel.font = attributes.messageLabelFont
+    }
+
+    open override func setupSubviews() {
+        super.setupSubviews()
+        messageContainerView.addSubview(initialsContainerView)
+        messageContainerView.addSubview(nameLabel)
+        messageContainerView.addSubview(disclosureImageView)
+        initialsContainerView.addSubview(initialsLabel)
+        setupConstraints()
+    }
+    
+    open override func prepareForReuse() {
+        super.prepareForReuse()
+        nameLabel.text = ""
+        initialsLabel.text = ""
+    }
+    
+    open func setupConstraints() {
+        initialsContainerView.constraint(equalTo: CGSize(width: 26, height: 26))
+        let initialsConstraints = initialsContainerView.addConstraints(left: messageContainerView.leftAnchor, centerY: messageContainerView.centerYAnchor,
+                                                        leftConstant: 5)
+        initialsConstraints.first?.identifier = ConstraintsID.initialsContainerLeftConstraint.rawValue
+        initialsContainerView.layer.cornerRadius = 13
+        initialsLabel.fillSuperview()
+        disclosureImageView.constraint(equalTo: CGSize(width: 20, height: 20))
+        let disclosureConstraints = disclosureImageView.addConstraints(right: messageContainerView.rightAnchor, centerY: messageContainerView.centerYAnchor,
+                                           rightConstant: -10)
+        disclosureConstraints.first?.identifier = ConstraintsID.disclouserRigtConstraint.rawValue
+        nameLabel.addConstraints(messageContainerView.topAnchor,
+                                 left: initialsContainerView.rightAnchor,
+                                 bottom: messageContainerView.bottomAnchor,
+                                 right: disclosureImageView.leftAnchor,
+                                 topConstant: 0,
+                                 leftConstant: 10,
+                                 bottomConstant: 0,
+                                 rightConstant: 5)
+    }
+    
+    // MARK: - Configure Cell
+    open override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {
+        super.configure(with: message, at: indexPath, and: messagesCollectionView)
+        // setup data
+        guard case let .contact(contactItem) = message.kind else { fatalError("Failed decorate audio cell") }
+        nameLabel.text = contactItem.displayName
+        initialsLabel.text = contactItem.initials
+        // setup constraints
+        guard let dataSource = messagesCollectionView.messagesDataSource else {
+            fatalError(MessageKitError.nilMessagesDataSource)
+        }
+        let initialsContainerLeftConstraint = messageContainerView.constraints.filter { (constraint) -> Bool in
+            return constraint.identifier == ConstraintsID.initialsContainerLeftConstraint.rawValue
+        }.first
+        let disclouserRightConstraint = messageContainerView.constraints.filter { (constraint) -> Bool in
+            return constraint.identifier == ConstraintsID.disclouserRigtConstraint.rawValue
+        }.first
+        if dataSource.isFromCurrentSender(message: message) { // outgoing message
+            initialsContainerLeftConstraint?.constant = 5
+            disclouserRightConstraint?.constant = -10
+        } else { // incoming message
+            initialsContainerLeftConstraint?.constant = 10
+            disclouserRightConstraint?.constant = -5
+        }
+        // setup colors
+        guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else {
+            fatalError(MessageKitError.nilMessagesDisplayDelegate)
+        }
+        let textColor = displayDelegate.textColor(for: message, at: indexPath, in: messagesCollectionView)
+        nameLabel.textColor = textColor
+        disclosureImageView.tintColor = textColor
+    }
+    
+}

+ 111 - 0
deltachat-ios/MessageKit/Views/Cells/LocationMessageCell.swift

@@ -0,0 +1,111 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+import MapKit
+
+/// A subclass of `MessageContentCell` used to display location messages.
+open class LocationMessageCell: MessageContentCell {
+
+    /// The activity indicator to be displayed while the map image is loading.
+    open var activityIndicator = UIActivityIndicatorView(style: .gray)
+
+    /// The image view holding the map image.
+    open var imageView = UIImageView()
+    
+    private weak var snapShotter: MKMapSnapshotter?
+
+    open override func setupSubviews() {
+        super.setupSubviews()
+        imageView.contentMode = .scaleAspectFill
+        messageContainerView.addSubview(imageView)
+        messageContainerView.addSubview(activityIndicator)
+        setupConstraints()
+    }
+
+    /// Responsible for setting up the constraints of the cell's subviews.
+    open func setupConstraints() {
+        imageView.fillSuperview()
+        activityIndicator.centerInSuperview()
+    }
+    
+    open override func prepareForReuse() {
+        super.prepareForReuse()
+        snapShotter?.cancel()
+    }
+
+    open override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {
+        super.configure(with: message, at: indexPath, and: messagesCollectionView)
+        guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else {
+            fatalError(MessageKitError.nilMessagesDisplayDelegate)
+        }
+        let options = displayDelegate.snapshotOptionsForLocation(message: message, at: indexPath, in: messagesCollectionView)
+        let annotationView = displayDelegate.annotationViewForLocation(message: message, at: indexPath, in: messagesCollectionView)
+        let animationBlock = displayDelegate.animationBlockForLocation(message: message, at: indexPath, in: messagesCollectionView)
+
+        guard case let .location(locationItem) = message.kind else { fatalError("") }
+
+        activityIndicator.startAnimating()
+
+        let snapshotOptions = MKMapSnapshotter.Options()
+        snapshotOptions.region = MKCoordinateRegion(center: locationItem.location.coordinate, span: options.span)
+        snapshotOptions.showsBuildings = options.showsBuildings
+        snapshotOptions.showsPointsOfInterest = options.showsPointsOfInterest
+
+        let snapShotter = MKMapSnapshotter(options: snapshotOptions)
+        self.snapShotter = snapShotter
+        snapShotter.start { (snapshot, error) in
+            defer {
+                self.activityIndicator.stopAnimating()
+            }
+            guard let snapshot = snapshot, error == nil else {
+                //show an error image?
+                return
+            }
+
+            guard let annotationView = annotationView else {
+                self.imageView.image = snapshot.image
+                return
+            }
+
+            UIGraphicsBeginImageContextWithOptions(snapshotOptions.size, true, 0)
+
+            snapshot.image.draw(at: .zero)
+
+            var point = snapshot.point(for: locationItem.location.coordinate)
+            //Move point to reflect annotation anchor
+            point.x -= annotationView.bounds.size.width / 2
+            point.y -= annotationView.bounds.size.height / 2
+            point.x += annotationView.centerOffset.x
+            point.y += annotationView.centerOffset.y
+
+            annotationView.image?.draw(at: point)
+            let composedImage = UIGraphicsGetImageFromCurrentImageContext()
+
+            UIGraphicsEndImageContext()
+            self.imageView.image = composedImage
+            animationBlock?(self.imageView)
+        }
+    }
+}

+ 84 - 0
deltachat-ios/MessageKit/Views/Cells/MediaMessageCell.swift

@@ -0,0 +1,84 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+/// A subclass of `MessageContentCell` used to display video and audio messages.
+open class MediaMessageCell: MessageContentCell {
+
+    /// The play button view to display on video messages.
+    open lazy var playButtonView: PlayButtonView = {
+        let playButtonView = PlayButtonView()
+        return playButtonView
+    }()
+
+    /// The image view display the media content.
+    open var imageView: UIImageView = {
+        let imageView = UIImageView()
+        imageView.contentMode = .scaleAspectFill
+        return imageView
+    }()
+
+    // MARK: - Methods
+
+    /// Responsible for setting up the constraints of the cell's subviews.
+    open func setupConstraints() {
+        imageView.fillSuperview()
+        playButtonView.centerInSuperview()
+        playButtonView.constraint(equalTo: CGSize(width: 35, height: 35))
+    }
+
+    open override func setupSubviews() {
+        super.setupSubviews()
+        messageContainerView.addSubview(imageView)
+        messageContainerView.addSubview(playButtonView)
+        setupConstraints()
+    }
+    
+    open override func prepareForReuse() {
+        super.prepareForReuse()
+        self.imageView.image = nil
+    }
+
+    open override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {
+        super.configure(with: message, at: indexPath, and: messagesCollectionView)
+
+        guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else {
+            fatalError(MessageKitError.nilMessagesDisplayDelegate)
+        }
+
+        switch message.kind {
+        case .photo(let mediaItem):
+            imageView.image = mediaItem.image ?? mediaItem.placeholderImage
+            playButtonView.isHidden = true
+        case .video(let mediaItem):
+            imageView.image = mediaItem.image ?? mediaItem.placeholderImage
+            playButtonView.isHidden = false
+        default:
+            break
+        }
+
+        displayDelegate.configureMediaMessageImageView(imageView, for: message, at: indexPath, in: messagesCollectionView)
+    }
+}

+ 45 - 0
deltachat-ios/MessageKit/Views/Cells/MessageCollectionViewCell.swift

@@ -0,0 +1,45 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+/// A subclass of `UICollectionViewCell` to be used inside of a `MessagesCollectionView`.
+open class MessageCollectionViewCell: UICollectionViewCell {
+
+    // MARK: - Initializers
+
+    public override init(frame: CGRect) {
+        super.init(frame: frame)
+    }
+
+    public required init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+    }
+
+    /// Handle tap gesture on contentView and its subviews.
+    open func handleTapGesture(_ gesture: UIGestureRecognizer) {
+        // Should be overridden
+    }
+
+}

+ 348 - 0
deltachat-ios/MessageKit/Views/Cells/MessageContentCell.swift

@@ -0,0 +1,348 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+/// A subclass of `MessageCollectionViewCell` used to display text, media, and location messages.
+open class MessageContentCell: MessageCollectionViewCell {
+
+    /// The image view displaying the avatar.
+    open var avatarView = AvatarView()
+
+    /// The container used for styling and holding the message's content view.
+    open var messageContainerView: MessageContainerView = {
+        let containerView = MessageContainerView()
+        containerView.clipsToBounds = true
+        containerView.layer.masksToBounds = true
+        return containerView
+    }()
+
+    /// The top label of the cell.
+    open var cellTopLabel: InsetLabel = {
+        let label = InsetLabel()
+        label.numberOfLines = 0
+        label.textAlignment = .center
+        return label
+    }()
+    
+    /// The bottom label of the cell.
+    open var cellBottomLabel: InsetLabel = {
+        let label = InsetLabel()
+        label.numberOfLines = 0
+        label.textAlignment = .center
+        return label
+    }()
+    
+    /// The top label of the messageBubble.
+    open var messageTopLabel: InsetLabel = {
+        let label = InsetLabel()
+        label.numberOfLines = 0
+        return label
+    }()
+
+    /// The bottom label of the messageBubble.
+    open var messageBottomLabel: InsetLabel = {
+        let label = InsetLabel()
+        label.numberOfLines = 0
+        return label
+    }()
+
+    // Should only add customized subviews - don't change accessoryView itself.
+    open var accessoryView: UIView = UIView()
+
+    /// The `MessageCellDelegate` for the cell.
+    open weak var delegate: MessageCellDelegate?
+
+    public override init(frame: CGRect) {
+        super.init(frame: frame)
+        contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+        setupSubviews()
+    }
+
+    required public init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+        contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+        setupSubviews()
+    }
+
+    open func setupSubviews() {
+        contentView.addSubview(accessoryView)
+        contentView.addSubview(cellTopLabel)
+        contentView.addSubview(messageTopLabel)
+        contentView.addSubview(messageBottomLabel)
+        contentView.addSubview(cellBottomLabel)
+        contentView.addSubview(messageContainerView)
+        contentView.addSubview(avatarView)
+    }
+
+    open override func prepareForReuse() {
+        super.prepareForReuse()
+        cellTopLabel.text = nil
+        cellBottomLabel.text = nil
+        messageTopLabel.text = nil
+        messageBottomLabel.text = nil
+    }
+
+    // MARK: - Configuration
+
+    open override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
+        super.apply(layoutAttributes)
+        guard let attributes = layoutAttributes as? MessagesCollectionViewLayoutAttributes else { return }
+        // Call this before other laying out other subviews
+        layoutMessageContainerView(with: attributes)
+        layoutMessageBottomLabel(with: attributes)
+        layoutCellBottomLabel(with: attributes)
+        layoutCellTopLabel(with: attributes)
+        layoutMessageTopLabel(with: attributes)
+        layoutAvatarView(with: attributes)
+        layoutAccessoryView(with: attributes)
+    }
+
+    /// Used to configure the cell.
+    ///
+    /// - Parameters:
+    ///   - message: The `MessageType` this cell displays.
+    ///   - indexPath: The `IndexPath` for this cell.
+    ///   - messagesCollectionView: The `MessagesCollectionView` in which this cell is contained.
+    open func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {
+        guard let dataSource = messagesCollectionView.messagesDataSource else {
+            fatalError(MessageKitError.nilMessagesDataSource)
+        }
+        guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else {
+            fatalError(MessageKitError.nilMessagesDisplayDelegate)
+        }
+
+        delegate = messagesCollectionView.messageCellDelegate
+
+        let messageColor = displayDelegate.backgroundColor(for: message, at: indexPath, in: messagesCollectionView)
+        let messageStyle = displayDelegate.messageStyle(for: message, at: indexPath, in: messagesCollectionView)
+
+        displayDelegate.configureAvatarView(avatarView, for: message, at: indexPath, in: messagesCollectionView)
+
+        displayDelegate.configureAccessoryView(accessoryView, for: message, at: indexPath, in: messagesCollectionView)
+
+        messageContainerView.backgroundColor = messageColor
+        messageContainerView.style = messageStyle
+
+        let topCellLabelText = dataSource.cellTopLabelAttributedText(for: message, at: indexPath)
+        let bottomCellLabelText = dataSource.cellBottomLabelAttributedText(for: message, at: indexPath)
+        let topMessageLabelText = dataSource.messageTopLabelAttributedText(for: message, at: indexPath)
+        let bottomMessageLabelText = dataSource.messageBottomLabelAttributedText(for: message, at: indexPath)
+
+        cellTopLabel.attributedText = topCellLabelText
+        cellBottomLabel.attributedText = bottomCellLabelText
+        messageTopLabel.attributedText = topMessageLabelText
+        messageBottomLabel.attributedText = bottomMessageLabelText
+    }
+
+    /// Handle tap gesture on contentView and its subviews.
+    open override func handleTapGesture(_ gesture: UIGestureRecognizer) {
+        let touchLocation = gesture.location(in: self)
+
+        switch true {
+        case messageContainerView.frame.contains(touchLocation) && !cellContentView(canHandle: convert(touchLocation, to: messageContainerView)):
+            delegate?.didTapMessage(in: self)
+        case avatarView.frame.contains(touchLocation):
+            delegate?.didTapAvatar(in: self)
+        case cellTopLabel.frame.contains(touchLocation):
+            delegate?.didTapCellTopLabel(in: self)
+        case cellBottomLabel.frame.contains(touchLocation):
+            delegate?.didTapCellBottomLabel(in: self)
+        case messageTopLabel.frame.contains(touchLocation):
+            delegate?.didTapMessageTopLabel(in: self)
+        case messageBottomLabel.frame.contains(touchLocation):
+            delegate?.didTapMessageBottomLabel(in: self)
+        case accessoryView.frame.contains(touchLocation):
+            delegate?.didTapAccessoryView(in: self)
+        default:
+            delegate?.didTapBackground(in: self)
+        }
+    }
+
+    /// Handle long press gesture, return true when gestureRecognizer's touch point in `messageContainerView`'s frame
+    open override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
+        let touchPoint = gestureRecognizer.location(in: self)
+        guard gestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) else { return false }
+        return messageContainerView.frame.contains(touchPoint)
+    }
+
+    /// Handle `ContentView`'s tap gesture, return false when `ContentView` doesn't needs to handle gesture
+    open func cellContentView(canHandle touchPoint: CGPoint) -> Bool {
+        return false
+    }
+
+    // MARK: - Origin Calculations
+
+    /// Positions the cell's `AvatarView`.
+    /// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
+    open func layoutAvatarView(with attributes: MessagesCollectionViewLayoutAttributes) {
+        var origin: CGPoint = .zero
+        let padding = attributes.avatarLeadingTrailingPadding
+
+        switch attributes.avatarPosition.horizontal {
+        case .cellLeading:
+            origin.x = padding
+        case .cellTrailing:
+            origin.x = attributes.frame.width - attributes.avatarSize.width - padding
+        case .natural:
+            fatalError(MessageKitError.avatarPositionUnresolved)
+        }
+
+        switch attributes.avatarPosition.vertical {
+        case .messageLabelTop:
+            origin.y = messageTopLabel.frame.minY
+        case .messageTop: // Needs messageContainerView frame to be set
+            origin.y = messageContainerView.frame.minY
+        case .messageBottom: // Needs messageContainerView frame to be set
+            origin.y = messageContainerView.frame.maxY - attributes.avatarSize.height
+        case .messageCenter: // Needs messageContainerView frame to be set
+            origin.y = messageContainerView.frame.midY - (attributes.avatarSize.height/2)
+        case .cellBottom:
+            origin.y = attributes.frame.height - attributes.avatarSize.height
+        default:
+            break
+        }
+
+        avatarView.frame = CGRect(origin: origin, size: attributes.avatarSize)
+    }
+
+    /// Positions the cell's `MessageContainerView`.
+    /// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
+    open func layoutMessageContainerView(with attributes: MessagesCollectionViewLayoutAttributes) {
+        var origin: CGPoint = .zero
+
+        switch attributes.avatarPosition.vertical {
+        case .messageBottom:
+            origin.y = attributes.size.height -
+                attributes.messageContainerPadding.bottom -
+                attributes.cellBottomLabelSize.height -
+                attributes.messageBottomLabelSize.height -
+                attributes.messageContainerSize.height -
+                attributes.messageContainerPadding.top
+        case .messageCenter:
+            if attributes.avatarSize.height > attributes.messageContainerSize.height {
+                let messageHeight = attributes.messageContainerSize.height + attributes.messageContainerPadding.vertical
+                origin.y = (attributes.size.height / 2) - (messageHeight / 2)
+            } else {
+                fallthrough
+            }
+        default:
+            if attributes.accessoryViewSize.height > attributes.messageContainerSize.height {
+                let messageHeight = attributes.messageContainerSize.height + attributes.messageContainerPadding.vertical
+                origin.y = (attributes.size.height / 2) - (messageHeight / 2)
+            } else {
+                origin.y = attributes.cellTopLabelSize.height + attributes.messageTopLabelSize.height + attributes.messageContainerPadding.top
+            }
+        }
+
+        let avatarPadding = attributes.avatarLeadingTrailingPadding
+        switch attributes.avatarPosition.horizontal {
+        case .cellLeading:
+            origin.x = attributes.avatarSize.width + attributes.messageContainerPadding.left + avatarPadding
+        case .cellTrailing:
+            origin.x = attributes.frame.width - attributes.avatarSize.width - attributes.messageContainerSize.width - attributes.messageContainerPadding.right - avatarPadding
+        case .natural:
+            fatalError(MessageKitError.avatarPositionUnresolved)
+        }
+
+        messageContainerView.frame = CGRect(origin: origin, size: attributes.messageContainerSize)
+    }
+
+    /// Positions the cell's top label.
+    /// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
+    open func layoutCellTopLabel(with attributes: MessagesCollectionViewLayoutAttributes) {
+        cellTopLabel.frame = CGRect(origin: .zero, size: attributes.cellTopLabelSize)
+    }
+    
+    /// Positions the cell's bottom label.
+    /// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
+    open func layoutCellBottomLabel(with attributes: MessagesCollectionViewLayoutAttributes) {
+        cellBottomLabel.textAlignment = attributes.cellBottomLabelAlignment.textAlignment
+        cellBottomLabel.textInsets = attributes.cellBottomLabelAlignment.textInsets
+        
+        let y = messageBottomLabel.frame.maxY
+        let origin = CGPoint(x: 0, y: y)
+        
+        cellBottomLabel.frame = CGRect(origin: origin, size: attributes.cellBottomLabelSize)
+    }
+    
+    /// Positions the message bubble's top label.
+    /// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
+    open func layoutMessageTopLabel(with attributes: MessagesCollectionViewLayoutAttributes) {
+        messageTopLabel.textAlignment = attributes.messageTopLabelAlignment.textAlignment
+        messageTopLabel.textInsets = attributes.messageTopLabelAlignment.textInsets
+
+        let y = messageContainerView.frame.minY - attributes.messageContainerPadding.top - attributes.messageTopLabelSize.height
+        let origin = CGPoint(x: 0, y: y)
+        
+        messageTopLabel.frame = CGRect(origin: origin, size: attributes.messageTopLabelSize)
+    }
+
+    /// Positions the message bubble's bottom label.
+    /// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
+    open func layoutMessageBottomLabel(with attributes: MessagesCollectionViewLayoutAttributes) {
+        messageBottomLabel.textAlignment = attributes.messageBottomLabelAlignment.textAlignment
+        messageBottomLabel.textInsets = attributes.messageBottomLabelAlignment.textInsets
+
+        let y = messageContainerView.frame.maxY + attributes.messageContainerPadding.bottom
+        let origin = CGPoint(x: 0, y: y)
+
+        messageBottomLabel.frame = CGRect(origin: origin, size: attributes.messageBottomLabelSize)
+    }
+
+    /// Positions the cell's accessory view.
+    /// - attributes: The `MessagesCollectionViewLayoutAttributes` for the cell.
+    open func layoutAccessoryView(with attributes: MessagesCollectionViewLayoutAttributes) {
+        
+        var origin: CGPoint = .zero
+        
+        // Accessory view is set at the side space of the messageContainerView
+        switch attributes.accessoryViewPosition {
+        case .messageLabelTop:
+            origin.y = messageTopLabel.frame.minY
+        case .messageTop:
+            origin.y = messageContainerView.frame.minY
+        case .messageBottom:
+            origin.y = messageContainerView.frame.maxY - attributes.accessoryViewSize.height
+        case .messageCenter:
+            origin.y = messageContainerView.frame.midY - (attributes.accessoryViewSize.height / 2)
+        case .cellBottom:
+            origin.y = attributes.frame.height - attributes.accessoryViewSize.height
+        default:
+            break
+        }
+
+        // Accessory view is always on the opposite side of avatar
+        switch attributes.avatarPosition.horizontal {
+        case .cellLeading:
+            origin.x = messageContainerView.frame.maxX + attributes.accessoryViewPadding.left
+        case .cellTrailing:
+            origin.x = messageContainerView.frame.minX - attributes.accessoryViewPadding.right - attributes.accessoryViewSize.width
+        case .natural:
+            fatalError(MessageKitError.avatarPositionUnresolved)
+        }
+
+        accessoryView.frame = CGRect(origin: origin, size: attributes.accessoryViewSize)
+    }
+}

+ 101 - 0
deltachat-ios/MessageKit/Views/Cells/TextMessageCell.swift

@@ -0,0 +1,101 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+/// A subclass of `MessageContentCell` used to display text messages.
+open class TextMessageCell: MessageContentCell {
+
+    // MARK: - Properties
+
+    /// The `MessageCellDelegate` for the cell.
+    open override weak var delegate: MessageCellDelegate? {
+        didSet {
+            messageLabel.delegate = delegate
+        }
+    }
+
+    /// The label used to display the message's text.
+    open var messageLabel = MessageLabel()
+
+    // MARK: - Methods
+
+    open override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
+        super.apply(layoutAttributes)
+        if let attributes = layoutAttributes as? MessagesCollectionViewLayoutAttributes {
+            messageLabel.textInsets = attributes.messageLabelInsets
+            messageLabel.messageLabelFont = attributes.messageLabelFont
+            messageLabel.frame = messageContainerView.bounds
+        }
+    }
+
+    open override func prepareForReuse() {
+        super.prepareForReuse()
+        messageLabel.attributedText = nil
+        messageLabel.text = nil
+    }
+
+    open override func setupSubviews() {
+        super.setupSubviews()
+        messageContainerView.addSubview(messageLabel)
+    }
+
+    open override func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {
+        super.configure(with: message, at: indexPath, and: messagesCollectionView)
+
+        guard let displayDelegate = messagesCollectionView.messagesDisplayDelegate else {
+            fatalError(MessageKitError.nilMessagesDisplayDelegate)
+        }
+
+        let enabledDetectors = displayDelegate.enabledDetectors(for: message, at: indexPath, in: messagesCollectionView)
+
+        messageLabel.configure {
+            messageLabel.enabledDetectors = enabledDetectors
+            for detector in enabledDetectors {
+                let attributes = displayDelegate.detectorAttributes(for: detector, and: message, at: indexPath)
+                messageLabel.setAttributes(attributes, detector: detector)
+            }
+            switch message.kind {
+            case .text(let text), .emoji(let text):
+                let textColor = displayDelegate.textColor(for: message, at: indexPath, in: messagesCollectionView)
+                messageLabel.text = text
+                messageLabel.textColor = textColor
+                if let font = messageLabel.messageLabelFont {
+                    messageLabel.font = font
+                }
+            case .attributedText(let text):
+                messageLabel.attributedText = text
+            default:
+                break
+            }
+        }
+    }
+    
+    /// Used to handle the cell's contentView's tap gesture.
+    /// Return false when the contentView does not need to handle the gesture.
+    open override func cellContentView(canHandle touchPoint: CGPoint) -> Bool {
+        return messageLabel.handleGesture(touchPoint)
+    }
+
+}

+ 66 - 0
deltachat-ios/MessageKit/Views/Cells/TypingIndicatorCell.swift

@@ -0,0 +1,66 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+/// A subclass of `MessageCollectionViewCell` used to display the typing indicator.
+open class TypingIndicatorCell: MessageCollectionViewCell {
+    
+    // MARK: - Subviews
+
+    public var insets = UIEdgeInsets(top: 15, left: 0, bottom: 0, right: 0)
+    
+    public let typingBubble = TypingBubble()
+    
+    // MARK: - Initialization
+    
+    public override init(frame: CGRect) {
+        super.init(frame: frame)
+        setupSubviews()
+    }
+    
+    required public init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+        setupSubviews()
+    }
+    
+    open func setupSubviews() {
+        addSubview(typingBubble)
+    }
+    
+    open override func prepareForReuse() {
+        super.prepareForReuse()
+        if typingBubble.isAnimating {
+            typingBubble.stopAnimating()
+        }
+    }
+    
+    // MARK: - Layout
+    
+    open override func layoutSubviews() {
+        super.layoutSubviews()
+        typingBubble.frame = bounds.inset(by: insets)
+    }
+    
+}

+ 40 - 0
deltachat-ios/MessageKit/Views/HeadersFooters/MessageReusableView.swift

@@ -0,0 +1,40 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import Foundation
+import UIKit
+
+open class MessageReusableView: UICollectionReusableView {
+
+    // MARK: - Initializers
+
+    public override init(frame: CGRect) {
+        super.init(frame: frame)
+    }
+
+    public required init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+    }
+
+}

+ 38 - 0
deltachat-ios/MessageKit/Views/InsetLabel.swift

@@ -0,0 +1,38 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+open class InsetLabel: UILabel {
+
+    open var textInsets: UIEdgeInsets = .zero {
+        didSet { setNeedsDisplay() }
+    }
+
+    open override func drawText(in rect: CGRect) {
+        let insetRect = rect.inset(by: textInsets)
+        super.drawText(in: insetRect)
+    }
+
+}

+ 88 - 0
deltachat-ios/MessageKit/Views/MessageContainerView.swift

@@ -0,0 +1,88 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+open class MessageContainerView: UIImageView {
+
+    // MARK: - Properties
+
+    private let imageMask = UIImageView()
+
+    open var style: MessageStyle = .none {
+        didSet {
+            applyMessageStyle()
+        }
+    }
+
+    open override var frame: CGRect {
+        didSet {
+            sizeMaskToView()
+        }
+    }
+
+    // MARK: - Methods
+
+    private func sizeMaskToView() {
+        switch style {
+        case .none, .custom:
+            break
+        case .bubble, .bubbleTail, .bubbleOutline, .bubbleTailOutline:
+            imageMask.frame = bounds
+        }
+    }
+
+    private func applyMessageStyle() {
+        switch style {
+        case .bubble, .bubbleTail:
+            imageMask.image = style.image
+            sizeMaskToView()
+            mask = imageMask
+            image = nil
+        case .bubbleOutline(let color):
+            let bubbleStyle: MessageStyle = .bubble
+            imageMask.image = bubbleStyle.image
+            sizeMaskToView()
+            mask = imageMask
+            image = style.image?.withRenderingMode(.alwaysTemplate)
+            tintColor = color
+        case .bubbleTailOutline(let color, let tail, let corner):
+            let bubbleStyle: MessageStyle = .bubbleTail(tail, corner)
+            imageMask.image = bubbleStyle.image
+            sizeMaskToView()
+            mask = imageMask
+            image = style.image?.withRenderingMode(.alwaysTemplate)
+            tintColor = color
+        case .none:
+            mask = nil
+            image = nil
+            tintColor = nil
+        case .custom(let configurationClosure):
+            mask = nil
+            image = nil
+            tintColor = nil
+            configurationClosure(self)
+        }
+    }
+}

+ 546 - 0
deltachat-ios/MessageKit/Views/MessageLabel.swift

@@ -0,0 +1,546 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+open class MessageLabel: UILabel {
+
+    // MARK: - Private Properties
+
+    private lazy var layoutManager: NSLayoutManager = {
+        let layoutManager = NSLayoutManager()
+        layoutManager.addTextContainer(self.textContainer)
+        return layoutManager
+    }()
+
+    private lazy var textContainer: NSTextContainer = {
+        let textContainer = NSTextContainer()
+        textContainer.lineFragmentPadding = 0
+        textContainer.maximumNumberOfLines = self.numberOfLines
+        textContainer.lineBreakMode = self.lineBreakMode
+        textContainer.size = self.bounds.size
+        return textContainer
+    }()
+
+    private lazy var textStorage: NSTextStorage = {
+        let textStorage = NSTextStorage()
+        textStorage.addLayoutManager(self.layoutManager)
+        return textStorage
+    }()
+
+    internal lazy var rangesForDetectors: [DetectorType: [(NSRange, MessageTextCheckingType)]] = [:]
+    
+    private var isConfiguring: Bool = false
+
+    // MARK: - Public Properties
+
+    open weak var delegate: MessageLabelDelegate?
+
+    open var enabledDetectors: [DetectorType] = [] {
+        didSet {
+            setTextStorage(attributedText, shouldParse: true)
+        }
+    }
+
+    open override var attributedText: NSAttributedString? {
+        didSet {
+            setTextStorage(attributedText, shouldParse: true)
+        }
+    }
+
+    open override var text: String? {
+        didSet {
+            setTextStorage(attributedText, shouldParse: true)
+        }
+    }
+
+    open override var font: UIFont! {
+        didSet {
+            setTextStorage(attributedText, shouldParse: false)
+        }
+    }
+
+    open override var textColor: UIColor! {
+        didSet {
+            setTextStorage(attributedText, shouldParse: false)
+        }
+    }
+
+    open override var lineBreakMode: NSLineBreakMode {
+        didSet {
+            textContainer.lineBreakMode = lineBreakMode
+            if !isConfiguring { setNeedsDisplay() }
+        }
+    }
+
+    open override var numberOfLines: Int {
+        didSet {
+            textContainer.maximumNumberOfLines = numberOfLines
+            if !isConfiguring { setNeedsDisplay() }
+        }
+    }
+
+    open override var textAlignment: NSTextAlignment {
+        didSet {
+            setTextStorage(attributedText, shouldParse: false)
+        }
+    }
+
+    open var textInsets: UIEdgeInsets = .zero {
+        didSet {
+            if !isConfiguring { setNeedsDisplay() }
+        }
+    }
+
+    open override var intrinsicContentSize: CGSize {
+        var size = super.intrinsicContentSize
+        size.width += textInsets.horizontal
+        size.height += textInsets.vertical
+        return size
+    }
+    
+    internal var messageLabelFont: UIFont?
+
+    private var attributesNeedUpdate = false
+
+    public static var defaultAttributes: [NSAttributedString.Key: Any] = {
+        return [
+            NSAttributedString.Key.foregroundColor: UIColor.darkText,
+            NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue,
+            NSAttributedString.Key.underlineColor: UIColor.darkText
+        ]
+    }()
+
+    open internal(set) var addressAttributes: [NSAttributedString.Key: Any] = defaultAttributes
+
+    open internal(set) var dateAttributes: [NSAttributedString.Key: Any] = defaultAttributes
+
+    open internal(set) var phoneNumberAttributes: [NSAttributedString.Key: Any] = defaultAttributes
+
+    open internal(set) var urlAttributes: [NSAttributedString.Key: Any] = defaultAttributes
+    
+    open internal(set) var transitInformationAttributes: [NSAttributedString.Key: Any] = defaultAttributes
+    
+    open internal(set) var hashtagAttributes: [NSAttributedString.Key: Any] = defaultAttributes
+    
+    open internal(set) var mentionAttributes: [NSAttributedString.Key: Any] = defaultAttributes
+
+    open internal(set) var customAttributes: [NSRegularExpression: [NSAttributedString.Key: Any]] = [:]
+
+    public func setAttributes(_ attributes: [NSAttributedString.Key: Any], detector: DetectorType) {
+        switch detector {
+        case .phoneNumber:
+            phoneNumberAttributes = attributes
+        case .address:
+            addressAttributes = attributes
+        case .date:
+            dateAttributes = attributes
+        case .url:
+            urlAttributes = attributes
+        case .transitInformation:
+            transitInformationAttributes = attributes
+        case .mention:
+            mentionAttributes = attributes
+        case .hashtag:
+            hashtagAttributes = attributes
+        case .custom(let regex):
+            customAttributes[regex] = attributes
+        }
+        if isConfiguring {
+            attributesNeedUpdate = true
+        } else {
+            updateAttributes(for: [detector])
+        }
+    }
+
+    // MARK: - Initializers
+
+    public override init(frame: CGRect) {
+        super.init(frame: frame)
+        setupView()
+    }
+
+    public required init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+        setupView()
+    }
+
+    // MARK: - Open Methods
+
+    open override func drawText(in rect: CGRect) {
+
+        let insetRect = rect.inset(by: textInsets)
+        textContainer.size = CGSize(width: insetRect.width, height: rect.height)
+
+        let origin = insetRect.origin
+        let range = layoutManager.glyphRange(for: textContainer)
+
+        layoutManager.drawBackground(forGlyphRange: range, at: origin)
+        layoutManager.drawGlyphs(forGlyphRange: range, at: origin)
+    }
+
+    // MARK: - Public Methods
+    
+    public func configure(block: () -> Void) {
+        isConfiguring = true
+        block()
+        if attributesNeedUpdate {
+            updateAttributes(for: enabledDetectors)
+        }
+        attributesNeedUpdate = false
+        isConfiguring = false
+        setNeedsDisplay()
+    }
+
+    // MARK: - Private Methods
+
+    private func setTextStorage(_ newText: NSAttributedString?, shouldParse: Bool) {
+
+        guard let newText = newText, newText.length > 0 else {
+            textStorage.setAttributedString(NSAttributedString())
+            setNeedsDisplay()
+            return
+        }
+        
+        let style = paragraphStyle(for: newText)
+        let range = NSRange(location: 0, length: newText.length)
+        
+        let mutableText = NSMutableAttributedString(attributedString: newText)
+        mutableText.addAttribute(.paragraphStyle, value: style, range: range)
+        
+        if shouldParse {
+            rangesForDetectors.removeAll()
+            let results = parse(text: mutableText)
+            setRangesForDetectors(in: results)
+        }
+        
+        for (detector, rangeTuples) in rangesForDetectors {
+            if enabledDetectors.contains(detector) {
+                let attributes = detectorAttributes(for: detector)
+                rangeTuples.forEach { (range, _) in
+                    mutableText.addAttributes(attributes, range: range)
+                }
+            }
+        }
+
+        let modifiedText = NSAttributedString(attributedString: mutableText)
+        textStorage.setAttributedString(modifiedText)
+
+        if !isConfiguring { setNeedsDisplay() }
+
+    }
+    
+    private func paragraphStyle(for text: NSAttributedString) -> NSParagraphStyle {
+        guard text.length > 0 else { return NSParagraphStyle() }
+        
+        var range = NSRange(location: 0, length: text.length)
+        let existingStyle = text.attribute(.paragraphStyle, at: 0, effectiveRange: &range) as? NSMutableParagraphStyle
+        let style = existingStyle ?? NSMutableParagraphStyle()
+        
+        style.lineBreakMode = lineBreakMode
+        style.alignment = textAlignment
+        
+        return style
+    }
+
+    private func updateAttributes(for detectors: [DetectorType]) {
+
+        guard let attributedText = attributedText, attributedText.length > 0 else { return }
+        let mutableAttributedString = NSMutableAttributedString(attributedString: attributedText)
+
+        for detector in detectors {
+            guard let rangeTuples = rangesForDetectors[detector] else { continue }
+
+            for (range, _)  in rangeTuples {
+                let attributes = detectorAttributes(for: detector)
+                mutableAttributedString.addAttributes(attributes, range: range)
+            }
+
+            let updatedString = NSAttributedString(attributedString: mutableAttributedString)
+            textStorage.setAttributedString(updatedString)
+        }
+    }
+
+    private func detectorAttributes(for detectorType: DetectorType) -> [NSAttributedString.Key: Any] {
+
+        switch detectorType {
+        case .address:
+            return addressAttributes
+        case .date:
+            return dateAttributes
+        case .phoneNumber:
+            return phoneNumberAttributes
+        case .url:
+            return urlAttributes
+        case .transitInformation:
+            return transitInformationAttributes
+        case .mention:
+            return mentionAttributes
+        case .hashtag:
+            return hashtagAttributes
+        case .custom(let regex):
+            return customAttributes[regex] ?? MessageLabel.defaultAttributes
+        }
+
+    }
+
+    private func detectorAttributes(for checkingResultType: NSTextCheckingResult.CheckingType) -> [NSAttributedString.Key: Any] {
+        switch checkingResultType {
+        case .address:
+            return addressAttributes
+        case .date:
+            return dateAttributes
+        case .phoneNumber:
+            return phoneNumberAttributes
+        case .link:
+            return urlAttributes
+        case .transitInformation:
+            return transitInformationAttributes
+        default:
+            fatalError(MessageKitError.unrecognizedCheckingResult)
+        }
+    }
+    
+    private func setupView() {
+        numberOfLines = 0
+        lineBreakMode = .byWordWrapping
+    }
+
+    // MARK: - Parsing Text
+
+    private func parse(text: NSAttributedString) -> [NSTextCheckingResult] {
+        guard enabledDetectors.isEmpty == false else { return [] }
+        let range = NSRange(location: 0, length: text.length)
+        var matches = [NSTextCheckingResult]()
+
+        // Get matches of all .custom DetectorType and add it to matches array
+        let regexs = enabledDetectors
+            .filter { $0.isCustom }
+            .map { parseForMatches(with: $0, in: text, for: range) }
+            .joined()
+        matches.append(contentsOf: regexs)
+
+        // Get all Checking Types of detectors, except for .custom because they contain their own regex
+        let detectorCheckingTypes = enabledDetectors
+            .filter { !$0.isCustom }
+            .reduce(0) { $0 | $1.textCheckingType.rawValue }
+        if detectorCheckingTypes > 0, let detector = try? NSDataDetector(types: detectorCheckingTypes) {
+            let detectorMatches = detector.matches(in: text.string, options: [], range: range)
+            matches.append(contentsOf: detectorMatches)
+        }
+
+        guard enabledDetectors.contains(.url) else {
+            return matches
+        }
+
+        // Enumerate NSAttributedString NSLinks and append ranges
+        var results: [NSTextCheckingResult] = matches
+
+        text.enumerateAttribute(NSAttributedString.Key.link, in: range, options: []) { value, range, _ in
+            guard let url = value as? URL else { return }
+            let result = NSTextCheckingResult.linkCheckingResult(range: range, url: url)
+            results.append(result)
+        }
+
+        return results
+    }
+
+    private func parseForMatches(with detector: DetectorType, in text: NSAttributedString, for range: NSRange) -> [NSTextCheckingResult] {
+        switch detector {
+        case .custom(let regex):
+            return regex.matches(in: text.string, options: [], range: range)
+        default:
+            fatalError("You must pass a .custom DetectorType")
+        }
+    }
+
+    private func setRangesForDetectors(in checkingResults: [NSTextCheckingResult]) {
+
+        guard checkingResults.isEmpty == false else { return }
+        
+        for result in checkingResults {
+
+            switch result.resultType {
+            case .address:
+                var ranges = rangesForDetectors[.address] ?? []
+                let tuple: (NSRange, MessageTextCheckingType) = (result.range, .addressComponents(result.addressComponents))
+                ranges.append(tuple)
+                rangesForDetectors.updateValue(ranges, forKey: .address)
+            case .date:
+                var ranges = rangesForDetectors[.date] ?? []
+                let tuple: (NSRange, MessageTextCheckingType) = (result.range, .date(result.date))
+                ranges.append(tuple)
+                rangesForDetectors.updateValue(ranges, forKey: .date)
+            case .phoneNumber:
+                var ranges = rangesForDetectors[.phoneNumber] ?? []
+                let tuple: (NSRange, MessageTextCheckingType) = (result.range, .phoneNumber(result.phoneNumber))
+                ranges.append(tuple)
+                rangesForDetectors.updateValue(ranges, forKey: .phoneNumber)
+            case .link:
+                var ranges = rangesForDetectors[.url] ?? []
+                let tuple: (NSRange, MessageTextCheckingType) = (result.range, .link(result.url))
+                ranges.append(tuple)
+                rangesForDetectors.updateValue(ranges, forKey: .url)
+            case .transitInformation:
+                var ranges = rangesForDetectors[.transitInformation] ?? []
+                let tuple: (NSRange, MessageTextCheckingType) = (result.range, .transitInfoComponents(result.components))
+                ranges.append(tuple)
+                rangesForDetectors.updateValue(ranges, forKey: .transitInformation)
+            case .regularExpression:
+                guard let text = text, let regex = result.regularExpression, let range = Range(result.range, in: text) else { return }
+                let detector = DetectorType.custom(regex)
+                var ranges = rangesForDetectors[detector] ?? []
+                let tuple: (NSRange, MessageTextCheckingType) = (result.range, .custom(pattern: regex.pattern, match: String(text[range])))
+                ranges.append(tuple)
+                rangesForDetectors.updateValue(ranges, forKey: detector)
+            default:
+                fatalError("Received an unrecognized NSTextCheckingResult.CheckingType")
+            }
+
+        }
+
+    }
+
+    // MARK: - Gesture Handling
+
+    private func stringIndex(at location: CGPoint) -> Int? {
+        guard textStorage.length > 0 else { return nil }
+
+        var location = location
+
+        location.x -= textInsets.left
+        location.y -= textInsets.top
+
+        let index = layoutManager.glyphIndex(for: location, in: textContainer)
+
+        let lineRect = layoutManager.lineFragmentUsedRect(forGlyphAt: index, effectiveRange: nil)
+        
+        var characterIndex: Int?
+        
+        if lineRect.contains(location) {
+            characterIndex = layoutManager.characterIndexForGlyph(at: index)
+        }
+        
+        return characterIndex
+
+    }
+
+  open func handleGesture(_ touchLocation: CGPoint) -> Bool {
+
+        guard let index = stringIndex(at: touchLocation) else { return false }
+
+        for (detectorType, ranges) in rangesForDetectors {
+            for (range, value) in ranges {
+                if range.contains(index) {
+                    handleGesture(for: detectorType, value: value)
+                    return true
+                }
+            }
+        }
+        return false
+    }
+
+    // swiftlint:disable cyclomatic_complexity
+    private func handleGesture(for detectorType: DetectorType, value: MessageTextCheckingType) {
+        
+        switch value {
+        case let .addressComponents(addressComponents):
+            var transformedAddressComponents = [String: String]()
+            guard let addressComponents = addressComponents else { return }
+            addressComponents.forEach { (key, value) in
+                transformedAddressComponents[key.rawValue] = value
+            }
+            handleAddress(transformedAddressComponents)
+        case let .phoneNumber(phoneNumber):
+            guard let phoneNumber = phoneNumber else { return }
+            handlePhoneNumber(phoneNumber)
+        case let .date(date):
+            guard let date = date else { return }
+            handleDate(date)
+        case let .link(url):
+            guard let url = url else { return }
+            handleURL(url)
+        case let .transitInfoComponents(transitInformation):
+            var transformedTransitInformation = [String: String]()
+            guard let transitInformation = transitInformation else { return }
+            transitInformation.forEach { (key, value) in
+                transformedTransitInformation[key.rawValue] = value
+            }
+            handleTransitInformation(transformedTransitInformation)
+        case let .custom(pattern, match):
+            guard let match = match else { return }
+            switch detectorType {
+            case .hashtag:
+                handleHashtag(match)
+            case .mention:
+                handleMention(match)
+            default:
+                handleCustom(pattern, match: match)
+            }
+        }
+    }
+    // swiftlint:enable cyclomatic_complexity
+    
+    private func handleAddress(_ addressComponents: [String: String]) {
+        delegate?.didSelectAddress(addressComponents)
+    }
+    
+    private func handleDate(_ date: Date) {
+        delegate?.didSelectDate(date)
+    }
+    
+    private func handleURL(_ url: URL) {
+        delegate?.didSelectURL(url)
+    }
+    
+    private func handlePhoneNumber(_ phoneNumber: String) {
+        delegate?.didSelectPhoneNumber(phoneNumber)
+    }
+    
+    private func handleTransitInformation(_ components: [String: String]) {
+        delegate?.didSelectTransitInformation(components)
+    }
+
+    private func handleHashtag(_ hashtag: String) {
+        delegate?.didSelectHashtag(hashtag)
+    }
+
+    private func handleMention(_ mention: String) {
+        delegate?.didSelectMention(mention)
+    }
+
+    private func handleCustom(_ pattern: String, match: String) {
+        delegate?.didSelectCustom(pattern, match: match)
+    }
+
+}
+
+internal enum MessageTextCheckingType {
+    case addressComponents([NSTextCheckingKey: String]?)
+    case date(Date?)
+    case phoneNumber(String?)
+    case link(URL?)
+    case transitInfoComponents([NSTextCheckingKey: String]?)
+    case custom(pattern: String, match: String?)
+}

+ 195 - 0
deltachat-ios/MessageKit/Views/MessagesCollectionView.swift

@@ -0,0 +1,195 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+open class MessagesCollectionView: UICollectionView {
+
+    // MARK: - Properties
+
+    open weak var messagesDataSource: MessagesDataSource?
+
+    open weak var messagesDisplayDelegate: MessagesDisplayDelegate?
+
+    open weak var messagesLayoutDelegate: MessagesLayoutDelegate?
+
+    open weak var messageCellDelegate: MessageCellDelegate?
+
+    open var isTypingIndicatorHidden: Bool {
+        return messagesCollectionViewFlowLayout.isTypingIndicatorViewHidden
+    }
+
+    private var indexPathForLastItem: IndexPath? {
+        let lastSection = numberOfSections - 1
+        guard lastSection >= 0, numberOfItems(inSection: lastSection) > 0 else { return nil }
+        return IndexPath(item: numberOfItems(inSection: lastSection) - 1, section: lastSection)
+    }
+
+    open var messagesCollectionViewFlowLayout: MessagesCollectionViewFlowLayout {
+        guard let layout = collectionViewLayout as? MessagesCollectionViewFlowLayout else {
+            fatalError(MessageKitError.layoutUsedOnForeignType)
+        }
+        return layout
+    }
+
+    // MARK: - Initializers
+
+    public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
+        super.init(frame: frame, collectionViewLayout: layout)
+        backgroundColor = .white
+        registerReusableViews()
+        setupGestureRecognizers()
+    }
+    
+    required public init?(coder aDecoder: NSCoder) {
+        super.init(frame: .zero, collectionViewLayout: MessagesCollectionViewFlowLayout())
+    }
+
+    public convenience init() {
+        self.init(frame: .zero, collectionViewLayout: MessagesCollectionViewFlowLayout())
+    }
+
+    // MARK: - Methods
+    
+    private func registerReusableViews() {
+        register(TextMessageCell.self)
+        register(MediaMessageCell.self)
+        register(LocationMessageCell.self)
+        register(AudioMessageCell.self)
+        register(ContactMessageCell.self)
+        register(TypingIndicatorCell.self)
+        register(MessageReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader)
+        register(MessageReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter)
+    }
+    
+    private func setupGestureRecognizers() {
+        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
+        tapGesture.delaysTouchesBegan = true
+        addGestureRecognizer(tapGesture)
+    }
+    
+    @objc
+    open func handleTapGesture(_ gesture: UIGestureRecognizer) {
+        guard gesture.state == .ended else { return }
+        
+        let touchLocation = gesture.location(in: self)
+        guard let indexPath = indexPathForItem(at: touchLocation) else { return }
+        
+        let cell = cellForItem(at: indexPath) as? MessageCollectionViewCell
+        cell?.handleTapGesture(gesture)
+    }
+
+    public func scrollToBottom(animated: Bool = false) {
+        let collectionViewContentHeight = collectionViewLayout.collectionViewContentSize.height
+
+        performBatchUpdates(nil) { _ in
+            self.scrollRectToVisible(CGRect(0.0, collectionViewContentHeight - 1.0, 1.0, 1.0), animated: animated)
+        }
+    }
+    
+    public func reloadDataAndKeepOffset() {
+        // stop scrolling
+        setContentOffset(contentOffset, animated: false)
+        
+        // calculate the offset and reloadData
+        let beforeContentSize = contentSize
+        reloadData()
+        layoutIfNeeded()
+        let afterContentSize = contentSize
+        
+        // reset the contentOffset after data is updated
+        let newOffset = CGPoint(
+            x: contentOffset.x + (afterContentSize.width - beforeContentSize.width),
+            y: contentOffset.y + (afterContentSize.height - beforeContentSize.height))
+        setContentOffset(newOffset, animated: false)
+    }
+
+    // MARK: - Typing Indicator API
+
+    /// Notifies the layout that the typing indicator will change state
+    ///
+    /// - Parameters:
+    ///   - isHidden: A Boolean value that is to be the new state of the typing indicator
+    internal func setTypingIndicatorViewHidden(_ isHidden: Bool) {
+        messagesCollectionViewFlowLayout.setTypingIndicatorViewHidden(isHidden)
+    }
+    
+    /// A method that by default checks if the section is the last in the
+    /// `messagesCollectionView` and that `isTypingIndicatorViewHidden`
+    /// is FALSE
+    ///
+    /// - Parameter section
+    /// - Returns: A Boolean indicating if the TypingIndicator should be presented at the given section
+    public func isSectionReservedForTypingIndicator(_ section: Int) -> Bool {
+        return messagesCollectionViewFlowLayout.isSectionReservedForTypingIndicator(section)
+    }
+
+    // MARK: View Register/Dequeue
+
+    /// Registers a particular cell using its reuse-identifier
+    public func register<T: UICollectionViewCell>(_ cellClass: T.Type) {
+        register(cellClass, forCellWithReuseIdentifier: String(describing: T.self))
+    }
+
+    /// Registers a reusable view for a specific SectionKind
+    public func register<T: UICollectionReusableView>(_ reusableViewClass: T.Type, forSupplementaryViewOfKind kind: String) {
+        register(reusableViewClass,
+                 forSupplementaryViewOfKind: kind,
+                 withReuseIdentifier: String(describing: T.self))
+    }
+    
+    /// Registers a nib with reusable view for a specific SectionKind
+    public func register<T: UICollectionReusableView>(_ nib: UINib? = UINib(nibName: String(describing: T.self), bundle: nil), headerFooterClassOfNib headerFooterClass: T.Type, forSupplementaryViewOfKind kind: String) {
+        register(nib,
+                 forSupplementaryViewOfKind: kind,
+                 withReuseIdentifier: String(describing: T.self))        
+    }
+
+    /// Generically dequeues a cell of the correct type allowing you to avoid scattering your code with guard-let-else-fatal
+    public func dequeueReusableCell<T: UICollectionViewCell>(_ cellClass: T.Type, for indexPath: IndexPath) -> T {
+        guard let cell = dequeueReusableCell(withReuseIdentifier: String(describing: T.self), for: indexPath) as? T else {
+            fatalError("Unable to dequeue \(String(describing: cellClass)) with reuseId of \(String(describing: T.self))")
+        }
+        return cell
+    }
+
+    /// Generically dequeues a header of the correct type allowing you to avoid scattering your code with guard-let-else-fatal
+    public func dequeueReusableHeaderView<T: UICollectionReusableView>(_ viewClass: T.Type, for indexPath: IndexPath) -> T {
+        let view = dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: String(describing: T.self), for: indexPath)
+        guard let viewType = view as? T else {
+            fatalError("Unable to dequeue \(String(describing: viewClass)) with reuseId of \(String(describing: T.self))")
+        }
+        return viewType
+    }
+
+    /// Generically dequeues a footer of the correct type allowing you to avoid scattering your code with guard-let-else-fatal
+    public func dequeueReusableFooterView<T: UICollectionReusableView>(_ viewClass: T.Type, for indexPath: IndexPath) -> T {
+        let view = dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: String(describing: T.self), for: indexPath)
+        guard let viewType = view as? T else {
+            fatalError("Unable to dequeue \(String(describing: viewClass)) with reuseId of \(String(describing: T.self))")
+        }
+        return viewType
+    }
+
+}

+ 122 - 0
deltachat-ios/MessageKit/Views/PlayButtonView.swift

@@ -0,0 +1,122 @@
+/*
+ MIT License
+
+ Copyright (c) 2017-2019 MessageKit
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+open class PlayButtonView: UIView {
+
+    // MARK: - Properties
+
+    public let triangleView = UIView()
+
+    private var triangleCenterXConstraint: NSLayoutConstraint?
+    private var cacheFrame: CGRect = .zero
+
+    // MARK: - Initializers
+
+    public override init(frame: CGRect) {
+        super.init(frame: frame)
+
+        setupSubviews()
+        setupConstraints()
+        setupView()
+    }
+
+    required public init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+        
+        setupSubviews()
+        setupConstraints()
+        setupView()
+    }
+
+    // MARK: - Methods
+    
+    open override func layoutSubviews() {
+        super.layoutSubviews()
+        
+        guard !cacheFrame.equalTo(frame) else { return }
+        cacheFrame = frame
+        
+        updateTriangleConstraints()
+        applyCornerRadius()
+        applyTriangleMask()
+    }
+
+    private func setupSubviews() {
+        addSubview(triangleView)
+    }
+    
+    private func setupView() {
+        triangleView.clipsToBounds = true
+        triangleView.backgroundColor = .black
+        
+        backgroundColor = .playButtonLightGray
+    }
+
+    private func setupConstraints() {
+        triangleView.translatesAutoresizingMaskIntoConstraints = false
+
+        let centerX = triangleView.centerXAnchor.constraint(equalTo: centerXAnchor)
+        let centerY = triangleView.centerYAnchor.constraint(equalTo: centerYAnchor)
+        let width = triangleView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.5)
+        let height = triangleView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.5)
+
+        triangleCenterXConstraint = centerX
+
+        NSLayoutConstraint.activate([centerX, centerY, width, height])
+    }
+
+    private func triangleMask(for frame: CGRect) -> CAShapeLayer {
+        let shapeLayer = CAShapeLayer()
+        let trianglePath = UIBezierPath()
+
+        let point1 = CGPoint(x: frame.minX, y: frame.minY)
+        let point2 = CGPoint(x: frame.maxX, y: frame.maxY/2)
+        let point3 = CGPoint(x: frame.minX, y: frame.maxY)
+
+        trianglePath .move(to: point1)
+        trianglePath .addLine(to: point2)
+        trianglePath .addLine(to: point3)
+        trianglePath .close()
+
+        shapeLayer.path = trianglePath.cgPath
+
+        return shapeLayer
+    }
+
+    private func updateTriangleConstraints() {
+        triangleCenterXConstraint?.constant = frame.width/8
+    }
+
+    private func applyTriangleMask() {
+        let rect = CGRect(origin: .zero, size: triangleView.bounds.size)
+        triangleView.layer.mask = triangleMask(for: rect)
+    }
+
+    private func applyCornerRadius() {
+        layer.cornerRadius = frame.width / 2
+    }
+    
+}

+ 156 - 0
deltachat-ios/MessageKit/Views/TypingBubble.swift

@@ -0,0 +1,156 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+import UIKit
+
+/// A subclass of `UIView` that mimics the iMessage typing bubble
+open class TypingBubble: UIView {
+    
+    // MARK: - Properties
+    
+    open var isPulseEnabled: Bool = true
+    
+    public private(set) var isAnimating: Bool = false
+    
+    open override var backgroundColor: UIColor? {
+        set {
+            [contentBubble, cornerBubble, tinyBubble].forEach { $0.backgroundColor = newValue }
+        }
+        get {
+            return contentBubble.backgroundColor
+        }
+    }
+    
+    private struct AnimationKeys {
+        static let pulse = "typingBubble.pulse"
+    }
+    
+    // MARK: - Subviews
+    
+    /// The indicator used to display the typing animation.
+    public let typingIndicator = TypingIndicator()
+    
+    public let contentBubble = UIView()
+    
+    public let cornerBubble = BubbleCircle()
+    
+    public let tinyBubble = BubbleCircle()
+    
+    // MARK: - Animation Layers
+    
+    open var contentPulseAnimationLayer: CABasicAnimation {
+        let animation = CABasicAnimation(keyPath: "transform.scale")
+        animation.fromValue = 1
+        animation.toValue = 1.04
+        animation.duration = 1
+        animation.repeatCount = .infinity
+        animation.autoreverses = true
+        return animation
+    }
+    
+    open var circlePulseAnimationLayer: CABasicAnimation {
+        let animation = CABasicAnimation(keyPath: "transform.scale")
+        animation.fromValue = 1
+        animation.toValue = 1.1
+        animation.duration = 0.5
+        animation.repeatCount = .infinity
+        animation.autoreverses = true
+        return animation
+    }
+    
+    public override init(frame: CGRect) {
+        super.init(frame: frame)
+        setupSubviews()
+    }
+    
+    public required init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+        setupSubviews()
+    }
+    
+    open func setupSubviews() {
+        addSubview(tinyBubble)
+        addSubview(cornerBubble)
+        addSubview(contentBubble)
+        contentBubble.addSubview(typingIndicator)
+        backgroundColor = .incomingGray
+    }
+    
+    // MARK: - Layout
+    
+    open override func layoutSubviews() {
+        super.layoutSubviews()
+        
+        // To maintain the iMessage like bubble the width:height ratio of the frame
+        // must be close to 1.65
+        let ratio = bounds.width / bounds.height
+        let extraRightInset = bounds.width - 1.65/ratio*bounds.width
+        
+        let tinyBubbleRadius: CGFloat = bounds.height / 6
+        tinyBubble.frame = CGRect(x: 0,
+                                  y: bounds.height - tinyBubbleRadius,
+                                  width: tinyBubbleRadius,
+                                  height: tinyBubbleRadius)
+        
+        let cornerBubbleRadius = tinyBubbleRadius * 2
+        let offset: CGFloat = tinyBubbleRadius / 6
+        cornerBubble.frame = CGRect(x: tinyBubbleRadius - offset,
+                                    y: bounds.height - (1.5 * cornerBubbleRadius) + offset,
+                                    width: cornerBubbleRadius,
+                                    height: cornerBubbleRadius)
+        
+        let contentBubbleFrame = CGRect(x: tinyBubbleRadius + offset,
+                              y: 0,
+                              width: bounds.width - (tinyBubbleRadius + offset) - extraRightInset,
+                              height: bounds.height - (tinyBubbleRadius + offset))
+        let contentBubbleFrameCornerRadius = contentBubbleFrame.height / 2
+        
+        contentBubble.frame = contentBubbleFrame
+        contentBubble.layer.cornerRadius = contentBubbleFrameCornerRadius
+            
+        let insets = UIEdgeInsets(top: offset, left: contentBubbleFrameCornerRadius / 1.25, bottom: offset, right: contentBubbleFrameCornerRadius / 1.25)
+        typingIndicator.frame = contentBubble.bounds.inset(by: insets)
+    }
+    
+    // MARK: - Animation API
+    
+    open func startAnimating() {
+        defer { isAnimating = true }
+        guard !isAnimating else { return }
+        typingIndicator.startAnimating()
+        if isPulseEnabled {
+            contentBubble.layer.add(contentPulseAnimationLayer, forKey: AnimationKeys.pulse)
+            [cornerBubble, tinyBubble].forEach { $0.layer.add(circlePulseAnimationLayer, forKey: AnimationKeys.pulse) }
+        }
+    }
+    
+    open func stopAnimating() {
+        defer { isAnimating = false }
+        guard isAnimating else { return }
+        typingIndicator.stopAnimating()
+        [contentBubble, cornerBubble, tinyBubble].forEach { $0.layer.removeAnimation(forKey: AnimationKeys.pulse) }
+    }
+    
+}

+ 165 - 0
deltachat-ios/MessageKit/Views/TypingIndicator.swift

@@ -0,0 +1,165 @@
+/*
+ MIT License
+ 
+ Copyright (c) 2017-2019 MessageKit
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import UIKit
+
+/// A `UIView` subclass that holds 3 dots which can be animated
+open class TypingIndicator: UIView {
+    
+    // MARK: - Properties
+    
+    /// The offset that each dot will transform by during the bounce animation
+    public var bounceOffset: CGFloat = 2.5
+    
+    /// A convenience accessor for the `backgroundColor` of each dot
+    open var dotColor: UIColor = UIColor.lightGray {
+        didSet {
+            dots.forEach { $0.backgroundColor = dotColor }
+        }
+    }
+    
+    /// A flag that determines if the bounce animation is added in `startAnimating()`
+    public var isBounceEnabled: Bool = false
+    
+    /// A flag that determines if the opacity animation is added in `startAnimating()`
+    public var isFadeEnabled: Bool = true
+    
+    /// A flag indicating the animation state
+    public private(set) var isAnimating: Bool = false
+    
+    /// Keys for each animation layer
+    private struct AnimationKeys {
+        static let offset = "typingIndicator.offset"
+        static let bounce = "typingIndicator.bounce"
+        static let opacity = "typingIndicator.opacity"
+    }
+    
+    /// The `CABasicAnimation` applied when `isBounceEnabled` is TRUE to move the dot to the correct
+    /// initial offset
+    open var initialOffsetAnimationLayer: CABasicAnimation {
+        let animation = CABasicAnimation(keyPath: "transform.translation.y")
+        animation.byValue = -bounceOffset
+        animation.duration = 0.5
+        animation.isRemovedOnCompletion = true
+        return animation
+    }
+    
+    /// The `CABasicAnimation` applied when `isBounceEnabled` is TRUE
+    open var bounceAnimationLayer: CABasicAnimation {
+        let animation = CABasicAnimation(keyPath: "transform.translation.y")
+        animation.toValue = -bounceOffset
+        animation.fromValue = bounceOffset
+        animation.duration = 0.5
+        animation.repeatCount = .infinity
+        animation.autoreverses = true
+        return animation
+    }
+    
+    /// The `CABasicAnimation` applied when `isFadeEnabled` is TRUE
+    open var opacityAnimationLayer: CABasicAnimation {
+        let animation = CABasicAnimation(keyPath: "opacity")
+        animation.fromValue = 1
+        animation.toValue = 0.5
+        animation.duration = 0.5
+        animation.repeatCount = .infinity
+        animation.autoreverses = true
+        return animation
+    }
+    
+    // MARK: - Subviews
+    
+    public let stackView = UIStackView()
+    
+    public let dots: [BubbleCircle] = {
+        return [BubbleCircle(), BubbleCircle(), BubbleCircle()]
+    }()
+    
+    // MARK: - Initialization
+    
+    public override init(frame: CGRect) {
+        super.init(frame: frame)
+        setupView()
+    }
+    
+    public required init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+        setupView()
+    }
+    
+    /// Sets up the view
+    private func setupView() {
+        dots.forEach {
+            $0.backgroundColor = dotColor
+            $0.heightAnchor.constraint(equalTo: $0.widthAnchor).isActive = true
+            stackView.addArrangedSubview($0)
+        }
+        stackView.axis = .horizontal
+        stackView.alignment = .center
+        stackView.distribution = .fillEqually
+        addSubview(stackView)
+    }
+    
+    // MARK: - Layout
+    
+    open override func layoutSubviews() {
+        super.layoutSubviews()
+        stackView.frame = bounds
+        stackView.spacing = bounds.width > 0 ? 5 : 0
+    }
+    
+    // MARK: - Animation API
+    
+    /// Sets the state of the `TypingIndicator` to animating and applies animation layers
+    open func startAnimating() {
+        defer { isAnimating = true }
+        guard !isAnimating else { return }
+        var delay: TimeInterval = 0
+        for dot in dots {
+            DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
+                guard let `self` = self else { return }
+                if self.isBounceEnabled {
+                    dot.layer.add(self.initialOffsetAnimationLayer, forKey: AnimationKeys.offset)
+                    let bounceLayer = self.bounceAnimationLayer
+                    bounceLayer.timeOffset = delay + 0.33
+                    dot.layer.add(bounceLayer, forKey: AnimationKeys.bounce)
+                }
+                if self.isFadeEnabled {
+                    dot.layer.add(self.opacityAnimationLayer, forKey: AnimationKeys.opacity)
+                }
+            }
+            delay += 0.33
+        }
+    }
+    
+    /// Sets the state of the `TypingIndicator` to not animating and removes animation layers
+    open func stopAnimating() {
+        defer { isAnimating = false }
+        guard isAnimating else { return }
+        dots.forEach {
+            $0.layer.removeAnimation(forKey: AnimationKeys.bounce)
+            $0.layer.removeAnimation(forKey: AnimationKeys.opacity)
+        }
+    }
+    
+}

+ 1 - 1
deltachat-ios/Model/Location.swift

@@ -1,6 +1,6 @@
 import CoreLocation
 import Foundation
-import MessageKit
+import UIKit
 
 struct Location: LocationItem {
     var location: CLLocation

+ 1 - 1
deltachat-ios/Model/Media.swift

@@ -1,6 +1,6 @@
 import CoreLocation
 import Foundation
-import MessageKit
+import UIKit
 
 struct Media: MediaItem {
     var url: URL?

+ 1 - 1
deltachat-ios/Model/Message.swift

@@ -1,6 +1,6 @@
 import CoreLocation
 import Foundation
-import MessageKit
+import UIKit
 
 struct Message: MessageType {
     var messageId: String

+ 0 - 1
deltachat-ios/View/CustomMessageCell.swift

@@ -1,4 +1,3 @@
-import MessageKit
 import UIKit
 
 open class CustomMessageCell: UICollectionViewCell {