diff --git a/Ring/Ring.xcodeproj/project.pbxproj b/Ring/Ring.xcodeproj/project.pbxproj index aa7323ab83504d3ce29af76d6e64215f726151dd..f0d96eac5215592c00e3f802ea8f27bf5ecbdb98 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 */, diff --git a/Ring/Ring/AppDelegate.swift b/Ring/Ring/AppDelegate.swift index 6deb2d7830e053db795efccb3f64a10e6e833e2e..57f30ffbf9b3e34349d3b755c2c57d654f44ed0b 100644 --- a/Ring/Ring/AppDelegate.swift +++ b/Ring/Ring/AppDelegate.swift @@ -58,6 +58,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { self.window = UIWindow(frame: UIScreen.main.bounds) + UserDefaults.standard.setValue(false, forKey: "_UIConstraintBasedLayoutLogUnsatisfiable") + // initialize log format let console = ConsoleDestination() console.format = "$Dyyyy-MM-dd HH:mm:ss.SSS$d $C$L$c: $M" diff --git a/Ring/Ring/Extensions/Chameleon+Ring.swift b/Ring/Ring/Extensions/Chameleon+Ring.swift index 0146a074cd2ef35b9bc268b935d8a2fff51d41f9..f3961e46b7f8f63662b3d7e7b2d1674cf6df4c39 100644 --- a/Ring/Ring/Extensions/Chameleon+Ring.swift +++ b/Ring/Ring/Extensions/Chameleon+Ring.swift @@ -31,15 +31,12 @@ extension Chameleon { case .contrast: contentColor = ContrastColorOf(primaryColor, returnFlat: false) secondaryContentColor = ContrastColorOf(secondaryColor, returnFlat: false) - break case .light: contentColor = UIColor.white secondaryContentColor = UIColor.white - break case .dark: contentColor = UIColor.flatBlackColorDark() secondaryContentColor = UIColor.flatBlackColorDark() - break } UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).tintColor = UIColor.flatGray() @@ -48,12 +45,12 @@ extension Chameleon { MessageBubble.appearance().backgroundColor = secondaryColor MessageBubble.appearance(whenContainedInInstancesOf: [MessageCellSent.self]).tintColor = contentColor - MessageBubble.appearance(whenContainedInInstancesOf: [MessageCellSent.self]).backgroundColor = primaryColor - UILabel.appearance(whenContainedInInstancesOf: [MessageBubble.self, MessageCellSent.self]).textColor = contentColor + MessageBubble.appearance(whenContainedInInstancesOf: [MessageCellSent.self]).backgroundColor = UIColor.ringMsgCellSent + UILabel.appearance(whenContainedInInstancesOf: [MessageBubble.self, MessageCellSent.self]).textColor = UIColor.ringMsgCellSentText MessageBubble.appearance(whenContainedInInstancesOf: [MessageCellReceived.self]).tintColor = secondaryContentColor - MessageBubble.appearance(whenContainedInInstancesOf: [MessageCellReceived.self]).backgroundColor = secondaryColor - UILabel.appearance(whenContainedInInstancesOf: [MessageBubble.self, MessageCellReceived.self]).textColor = secondaryContentColor + MessageBubble.appearance(whenContainedInInstancesOf: [MessageCellReceived.self]).backgroundColor = UIColor.ringMsgCellReceived + UILabel.appearance(whenContainedInInstancesOf: [MessageBubble.self, MessageCellReceived.self]).textColor = UIColor.ringMsgCellReceivedText MessageBubble.appearance(whenContainedInInstancesOf: [MessageCellGenerated.self]).tintColor = UIColor.clear MessageBubble.appearance(whenContainedInInstancesOf: [MessageCellGenerated.self]).backgroundColor = UIColor.clear diff --git a/Ring/Ring/Extensions/UIColor+Ring.swift b/Ring/Ring/Extensions/UIColor+Ring.swift index 27d831c548a37dda982a03177d0f04ea147b45c5..ae88929776ac3e689bb11ffc5933efad3e10fe61 100644 --- a/Ring/Ring/Extensions/UIColor+Ring.swift +++ b/Ring/Ring/Extensions/UIColor+Ring.swift @@ -33,4 +33,24 @@ extension UIColor { blue: 96.0/255.0, alpha: 1.0) + static let ringMsgCellSent = UIColor(colorLiteralRed: 58.0/255.0, + green: 192.0/255.0, + blue: 210.0/255.0, + alpha: 1.0) + + static let ringMsgCellSentText = UIColor(colorLiteralRed: 255.0/255.0, + green: 255.0/255.0, + blue: 255.0/255.0, + alpha: 1.0) + + static let ringMsgCellReceived = UIColor(colorLiteralRed: 235.0/255.0, + green: 239.0/255.0, + blue: 239.0/255.0, + alpha: 1.0) + + static let ringMsgCellReceivedText = UIColor(colorLiteralRed: 48.0/255.0, + green: 48.0/255.0, + blue: 48.0/255.0, + alpha: 1.0) + } diff --git a/Ring/Ring/Extensions/UILabel+Ring.swift b/Ring/Ring/Extensions/UILabel+Ring.swift new file mode 100644 index 0000000000000000000000000000000000000000..106c380a29b24ac945643015e90c8a4bd0365efa --- /dev/null +++ b/Ring/Ring/Extensions/UILabel+Ring.swift @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2016 Savoir-faire Linux Inc. + * + * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import Foundation + +extension UILabel { + func setTextWithLineSpacing(withText: String, withLineSpacing: CGFloat) { + let attrString = NSMutableAttributedString(string: withText) + let style = NSMutableParagraphStyle() + style.lineSpacing = withLineSpacing + attrString.addAttribute(NSParagraphStyleAttributeName, + value: style, + range: NSRange(location: 0, length: withText.utf16.count)) + self.attributedText = attrString + } +} diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift new file mode 100644 index 0000000000000000000000000000000000000000..fa63c030205aee2ec2ea106338db4e94c9bf6595 --- /dev/null +++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCell.swift @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 Savoir-faire Linux Inc. + * + * Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com> + * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import UIKit +import Reusable + +class MessageCell: UITableViewCell, NibReusable { + + @IBOutlet weak var bubble: MessageBubble! + @IBOutlet weak var bubbleBottomConstraint: NSLayoutConstraint! + @IBOutlet weak var bubbleTopConstraint: NSLayoutConstraint! + @IBOutlet weak var messageLabel: UILabel! + @IBOutlet weak var bottomCorner: UIView! + @IBOutlet weak var topCorner: UIView! +} diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.swift b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.swift index 0b42e9a7cff506df2e4ba5d36b6817b9bec42f46..c504acdef93d38101e79aa30bbea7258cf75f0c7 100644 --- a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.swift +++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.swift @@ -21,9 +21,5 @@ import UIKit import Reusable -class MessageCellReceived: UITableViewCell, NibReusable { - - @IBOutlet weak var bubble: MessageBubble! - @IBOutlet weak var messageLabel: UILabel! - +class MessageCellReceived: MessageCell { } diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.xib b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.xib index 679b0e0e6f74c4e09375f91809fa431a1360c8e2..b381a8be6edd0eb6f85ef866c665e5b23b13e42e 100644 --- a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.xib +++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellReceived.xib @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12121" systemVersion="16E195" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> <device id="retina4_7" orientation="portrait"> <adaptation id="fullscreen"/> </device> @@ -15,48 +15,73 @@ <rect key="frame" x="0.0" y="0.0" width="510" height="47"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM"> - <rect key="frame" x="0.0" y="0.0" width="510" height="46.5"/> + <rect key="frame" x="0.0" y="0.0" width="510" height="47"/> <autoresizingMask key="autoresizingMask"/> <subviews> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="WBd-CS-7Qv" userLabel="Top Corner"> + <rect key="frame" x="16" y="8" width="15" height="15"/> + <color key="backgroundColor" red="1" green="0.0" blue="1" alpha="1" colorSpace="calibratedRGB"/> + <constraints> + <constraint firstAttribute="height" constant="15" id="fjJ-O1-VNm"/> + <constraint firstAttribute="width" constant="15" id="gch-Wg-ytg"/> + </constraints> + </view> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XcL-CH-BiH" userLabel="Bottom Corner"> + <rect key="frame" x="16" y="24" width="15" height="15"/> + <color key="backgroundColor" red="1" green="0.5" blue="0.0" alpha="1" colorSpace="calibratedRGB"/> + <constraints> + <constraint firstAttribute="width" constant="15" id="ocR-DU-zKZ"/> + <constraint firstAttribute="height" constant="15" id="ooc-tv-fiO"/> + </constraints> + </view> <view clipsSubviews="YES" contentMode="scaleToFill" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="kZJ-Ay-LTR" customClass="MessageBubble" customModule="Ring" customModuleProvider="target"> - <rect key="frame" x="16" y="8" width="152.5" height="30.5"/> + <rect key="frame" x="16" y="8" width="190.5" height="30.5"/> <subviews> - <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label Label Label Label " lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lyR-7c-S2k"> - <rect key="frame" x="8" y="4" width="136.5" height="22.5"/> - <fontDescription key="fontDescription" type="system" pointSize="12"/> - <nil key="textColor"/> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label Label Label Label" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lyR-7c-S2k"> + <rect key="frame" x="10" y="8" width="170.5" height="14.5"/> + <fontDescription key="fontDescription" type="system" pointSize="16"/> + <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> </label> </subviews> <color key="backgroundColor" red="0.66666666666666663" green="0.66666666666666663" blue="0.66666666666666663" alpha="1" colorSpace="calibratedRGB"/> <constraints> <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="30" id="1Kj-UZ-gu7"/> - <constraint firstItem="lyR-7c-S2k" firstAttribute="leading" secondItem="kZJ-Ay-LTR" secondAttribute="leading" constant="8" id="8m5-sR-xnh"/> - <constraint firstAttribute="bottom" secondItem="lyR-7c-S2k" secondAttribute="bottom" constant="4" id="gwN-uX-PWd"/> - <constraint firstAttribute="trailing" secondItem="lyR-7c-S2k" secondAttribute="trailing" constant="8" id="uzV-kG-oGN"/> - <constraint firstItem="lyR-7c-S2k" firstAttribute="top" secondItem="kZJ-Ay-LTR" secondAttribute="top" constant="4" id="ycc-WI-Jk6"/> + <constraint firstItem="lyR-7c-S2k" firstAttribute="leading" secondItem="kZJ-Ay-LTR" secondAttribute="leading" constant="10" id="8m5-sR-xnh"/> + <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="28" id="UWN-H4-Sh9"/> + <constraint firstAttribute="bottom" secondItem="lyR-7c-S2k" secondAttribute="bottom" constant="8" id="gwN-uX-PWd"/> + <constraint firstAttribute="trailing" secondItem="lyR-7c-S2k" secondAttribute="trailing" constant="10" id="uzV-kG-oGN"/> + <constraint firstItem="lyR-7c-S2k" firstAttribute="top" secondItem="kZJ-Ay-LTR" secondAttribute="top" constant="8" id="ycc-WI-Jk6"/> </constraints> <userDefinedRuntimeAttributes> <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius"> - <real key="value" value="4"/> + <integer key="value" value="15"/> </userDefinedRuntimeAttribute> </userDefinedRuntimeAttributes> </view> </subviews> <constraints> <constraint firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" constant="8" id="1QQ-bu-6Bl"/> + <constraint firstItem="XcL-CH-BiH" firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" id="2d4-0F-VWg"/> + <constraint firstItem="WBd-CS-7Qv" firstAttribute="top" secondItem="kZJ-Ay-LTR" secondAttribute="top" id="4Zp-8q-rFJ"/> <constraint firstAttribute="trailing" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" priority="1" constant="16" id="99Y-bR-Ioq"/> <constraint firstItem="kZJ-Ay-LTR" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="H2p-sc-9uM" secondAttribute="leading" priority="1" constant="64" id="Eso-cy-OYs"/> + <constraint firstItem="XcL-CH-BiH" firstAttribute="leading" secondItem="kZJ-Ay-LTR" secondAttribute="leading" id="GaI-yj-QFt"/> <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" constant="64" id="TCY-7X-mFs"/> <constraint firstItem="kZJ-Ay-LTR" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="8" id="jhd-A8-c1o"/> <constraint firstItem="kZJ-Ay-LTR" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="16" id="nWe-5k-Qpn"/> + <constraint firstItem="WBd-CS-7Qv" firstAttribute="leading" secondItem="kZJ-Ay-LTR" secondAttribute="leading" id="yBG-sT-w2a"/> </constraints> </tableViewCellContentView> <connections> + <outlet property="bottomCorner" destination="XcL-CH-BiH" id="4gw-IC-EAM"/> <outlet property="bubble" destination="kZJ-Ay-LTR" id="hdG-fG-L69"/> + <outlet property="bubbleBottomConstraint" destination="1QQ-bu-6Bl" id="a4F-pf-cXL"/> + <outlet property="bubbleTopConstraint" destination="jhd-A8-c1o" id="40k-2d-6rW"/> <outlet property="messageLabel" destination="lyR-7c-S2k" id="hd3-pz-Pwh"/> + <outlet property="topCorner" destination="WBd-CS-7Qv" id="GCm-Hv-5Ei"/> </connections> - <point key="canvasLocation" x="-411" y="-132"/> + <point key="canvasLocation" x="-411" y="-132.5"/> </tableViewCell> </objects> </document> diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.swift b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.swift index 336836d6fd4271da69993d565c3ed5a8cd95648d..7d5d96a72726824801c4741ae06f11520a2ff169 100644 --- a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.swift +++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.swift @@ -21,9 +21,5 @@ import UIKit import Reusable -class MessageCellSent: UITableViewCell, NibReusable { - - @IBOutlet weak var bubble: MessageBubble! - @IBOutlet weak var messageLabel: UILabel! - +class MessageCellSent: MessageCell { } diff --git a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.xib b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.xib index 04dc0efe4a881bf121603ea92110f882db6576f1..41e65e646798012f611666493a47500e7318eed3 100644 --- a/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.xib +++ b/Ring/Ring/Features/Conversations/Conversation/Cells/MessageCellSent.xib @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> <device id="retina4_7" orientation="portrait"> <adaptation id="fullscreen"/> </device> @@ -18,45 +18,70 @@ <rect key="frame" x="0.0" y="0.0" width="510" height="46.5"/> <autoresizingMask key="autoresizingMask"/> <subviews> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="hdz-AQ-xHI" userLabel="Bottom Corner"> + <rect key="frame" x="479" y="24" width="15" height="15"/> + <color key="backgroundColor" red="1" green="0.5" blue="0.0" alpha="1" colorSpace="calibratedRGB"/> + <constraints> + <constraint firstAttribute="height" constant="15" id="D0h-cW-9kB"/> + <constraint firstAttribute="width" constant="15" id="wlh-ar-Nsv"/> + </constraints> + </view> + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="EMh-bG-ilg" userLabel="Top Corner"> + <rect key="frame" x="479" y="8" width="15" height="15"/> + <color key="backgroundColor" red="1" green="0.0" blue="1" alpha="1" colorSpace="calibratedRGB"/> + <constraints> + <constraint firstAttribute="width" constant="15" id="zaa-Rn-ziw"/> + <constraint firstAttribute="height" constant="15" id="zuP-4P-1GS"/> + </constraints> + </view> <view clipsSubviews="YES" contentMode="scaleToFill" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="kZJ-Ay-LTR" customClass="MessageBubble" customModule="Ring" customModuleProvider="target"> - <rect key="frame" x="341.5" y="8" width="152.5" height="30.5"/> + <rect key="frame" x="303.5" y="8" width="190.5" height="31"/> <subviews> - <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label Label Label Label " lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lyR-7c-S2k"> - <rect key="frame" x="8" y="4" width="136.5" height="22.5"/> - <fontDescription key="fontDescription" type="system" pointSize="12"/> - <nil key="textColor"/> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label Label Label Label" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lyR-7c-S2k"> + <rect key="frame" x="10" y="8" width="170.5" height="15"/> + <fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="16"/> + <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> </label> </subviews> <color key="backgroundColor" red="0.66666666666666663" green="0.66666666666666663" blue="0.66666666666666663" alpha="1" colorSpace="calibratedRGB"/> <constraints> <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="30" id="1Kj-UZ-gu7"/> - <constraint firstItem="lyR-7c-S2k" firstAttribute="leading" secondItem="kZJ-Ay-LTR" secondAttribute="leading" constant="8" id="8m5-sR-xnh"/> - <constraint firstAttribute="bottom" secondItem="lyR-7c-S2k" secondAttribute="bottom" constant="4" id="gwN-uX-PWd"/> - <constraint firstAttribute="trailing" secondItem="lyR-7c-S2k" secondAttribute="trailing" constant="8" id="uzV-kG-oGN"/> - <constraint firstItem="lyR-7c-S2k" firstAttribute="top" secondItem="kZJ-Ay-LTR" secondAttribute="top" constant="4" id="ycc-WI-Jk6"/> + <constraint firstItem="lyR-7c-S2k" firstAttribute="leading" secondItem="kZJ-Ay-LTR" secondAttribute="leading" constant="10" id="8m5-sR-xnh"/> + <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="28" id="BZE-kP-hPK"/> + <constraint firstAttribute="bottom" secondItem="lyR-7c-S2k" secondAttribute="bottom" constant="8" id="gwN-uX-PWd"/> + <constraint firstAttribute="trailing" secondItem="lyR-7c-S2k" secondAttribute="trailing" constant="10" id="uzV-kG-oGN"/> + <constraint firstItem="lyR-7c-S2k" firstAttribute="top" secondItem="kZJ-Ay-LTR" secondAttribute="top" constant="8" id="ycc-WI-Jk6"/> </constraints> <userDefinedRuntimeAttributes> <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius"> - <real key="value" value="4"/> + <integer key="value" value="15"/> </userDefinedRuntimeAttribute> </userDefinedRuntimeAttributes> </view> </subviews> <constraints> - <constraint firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" constant="8" id="1QQ-bu-6Bl"/> + <constraint firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" constant="8" id="1QQ-bu-6Bl" userLabel="Bubble Bottom Constraint"/> <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" priority="1" constant="64" id="99Y-bR-Ioq"/> <constraint firstItem="kZJ-Ay-LTR" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" priority="1" constant="16" id="Eso-cy-OYs"/> + <constraint firstItem="EMh-bG-ilg" firstAttribute="trailing" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" id="MY3-Aj-94K"/> <constraint firstAttribute="trailing" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" constant="16" id="TCY-7X-mFs"/> <constraint firstItem="kZJ-Ay-LTR" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="8" id="jhd-A8-c1o"/> + <constraint firstItem="hdz-AQ-xHI" firstAttribute="trailing" secondItem="kZJ-Ay-LTR" secondAttribute="trailing" id="lSl-vu-Wkl"/> <constraint firstItem="kZJ-Ay-LTR" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="64" id="nWe-5k-Qpn"/> + <constraint firstItem="EMh-bG-ilg" firstAttribute="top" secondItem="kZJ-Ay-LTR" secondAttribute="top" id="zEh-jv-0Ha"/> + <constraint firstItem="hdz-AQ-xHI" firstAttribute="bottom" secondItem="kZJ-Ay-LTR" secondAttribute="bottom" id="zWA-Jg-F6Q"/> </constraints> </tableViewCellContentView> <connections> + <outlet property="bottomCorner" destination="hdz-AQ-xHI" id="ChE-BT-0LS"/> <outlet property="bubble" destination="kZJ-Ay-LTR" id="hdG-fG-L69"/> + <outlet property="bubbleBottomConstraint" destination="1QQ-bu-6Bl" id="woo-UQ-wXK"/> + <outlet property="bubbleTopConstraint" destination="jhd-A8-c1o" id="cll-eA-OC5"/> <outlet property="messageLabel" destination="lyR-7c-S2k" id="hd3-pz-Pwh"/> + <outlet property="topCorner" destination="EMh-bG-ilg" id="nHl-hn-BZ1"/> </connections> - <point key="canvasLocation" x="-411" y="-132"/> + <point key="canvasLocation" x="-411" y="-132.5"/> </tableViewCell> </objects> </document> diff --git a/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift b/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift index bf31333fc01b7c8e0736a8ff256da30caac5c43f..c938de3edb29778ccc04479a5e44dde4f8894cf3 100644 --- a/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift +++ b/Ring/Ring/Features/Conversations/Conversation/ConversationViewController.swift @@ -21,9 +21,20 @@ import UIKit import RxSwift import Reusable +import SwiftyBeaver + +enum BubbleChaining { + case singleMessage + case firstOfSequence + case lastOfSequence + case middleOfSequence + case error +} class ConversationViewController: UIViewController, UITextFieldDelegate, StoryboardBased, ViewModelBased { + let log = SwiftyBeaver.self + @IBOutlet weak var tableView: UITableView! @IBOutlet weak var spinnerView: UIView! @@ -33,6 +44,7 @@ class ConversationViewController: UIViewController, UITextFieldDelegate, Storybo var messageViewModels: [MessageViewModel]? var textFieldShouldEndEditing = false var bottomOffset: CGFloat = 0 + let scrollOffsetThreshold: CGFloat = 600 override func viewDidLoad() { super.viewDidLoad() @@ -147,7 +159,7 @@ class ConversationViewController: UIViewController, UITextFieldDelegate, Storybo fileprivate func scrollToBottomIfNeed() { if self.isBottomContentOffset { - self.scrollToBottom(animated: true) + self.scrollToBottom(animated: false) } } @@ -160,7 +172,9 @@ class ConversationViewController: UIViewController, UITextFieldDelegate, Storybo } fileprivate var isBottomContentOffset: Bool { - return self.tableView.contentOffset.y + self.tableView.contentInset.top >= bottomOffset + updateBottomOffset() + let offset = abs((self.tableView.contentOffset.y + self.tableView.contentInset.top) - bottomOffset) + return offset <= scrollOffsetThreshold } override var inputAccessoryView: UIView { @@ -189,6 +203,86 @@ class ConversationViewController: UIViewController, UITextFieldDelegate, Storybo return textFieldShouldEndEditing } + func isFirstMessage(cellForRowAt indexPath: IndexPath) -> Bool { + return indexPath.row == 0 + } + + func isLastMessage(cellForRowAt indexPath: IndexPath) -> Bool { + return self.messageViewModels?.count == indexPath.row + 1 + } + + func getBubbleChaining(cellForRowAt indexPath: IndexPath) -> BubbleChaining { + if let msgViewModel = self.messageViewModels?[indexPath.row] { + let msgOwner = msgViewModel.bubblePosition() + if self.messageViewModels?.count == 1 || indexPath.row == 0 { + if self.messageViewModels?.count == indexPath.row + 1 { + return BubbleChaining.singleMessage + } + let nextMsgViewModel = indexPath.row + 1 <= (self.messageViewModels?.count)! + ? self.messageViewModels?[indexPath.row + 1] : nil + if nextMsgViewModel != nil { + return msgOwner != nextMsgViewModel?.bubblePosition() + ? BubbleChaining.singleMessage : BubbleChaining.firstOfSequence + } + } else if self.messageViewModels?.count == indexPath.row + 1 { + let lastMsgViewModel = indexPath.row - 1 >= 0 && indexPath.row - 1 < (self.messageViewModels?.count)! + ? self.messageViewModels?[indexPath.row - 1] : nil + if lastMsgViewModel != nil { + return msgOwner != lastMsgViewModel?.bubblePosition() + ? BubbleChaining.singleMessage : BubbleChaining.lastOfSequence + } + } + let lastMsgViewModel = indexPath.row - 1 >= 0 && indexPath.row - 1 < (self.messageViewModels?.count)! + ? self.messageViewModels?[indexPath.row - 1] : nil + let nextMsgViewModel = indexPath.row + 1 <= (self.messageViewModels?.count)! + ? self.messageViewModels?[indexPath.row + 1] : nil + var chaining = BubbleChaining.singleMessage + if (lastMsgViewModel != nil) && (nextMsgViewModel != nil) { + if msgOwner != lastMsgViewModel?.bubblePosition() && msgOwner == nextMsgViewModel?.bubblePosition() { + chaining = BubbleChaining.firstOfSequence + } else if msgOwner != nextMsgViewModel?.bubblePosition() && msgOwner == lastMsgViewModel?.bubblePosition() { + chaining = BubbleChaining.lastOfSequence + } else if msgOwner == nextMsgViewModel?.bubblePosition() && msgOwner == lastMsgViewModel?.bubblePosition() { + chaining = BubbleChaining.middleOfSequence + } + } + return chaining + } + return BubbleChaining.error + } + + func applyBubbleStyleToCell(toCell cell: MessageCell, + withChaining chaining: BubbleChaining, + withContent content: String, + withType type: BubblePosition) { + + let bubbleColor = type == .received ? UIColor.ringMsgCellReceived : UIColor.ringMsgCellSent + + cell.messageLabel.setTextWithLineSpacing(withText: content, withLineSpacing: 2) + + cell.topCorner.isHidden = true + cell.topCorner.backgroundColor = bubbleColor + cell.bottomCorner.isHidden = true + cell.bottomCorner.backgroundColor = bubbleColor + cell.bubbleBottomConstraint.constant = 8 + cell.bubbleTopConstraint.constant = 8 + + switch chaining { + case .middleOfSequence: + cell.topCorner.isHidden = false + cell.bottomCorner.isHidden = false + cell.bubbleBottomConstraint.constant = 1 + cell.bubbleTopConstraint.constant = 1 + case .firstOfSequence: + cell.bottomCorner.isHidden = false + cell.bubbleBottomConstraint.constant = 1 + case .lastOfSequence: + cell.topCorner.isHidden = false + cell.bubbleTopConstraint.constant = 1 + default: break + } + } + } extension ConversationViewController: UITableViewDataSource { @@ -199,21 +293,38 @@ extension ConversationViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if let messageViewModel = self.messageViewModels?[indexPath.row] { + let chaining = self.getBubbleChaining(cellForRowAt: indexPath) if messageViewModel.bubblePosition() == .received { + // left side (incoming) let cell = tableView.dequeueReusableCell(for: indexPath, cellType: MessageCellReceived.self) - cell.messageLabel.text = messageViewModel.content + + // Format cell + applyBubbleStyleToCell(toCell: cell, withChaining: chaining, withContent: messageViewModel.content, withType: .received) + + // Special cases where top/bottom margins should be larger + if isFirstMessage(cellForRowAt: indexPath) { + cell.bubbleTopConstraint.constant = 16 + } else if isLastMessage(cellForRowAt: indexPath) { + cell.bubbleBottomConstraint.constant = 16 + } + return cell - } + } else { + // right side (outgoing) + let cell = tableView.dequeueReusableCell(for: indexPath, cellType: MessageCellSent.self) + + // Format cell + applyBubbleStyleToCell(toCell: cell, withChaining: chaining, withContent: messageViewModel.content, withType: .sent) + + // Special cases where top/bottom margins should be larger + if isFirstMessage(cellForRowAt: indexPath) { + cell.bubbleTopConstraint.constant = 16 + } else if isLastMessage(cellForRowAt: indexPath) { + cell.bubbleBottomConstraint.constant = 16 + } - if messageViewModel.bubblePosition() == .generated { - let cell = tableView.dequeueReusableCell(for: indexPath, cellType: MessageCellGenerated.self) - cell.messageLabel.text = messageViewModel.content return cell } - - let cell = tableView.dequeueReusableCell(for: indexPath, cellType: MessageCellSent.self) - cell.messageLabel.text = messageViewModel.content - return cell } return tableView.dequeueReusableCell(for: indexPath, cellType: MessageCellSent.self)