mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-29 15:52:40 +00:00
Merge pull request #2528 from vector-im/riot_2459
Reactions: Update quick reactions
This commit is contained in:
commit
9c830d04c6
14 changed files with 473 additions and 413 deletions
|
@ -13,6 +13,7 @@ Improvements:
|
|||
* Message Editing: Editing in the timeline (#2404).
|
||||
* Read receipts: They are now counted at the MatrixKit level.
|
||||
* Migrate to Swift 5.0.
|
||||
* Reactions: Update quick reactions (#2459).
|
||||
|
||||
Bug fix:
|
||||
* Device Verification: Fix user display name and device id colors in dark theme
|
||||
|
|
|
@ -76,13 +76,8 @@
|
|||
32891D75226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32891D73226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift */; };
|
||||
32891D76226728EF00C82226 /* DeviceVerificationDataLoadingViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32891D74226728EE00C82226 /* DeviceVerificationDataLoadingViewController.storyboard */; };
|
||||
32B1FEDB21A46F2C00637127 /* TermsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32B1FEDA21A46F2C00637127 /* TermsView.xib */; };
|
||||
32B94DF8228EC26400716A26 /* ReactionsMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B94DF1228EC26400716A26 /* ReactionsMenuView.swift */; };
|
||||
32B94DF9228EC26400716A26 /* ReactionsMenuViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B94DF2228EC26400716A26 /* ReactionsMenuViewAction.swift */; };
|
||||
32B94DFA228EC26400716A26 /* ReactionsMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B94DF3228EC26400716A26 /* ReactionsMenuButton.swift */; };
|
||||
32B94DFB228EC26400716A26 /* ReactionsMenuViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B94DF4228EC26400716A26 /* ReactionsMenuViewModelType.swift */; };
|
||||
32B94DFC228EC26400716A26 /* ReactionsMenuViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B94DF5228EC26400716A26 /* ReactionsMenuViewModel.swift */; };
|
||||
32B94DFD228EC26400716A26 /* ReactionsMenuReaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B94DF6228EC26400716A26 /* ReactionsMenuReaction.swift */; };
|
||||
32B94DFE228EC26400716A26 /* ReactionsMenuView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32B94DF7228EC26400716A26 /* ReactionsMenuView.xib */; };
|
||||
32BF994F21FA29A400698084 /* SettingsKeyBackupViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BF994E21FA29A400698084 /* SettingsKeyBackupViewModel.swift */; };
|
||||
32BF995121FA29DC00698084 /* SettingsKeyBackupViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BF995021FA29DC00698084 /* SettingsKeyBackupViewModelType.swift */; };
|
||||
32BF995321FA2A1300698084 /* SettingsKeyBackupViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BF995221FA2A1300698084 /* SettingsKeyBackupViewState.swift */; };
|
||||
|
@ -460,6 +455,12 @@
|
|||
B1CA3A2921EF692B000D1D89 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CA3A2821EF692B000D1D89 /* UIView.swift */; };
|
||||
B1CE9EFD22148703000FAE6A /* SignOutAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE9EFC22148703000FAE6A /* SignOutAlertPresenter.swift */; };
|
||||
B1CE9F062216FB09000FAE6A /* EncryptionKeysExportPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE9F052216FB09000FAE6A /* EncryptionKeysExportPresenter.swift */; };
|
||||
B1D1BDA622BBAFB500831367 /* ReactionsMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D1BDA522BBAFB500831367 /* ReactionsMenuView.swift */; };
|
||||
B1D1BDA822BBAFC900831367 /* ReactionsMenuView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1D1BDA722BBAFC900831367 /* ReactionsMenuView.xib */; };
|
||||
B1D211E222BD193C00D939BD /* ReactionsMenuViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D211E122BD193C00D939BD /* ReactionsMenuViewModel.swift */; };
|
||||
B1D211E422C18E3800D939BD /* ReactionsMenuViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D211E322C18E3800D939BD /* ReactionsMenuViewModelType.swift */; };
|
||||
B1D211E622C194A200D939BD /* ReactionsMenuViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D211E522C194A200D939BD /* ReactionsMenuViewState.swift */; };
|
||||
B1D211E822C195B400D939BD /* ReactionMenuItemViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D211E722C195B400D939BD /* ReactionMenuItemViewData.swift */; };
|
||||
B1D250D82118AA0A000F4E93 /* RoomPredecessorBubbleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B1D250D72118AA0A000F4E93 /* RoomPredecessorBubbleCell.m */; };
|
||||
B1D4752721EE4E630067973F /* KeyboardAvoider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D4752521EE4E620067973F /* KeyboardAvoider.swift */; };
|
||||
B1D4752821EE4E630067973F /* KeyboardNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D4752621EE4E620067973F /* KeyboardNotification.swift */; };
|
||||
|
@ -607,13 +608,8 @@
|
|||
32891D73226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingViewController.swift; sourceTree = "<group>"; };
|
||||
32891D74226728EE00C82226 /* DeviceVerificationDataLoadingViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = DeviceVerificationDataLoadingViewController.storyboard; sourceTree = "<group>"; };
|
||||
32B1FEDA21A46F2C00637127 /* TermsView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TermsView.xib; sourceTree = "<group>"; };
|
||||
32B94DF1228EC26400716A26 /* ReactionsMenuView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionsMenuView.swift; sourceTree = "<group>"; };
|
||||
32B94DF2228EC26400716A26 /* ReactionsMenuViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionsMenuViewAction.swift; sourceTree = "<group>"; };
|
||||
32B94DF3228EC26400716A26 /* ReactionsMenuButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionsMenuButton.swift; sourceTree = "<group>"; };
|
||||
32B94DF4228EC26400716A26 /* ReactionsMenuViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionsMenuViewModelType.swift; sourceTree = "<group>"; };
|
||||
32B94DF5228EC26400716A26 /* ReactionsMenuViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionsMenuViewModel.swift; sourceTree = "<group>"; };
|
||||
32B94DF6228EC26400716A26 /* ReactionsMenuReaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionsMenuReaction.swift; sourceTree = "<group>"; };
|
||||
32B94DF7228EC26400716A26 /* ReactionsMenuView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ReactionsMenuView.xib; sourceTree = "<group>"; };
|
||||
32BDC9A1211C2C870064AF51 /* zh_Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_Hant; path = zh_Hant.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
32BDC9A2211C2C870064AF51 /* zh_Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_Hant; path = zh_Hant.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
32BDC9A3211C2C870064AF51 /* zh_Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh_Hant; path = zh_Hant.lproj/Vector.strings; sourceTree = "<group>"; };
|
||||
|
@ -1197,6 +1193,12 @@
|
|||
B1CA3A2821EF692B000D1D89 /* UIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = "<group>"; };
|
||||
B1CE9EFC22148703000FAE6A /* SignOutAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignOutAlertPresenter.swift; sourceTree = "<group>"; };
|
||||
B1CE9F052216FB09000FAE6A /* EncryptionKeysExportPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncryptionKeysExportPresenter.swift; sourceTree = "<group>"; };
|
||||
B1D1BDA522BBAFB500831367 /* ReactionsMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsMenuView.swift; sourceTree = "<group>"; };
|
||||
B1D1BDA722BBAFC900831367 /* ReactionsMenuView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReactionsMenuView.xib; sourceTree = "<group>"; };
|
||||
B1D211E122BD193C00D939BD /* ReactionsMenuViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsMenuViewModel.swift; sourceTree = "<group>"; };
|
||||
B1D211E322C18E3800D939BD /* ReactionsMenuViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsMenuViewModelType.swift; sourceTree = "<group>"; };
|
||||
B1D211E522C194A200D939BD /* ReactionsMenuViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsMenuViewState.swift; sourceTree = "<group>"; };
|
||||
B1D211E722C195B400D939BD /* ReactionMenuItemViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionMenuItemViewData.swift; sourceTree = "<group>"; };
|
||||
B1D250D62118AA0A000F4E93 /* RoomPredecessorBubbleCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoomPredecessorBubbleCell.h; sourceTree = "<group>"; };
|
||||
B1D250D72118AA0A000F4E93 /* RoomPredecessorBubbleCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoomPredecessorBubbleCell.m; sourceTree = "<group>"; };
|
||||
B1D4752521EE4E620067973F /* KeyboardAvoider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardAvoider.swift; sourceTree = "<group>"; };
|
||||
|
@ -1510,13 +1512,14 @@
|
|||
32B94DF0228EC26400716A26 /* ReactionsMenu */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
32B94DF1228EC26400716A26 /* ReactionsMenuView.swift */,
|
||||
B1D211E322C18E3800D939BD /* ReactionsMenuViewModelType.swift */,
|
||||
B1D211E122BD193C00D939BD /* ReactionsMenuViewModel.swift */,
|
||||
B1D211E722C195B400D939BD /* ReactionMenuItemViewData.swift */,
|
||||
B1D1BDA522BBAFB500831367 /* ReactionsMenuView.swift */,
|
||||
B1D1BDA722BBAFC900831367 /* ReactionsMenuView.xib */,
|
||||
B1D211E522C194A200D939BD /* ReactionsMenuViewState.swift */,
|
||||
32B94DF2228EC26400716A26 /* ReactionsMenuViewAction.swift */,
|
||||
32B94DF3228EC26400716A26 /* ReactionsMenuButton.swift */,
|
||||
32B94DF4228EC26400716A26 /* ReactionsMenuViewModelType.swift */,
|
||||
32B94DF5228EC26400716A26 /* ReactionsMenuViewModel.swift */,
|
||||
32B94DF6228EC26400716A26 /* ReactionsMenuReaction.swift */,
|
||||
32B94DF7228EC26400716A26 /* ReactionsMenuView.xib */,
|
||||
);
|
||||
path = ReactionsMenu;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3578,7 +3581,6 @@
|
|||
B1107ECA2200B09F0038014B /* KeyBackupRecoverSuccessViewController.storyboard in Resources */,
|
||||
B1B5579C20EF575B00210D55 /* ForgotPasswordInputsView.xib in Resources */,
|
||||
F083BE011E7009ED00A9B29C /* third_party_licenses.html in Resources */,
|
||||
32B94DFE228EC26400716A26 /* ReactionsMenuView.xib in Resources */,
|
||||
B1098BFC21ECFE65000DDA48 /* PasswordStrengthView.xib in Resources */,
|
||||
B1B5573720EE6C4D00210D55 /* GroupParticipantsViewController.xib in Resources */,
|
||||
B110872421F098F0003554A5 /* ActivityIndicatorView.xib in Resources */,
|
||||
|
@ -3601,6 +3603,7 @@
|
|||
3232AB1422564D9100AD6A5C /* swiftgen-config.yml in Resources */,
|
||||
324A204F225FC571004FE8B0 /* DeviceVerificationIncomingViewController.storyboard in Resources */,
|
||||
3232AB4B2256558300AD6A5C /* TemplateScreenViewController.storyboard in Resources */,
|
||||
B1D1BDA822BBAFC900831367 /* ReactionsMenuView.xib in Resources */,
|
||||
32B1FEDB21A46F2C00637127 /* TermsView.xib in Resources */,
|
||||
B1B5578E20EF568D00210D55 /* GroupInviteTableViewCell.xib in Resources */,
|
||||
B1B5582020EF625800210D55 /* SimpleRoomTitleView.xib in Resources */,
|
||||
|
@ -3906,6 +3909,7 @@
|
|||
B1B558C320EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.m in Sources */,
|
||||
B110872521F098F0003554A5 /* ActivityIndicatorPresenter.swift in Sources */,
|
||||
32242F1521E8FBA900725742 /* DarkTheme.swift in Sources */,
|
||||
B1D211E222BD193C00D939BD /* ReactionsMenuViewModel.swift in Sources */,
|
||||
B140B4A621F89E7600E3F5FE /* KeyBackupSetupCoordinatorBridgePresenter.swift in Sources */,
|
||||
B1B5577420EE702900210D55 /* WidgetViewController.m in Sources */,
|
||||
B139C21B21FE5B9200BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift in Sources */,
|
||||
|
@ -3942,7 +3946,6 @@
|
|||
B1B5594520EF7BD000210D55 /* TableViewCellWithCollectionView.m in Sources */,
|
||||
32891D75226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift in Sources */,
|
||||
32891D712264DF7B00C82226 /* DeviceVerificationVerifiedViewController.swift in Sources */,
|
||||
32B94DFB228EC26400716A26 /* ReactionsMenuViewModelType.swift in Sources */,
|
||||
F083BDEF1E7009ED00A9B29C /* UINavigationController+Riot.m in Sources */,
|
||||
B1B5581F20EF625800210D55 /* SimpleRoomTitleView.m in Sources */,
|
||||
B1C562E2228C7C8D0037F12A /* RoomContextualMenuViewController.swift in Sources */,
|
||||
|
@ -3959,6 +3962,7 @@
|
|||
3232ABA7225730E100AD6A5C /* DeviceVerificationStartCoordinator.swift in Sources */,
|
||||
B1D4752721EE4E630067973F /* KeyboardAvoider.swift in Sources */,
|
||||
B1D4752821EE4E630067973F /* KeyboardNotification.swift in Sources */,
|
||||
B1D1BDA622BBAFB500831367 /* ReactionsMenuView.swift in Sources */,
|
||||
B1B5573C20EE6C4D00210D55 /* MasterTabBarController.m in Sources */,
|
||||
32F6B96E2270623100BBA352 /* DeviceVerificationDataLoadingViewModelType.swift in Sources */,
|
||||
B1B5592C20EF7A5D00210D55 /* TableViewCellWithButton.m in Sources */,
|
||||
|
@ -3998,14 +4002,12 @@
|
|||
3232ABAB225730E100AD6A5C /* DeviceVerificationCoordinator.swift in Sources */,
|
||||
B1B5583E20EF6E7F00210D55 /* GroupRoomTableViewCell.m in Sources */,
|
||||
B14F143522144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift in Sources */,
|
||||
32B94DF8228EC26400716A26 /* ReactionsMenuView.swift in Sources */,
|
||||
B1B5574F20EE6C4D00210D55 /* RoomsViewController.m in Sources */,
|
||||
B1B5572520EE6C4D00210D55 /* RoomMessagesSearchViewController.m in Sources */,
|
||||
B139C22121FE5D9D00BB68EC /* KeyBackupRecoverFromPassphraseViewState.swift in Sources */,
|
||||
B1B5579120EF568D00210D55 /* GroupInviteTableViewCell.m in Sources */,
|
||||
B1B5579A20EF575B00210D55 /* ForgotPasswordInputsView.m in Sources */,
|
||||
B1B12B2922942315002CB419 /* UITouch.swift in Sources */,
|
||||
32B94DFD228EC26400716A26 /* ReactionsMenuReaction.swift in Sources */,
|
||||
B1B558CC20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.m in Sources */,
|
||||
B1B5571D20EE6C4D00210D55 /* HomeViewController.m in Sources */,
|
||||
3232ABA6225730E100AD6A5C /* DeviceVerificationStartViewController.swift in Sources */,
|
||||
|
@ -4063,6 +4065,7 @@
|
|||
B140B4A221F87F7100E3F5FE /* OperationQueue.swift in Sources */,
|
||||
B1B5575120EE6C4D00210D55 /* AuthenticationViewController.m in Sources */,
|
||||
B1B5571820EE6C4D00210D55 /* CountryPickerViewController.m in Sources */,
|
||||
B1D211E622C194A200D939BD /* ReactionsMenuViewState.swift in Sources */,
|
||||
B17982FF2119FED2001FD722 /* GDPRConsentViewController.swift in Sources */,
|
||||
B1098BE121ECE09F000DDA48 /* Images.swift in Sources */,
|
||||
3232ABA4225730E100AD6A5C /* DeviceVerificationStartViewAction.swift in Sources */,
|
||||
|
@ -4120,6 +4123,7 @@
|
|||
B1B5599420EFC5E400210D55 /* DecryptionFailureTracker.m in Sources */,
|
||||
F083BDF01E7009ED00A9B29C /* UIViewController+RiotSearch.m in Sources */,
|
||||
F083BDF91E7009ED00A9B29C /* RoomEmailInvitation.m in Sources */,
|
||||
B1D211E422C18E3800D939BD /* ReactionsMenuViewModelType.swift in Sources */,
|
||||
324A2055225FC571004FE8B0 /* DeviceVerificationIncomingViewModelType.swift in Sources */,
|
||||
B1B5572C20EE6C4D00210D55 /* RoomParticipantsViewController.m in Sources */,
|
||||
B1B558EE20EF768F00210D55 /* RoomOutgoingAttachmentBubbleCell.m in Sources */,
|
||||
|
@ -4127,12 +4131,12 @@
|
|||
32BF994F21FA29A400698084 /* SettingsKeyBackupViewModel.swift in Sources */,
|
||||
B1B5574920EE6C4D00210D55 /* RiotSplitViewController.m in Sources */,
|
||||
B1B5574E20EE6C4D00210D55 /* DirectoryServerPickerViewController.m in Sources */,
|
||||
B1D211E822C195B400D939BD /* ReactionMenuItemViewData.swift in Sources */,
|
||||
B1DB4F0B223131600065DBFA /* String.swift in Sources */,
|
||||
3232AB522256558300AD6A5C /* TemplateScreenViewModel.swift in Sources */,
|
||||
B1B5575B20EE6C4D00210D55 /* HomeFilesSearchViewController.m in Sources */,
|
||||
B139C22521FF01C100BB68EC /* KeyBackupRecoverFromPassphraseCoordinator.swift in Sources */,
|
||||
B1098BFD21ECFE65000DDA48 /* PasswordStrengthManager.swift in Sources */,
|
||||
32B94DFC228EC26400716A26 /* ReactionsMenuViewModel.swift in Sources */,
|
||||
B1B558F520EF768F00210D55 /* RoomOutgoingTextMsgWithPaginationTitleBubbleCell.m in Sources */,
|
||||
3232AB482256558300AD6A5C /* FlowTemplateCoordinatorType.swift in Sources */,
|
||||
B1B558F820EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */,
|
||||
|
|
|
@ -16,9 +16,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
enum ReactionsMenuReaction: String {
|
||||
case agree = "👍"
|
||||
case disagree = "👎"
|
||||
case like = "🙂"
|
||||
case dislike = "😔"
|
||||
struct ReactionMenuItemViewData {
|
||||
let emoji: String
|
||||
let isSelected: Bool
|
||||
}
|
|
@ -18,7 +18,14 @@ import UIKit
|
|||
|
||||
class ReactionsMenuButton: UIButton, Themable {
|
||||
|
||||
// MARK: Private
|
||||
// MARK: - Constants
|
||||
|
||||
private enum Constants {
|
||||
static let borderWidthSelected: CGFloat = 1/UIScreen.main.scale
|
||||
static let borderColorAlpha: CGFloat = 0.15
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private var theme: Theme!
|
||||
|
||||
|
@ -38,8 +45,8 @@ class ReactionsMenuButton: UIButton, Themable {
|
|||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.layer.cornerRadius = self.frame.size.height / 2
|
||||
self.layer.borderWidth = self.isSelected ? 1 : 0
|
||||
self.layer.cornerRadius = self.frame.size.height / 3
|
||||
self.layer.borderWidth = self.isSelected ? Constants.borderWidthSelected : 0
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
@ -60,15 +67,14 @@ class ReactionsMenuButton: UIButton, Themable {
|
|||
func update(theme: Theme) {
|
||||
self.theme = theme
|
||||
|
||||
// TODO: Color for black theme
|
||||
self.setTitleColor(self.theme.textPrimaryColor, for: .normal)
|
||||
self.setTitleColor(self.theme.textPrimaryColor, for: .selected)
|
||||
|
||||
self.layer.borderColor = self.theme.tintColor.cgColor
|
||||
self.layer.borderColor = self.theme.tintColor.withAlphaComponent(Constants.borderColorAlpha).cgColor
|
||||
}
|
||||
|
||||
private func updateView() {
|
||||
backgroundColor = isSelected ? self.theme.tintBackgroundColor : self.theme.headerBackgroundColor
|
||||
backgroundColor = isSelected ? self.theme.tintBackgroundColor : UIColor.clear
|
||||
}
|
||||
|
||||
override open var isSelected: Bool {
|
||||
|
|
|
@ -17,93 +17,111 @@
|
|||
import UIKit
|
||||
import Reusable
|
||||
|
||||
final class ReactionsMenuView: UIView, NibOwnerLoadable {
|
||||
final class ReactionsMenuView: UIView, Themable, NibLoadable {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
private enum Constants {
|
||||
static let selectedReactionAnimationScale: CGFloat = 1.2
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Outlets
|
||||
@IBOutlet weak var agreeButton: UIButton!
|
||||
@IBOutlet weak var disagreeButton: UIButton!
|
||||
@IBOutlet weak var likeButton: UIButton!
|
||||
@IBOutlet weak var dislikeButton: UIButton!
|
||||
|
||||
@IBOutlet private weak var reactionsBackgroundView: UIView!
|
||||
@IBOutlet private weak var reactionsStackView: UIStackView!
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var reactionViewDatas: [ReactionMenuItemViewData] = []
|
||||
private var reactionButtons: [ReactionsMenuButton] = []
|
||||
private var tappedReactionButton: ReactionsMenuButton?
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var viewModel: ReactionsMenuViewModelType? {
|
||||
didSet {
|
||||
self.updateView()
|
||||
self.viewModel?.viewDelegate = self
|
||||
self.viewModel?.process(viewAction: .loadData)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
self.loadNibContent()
|
||||
self.commonInit()
|
||||
var reactionHasBeenTapped: Bool {
|
||||
return self.tappedReactionButton != nil
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.loadNibContent()
|
||||
self.commonInit()
|
||||
// MARK: - Life cycle
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
self.reactionsBackgroundView.layer.masksToBounds = true
|
||||
self.update(theme: ThemeService.shared().theme)
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
@IBAction private func agreeButtonAction(_ sender: Any) {
|
||||
self.viewModel?.process(viewAction: .toggleReaction(.agree))
|
||||
self.reactionsBackgroundView.layer.cornerRadius = self.reactionsBackgroundView.frame.size.height/2
|
||||
}
|
||||
|
||||
@IBAction private func disagreeButtonAction(_ sender: Any) {
|
||||
self.viewModel?.process(viewAction: .toggleReaction(.disagree))
|
||||
// MARK: - Public
|
||||
|
||||
func update(theme: Theme) {
|
||||
self.reactionsBackgroundView.backgroundColor = theme.headerBackgroundColor
|
||||
}
|
||||
|
||||
@IBAction private func likeButtonAction(_ sender: Any) {
|
||||
self.viewModel?.process(viewAction: .toggleReaction(.like))
|
||||
func selectionAnimationInstructionPart1() {
|
||||
guard let tappedButton = self.tappedReactionButton else {
|
||||
return
|
||||
}
|
||||
let scale = Constants.selectedReactionAnimationScale
|
||||
tappedButton.superview?.bringSubviewToFront(tappedButton)
|
||||
tappedButton.transform = CGAffineTransform(scaleX: scale, y: scale)
|
||||
}
|
||||
|
||||
@IBAction private func dislikeButtonAction(_ sender: Any) {
|
||||
self.viewModel?.process(viewAction: .toggleReaction(.dislike))
|
||||
func selectionAnimationInstructionPart2() {
|
||||
guard let tappedButton = self.tappedReactionButton else {
|
||||
return
|
||||
}
|
||||
tappedButton.transform = CGAffineTransform.identity
|
||||
tappedButton.isSelected.toggle()
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func commonInit() {
|
||||
private func fill(reactionsMenuViewDatas: [ReactionMenuItemViewData]) {
|
||||
self.reactionViewDatas = reactionsMenuViewDatas
|
||||
|
||||
agreeButton.setTitle(VectorL10n.roomEventActionReactionAgree(ReactionsMenuReaction.agree.rawValue), for: .normal)
|
||||
agreeButton.setTitle(VectorL10n.roomEventActionReactionAgree(ReactionsMenuReaction.agree.rawValue), for: .highlighted)
|
||||
disagreeButton.setTitle(VectorL10n.roomEventActionReactionDisagree(ReactionsMenuReaction.disagree.rawValue), for: .normal)
|
||||
disagreeButton.setTitle(VectorL10n.roomEventActionReactionDisagree(ReactionsMenuReaction.disagree.rawValue), for: .highlighted)
|
||||
likeButton.setTitle(VectorL10n.roomEventActionReactionLike(ReactionsMenuReaction.like.rawValue), for: .normal)
|
||||
likeButton.setTitle(VectorL10n.roomEventActionReactionLike(ReactionsMenuReaction.like.rawValue), for: .highlighted)
|
||||
dislikeButton.setTitle(VectorL10n.roomEventActionReactionDislike(ReactionsMenuReaction.dislike.rawValue), for: .normal)
|
||||
dislikeButton.setTitle(VectorL10n.roomEventActionReactionDislike(ReactionsMenuReaction.dislike.rawValue), for: .highlighted)
|
||||
self.reactionsStackView.vc_removeAllSubviews()
|
||||
|
||||
customizeViewRendering()
|
||||
for reactionViewData in self.reactionViewDatas {
|
||||
let reactionsMenuButton = ReactionsMenuButton()
|
||||
reactionsMenuButton.setTitle(reactionViewData.emoji, for: .normal)
|
||||
reactionsMenuButton.isSelected = reactionViewData.isSelected
|
||||
reactionsMenuButton.addTarget(self, action: #selector(reactionButtonAction), for: .touchUpInside)
|
||||
self.reactionsStackView.addArrangedSubview(reactionsMenuButton)
|
||||
self.reactionButtons.append(reactionsMenuButton)
|
||||
}
|
||||
}
|
||||
|
||||
private func customizeViewRendering() {
|
||||
self.backgroundColor = UIColor.clear
|
||||
}
|
||||
|
||||
private func updateView() {
|
||||
guard let viewModel = self.viewModel else {
|
||||
@objc private func reactionButtonAction(_ sender: ReactionsMenuButton) {
|
||||
guard let tappedReaction = sender.titleLabel?.text else {
|
||||
return
|
||||
}
|
||||
|
||||
agreeButton.isSelected = viewModel.isAgreeButtonSelected
|
||||
disagreeButton.isSelected = viewModel.isDisagreeButtonSelected
|
||||
likeButton.isSelected = viewModel.isLikeButtonSelected
|
||||
dislikeButton.isSelected = viewModel.isDislikeButtonSelected
|
||||
self.tappedReactionButton = sender
|
||||
self.viewModel?.process(viewAction: .tap(reaction: tappedReaction))
|
||||
}
|
||||
}
|
||||
|
||||
extension ReactionsMenuView: ReactionsMenuViewModelDelegate {
|
||||
func reactionsMenuViewModelDidUpdate(_ viewModel: ReactionsMenuViewModelType) {
|
||||
self.updateView()
|
||||
// MARK: - ReactionsMenuViewModelViewDelegate
|
||||
extension ReactionsMenuView: ReactionsMenuViewModelViewDelegate {
|
||||
|
||||
func reactionsMenuViewModel(_ viewModel: ReactionsMenuViewModel, didUpdateViewState viewState: ReactionsMenuViewState) {
|
||||
switch viewState {
|
||||
case .loaded(reactionsViewData: let reactionsViewData):
|
||||
self.fill(reactionsMenuViewDatas: reactionsViewData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,113 +1,117 @@
|
|||
<?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">
|
||||
<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="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" customClass="ReactionsMenuView" customModule="Riot" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="agreeButton" destination="30O-28-rwy" id="6aH-Wm-9y3"/>
|
||||
<outlet property="disagreeButton" destination="Ebe-PT-0R2" id="7FN-DF-Pxg"/>
|
||||
<outlet property="dislikeButton" destination="AgH-2U-HpP" id="rMC-aV-G1t"/>
|
||||
<outlet property="likeButton" destination="kao-MQ-QFq" id="SVi-dI-qWZ"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="100"/>
|
||||
<view contentMode="scaleToFill" id="7Xq-Wy-z0M" customClass="ReactionsMenuView" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="459" height="59"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="30O-28-rwy" customClass="ReactionsMenuButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="104" y="13" width="100" height="38"/>
|
||||
<color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="38" id="TII-Yn-bza"/>
|
||||
<constraint firstAttribute="width" constant="100" id="cjU-F5-DjB"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<state key="normal" title="Agree 👍">
|
||||
<color key="titleColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="agreeButtonAction:" destination="-1" eventType="touchUpInside" id="uLG-m0-aTt"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ebe-PT-0R2" customClass="ReactionsMenuButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="104" y="59" width="100" height="38"/>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="eXy-Kc-Ck7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="459" height="59"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="uzd-VB-mKT">
|
||||
<rect key="frame" x="5" y="5" width="449" height="49"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7a6-zJ-2tf" customClass="ReactionsMenuButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="54.5" height="49"/>
|
||||
<color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="100" id="807-lM-sNX"/>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="38" id="bIY-hX-5PC"/>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="100" id="j4m-Qi-NS5"/>
|
||||
<constraint firstAttribute="height" constant="38" id="tNZ-Kv-LxS"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<state key="normal" title="Disagree 👎">
|
||||
<fontDescription key="fontDescription" type="system" pointSize="24"/>
|
||||
<state key="normal" title="👍">
|
||||
<color key="titleColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="disagreeButtonAction:" destination="-1" eventType="touchUpInside" id="8Ir-5R-jpn"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="kao-MQ-QFq" customClass="ReactionsMenuButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="210" y="13" width="100" height="38"/>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="mHT-t1-k2f" customClass="ReactionsMenuButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="56.5" y="0.0" width="54.5" height="49"/>
|
||||
<color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="100" id="8hf-4f-egV"/>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="100" id="Gvk-Ap-Wtc"/>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="38" id="lZK-go-x5A"/>
|
||||
<constraint firstAttribute="height" constant="38" id="o0a-8j-0et"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<state key="normal" title="Like 🙂">
|
||||
<fontDescription key="fontDescription" type="system" pointSize="24"/>
|
||||
<state key="normal" title="👎">
|
||||
<color key="titleColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="likeButtonAction:" destination="-1" eventType="touchUpInside" id="3Bb-iJ-Q8R"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="AgH-2U-HpP" customClass="ReactionsMenuButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="210" y="59" width="100" height="38"/>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="InP-XP-sh8" customClass="ReactionsMenuButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="113" y="0.0" width="54" height="49"/>
|
||||
<color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="100" id="75c-dq-L2I"/>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="100" id="GgC-VF-je2"/>
|
||||
<constraint firstAttribute="width" constant="100" id="RCe-5D-dg5"/>
|
||||
<constraint firstAttribute="height" constant="38" id="cvn-yC-3VY"/>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="38" id="hVj-hk-Vob"/>
|
||||
<constraint firstAttribute="height" relation="lessThanOrEqual" constant="38" id="wNs-Eg-frx"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<state key="normal" title="Dislike 😔">
|
||||
<fontDescription key="fontDescription" type="system" pointSize="24"/>
|
||||
<state key="normal" title="🙂">
|
||||
<color key="titleColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
</state>
|
||||
<connections>
|
||||
<action selector="dislikeButtonAction:" destination="-1" eventType="touchUpInside" id="vE5-Jn-5WM"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="sbZ-tR-mCf" customClass="ReactionsMenuButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="169" y="0.0" width="54.5" height="49"/>
|
||||
<color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="24"/>
|
||||
<state key="normal" title="🎉">
|
||||
<color key="titleColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
</state>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4ze-DN-PCw" customClass="ReactionsMenuButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="225.5" y="0.0" width="54.5" height="49"/>
|
||||
<color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="24"/>
|
||||
<state key="normal" title="🙁">
|
||||
<color key="titleColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
</state>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="KFI-G0-oyg" customClass="ReactionsMenuButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="282" y="0.0" width="54.5" height="49"/>
|
||||
<color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="24"/>
|
||||
<state key="normal" title="❤️">
|
||||
<color key="titleColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
</state>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ren-Yo-15Q" customClass="ReactionsMenuButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="338.5" y="0.0" width="54" height="49"/>
|
||||
<color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="24"/>
|
||||
<state key="normal" title="🚀">
|
||||
<color key="titleColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
</state>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="M0A-Tx-Jz9" customClass="ReactionsMenuButton" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="394.5" y="0.0" width="54.5" height="49"/>
|
||||
<color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="24"/>
|
||||
<state key="normal" title="👀">
|
||||
<color key="titleColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
</state>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Ebe-PT-0R2" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="centerX" constant="-3" id="3kX-GA-4mf"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="centerX" secondItem="kao-MQ-QFq" secondAttribute="leading" constant="-3" id="FV3-zi-y6i"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="centerY" secondItem="Ebe-PT-0R2" secondAttribute="top" constant="-4" id="Hfb-BB-kgH"/>
|
||||
<constraint firstItem="AgH-2U-HpP" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="centerX" constant="3" id="LBW-Lg-fZv"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="centerY" secondItem="AgH-2U-HpP" secondAttribute="top" constant="-4" id="MAE-KT-eAQ"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="centerY" secondItem="30O-28-rwy" secondAttribute="bottom" constant="4" id="jTW-iy-hDv"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="centerY" secondItem="kao-MQ-QFq" secondAttribute="bottom" constant="4" id="sRb-2V-clB"/>
|
||||
<constraint firstItem="30O-28-rwy" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="centerX" constant="-3" id="xUt-vJ-1I1" userLabel="Agree 👍.trailing = Safe Area.centerX + 7"/>
|
||||
<constraint firstAttribute="trailing" secondItem="uzd-VB-mKT" secondAttribute="trailing" constant="5" id="fDZ-NX-EyG"/>
|
||||
<constraint firstItem="uzd-VB-mKT" firstAttribute="leading" secondItem="eXy-Kc-Ck7" secondAttribute="leading" constant="5" id="iCa-Ob-e7J"/>
|
||||
<constraint firstItem="uzd-VB-mKT" firstAttribute="top" secondItem="eXy-Kc-Ck7" secondAttribute="top" constant="5" id="msb-Ay-Bp6"/>
|
||||
<constraint firstAttribute="bottom" secondItem="uzd-VB-mKT" secondAttribute="bottom" constant="5" id="nJE-Gm-Rf1"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="eXy-Kc-Ck7" firstAttribute="top" secondItem="7Xq-Wy-z0M" secondAttribute="top" id="43J-YM-JHg"/>
|
||||
<constraint firstItem="eXy-Kc-Ck7" firstAttribute="leading" secondItem="7Xq-Wy-z0M" secondAttribute="leading" id="9dy-sK-ygm"/>
|
||||
<constraint firstAttribute="trailing" secondItem="eXy-Kc-Ck7" secondAttribute="trailing" id="E4Z-bt-BRw"/>
|
||||
<constraint firstAttribute="bottom" secondItem="eXy-Kc-Ck7" secondAttribute="bottom" id="Nb2-0z-IiS"/>
|
||||
</constraints>
|
||||
<nil key="simulatedTopBarMetrics"/>
|
||||
<nil key="simulatedBottomBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
||||
<point key="canvasLocation" x="128.98550724637681" y="35.491071428571423"/>
|
||||
<connections>
|
||||
<outlet property="reactionsBackgroundView" destination="eXy-Kc-Ck7" id="VYi-YD-mb9"/>
|
||||
<outlet property="reactionsStackView" destination="uzd-VB-mKT" id="DTV-Nh-bcm"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="444.20289855072468" y="-718.19196428571422"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
|
|
|
@ -18,5 +18,6 @@ import UIKit
|
|||
|
||||
/// Action chosen by the user
|
||||
enum ReactionsMenuViewAction {
|
||||
case toggleReaction(ReactionsMenuReaction)
|
||||
case loadData
|
||||
case tap(reaction: String)
|
||||
}
|
||||
|
|
|
@ -14,158 +14,71 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import UIKit
|
||||
import Foundation
|
||||
|
||||
@objc final class ReactionsMenuViewModel: NSObject, ReactionsMenuViewModelType {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private let reactions = ["👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀"]
|
||||
private var currentViewDatas: [ReactionMenuItemViewData] = []
|
||||
|
||||
// MARK: Private
|
||||
private let aggregations: MXAggregations
|
||||
private let roomId: String
|
||||
|
||||
private let aggregatedReactions: MXAggregatedReactions?
|
||||
private let reactionsViewData: [ReactionMenuItemViewData] = []
|
||||
private let eventId: String
|
||||
|
||||
// MARK: Public
|
||||
|
||||
private(set) var isAgreeButtonSelected: Bool = false
|
||||
private(set) var isDisagreeButtonSelected: Bool = false
|
||||
private(set) var isLikeButtonSelected: Bool = false
|
||||
private(set) var isDislikeButtonSelected: Bool = false
|
||||
|
||||
weak var viewDelegate: ReactionsMenuViewModelDelegate?
|
||||
@objc weak var coordinatorDelegate: ReactionsMenuViewModelCoordinatorDelegate?
|
||||
weak var viewDelegate: ReactionsMenuViewModelViewDelegate?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
@objc init(aggregations: MXAggregations, roomId: String, eventId: String) {
|
||||
self.aggregations = aggregations
|
||||
self.roomId = roomId
|
||||
@objc init(aggregatedReactions: MXAggregatedReactions?,
|
||||
eventId: String) {
|
||||
self.aggregatedReactions = aggregatedReactions
|
||||
self.eventId = eventId
|
||||
|
||||
super.init()
|
||||
|
||||
self.loadData()
|
||||
self.listenToDataUpdate()
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func process(viewAction: ReactionsMenuViewAction) {
|
||||
var reaction: ReactionsMenuReaction?
|
||||
var newState: Bool?
|
||||
|
||||
switch viewAction {
|
||||
case .toggleReaction(let menuReaction):
|
||||
reaction = menuReaction
|
||||
|
||||
switch menuReaction {
|
||||
case .agree:
|
||||
newState = !self.isAgreeButtonSelected
|
||||
case .disagree:
|
||||
newState = !self.isDisagreeButtonSelected
|
||||
case .like:
|
||||
newState = !self.isLikeButtonSelected
|
||||
case .dislike:
|
||||
newState = !self.isDislikeButtonSelected
|
||||
case .loadData:
|
||||
self.loadData()
|
||||
case .tap(let reaction):
|
||||
if let viewData = self.currentViewDatas.first(where: { $0.emoji == reaction }) {
|
||||
if viewData.isSelected {
|
||||
self.coordinatorDelegate?.reactionsMenuViewModel(self, didRemoveReaction: reaction, forEventId: self.eventId)
|
||||
} else {
|
||||
self.coordinatorDelegate?.reactionsMenuViewModel(self, didAddReaction: reaction, forEventId: self.eventId)
|
||||
}
|
||||
}
|
||||
|
||||
guard let theReaction = reaction, let theNewState = newState else {
|
||||
return
|
||||
}
|
||||
|
||||
self.react(withReaction: theReaction, selected: theNewState)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func resetData() {
|
||||
self.isAgreeButtonSelected = false
|
||||
self.isDisagreeButtonSelected = false
|
||||
self.isLikeButtonSelected = false
|
||||
self.isDislikeButtonSelected = false
|
||||
}
|
||||
|
||||
private func loadData() {
|
||||
guard let reactionCounts = self.aggregations.aggregatedReactions(onEvent: self.eventId, inRoom: self.roomId)?.withNonZeroCount()?.reactions else {
|
||||
return
|
||||
}
|
||||
let reactionCounts = self.aggregatedReactions?.withNonZeroCount()?.reactions ?? []
|
||||
|
||||
var quickReactionsWithUserReactedFlag: [String: Bool] = Dictionary(uniqueKeysWithValues: self.reactions.map { ($0, false) })
|
||||
|
||||
self.resetData()
|
||||
reactionCounts.forEach { (reactionCount) in
|
||||
if reactionCount.myUserHasReacted {
|
||||
if let reaction = ReactionsMenuReaction(rawValue: reactionCount.reaction) {
|
||||
switch reaction {
|
||||
case .agree:
|
||||
self.isAgreeButtonSelected = true
|
||||
case .disagree:
|
||||
self.isDisagreeButtonSelected = true
|
||||
case .like:
|
||||
self.isLikeButtonSelected = true
|
||||
case .dislike:
|
||||
self.isDislikeButtonSelected = true
|
||||
}
|
||||
}
|
||||
if let hasUserReacted = quickReactionsWithUserReactedFlag[reactionCount.reaction], hasUserReacted == false {
|
||||
quickReactionsWithUserReactedFlag[reactionCount.reaction] = reactionCount.myUserHasReacted
|
||||
}
|
||||
}
|
||||
|
||||
self.viewDelegate?.reactionsMenuViewModelDidUpdate(self)
|
||||
let reactionMenuItemViewDatas: [ReactionMenuItemViewData] = self.reactions.map { reaction -> ReactionMenuItemViewData in
|
||||
let isSelected = quickReactionsWithUserReactedFlag[reaction] ?? false
|
||||
return ReactionMenuItemViewData(emoji: reaction, isSelected: isSelected)
|
||||
}
|
||||
|
||||
private func listenToDataUpdate() {
|
||||
self.aggregations.listenToReactionCountUpdate(inRoom: self.roomId) { [weak self] (changes) in
|
||||
self.currentViewDatas = reactionMenuItemViewDatas
|
||||
|
||||
guard let sself = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if changes[sself.eventId] != nil {
|
||||
sself.loadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func react(withReaction reaction: ReactionsMenuReaction, selected: Bool) {
|
||||
|
||||
// If required, unreact first
|
||||
if selected {
|
||||
self.ensure3StateButtons(withReaction: reaction)
|
||||
}
|
||||
|
||||
let reactionString = reaction.rawValue
|
||||
|
||||
if selected {
|
||||
self.coordinatorDelegate?.reactionsMenuViewModel(self, didAddReaction: reactionString, forEventId: self.eventId)
|
||||
} else {
|
||||
self.coordinatorDelegate?.reactionsMenuViewModel(self, didRemoveReaction: reactionString, forEventId: self.eventId)
|
||||
}
|
||||
}
|
||||
|
||||
// We can like, dislike, be indifferent but we cannot like & dislike at the same time
|
||||
private func ensure3StateButtons(withReaction reaction: ReactionsMenuReaction) {
|
||||
var unreaction: ReactionsMenuReaction?
|
||||
|
||||
switch reaction {
|
||||
case .agree:
|
||||
if isDisagreeButtonSelected {
|
||||
unreaction = .disagree
|
||||
}
|
||||
case .disagree:
|
||||
if isAgreeButtonSelected {
|
||||
unreaction = .agree
|
||||
}
|
||||
case .like:
|
||||
if isDislikeButtonSelected {
|
||||
unreaction = .dislike
|
||||
}
|
||||
case .dislike:
|
||||
if isLikeButtonSelected {
|
||||
unreaction = .like
|
||||
}
|
||||
}
|
||||
|
||||
if let unreaction = unreaction {
|
||||
self.react(withReaction: unreaction, selected: false)
|
||||
}
|
||||
self.viewDelegate?.reactionsMenuViewModel(self, didUpdateViewState: ReactionsMenuViewState.loaded(reactionsViewData: reactionMenuItemViewDatas))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
protocol ReactionsMenuViewModelDelegate: class {
|
||||
func reactionsMenuViewModelDidUpdate(_ viewModel: ReactionsMenuViewModelType)
|
||||
protocol ReactionsMenuViewModelViewDelegate: class {
|
||||
func reactionsMenuViewModel(_ viewModel: ReactionsMenuViewModel, didUpdateViewState viewState: ReactionsMenuViewState)
|
||||
}
|
||||
|
||||
@objc protocol ReactionsMenuViewModelCoordinatorDelegate: class {
|
||||
|
@ -25,16 +25,10 @@ protocol ReactionsMenuViewModelDelegate: class {
|
|||
func reactionsMenuViewModel(_ viewModel: ReactionsMenuViewModel, didRemoveReaction reaction: String, forEventId eventId: String)
|
||||
}
|
||||
|
||||
|
||||
protocol ReactionsMenuViewModelType {
|
||||
|
||||
var isAgreeButtonSelected: Bool { get }
|
||||
var isDisagreeButtonSelected: Bool { get }
|
||||
var isLikeButtonSelected: Bool { get }
|
||||
var isDislikeButtonSelected: Bool { get }
|
||||
|
||||
var viewDelegate: ReactionsMenuViewModelDelegate? { get set }
|
||||
var coordinatorDelegate: ReactionsMenuViewModelCoordinatorDelegate? { get set }
|
||||
var viewDelegate: ReactionsMenuViewModelViewDelegate? { get set }
|
||||
|
||||
func process(viewAction: ReactionsMenuViewAction)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
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
|
||||
|
||||
/// ReactionsMenuView view state
|
||||
enum ReactionsMenuViewState {
|
||||
case loaded(reactionsViewData: [ReactionMenuItemViewData])
|
||||
}
|
|
@ -22,7 +22,7 @@ final class RoomContextualMenuPresenter: NSObject {
|
|||
// MARK: - Constants
|
||||
|
||||
private enum Constants {
|
||||
static let animationDuration: TimeInterval = 0.3
|
||||
static let animationDuration: TimeInterval = 0.2
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
@ -42,24 +42,28 @@ final class RoomContextualMenuPresenter: NSObject {
|
|||
func present(roomContextualMenuViewController: RoomContextualMenuViewController,
|
||||
from viewController: UIViewController,
|
||||
on view: UIView,
|
||||
contentToReactFrame: CGRect, // Not nullable for compatibility with Obj-C
|
||||
animated: Bool,
|
||||
completion: (() -> Void)?) {
|
||||
guard self.roomContextualMenuViewController == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
roomContextualMenuViewController.view.alpha = 0
|
||||
|
||||
viewController.vc_addChildViewController(viewController: roomContextualMenuViewController, onView: view)
|
||||
|
||||
self.roomContextualMenuViewController = roomContextualMenuViewController
|
||||
|
||||
roomContextualMenuViewController.contentToReactFrame = contentToReactFrame
|
||||
|
||||
roomContextualMenuViewController.hideMenuToolbar()
|
||||
roomContextualMenuViewController.prepareReactionsMenuAnimations()
|
||||
roomContextualMenuViewController.hideReactionsMenu()
|
||||
|
||||
roomContextualMenuViewController.view.layoutIfNeeded()
|
||||
|
||||
let animationInstructions: (() -> Void) = {
|
||||
roomContextualMenuViewController.showMenuToolbar()
|
||||
roomContextualMenuViewController.view.alpha = 1
|
||||
roomContextualMenuViewController.showReactionsMenu()
|
||||
roomContextualMenuViewController.view.layoutIfNeeded()
|
||||
}
|
||||
|
||||
|
@ -77,37 +81,43 @@ final class RoomContextualMenuPresenter: NSObject {
|
|||
|
||||
func hideContextualMenu(animated: Bool, completion: (() -> Void)?) {
|
||||
guard let roomContextualMenuViewController = self.roomContextualMenuViewController else {
|
||||
completion?()
|
||||
return
|
||||
}
|
||||
|
||||
let animationInstructions: (() -> Void) = {
|
||||
roomContextualMenuViewController.hideMenuToolbar()
|
||||
roomContextualMenuViewController.view.alpha = 0
|
||||
roomContextualMenuViewController.hideReactionsMenu()
|
||||
roomContextualMenuViewController.view.layoutIfNeeded()
|
||||
}
|
||||
|
||||
let animationCompletionInstructions: (() -> Void) = {
|
||||
roomContextualMenuViewController.vc_removeFromParent()
|
||||
|
||||
// TODO: To remove once the retain cycle caused by reactionsMenuViewModel is fixed
|
||||
self.roomContextualMenuViewController = nil
|
||||
|
||||
completion?()
|
||||
}
|
||||
|
||||
if animated {
|
||||
if roomContextualMenuViewController.shouldPerformTappedReactionAnimation {
|
||||
UIView.animate(withDuration: 0.15, animations: {
|
||||
roomContextualMenuViewController.selectedReactionAnimationsIntructionsPart1()
|
||||
}, completion: { _ in
|
||||
UIView.animate(withDuration: Constants.animationDuration, animations: {
|
||||
roomContextualMenuViewController.selectedReactionAnimationsIntructionsPart2()
|
||||
animationInstructions()
|
||||
}, completion: { completed in
|
||||
animationCompletionInstructions()
|
||||
})
|
||||
})
|
||||
} else {
|
||||
UIView.animate(withDuration: Constants.animationDuration, animations: {
|
||||
animationInstructions()
|
||||
}, completion: { completed in
|
||||
animationCompletionInstructions()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
animationInstructions()
|
||||
animationCompletionInstructions()
|
||||
}
|
||||
}
|
||||
|
||||
func showReactionsMenu(reactionsMenuViewModel: ReactionsMenuViewModel, aroundFrame frame: CGRect) {
|
||||
self.roomContextualMenuViewController?.showReactionsMenu(withViewModel: reactionsMenuViewModel, aroundFrame: frame)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,19 +21,20 @@
|
|||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Szx-Dr-Ndt">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="793"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Vdy-rp-3g9" customClass="ReactionsMenuView" customModule="Riot" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="100" width="414" height="100"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Vdy-rp-3g9">
|
||||
<rect key="frame" x="10" y="150" width="394" height="50"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="100" id="D3i-aE-kdL"/>
|
||||
<constraint firstAttribute="height" constant="50" id="D3i-aE-kdL"/>
|
||||
<constraint firstAttribute="width" relation="lessThanOrEqual" priority="999" constant="400" id="Kdn-SI-BvA"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Vdy-rp-3g9" firstAttribute="bottom" secondItem="Szx-Dr-Ndt" secondAttribute="top" constant="200" id="0GH-Qk-vKA"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Vdy-rp-3g9" secondAttribute="trailing" id="9Gu-dr-IPv"/>
|
||||
<constraint firstItem="Vdy-rp-3g9" firstAttribute="leading" secondItem="Szx-Dr-Ndt" secondAttribute="leading" id="h78-sP-xDx"/>
|
||||
<constraint firstItem="Vdy-rp-3g9" firstAttribute="width" secondItem="Szx-Dr-Ndt" secondAttribute="width" priority="750" id="Io5-6N-mvK"/>
|
||||
<constraint firstItem="Vdy-rp-3g9" firstAttribute="centerX" secondItem="Szx-Dr-Ndt" secondAttribute="centerX" id="epY-Ub-0d5"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0GC-JU-rI3" customClass="RoomContextualMenuToolbarView" customModule="Riot" customModuleProvider="target">
|
||||
|
@ -49,8 +50,10 @@
|
|||
<constraint firstAttribute="trailing" secondItem="Szx-Dr-Ndt" secondAttribute="trailing" id="2eB-6O-P3h"/>
|
||||
<constraint firstItem="Szx-Dr-Ndt" firstAttribute="leading" secondItem="X0o-r8-auN" secondAttribute="leading" id="4qK-G6-nr9"/>
|
||||
<constraint firstItem="Szx-Dr-Ndt" firstAttribute="top" secondItem="X0o-r8-auN" secondAttribute="top" id="GVa-P9-DcG"/>
|
||||
<constraint firstItem="Vdy-rp-3g9" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="225-y0-Elg" secondAttribute="leading" constant="10" id="R2Z-jH-SRN"/>
|
||||
<constraint firstItem="0GC-JU-rI3" firstAttribute="leading" secondItem="X0o-r8-auN" secondAttribute="leading" id="TZJ-nm-Ppz"/>
|
||||
<constraint firstItem="0GC-JU-rI3" firstAttribute="top" secondItem="Szx-Dr-Ndt" secondAttribute="bottom" id="Wyl-wh-kh4"/>
|
||||
<constraint firstItem="225-y0-Elg" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Vdy-rp-3g9" secondAttribute="trailing" constant="10" id="j9U-2p-huj"/>
|
||||
<constraint firstAttribute="trailing" secondItem="0GC-JU-rI3" secondAttribute="trailing" id="lzM-FD-x89"/>
|
||||
<constraint firstItem="225-y0-Elg" firstAttribute="bottom" secondItem="0GC-JU-rI3" secondAttribute="bottom" id="s4i-80-0iu"/>
|
||||
</constraints>
|
||||
|
@ -61,7 +64,7 @@
|
|||
<outlet property="menuToolbarView" destination="0GC-JU-rI3" id="j0z-I8-Pcr"/>
|
||||
<outlet property="menuToolbarViewBottomConstraint" destination="s4i-80-0iu" id="E5w-5m-m5O"/>
|
||||
<outlet property="menuToolbarViewHeightConstraint" destination="ynL-KP-iB4" id="Zeb-b0-Yil"/>
|
||||
<outlet property="reactionsMenuView" destination="Vdy-rp-3g9" id="jJT-mz-vg6"/>
|
||||
<outlet property="reactionsMenuContainerView" destination="Vdy-rp-3g9" id="hRj-C0-5VR"/>
|
||||
<outlet property="reactionsMenuViewBottomConstraint" destination="0GH-Qk-vKA" id="8lg-XL-JgW"/>
|
||||
<outlet property="reactionsMenuViewHeightConstraint" destination="D3i-aE-kdL" id="CCr-hW-2dv"/>
|
||||
</connections>
|
||||
|
|
|
@ -18,12 +18,18 @@ import UIKit
|
|||
|
||||
@objc protocol RoomContextualMenuViewControllerDelegate: class {
|
||||
func roomContextualMenuViewControllerDidTapBackgroundOverlay(_ viewController: RoomContextualMenuViewController)
|
||||
func roomContextualMenuViewControllerDidReaction(_ viewController: RoomContextualMenuViewController)
|
||||
}
|
||||
|
||||
@objcMembers
|
||||
final class RoomContextualMenuViewController: UIViewController, Themable {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
private enum Constants {
|
||||
static let reactionsMenuViewVerticalMargin: CGFloat = 10.0
|
||||
static let reactionsMenuViewHiddenScale: CGFloat = 0.97
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Outlets
|
||||
|
@ -34,15 +40,20 @@ final class RoomContextualMenuViewController: UIViewController, Themable {
|
|||
@IBOutlet private weak var menuToolbarViewHeightConstraint: NSLayoutConstraint!
|
||||
@IBOutlet private weak var menuToolbarViewBottomConstraint: NSLayoutConstraint!
|
||||
|
||||
@IBOutlet private weak var reactionsMenuView: ReactionsMenuView!
|
||||
@IBOutlet private weak var reactionsMenuContainerView: UIView!
|
||||
@IBOutlet private weak var reactionsMenuViewHeightConstraint: NSLayoutConstraint!
|
||||
@IBOutlet private weak var reactionsMenuViewBottomConstraint: NSLayoutConstraint!
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var theme: Theme!
|
||||
private var errorPresenter: MXKErrorPresentation!
|
||||
private var contextualMenuItems: [RoomContextualMenuItem] = []
|
||||
private var reactionsMenuViewModel: ReactionsMenuViewModel?
|
||||
|
||||
private weak var reactionsMenuView: ReactionsMenuView?
|
||||
|
||||
private var reactionsMenuViewBottomStartConstraintConstant: CGFloat?
|
||||
private var reactionsMenuViewBottomEndConstraintConstant: CGFloat?
|
||||
|
||||
private var hiddenToolbarViewBottomConstant: CGFloat {
|
||||
let bottomSafeAreaHeight: CGFloat
|
||||
|
@ -58,31 +69,41 @@ final class RoomContextualMenuViewController: UIViewController, Themable {
|
|||
|
||||
// MARK: Public
|
||||
|
||||
var contentToReactFrame: CGRect?
|
||||
var shouldPerformTappedReactionAnimation: Bool {
|
||||
return self.reactionsMenuView?.reactionHasBeenTapped ?? false
|
||||
}
|
||||
|
||||
weak var delegate: RoomContextualMenuViewControllerDelegate?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
class func instantiate(with contextualMenuItems: [RoomContextualMenuItem]) -> RoomContextualMenuViewController {
|
||||
class func instantiate(with contextualMenuItems: [RoomContextualMenuItem],
|
||||
reactionsMenuViewModel: ReactionsMenuViewModel?) -> RoomContextualMenuViewController {
|
||||
let viewController = StoryboardScene.RoomContextualMenuViewController.initialScene.instantiate()
|
||||
viewController.theme = ThemeService.shared().theme
|
||||
viewController.contextualMenuItems = contextualMenuItems
|
||||
viewController.reactionsMenuViewModel = reactionsMenuViewModel
|
||||
return viewController
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Life cycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
self.reactionsMenuView.isHidden = true
|
||||
self.reactionsMenuContainerView.isHidden = true
|
||||
|
||||
if let reactionsMenuViewModel = self.reactionsMenuViewModel {
|
||||
self.setupReactionsMenu(with: reactionsMenuViewModel)
|
||||
}
|
||||
|
||||
self.backgroundOverlayView.isUserInteractionEnabled = true
|
||||
self.menuToolbarView.fill(contextualMenuItems: self.contextualMenuItems)
|
||||
self.setupBackgroundOverlayGestureRecognizers()
|
||||
|
||||
self.errorPresenter = MXKErrorAlertPresentation()
|
||||
|
||||
self.registerThemeServiceDidChangeThemeNotification()
|
||||
self.update(theme: self.theme)
|
||||
}
|
||||
|
@ -91,31 +112,87 @@ final class RoomContextualMenuViewController: UIViewController, Themable {
|
|||
|
||||
func showMenuToolbar() {
|
||||
self.menuToolbarViewBottomConstraint.constant = 0
|
||||
self.menuToolbarView.alpha = 1
|
||||
}
|
||||
|
||||
func hideMenuToolbar() {
|
||||
self.menuToolbarViewBottomConstraint.constant = self.hiddenToolbarViewBottomConstant
|
||||
self.menuToolbarView.alpha = 0
|
||||
}
|
||||
|
||||
func showReactionsMenu(withViewModel viewModel: ReactionsMenuViewModel, aroundFrame frame: CGRect) {
|
||||
self.reactionsMenuView.viewModel = viewModel
|
||||
self.reactionsMenuView.isHidden = false
|
||||
func prepareReactionsMenuAnimations() {
|
||||
guard let frame = self.contentToReactFrame else {
|
||||
return
|
||||
}
|
||||
|
||||
let menuHeight = self.reactionsMenuViewHeightConstraint.constant
|
||||
let verticalMargin = Constants.reactionsMenuViewVerticalMargin
|
||||
|
||||
let reactionsMenuViewBottomStartConstraintConstant: CGFloat?
|
||||
let reactionsMenuViewBottomEndConstraintConstant: CGFloat?
|
||||
|
||||
// Try to display the menu at the top of the message first
|
||||
// Then, try at the bottom
|
||||
// Else, keep the position defined in the storyboard
|
||||
if frame.origin.y >= self.reactionsMenuViewHeightConstraint.constant {
|
||||
self.reactionsMenuViewBottomConstraint.constant = frame.origin.y
|
||||
if frame.origin.y - verticalMargin >= menuHeight {
|
||||
let menuViewBottomY = frame.origin.y - verticalMargin
|
||||
reactionsMenuViewBottomStartConstraintConstant = menuViewBottomY + menuHeight/2
|
||||
reactionsMenuViewBottomEndConstraintConstant = menuViewBottomY
|
||||
} else {
|
||||
let frameBottomY = frame.origin.y + frame.size.height
|
||||
let frameBottomY = frame.origin.y + frame.size.height + verticalMargin
|
||||
let visibleViewHeight = self.view.frame.size.height - self.menuToolbarView.frame.size.height
|
||||
|
||||
if frameBottomY + menuHeight < visibleViewHeight {
|
||||
self.reactionsMenuViewBottomConstraint.constant = frameBottomY + menuHeight
|
||||
let menuViewBottomY = frameBottomY + menuHeight
|
||||
|
||||
reactionsMenuViewBottomEndConstraintConstant = menuViewBottomY
|
||||
reactionsMenuViewBottomStartConstraintConstant = menuViewBottomY - menuHeight/2
|
||||
} else {
|
||||
reactionsMenuViewBottomEndConstraintConstant = nil
|
||||
reactionsMenuViewBottomStartConstraintConstant = nil
|
||||
}
|
||||
}
|
||||
|
||||
self.reactionsMenuViewBottomStartConstraintConstant = reactionsMenuViewBottomStartConstraintConstant
|
||||
self.reactionsMenuViewBottomEndConstraintConstant = reactionsMenuViewBottomEndConstraintConstant
|
||||
|
||||
self.reactionsMenuContainerView.isHidden = false
|
||||
}
|
||||
|
||||
func showReactionsMenu() {
|
||||
guard let reactionsMenuView = self.reactionsMenuView else {
|
||||
return
|
||||
}
|
||||
|
||||
if let reactionsMenuViewBottomEndConstraintConstant = self.reactionsMenuViewBottomEndConstraintConstant {
|
||||
self.reactionsMenuViewBottomConstraint.constant = reactionsMenuViewBottomEndConstraintConstant
|
||||
}
|
||||
|
||||
reactionsMenuView.alpha = 1
|
||||
reactionsMenuContainerView.transform = CGAffineTransform.identity
|
||||
}
|
||||
|
||||
func hideReactionsMenu() {
|
||||
guard let reactionsMenuView = self.reactionsMenuView else {
|
||||
return
|
||||
}
|
||||
|
||||
if let reactionsMenuViewBottomStartConstraintConstant = self.reactionsMenuViewBottomStartConstraintConstant {
|
||||
self.reactionsMenuViewBottomConstraint.constant = reactionsMenuViewBottomStartConstraintConstant
|
||||
}
|
||||
|
||||
reactionsMenuView.alpha = 0
|
||||
|
||||
let transformScale = Constants.reactionsMenuViewHiddenScale
|
||||
self.reactionsMenuContainerView.transform = CGAffineTransform(scaleX: transformScale, y: transformScale)
|
||||
}
|
||||
|
||||
func selectedReactionAnimationsIntructionsPart1() {
|
||||
self.reactionsMenuView?.selectionAnimationInstructionPart1()
|
||||
}
|
||||
|
||||
func selectedReactionAnimationsIntructionsPart2() {
|
||||
self.reactionsMenuView?.selectionAnimationInstructionPart2()
|
||||
}
|
||||
|
||||
func update(theme: Theme) {
|
||||
|
@ -124,6 +201,13 @@ final class RoomContextualMenuViewController: UIViewController, Themable {
|
|||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupReactionsMenu(with viewModel: ReactionsMenuViewModel) {
|
||||
let reactionsMenuView = ReactionsMenuView.loadFromNib()
|
||||
self.reactionsMenuContainerView.vc_addSubViewMatchingParent(reactionsMenuView)
|
||||
reactionsMenuView.viewModel = viewModel
|
||||
self.reactionsMenuView = reactionsMenuView
|
||||
}
|
||||
|
||||
private func setupBackgroundOverlayGestureRecognizers() {
|
||||
|
||||
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handle(gestureRecognizer:)))
|
||||
|
@ -155,6 +239,6 @@ extension RoomContextualMenuViewController: UIGestureRecognizerDelegate {
|
|||
|
||||
// Avoid triggering background overlay gesture recognizers when touching reactions menu
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
|
||||
return touch.vc_isInside(view: self.reactionsMenuView) == false
|
||||
return touch.vc_isInside(view: self.reactionsMenuContainerView) == false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5158,21 +5158,13 @@
|
|||
return;
|
||||
}
|
||||
|
||||
[self selectEventWithId:event.eventId];
|
||||
NSString *selectedEventId = event.eventId;
|
||||
|
||||
[self selectEventWithId:selectedEventId];
|
||||
|
||||
NSArray<RoomContextualMenuItem*>* contextualMenuItems = [self contextualMenuItemsForEvent:event andCell:cell];
|
||||
|
||||
RoomContextualMenuViewController *roomContextualMenuViewController = [RoomContextualMenuViewController instantiateWith:contextualMenuItems];
|
||||
roomContextualMenuViewController.delegate = self;
|
||||
|
||||
[self enableOverlayContainerUserInteractions:YES];
|
||||
|
||||
[self.roomContextualMenuPresenter presentWithRoomContextualMenuViewController:roomContextualMenuViewController
|
||||
from:self
|
||||
on:self.overlayContainerView
|
||||
animated:YES
|
||||
completion:^{
|
||||
}];
|
||||
ReactionsMenuViewModel *reactionsMenuViewModel;
|
||||
CGRect bubbleComponentFrameInOverlayView = CGRectNull;
|
||||
|
||||
if (RiotSettings.shared.messageReaction && [cell isKindOfClass:MXKRoomBubbleTableViewCell.class] && [self.roomDataSource canReactToEventWithId:event.eventId])
|
||||
{
|
||||
|
@ -5181,7 +5173,7 @@
|
|||
NSArray *bubbleComponents = bubbleCellData.bubbleComponents;
|
||||
|
||||
NSInteger foundComponentIndex = [bubbleComponents indexOfObjectPassingTest:^BOOL(MXKRoomBubbleComponent * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
if (obj.event.eventId == event.eventId)
|
||||
if (obj.event.eventId == selectedEventId)
|
||||
{
|
||||
*stop = YES;
|
||||
return YES;
|
||||
|
@ -5201,16 +5193,28 @@
|
|||
bubbleComponentFrame = roomBubbleTableViewCell.frame;
|
||||
}
|
||||
|
||||
CGRect bubbleComponentFrameInOverlayView = [self.bubblesTableView convertRect:bubbleComponentFrame toView:self.overlayContainerView];
|
||||
bubbleComponentFrameInOverlayView = [self.bubblesTableView convertRect:bubbleComponentFrame toView:self.overlayContainerView];
|
||||
|
||||
NSString *roomId = self.roomDataSource.roomId;
|
||||
MXAggregations *aggregations = self.mainSession.aggregations;
|
||||
MXAggregatedReactions *aggregatedReactions = [aggregations aggregatedReactionsOnEvent:selectedEventId inRoom:roomId];
|
||||
|
||||
ReactionsMenuViewModel *reactionsMenuViewModel = [[ReactionsMenuViewModel alloc] initWithAggregations:aggregations roomId:roomId eventId:event.eventId];
|
||||
reactionsMenuViewModel = [[ReactionsMenuViewModel alloc] initWithAggregatedReactions:aggregatedReactions eventId:selectedEventId];
|
||||
reactionsMenuViewModel.coordinatorDelegate = self;
|
||||
|
||||
[self.roomContextualMenuPresenter showReactionsMenuWithReactionsMenuViewModel:reactionsMenuViewModel aroundFrame:bubbleComponentFrameInOverlayView];
|
||||
}
|
||||
|
||||
RoomContextualMenuViewController *roomContextualMenuViewController = [RoomContextualMenuViewController instantiateWith:contextualMenuItems reactionsMenuViewModel:reactionsMenuViewModel];
|
||||
roomContextualMenuViewController.delegate = self;
|
||||
|
||||
[self enableOverlayContainerUserInteractions:YES];
|
||||
|
||||
[self.roomContextualMenuPresenter presentWithRoomContextualMenuViewController:roomContextualMenuViewController
|
||||
from:self
|
||||
on:self.overlayContainerView
|
||||
contentToReactFrame:bubbleComponentFrameInOverlayView
|
||||
animated:YES
|
||||
completion:^{
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)hideContextualMenuAnimated:(BOOL)animated
|
||||
|
@ -5259,17 +5263,14 @@
|
|||
[self hideContextualMenuAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)roomContextualMenuViewControllerDidReaction:(RoomContextualMenuViewController *)viewController
|
||||
{
|
||||
[self hideContextualMenuAnimated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - ReactionsMenuViewModelCoordinatorDelegate
|
||||
|
||||
- (void)reactionsMenuViewModel:(ReactionsMenuViewModel *)viewModel didAddReaction:(NSString *)reaction forEventId:(NSString *)eventId
|
||||
{
|
||||
MXWeakify(self);
|
||||
|
||||
[self hideContextualMenuAnimated:YES completion:^{
|
||||
|
||||
[self.roomDataSource addReaction:reaction forEventId:eventId success:^{
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
@ -5277,14 +5278,15 @@
|
|||
|
||||
[self.errorPresenter presentErrorFromViewController:self forError:error animated:YES handler:nil];
|
||||
}];
|
||||
|
||||
[self hideContextualMenuAnimated:YES];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)reactionsMenuViewModel:(ReactionsMenuViewModel *)viewModel didRemoveReaction:(NSString *)reaction forEventId:(NSString *)eventId
|
||||
{
|
||||
MXWeakify(self);
|
||||
|
||||
[self hideContextualMenuAnimated:YES completion:^{
|
||||
|
||||
[self.roomDataSource removeReaction:reaction forEventId:eventId success:^{
|
||||
|
||||
} failure:^(NSError *error) {
|
||||
|
@ -5293,7 +5295,7 @@
|
|||
[self.errorPresenter presentErrorFromViewController:self forError:error animated:YES handler:nil];
|
||||
}];
|
||||
|
||||
[self hideContextualMenuAnimated:YES];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
Loading…
Reference in a new issue