Merge pull request #2445 from vector-im/riot_2396

Reactions: Display existing reactions below the message
This commit is contained in:
SBiOSoftWhare
2019-05-21 01:33:38 +02:00
committed by GitHub
15 changed files with 654 additions and 42 deletions
+1
View File
@@ -4,6 +4,7 @@ Changes in 0.8.7 (2019-xx-xx)
Improvements:
* RoomVC: When replying, use a "Reply" button instead of "Send".
* RoomVC: New message actions (#2394).
* Reactions: Display existing reactions below the message (#2396).
Changes in 0.8.6 (2019-05-06)
===============================================
+1
View File
@@ -79,6 +79,7 @@ abstract_target 'RiotPods' do
target "Riot" do
import_MatrixKit
pod 'DGCollectionViewLeftAlignFlowLayout', '~> 1.0.4'
end
target "RiotShareExtension" do
+51
View File
@@ -194,6 +194,14 @@
B169331720F3CBE000746532 /* RecentCellData.m in Sources */ = {isa = PBXBuildFile; fileRef = B16932F920F3C51900746532 /* RecentCellData.m */; };
B17982FF2119FED2001FD722 /* GDPRConsentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B17982FE2119FED2001FD722 /* GDPRConsentViewController.swift */; };
B1798302211B13B3001FD722 /* OnBoardingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1798301211B13B3001FD722 /* OnBoardingManager.swift */; };
B1963B2B228F1C4900CBA17F /* BubbleReactionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B25228F1C4800CBA17F /* BubbleReactionsView.swift */; };
B1963B2C228F1C4900CBA17F /* BubbleReactionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1963B26228F1C4800CBA17F /* BubbleReactionViewCell.xib */; };
B1963B2D228F1C4900CBA17F /* BubbleReactionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B27228F1C4800CBA17F /* BubbleReactionsViewModel.swift */; };
B1963B2E228F1C4900CBA17F /* BubbleReactionViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B28228F1C4800CBA17F /* BubbleReactionViewData.swift */; };
B1963B2F228F1C4900CBA17F /* BubbleReactionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B29228F1C4800CBA17F /* BubbleReactionViewCell.swift */; };
B1963B30228F1C4900CBA17F /* BubbleReactionsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1963B2A228F1C4800CBA17F /* BubbleReactionsView.xib */; };
B1963B32228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B31228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift */; };
B1963B3822933BC800CBA17F /* AutosizedCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B3722933BC800CBA17F /* AutosizedCollectionView.swift */; };
B19EFA3921F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19EFA3821F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift */; };
B19EFA3B21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19EFA3A21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift */; };
B1A5B33E227ADF2A004CBA85 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A5B33D227ADF2A004CBA85 /* UIImage.swift */; };
@@ -784,6 +792,14 @@
B169331320F3CAFC00746532 /* PublicRoomsDirectoryDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PublicRoomsDirectoryDataSource.h; sourceTree = "<group>"; };
B17982FE2119FED2001FD722 /* GDPRConsentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GDPRConsentViewController.swift; sourceTree = "<group>"; };
B1798301211B13B3001FD722 /* OnBoardingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnBoardingManager.swift; sourceTree = "<group>"; };
B1963B25228F1C4800CBA17F /* BubbleReactionsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleReactionsView.swift; sourceTree = "<group>"; };
B1963B26228F1C4800CBA17F /* BubbleReactionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BubbleReactionViewCell.xib; sourceTree = "<group>"; };
B1963B27228F1C4800CBA17F /* BubbleReactionsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleReactionsViewModel.swift; sourceTree = "<group>"; };
B1963B28228F1C4800CBA17F /* BubbleReactionViewData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleReactionViewData.swift; sourceTree = "<group>"; };
B1963B29228F1C4800CBA17F /* BubbleReactionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleReactionViewCell.swift; sourceTree = "<group>"; };
B1963B2A228F1C4800CBA17F /* BubbleReactionsView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BubbleReactionsView.xib; sourceTree = "<group>"; };
B1963B31228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleReactionsViewModelType.swift; sourceTree = "<group>"; };
B1963B3722933BC800CBA17F /* AutosizedCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutosizedCollectionView.swift; sourceTree = "<group>"; };
B19EFA3821F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverCoordinatorType.swift; sourceTree = "<group>"; };
B19EFA3A21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverCoordinator.swift; sourceTree = "<group>"; };
B1A5B33D227ADF2A004CBA85 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = "<group>"; };
@@ -1936,6 +1952,29 @@
path = OnBoarding;
sourceTree = "<group>";
};
B1963B24228F1C4800CBA17F /* BubbleReactions */ = {
isa = PBXGroup;
children = (
B1963B31228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift */,
B1963B27228F1C4800CBA17F /* BubbleReactionsViewModel.swift */,
B1963B25228F1C4800CBA17F /* BubbleReactionsView.swift */,
B1963B2A228F1C4800CBA17F /* BubbleReactionsView.xib */,
B1963B28228F1C4800CBA17F /* BubbleReactionViewData.swift */,
B1963B29228F1C4800CBA17F /* BubbleReactionViewCell.swift */,
B1963B26228F1C4800CBA17F /* BubbleReactionViewCell.xib */,
);
path = BubbleReactions;
sourceTree = "<group>";
};
B1963B3622933B9500CBA17F /* CollectionView */ = {
isa = PBXGroup;
children = (
B1963B3722933BC800CBA17F /* AutosizedCollectionView.swift */,
);
name = CollectionView;
path = Riot/Modules/Common/CollectionView;
sourceTree = SOURCE_ROOT;
};
B1B5567620EE6C4C00210D55 /* Modules */ = {
isa = PBXGroup;
children = (
@@ -2050,6 +2089,7 @@
B1B556A420EE6C4C00210D55 /* Members */,
B1B5569020EE6C4C00210D55 /* Settings */,
B1C562D7228C0B4C0037F12A /* ContextualMenu */,
B1963B24228F1C4800CBA17F /* BubbleReactions */,
);
path = Room;
sourceTree = "<group>";
@@ -2231,6 +2271,7 @@
B1B556CD20EE6C4C00210D55 /* Common */ = {
isa = PBXGroup;
children = (
B1963B3622933B9500CBA17F /* CollectionView */,
B1B556CE20EE6C4C00210D55 /* WebViewController */,
B1B556D120EE6C4C00210D55 /* NavigationController */,
B1B556D420EE6C4C00210D55 /* SegmentedViewController */,
@@ -3537,6 +3578,7 @@
B1B5593920EF7BAC00210D55 /* TableViewCellWithCheckBoxes.xib in Resources */,
B1B557C120EF5B4500210D55 /* DisabledRoomInputToolbarView.xib in Resources */,
32891D6C2264CBA300C82226 /* SimpleScreenTemplateViewController.storyboard in Resources */,
B1963B2C228F1C4900CBA17F /* BubbleReactionViewCell.xib in Resources */,
B1664DA320F4F96200808783 /* Vector.strings in Resources */,
B1B557C720EF5CD400210D55 /* DirectoryServerDetailTableViewCell.xib in Resources */,
B1B5582620EF638A00210D55 /* RoomMemberTitleView.xib in Resources */,
@@ -3588,6 +3630,7 @@
B1B5590F20EF782800210D55 /* TableViewCellWithPhoneNumberTextField.xib in Resources */,
B1B5578520EF564900210D55 /* GroupTableViewCellWithSwitch.xib in Resources */,
B1B557B320EF5AEF00210D55 /* EventDetailsView.xib in Resources */,
B1963B30228F1C4900CBA17F /* BubbleReactionsView.xib in Resources */,
B1B557DD20EF5FBB00210D55 /* FilesSearchTableViewCell.xib in Resources */,
B1B5590320EF768F00210D55 /* RoomSelectedStickerBubbleCell.xib in Resources */,
3232ABB62257BE6400AD6A5C /* DeviceVerificationVerifyViewController.storyboard in Resources */,
@@ -3634,6 +3677,7 @@
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-RiotPods-Riot/Pods-RiotPods-Riot-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework",
"${BUILT_PRODUCTS_DIR}/DGCollectionViewLeftAlignFlowLayout/DGCollectionViewLeftAlignFlowLayout.framework",
"${BUILT_PRODUCTS_DIR}/DTCoreText/DTCoreText.framework",
"${BUILT_PRODUCTS_DIR}/DTFoundation/DTFoundation.framework",
"${BUILT_PRODUCTS_DIR}/GBDeviceInfo/GBDeviceInfo.framework",
@@ -3659,6 +3703,7 @@
);
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DGCollectionViewLeftAlignFlowLayout.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DTCoreText.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DTFoundation.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GBDeviceInfo.framework",
@@ -3882,6 +3927,7 @@
B1098BDF21ECE09F000DDA48 /* Strings.swift in Sources */,
B1B558C420EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */,
3232ABC022594C0900AD6A5C /* VerifyEmojiCollectionViewCell.swift in Sources */,
B1963B2E228F1C4900CBA17F /* BubbleReactionViewData.swift in Sources */,
B1B5572F20EE6C4D00210D55 /* ReadReceiptsViewController.m in Sources */,
B1B558CB20EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m in Sources */,
B169330B20F3CA3A00746532 /* Contact.m in Sources */,
@@ -3914,6 +3960,7 @@
B1B5593B20EF7BAC00210D55 /* TableViewCellWithCheckBoxAndLabel.m in Sources */,
B1B5581A20EF625800210D55 /* ExpandedRoomTitleView.m in Sources */,
B1107EC82200B0720038014B /* KeyBackupRecoverSuccessViewController.swift in Sources */,
B1963B2F228F1C4900CBA17F /* BubbleReactionViewCell.swift in Sources */,
B1B558E920EF768F00210D55 /* RoomSelectedStickerBubbleCell.m in Sources */,
B1B558DF20EF768F00210D55 /* RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m in Sources */,
F083BE041E7009ED00A9B29C /* Tools.m in Sources */,
@@ -3949,6 +3996,7 @@
32F6B96E2270623100BBA352 /* DeviceVerificationDataLoadingViewModelType.swift in Sources */,
B1B5592C20EF7A5D00210D55 /* TableViewCellWithButton.m in Sources */,
32242F1421E8FBA900725742 /* DefaultTheme.swift in Sources */,
B1963B2D228F1C4900CBA17F /* BubbleReactionsViewModel.swift in Sources */,
32242F1321E8FBA900725742 /* Theme.swift in Sources */,
B1B5582520EF638A00210D55 /* RoomMemberTitleView.m in Sources */,
B1B5582C20EF666100210D55 /* DirectoryRecentTableViewCell.m in Sources */,
@@ -4029,6 +4077,7 @@
B1798302211B13B3001FD722 /* OnBoardingManager.swift in Sources */,
B1B5573520EE6C4D00210D55 /* GroupDetailsViewController.m in Sources */,
B10B3B5B2201DD740072C76B /* KeyBackupBannerCell.swift in Sources */,
B1963B32228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift in Sources */,
B1098BFA21ECFE65000DDA48 /* KeyBackupSetupPassphraseViewModel.swift in Sources */,
B1B5575220EE6C4D00210D55 /* RoomKeyRequestViewController.m in Sources */,
F083BD1E1E7009ED00A9B29C /* AppDelegate.m in Sources */,
@@ -4082,8 +4131,10 @@
B110872621F098F0003554A5 /* ActivityIndicatorView.swift in Sources */,
B19EFA3921F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift in Sources */,
B1E5368D21FB7245001F3AFF /* KeyBackupRecoverFromPassphraseViewController.swift in Sources */,
B1963B3822933BC800CBA17F /* AutosizedCollectionView.swift in Sources */,
B169330320F3C98900746532 /* RoomBubbleCellData.m in Sources */,
B1B557CC20EF5D8000210D55 /* DirectoryServerTableViewCell.m in Sources */,
B1963B2B228F1C4900CBA17F /* BubbleReactionsView.swift in Sources */,
B1B5575C20EE6C4D00210D55 /* DirectoryViewController.m in Sources */,
B1B558BD20EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.m in Sources */,
B1B5577020EE702800210D55 /* WidgetPickerViewController.m in Sources */,
+3 -3
View File
@@ -30,8 +30,8 @@ class DarkTheme: NSObject, Theme {
var searchBackgroundColor: UIColor = UIColor(rgb: 0x181B21)
var searchPlaceholderColor: UIColor = UIColor(rgb: 0x61708B)
var headerBackgroundColor: UIColor = UIColor(rgb: 0x15171B)
var headerBorderColor: UIColor = UIColor(rgb: 0x22262E)
var headerBackgroundColor: UIColor = UIColor(rgb: 0x22262E)
var headerBorderColor: UIColor = UIColor(rgb: 0x181B21)
var headerTextPrimaryColor: UIColor = UIColor(rgb: 0xA1B2D1)
var headerTextSecondaryColor: UIColor = UIColor(rgb: 0xC8C8CD)
@@ -39,7 +39,7 @@ class DarkTheme: NSObject, Theme {
var textSecondaryColor: UIColor = UIColor(rgb: 0xA1B2D1)
var tintColor: UIColor = UIColor(rgb: 0x03B381)
var tintBackgroundColor: UIColor = UIColor(rgb: 0xe9fff9)
var tintBackgroundColor: UIColor = UIColor(rgb: 0x1F6954)
var unreadRoomIndentColor: UIColor = UIColor(rgb: 0x2E3648)
var lineBreakColor: UIColor = UIColor(rgb: 0x61708B)
@@ -30,7 +30,7 @@ class DefaultTheme: NSObject, Theme {
var searchBackgroundColor: UIColor = UIColor(rgb: 0xFFFFFF)
var searchPlaceholderColor: UIColor = UIColor(rgb: 0x61708B)
var headerBackgroundColor: UIColor = UIColor(rgb: 0xF2F5F8)
var headerBackgroundColor: UIColor = UIColor(rgb: 0xF3F8FD)
var headerBorderColor: UIColor = UIColor(rgb: 0xE9EDF1)
var headerTextPrimaryColor: UIColor = UIColor(rgb: 0x61708B)
var headerTextSecondaryColor: UIColor = UIColor(rgb: 0xC8C8CD)
@@ -0,0 +1,31 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
/// AutosizedCollectionView is a convenient UICollectionView that makes dynamic sizing easier when using Auto Layout
class AutosizedCollectionView: UICollectionView {
override var contentSize: CGSize {
didSet {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
return self.contentSize
}
}
@@ -0,0 +1,107 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import UIKit
import Reusable
final class BubbleReactionViewCell: UICollectionViewCell, NibReusable, Themable {
// MARK: - Constants
private enum Constants {
static let selectedBorderWidth: CGFloat = 1.0
}
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var reactionBackgroundView: UIView!
@IBOutlet private weak var emojiLabel: UILabel!
@IBOutlet private weak var countLabel: UILabel!
// MARK: Private
private var theme: Theme?
// MARK: Public
private var isReactionSelected: Bool = false
// MARK: - Life cycle
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.reactionBackgroundView.layer.masksToBounds = true
}
override func layoutSubviews() {
super.layoutSubviews()
self.reactionBackgroundView.layer.cornerRadius = self.reactionBackgroundView.frame.size.height/2.0
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
if #available(iOS 12.0, *) {
/*
On iOS 12, there are issues with self-sizing cells as described in Apple release notes (https://developer.apple.com/documentation/ios_release_notes/ios_12_release_notes) :
"You might encounter issues with systemLayoutSizeFitting(_:) when using a UICollectionViewCell subclass that requires updateConstraints().
(42138227) Workaround: Don't call the cell's setNeedsUpdateConstraints() method unless you need to support live constraint changes.
If you need to support live constraint changes, call updateConstraintsIfNeeded() before calling systemLayoutSizeFitting(_:)."
*/
self.updateConstraintsIfNeeded()
}
return super.preferredLayoutAttributesFitting(layoutAttributes)
}
// MARK: - Public
func fill(viewData: BubbleReactionViewData) {
self.emojiLabel.text = viewData.emoji
self.countLabel.text = viewData.countString
self.isReactionSelected = viewData.isCurrentUserReacted
self.updateViews()
}
func update(theme: Theme) {
self.theme = theme
self.reactionBackgroundView.layer.borderColor = self.theme?.tintColor.cgColor
self.countLabel.textColor = self.theme?.textPrimaryColor
self.updateViews()
}
// MARK: - Private
private func updateViews() {
let reactionBackgroundColor: UIColor?
let reactionBackgroundBorderWidth: CGFloat
if self.isReactionSelected {
reactionBackgroundColor = self.theme?.tintBackgroundColor
reactionBackgroundBorderWidth = Constants.selectedBorderWidth
} else {
reactionBackgroundColor = self.theme?.headerBackgroundColor
reactionBackgroundBorderWidth = 0.0
}
self.reactionBackgroundView.layer.borderWidth = reactionBackgroundBorderWidth
self.reactionBackgroundView.backgroundColor = reactionBackgroundColor
}
}
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="gTV-IL-0wX" customClass="BubbleReactionViewCell" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="66" height="22"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="66" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jjn-uq-kYN">
<rect key="frame" x="0.0" y="0.0" width="66" height="22"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="👍" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gyn-ux-gmi">
<rect key="frame" x="6" y="4" width="26" height="14"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="230" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DQc-oh-Wus">
<rect key="frame" x="34" y="4" width="26" height="14"/>
<constraints>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="26" id="qaD-Xb-kVP"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.95294117647058818" green="0.97254901960784312" blue="0.99215686274509807" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="DQc-oh-Wus" firstAttribute="centerY" secondItem="gyn-ux-gmi" secondAttribute="centerY" id="E1I-l3-qql"/>
<constraint firstItem="gyn-ux-gmi" firstAttribute="leading" secondItem="jjn-uq-kYN" secondAttribute="leading" constant="6" id="OZ2-Xd-9f3"/>
<constraint firstItem="gyn-ux-gmi" firstAttribute="top" secondItem="jjn-uq-kYN" secondAttribute="top" constant="4" id="et3-mn-8dh"/>
<constraint firstAttribute="trailing" secondItem="DQc-oh-Wus" secondAttribute="trailing" constant="6" id="oJE-06-g5e"/>
<constraint firstAttribute="bottom" secondItem="gyn-ux-gmi" secondAttribute="bottom" constant="4" id="thv-mo-0kv"/>
<constraint firstItem="DQc-oh-Wus" firstAttribute="leading" secondItem="gyn-ux-gmi" secondAttribute="trailing" constant="2" id="y7s-iU-ycf"/>
</constraints>
</view>
</subviews>
</view>
<constraints>
<constraint firstItem="jjn-uq-kYN" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="E5H-9a-us9"/>
<constraint firstAttribute="bottom" secondItem="jjn-uq-kYN" secondAttribute="bottom" id="Ggm-1f-3EB"/>
<constraint firstItem="jjn-uq-kYN" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" id="iT6-rk-qd6"/>
<constraint firstAttribute="trailing" secondItem="jjn-uq-kYN" secondAttribute="trailing" id="ztM-7J-Dnf"/>
</constraints>
<viewLayoutGuide key="safeArea" id="ZTg-uK-7eu"/>
<size key="customSize" width="443" height="170"/>
<connections>
<outlet property="countLabel" destination="DQc-oh-Wus" id="GZt-aS-umB"/>
<outlet property="emojiLabel" destination="gyn-ux-gmi" id="qme-DX-UAc"/>
<outlet property="reactionBackgroundView" destination="jjn-uq-kYN" id="Q0x-Np-wEa"/>
</connections>
<point key="canvasLocation" x="-459.4202898550725" y="-207.58928571428569"/>
</collectionViewCell>
</objects>
</document>
@@ -0,0 +1,23 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
struct BubbleReactionViewData {
let emoji: String
let countString: String
let isCurrentUserReacted: Bool
}
@@ -0,0 +1,138 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
import MatrixSDK
import Reusable
import DGCollectionViewLeftAlignFlowLayout
@objcMembers
final class BubbleReactionsView: UIView, NibOwnerLoadable {
// MARK: - Constants
private enum Constants {
static let minimumInteritemSpacing: CGFloat = 6.0
static let minimumLineSpacing: CGFloat = 2.0
}
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var collectionView: UICollectionView!
// MARK: Private
private var reactionsViewData: [BubbleReactionViewData] = []
private var theme: Theme?
// MARK: Public
// Do not use `BubbleReactionsViewModelType` here due to Objective-C incompatibily
var viewModel: BubbleReactionsViewModel? {
didSet {
self.viewModel?.viewDelegate = self
self.viewModel?.process(viewAction: .loadData)
}
}
// MARK: - Setup
private func commonInit() {
self.collectionView.isScrollEnabled = false
self.collectionView.delegate = self
self.collectionView.dataSource = self
self.collectionView.collectionViewLayout = DGCollectionViewLeftAlignFlowLayout()
if let collectionViewFlowLayout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
collectionViewFlowLayout.minimumInteritemSpacing = Constants.minimumInteritemSpacing
collectionViewFlowLayout.minimumLineSpacing = Constants.minimumLineSpacing
}
self.collectionView.register(cellType: BubbleReactionViewCell.self)
self.collectionView.reloadData()
}
convenience init() {
self.init(frame: CGRect.zero)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.loadNibContent()
self.commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.loadNibContent()
self.commonInit()
}
// MARK: - Public
func update(theme: Theme) {
self.theme = theme
self.collectionView.reloadData()
}
func fill(reactionsViewData: [BubbleReactionViewData]) {
self.reactionsViewData = reactionsViewData
self.collectionView.reloadData()
}
}
// MARK: - UICollectionViewDataSource
extension BubbleReactionsView: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.reactionsViewData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: BubbleReactionViewCell = collectionView.dequeueReusableCell(for: indexPath)
if let theme = self.theme {
cell.update(theme: theme)
}
let viewData = self.reactionsViewData[indexPath.row]
cell.fill(viewData: viewData)
return cell
}
}
// MARK: - UICollectionViewDelegate
extension BubbleReactionsView: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.viewModel?.process(viewAction: .tapReaction(index: indexPath.row))
}
}
// MARK: - BubbleReactionsViewModelViewDelegate
extension BubbleReactionsView: BubbleReactionsViewModelViewDelegate {
func bubbleReactionsViewModel(_ viewModel: BubbleReactionsViewModel, didUpdateViewState viewState: BubbleReactionsViewState) {
switch viewState {
case .loaded(reactionsViewData: let reactionsViewData):
self.fill(reactionsViewData: reactionsViewData)
}
}
}
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="BubbleReactionsView" customModule="Riot" customModuleProvider="target">
<connections>
<outlet property="collectionView" destination="uTe-bw-bWE" id="EGc-ca-YSM"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="422" height="136"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="none" translatesAutoresizingMaskIntoConstraints="NO" id="uTe-bw-bWE" customClass="AutosizedCollectionView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="422" height="136"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="0.0" minimumInteritemSpacing="0.0" id="qeb-Ii-egn">
<size key="itemSize" width="1" height="1"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
</collectionView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="uTe-bw-bWE" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="NbI-pU-tZb"/>
<constraint firstAttribute="bottom" secondItem="uTe-bw-bWE" secondAttribute="bottom" id="lwj-Lm-yDr"/>
<constraint firstAttribute="trailing" secondItem="uTe-bw-bWE" secondAttribute="trailing" id="rP3-ME-Y83"/>
<constraint firstItem="uTe-bw-bWE" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="uSd-DZ-bOf"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="-423" y="11"/>
</view>
</objects>
</document>
@@ -0,0 +1,66 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
@objc final class BubbleReactionsViewModel: NSObject, BubbleReactionsViewModelType {
// MARK: - Properties
// MARK: Private
private let aggregatedReactions: MXAggregatedReactions
private let reactionsViewData: [BubbleReactionViewData]
private let eventId: String
// MARK: Public
@objc weak var viewModelDelegate: BubbleReactionsViewModelDelegate?
weak var viewDelegate: BubbleReactionsViewModelViewDelegate?
// MARK: - Setup
@objc init(aggregatedReactions: MXAggregatedReactions,
eventId: String) {
self.aggregatedReactions = aggregatedReactions
self.eventId = eventId
self.reactionsViewData = aggregatedReactions.reactions.map { (reactionCount) -> BubbleReactionViewData in
return BubbleReactionViewData(emoji: reactionCount.reaction, countString: "\(reactionCount.count)", isCurrentUserReacted: reactionCount.myUserHasReacted)
}
}
// MARK: - Public
func process(viewAction: BubbleReactionsViewAction) {
switch viewAction {
case .loadData:
self.viewDelegate?.bubbleReactionsViewModel(self, didUpdateViewState: .loaded(reactionsViewData: self.reactionsViewData))
case .tapReaction(let index):
guard index < self.aggregatedReactions.reactions.count else {
return
}
let reactionCount = self.aggregatedReactions.reactions[index]
if reactionCount.myUserHasReacted {
self.viewModelDelegate?.bubbleReactionsViewModel(self, didRemoveReaction: reactionCount, forEventId: self.eventId)
} else {
self.viewModelDelegate?.bubbleReactionsViewModel(self, didAddReaction: reactionCount, forEventId: self.eventId)
}
case .addNewReaction:
break
}
}
}
@@ -0,0 +1,43 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
enum BubbleReactionsViewAction {
case loadData
case tapReaction(index: Int)
case addNewReaction
}
enum BubbleReactionsViewState {
case loaded(reactionsViewData: [BubbleReactionViewData])
}
@objc protocol BubbleReactionsViewModelDelegate: class {
func bubbleReactionsViewModel(_ viewModel: BubbleReactionsViewModel, didAddReaction reactionCount: MXReactionCount, forEventId eventId: String)
func bubbleReactionsViewModel(_ viewModel: BubbleReactionsViewModel, didRemoveReaction reactionCount: MXReactionCount, forEventId eventId: String)
}
protocol BubbleReactionsViewModelViewDelegate: class {
func bubbleReactionsViewModel(_ viewModel: BubbleReactionsViewModel, didUpdateViewState viewState: BubbleReactionsViewState)
}
protocol BubbleReactionsViewModelType {
var viewModelDelegate: BubbleReactionsViewModelDelegate? { get set }
var viewDelegate: BubbleReactionsViewModelViewDelegate? { get set }
func process(viewAction: BubbleReactionsViewAction)
}
@@ -22,6 +22,8 @@
#import "AvatarGenerator.h"
#import "Tools.h"
#import "Riot-Swift.h"
static NSAttributedString *timestampVerticalWhitespace = nil;
static NSAttributedString *readReceiptVerticalWhitespace = nil;
@@ -357,11 +359,28 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil;
{
// Add vertical whitespace in case of read receipts.
NSUInteger reactionCount = self.reactions[eventId].reactions.count;
MXAggregatedReactions *aggregatedReactions = self.reactions[eventId];
if (reactionCount)
{
// TODO: Set right height: 22 + 8
// TODO: Set right dynamic line count: reactionCount / 6
CGFloat height = (22 + 8) * ((reactionCount / 6) + 1);
CGSize fittingSize = UILayoutFittingCompressedSize;
fittingSize.width = self.maxTextViewWidth;
static BubbleReactionsView *bubbleReactionsView;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
bubbleReactionsView = [BubbleReactionsView new];
});
bubbleReactionsView.frame = CGRectMake(0, 0, self.maxTextViewWidth, 1.0);
BubbleReactionsViewModel *viemModel = [[BubbleReactionsViewModel alloc] initWithAggregatedReactions:aggregatedReactions eventId:eventId];
bubbleReactionsView.viewModel = viemModel;
[bubbleReactionsView layoutIfNeeded];
CGFloat height = [bubbleReactionsView systemLayoutSizeFittingSize:fittingSize].height;
[attributedString appendAttributedString:[RoomBubbleCellData verticalWhitespaceForHeight: height]];
}
@@ -503,7 +522,7 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil;
return [[NSAttributedString alloc] initWithString:returnString attributes:@{NSForegroundColorAttributeName : [UIColor blackColor],
NSFontAttributeName: [UIFont systemFontOfSize:4]}];
NSFontAttributeName: [UIFont systemFontOfSize:6]}];
}
- (BOOL)hasSameSenderAsBubbleCellData:(id<MXKRoomBubbleCellDataStoring>)bubbleCellData
+51 -34
View File
@@ -27,7 +27,11 @@
#import "MXRoom+Riot.h"
@interface RoomDataSource()
static CGFloat kBubbleReactionsViewLeftMargin = 55.0;
static CGFloat kBubbleReactionsViewRightMargin = 15.0;
@interface RoomDataSource() <BubbleReactionsViewModelDelegate>
{
// Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change.
id kThemeServiceDidChangeThemeNotificationObserver;
@@ -275,6 +279,7 @@
while (index--)
{
MXKRoomBubbleComponent *component = bubbleComponents[index];
NSString *componentEventId = component.event.eventId;
if (component.event.sentState != MXEventSentStateFailed)
{
@@ -379,54 +384,46 @@
[NSLayoutConstraint activateConstraints:@[widthConstraint, heightConstraint, topConstraint, trailingConstraint]];
}
}
MXAggregatedReactions* reactions = cellData.reactions[component.event.eventId];
MXAggregatedReactions* reactions = cellData.reactions[componentEventId];
if (reactions && !isCollapsableCellCollapsed)
{
// TODO: Use final ReactionsView
UITextView* reactionsContainer = [UITextView new];
reactionsContainer.translatesAutoresizingMaskIntoConstraints = NO;
[bubbleCell.contentView addSubview:reactionsContainer];
reactionsContainer.layer.borderColor = UIColor.orangeColor.CGColor;
reactionsContainer.layer.borderWidth = 1;
BubbleReactionsViewModel *bubbleReactionsViewModel = [[BubbleReactionsViewModel alloc] initWithAggregatedReactions:reactions eventId:componentEventId];
BubbleReactionsView *reactionsView = [BubbleReactionsView new];
reactionsView.viewModel = bubbleReactionsViewModel;
[reactionsView updateWithTheme:ThemeService.shared.theme];
bubbleReactionsViewModel.viewModelDelegate = self;
reactionsView.translatesAutoresizingMaskIntoConstraints = NO;
[bubbleCell.contentView addSubview:reactionsView];
if (!bubbleCell.tmpSubviews)
{
bubbleCell.tmpSubviews = [NSMutableArray array];
}
[bubbleCell.tmpSubviews addObject:reactionsContainer];
[bubbleCell.tmpSubviews addObject:reactionsView];
// At the bottom, we have read receipts or nothing
NSLayoutConstraint *bottomConstraint;
if (avatarsContainer)
{
bottomConstraint = [reactionsContainer.bottomAnchor constraintEqualToAnchor:avatarsContainer.topAnchor];
bottomConstraint = [reactionsView.bottomAnchor constraintEqualToAnchor:avatarsContainer.topAnchor];
}
else
{
bottomConstraint = [reactionsContainer.bottomAnchor constraintEqualToAnchor:reactionsContainer.superview.topAnchor constant:bottomPositionY];
bottomConstraint = [reactionsView.bottomAnchor constraintEqualToAnchor:reactionsView.superview.topAnchor constant:bottomPositionY];
}
// TODO: To refine
CGFloat viewHeight = 22;
// Force receipts container size
[NSLayoutConstraint activateConstraints:
@[
[reactionsContainer.leadingAnchor constraintEqualToAnchor:reactionsContainer.superview.leadingAnchor constant:50],
[reactionsContainer.trailingAnchor constraintEqualToAnchor:reactionsContainer.superview.trailingAnchor constant:-6],
[reactionsContainer.heightAnchor constraintEqualToConstant:viewHeight],
bottomConstraint
]];
// TODO: To remove
NSMutableString *reactionsString = [NSMutableString string];
for (MXReactionCount *reactionCount in reactions.reactions)
{
[reactionsString appendFormat:@"%@: %@ ", reactionCount.reaction, @(reactionCount.count)];
}
reactionsContainer.text = reactionsString;
@[
[reactionsView.leadingAnchor constraintEqualToAnchor:reactionsView.superview.leadingAnchor constant:kBubbleReactionsViewLeftMargin],
[reactionsView.trailingAnchor constraintEqualToAnchor:reactionsView.superview.trailingAnchor constant:-kBubbleReactionsViewRightMargin],
bottomConstraint
]];
}
// Check whether the read marker must be displayed here.
@@ -439,7 +436,7 @@
bubbleCell.bubbleOverlayContainer.userInteractionEnabled = NO;
bubbleCell.bubbleOverlayContainer.hidden = NO;
if ([component.event.eventId isEqualToString:self.room.accountData.readMarkerEventId])
if ([componentEventId isEqualToString:self.room.accountData.readMarkerEventId])
{
bubbleCell.readMarkerView = [[UIView alloc] initWithFrame:CGRectMake(0, bottomPositionY - 2, bubbleCell.bubbleOverlayContainer.frame.size.width, 2)];
bubbleCell.readMarkerView.backgroundColor = ThemeService.shared.theme.tintColor;
@@ -580,4 +577,24 @@
return jitsiWidget;
}
#pragma mark - BubbleReactionsViewModelDelegate
- (void)bubbleReactionsViewModel:(BubbleReactionsViewModel *)viewModel didAddReaction:(MXReactionCount *)reactionCount forEventId:(NSString *)eventId
{
[self.mxSession.aggregations sendReaction:reactionCount.reaction toEvent:eventId inRoom:self.roomId success:^(NSString * _Nonnull eventId) {
} failure:^(NSError * _Nonnull error) {
NSLog(@"[MXKRoomDataSource] Fail to send reaction on eventId: %@", eventId);
}];
}
- (void)bubbleReactionsViewModel:(BubbleReactionsViewModel *)viewModel didRemoveReaction:(MXReactionCount * _Nonnull)reactionCount forEventId:(NSString * _Nonnull)eventId
{
[self.mxSession.aggregations unReactOnReaction:reactionCount.reaction toEvent:eventId inRoom:self.roomId success:^{
} failure:^(NSError * _Nonnull error) {
NSLog(@"[MXKRoomDataSource] Fail to unreact on eventId: %@", eventId);
}];
}
@end