conversations: implement message grouping

- Calculates the grouping property of each message, determining
  whether it's first, middle, or last within a sequence either
  sent or received.

- Adjusts the top and bottom constraints for each message bubble
  according to its sequencing.

- Fixes the scroll-to-bottom feature by updating the bottoOffset
  value before conditionally scrolling, removing the scroll
  animation, and only scrolling down when the user is near the
  end of the chat.

- Applies a message bubble grouping style, adjusts line spacing
  of the content, and decouples the theming of the message
  bubble and text colors.

Change-Id: I9118c2bbca0433573c877450c73bd6dc5c9229a0
Reviewed-by: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
diff --git a/Ring/Ring.xcodeproj/project.pbxproj b/Ring/Ring.xcodeproj/project.pbxproj
index aa7323a..f0d96ea 100644
--- a/Ring/Ring.xcodeproj/project.pbxproj
+++ b/Ring/Ring.xcodeproj/project.pbxproj
@@ -186,6 +186,8 @@
 		56BBC9D41EDC7A6D00CDAF8B /* libargon2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 56BBC9D31EDC7A6D00CDAF8B /* libargon2.a */; };
 		56BBC9DF1EDDC9D300CDAF8B /* LookupNameResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 56BBC9DE1EDDC9D300CDAF8B /* LookupNameResponse.m */; };
 		56C715FF1F0D36C600770048 /* ContactsAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 56C715FE1F0D36C600770048 /* ContactsAdapter.mm */; };
+		621231F91F880EDF009B86F0 /* UILabel+Ring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621231F81F880EDF009B86F0 /* UILabel+Ring.swift */; };
+		621231FB1F8D6FEE009B86F0 /* MessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621231FA1F8D6FEE009B86F0 /* MessageCell.swift */; };
 		62A88D371F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A88D361F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift */; };
 		62A88D391F6C323500F8AB18 /* PresenceAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 62A88D381F6C323500F8AB18 /* PresenceAdapter.mm */; };
 		62A88D3B1F6C3ACC00F8AB18 /* PresenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A88D3A1F6C3ACC00F8AB18 /* PresenceService.swift */; };
@@ -417,6 +419,8 @@
 		56C715FE1F0D36C600770048 /* ContactsAdapter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ContactsAdapter.mm; sourceTree = "<group>"; };
 		56C716001F0D36D900770048 /* ContactsAdapterDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsAdapterDelegate.swift; sourceTree = "<group>"; };
 		56C716021F0D466100770048 /* ContactsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsService.swift; sourceTree = "<group>"; };
+		621231F81F880EDF009B86F0 /* UILabel+Ring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+Ring.swift"; sourceTree = "<group>"; };
+		621231FA1F8D6FEE009B86F0 /* MessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCell.swift; sourceTree = "<group>"; };
 		62A88D351F6C2E5F00F8AB18 /* PresenceAdapter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PresenceAdapter.h; sourceTree = "<group>"; };
 		62A88D361F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresenceAdapterDelegate.swift; sourceTree = "<group>"; };
 		62A88D381F6C323500F8AB18 /* PresenceAdapter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PresenceAdapter.mm; sourceTree = "<group>"; };
@@ -633,6 +637,7 @@
 				1A2D18A51F27F7A400B2C785 /* UIViewController+Rx.swift */,
 				0586C94A1F684DF600613517 /* UIImage+Helpers.swift */,
 				0EE1B54D1F75ACDE00BA98EE /* CNContactVCardSerialization+Helpers.swift */,
+				621231F81F880EDF009B86F0 /* UILabel+Ring.swift */,
 			);
 			path = Extensions;
 			sourceTree = "<group>";
@@ -957,6 +962,7 @@
 				1A2D18F11F292D7200B2C785 /* MessageCellReceived.swift */,
 				1A2D18F21F292D7200B2C785 /* MessageCellReceived.xib */,
 				1A2D18F31F292D7200B2C785 /* MessageCellSent.swift */,
+				621231FA1F8D6FEE009B86F0 /* MessageCell.swift */,
 				1A2D18F41F292D7200B2C785 /* MessageCellSent.xib */,
 				0E403F801F7D797300C80BC2 /* MessageCellGenerated.swift */,
 				0E403F821F7D79B000C80BC2 /* MessageCellGenerated.xib */,
@@ -1274,6 +1280,7 @@
 			files = (
 				557086521E8ADB9D001A7CE4 /* SystemAdapter.mm in Sources */,
 				0586C94B1F684DF600613517 /* UIImage+Helpers.swift in Sources */,
+				621231F91F880EDF009B86F0 /* UILabel+Ring.swift in Sources */,
 				1A2D18AC1F29149D00B2C785 /* MeCoordinator.swift in Sources */,
 				1A2D18C51F29180700B2C785 /* ContactModel.swift in Sources */,
 				1A2D18F71F292D7200B2C785 /* MessageCellSent.swift in Sources */,
@@ -1310,6 +1317,7 @@
 				56308BA71EA00E5700660275 /* NameRegistrationResponse.m in Sources */,
 				1A3CA32D1F13DA7200283748 /* Chameleon+Ring.swift in Sources */,
 				1ABE07E21F0D924700D36361 /* Strings.swift in Sources */,
+				621231FB1F8D6FEE009B86F0 /* MessageCell.swift in Sources */,
 				56AC650E1E85694D00EA1AA9 /* DesignableTextField.swift in Sources */,
 				1A2D189A1F2642C000B2C785 /* NotificationCenter+Ring.swift in Sources */,
 				1A2D18FC1F292DAD00B2C785 /* ConversationCell.swift in Sources */,