From 3dda1f17e1509c7c5bd31b3994dfb577d114248f Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 13 May 2019 15:21:16 +1000 Subject: [PATCH] Implement incoming friend request UI --- Signal.xcodeproj/project.pbxproj | 12 +++- Signal/src/Loki/FriendRequestView.swift | 63 +++++++++++++++++++ .../src/Loki/FriendRequestViewDelegate.swift | 5 ++ ...boardingAccountDetailsViewController.swift | 2 +- .../OnboardingKeyPairViewController.swift | 2 +- Signal/src/Loki/Poller.swift | 6 +- .../ConversationView/Cells/OWSMessageCell.h | 2 + .../ConversationView/Cells/OWSMessageCell.m | 23 +++++++ .../ConversationViewController.m | 14 +++++ .../translations/en.lproj/Localizable.strings | 3 + SignalMessaging/utils/ThreadUtil.h | 2 + 11 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 Signal/src/Loki/FriendRequestView.swift create mode 100644 Signal/src/Loki/FriendRequestViewDelegate.swift rename Signal/src/{ViewControllers/Registration => Loki}/OnboardingAccountDetailsViewController.swift (98%) rename Signal/src/{ViewControllers/Registration => Loki}/OnboardingKeyPairViewController.swift (99%) diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 3eeae078a..c5a7426fb 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -555,6 +555,8 @@ B6B226971BE4B7D200860F4D /* ContactsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6B226961BE4B7D200860F4D /* ContactsUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; B6F509971AA53F760068F56A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B6F509951AA53F760068F56A /* Localizable.strings */; }; B6FE7EB71ADD62FA00A6D22F /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */; }; + B8162F0322891AD600D46544 /* FriendRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8162F0222891AD600D46544 /* FriendRequestView.swift */; }; + B8162F0522892C5F00D46544 /* FriendRequestViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8162F0422892C5F00D46544 /* FriendRequestViewDelegate.swift */; }; B821F2F82272CED3002C88C0 /* OnboardingAccountDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B821F2F72272CED3002C88C0 /* OnboardingAccountDetailsViewController.swift */; }; B821F2FA2272CEEE002C88C0 /* OnboardingKeyPairViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B821F2F92272CEEE002C88C0 /* OnboardingKeyPairViewController.swift */; }; B843951A228510FB000563FE /* Poller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8439519228510FB000563FE /* Poller.swift */; }; @@ -1340,6 +1342,8 @@ B6BC3D0C1AA544B100C2907F /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = translations/da.lproj/Localizable.strings; sourceTree = ""; }; B6F509961AA53F760068F56A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = translations/en.lproj/Localizable.strings; sourceTree = ""; }; B6FE7EB61ADD62FA00A6D22F /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = System/Library/Frameworks/PushKit.framework; sourceTree = SDKROOT; }; + B8162F0222891AD600D46544 /* FriendRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendRequestView.swift; sourceTree = ""; }; + B8162F0422892C5F00D46544 /* FriendRequestViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendRequestViewDelegate.swift; sourceTree = ""; }; B821F2F72272CED3002C88C0 /* OnboardingAccountDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingAccountDetailsViewController.swift; sourceTree = ""; }; B821F2F92272CEEE002C88C0 /* OnboardingKeyPairViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingKeyPairViewController.swift; sourceTree = ""; }; B8439519228510FB000563FE /* Poller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Poller.swift; path = Signal/src/Loki/Poller.swift; sourceTree = SOURCE_ROOT; }; @@ -1526,13 +1530,11 @@ children = ( 3441FD9E21A3604F00BB9542 /* BackupRestoreViewController.swift */, 349ED98F221B0194008045B0 /* Onboarding2FAViewController.swift */, - B821F2F72272CED3002C88C0 /* OnboardingAccountDetailsViewController.swift */, 3448E1612213585C004B052E /* OnboardingBaseViewController.swift */, 3448E1652215B313004B052E /* OnboardingCaptchaViewController.swift */, 3448E15D221333F5004B052E /* OnboardingController.swift */, 3448E15B22133274004B052E /* OnboardingPermissionsViewController.swift */, 34A4C61F22175C5C0042EF2E /* OnboardingProfileViewController.swift */, - B821F2F92272CEEE002C88C0 /* OnboardingKeyPairViewController.swift */, 3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */, 34A4C61D221613D00042EF2E /* OnboardingVerificationViewController.swift */, 346E9D5321B040B600562252 /* RegistrationController.swift */, @@ -2599,6 +2601,10 @@ B8439518228510E9000563FE /* Loki */ = { isa = PBXGroup; children = ( + B8162F0222891AD600D46544 /* FriendRequestView.swift */, + B8162F0422892C5F00D46544 /* FriendRequestViewDelegate.swift */, + B821F2F72272CED3002C88C0 /* OnboardingAccountDetailsViewController.swift */, + B821F2F92272CEEE002C88C0 /* OnboardingKeyPairViewController.swift */, B8439519228510FB000563FE /* Poller.swift */, ); path = Loki; @@ -3675,10 +3681,12 @@ 457C87B82032645C008D52D6 /* DebugUINotifications.swift in Sources */, 4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */, 4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */, + B8162F0522892C5F00D46544 /* FriendRequestViewDelegate.swift in Sources */, B821F2FA2272CEEE002C88C0 /* OnboardingKeyPairViewController.swift in Sources */, 4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */, 3448E16022134C89004B052E /* OnboardingSplashViewController.swift in Sources */, 34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */, + B8162F0322891AD600D46544 /* FriendRequestView.swift in Sources */, 458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */, 34B6A905218B4C91007C4606 /* TypingIndicatorInteraction.swift in Sources */, 4517642B1DE939FD00EDB8B9 /* ContactCell.swift in Sources */, diff --git a/Signal/src/Loki/FriendRequestView.swift b/Signal/src/Loki/FriendRequestView.swift new file mode 100644 index 000000000..057caaa3b --- /dev/null +++ b/Signal/src/Loki/FriendRequestView.swift @@ -0,0 +1,63 @@ + +@objc final class FriendRequestView : UIView { + @objc var message: TSIncomingMessage! { didSet { handleMessageChanged() } } + @objc weak var delegate: FriendRequestViewDelegate? + + // MARK: Components + private lazy var label: UILabel = { + let result = UILabel() + result.textColor = Theme.secondaryColor + result.font = UIFont.ows_dynamicTypeSubheadlineClamped + result.numberOfLines = 0 + result.textAlignment = .center + result.lineBreakMode = .byWordWrapping + return result + }() + + // MARK: Initialization + required init?(coder: NSCoder) { + super.init(coder: coder) + initialize() + } + + override init(frame: CGRect) { + super.init(frame: frame) + initialize() + } + + private func initialize() { + let mainStackView = UIStackView() + mainStackView.axis = .vertical + mainStackView.distribution = .fill + mainStackView.addArrangedSubview(label) + let buttonStackView = UIStackView() + buttonStackView.axis = .horizontal + buttonStackView.distribution = .fillEqually + mainStackView.addArrangedSubview(buttonStackView) + let buttonFont = UIFont.ows_dynamicTypeBodyClamped.ows_mediumWeight() + let buttonHeight = buttonFont.pointSize * 48 / 17 + let acceptButton = OWSFlatButton.button(title: NSLocalizedString("Accept", comment: ""), font: buttonFont, titleColor: .ows_materialBlue, backgroundColor: .clear, target: self, selector: #selector(accept)) + acceptButton.autoSetDimension(.height, toSize: buttonHeight) + buttonStackView.addArrangedSubview(acceptButton) + let declineButton = OWSFlatButton.button(title: NSLocalizedString("Decline", comment: ""), font: buttonFont, titleColor: .ows_destructiveRed, backgroundColor: .clear, target: self, selector: #selector(decline)) + declineButton.autoSetDimension(.height, toSize: buttonHeight) + buttonStackView.addArrangedSubview(declineButton) + addSubview(mainStackView) + mainStackView.autoPin(toEdgesOf: self) + } + + // MARK: Updating + private func handleMessageChanged() { + assert(message != nil) + label.text = String(format: NSLocalizedString("%@ sent you a friend request", comment: ""), message.authorId) + } + + // MARK: Interaction + @objc private func accept() { + delegate?.acceptFriendRequest(message) + } + + @objc private func decline() { + delegate?.declineFriendRequest(message) + } +} diff --git a/Signal/src/Loki/FriendRequestViewDelegate.swift b/Signal/src/Loki/FriendRequestViewDelegate.swift new file mode 100644 index 000000000..2e8bc1111 --- /dev/null +++ b/Signal/src/Loki/FriendRequestViewDelegate.swift @@ -0,0 +1,5 @@ + +@objc protocol FriendRequestViewDelegate { + @objc func acceptFriendRequest(_ friendRequest: TSIncomingMessage) + @objc func declineFriendRequest(_ friendRequest: TSIncomingMessage) +} diff --git a/Signal/src/ViewControllers/Registration/OnboardingAccountDetailsViewController.swift b/Signal/src/Loki/OnboardingAccountDetailsViewController.swift similarity index 98% rename from Signal/src/ViewControllers/Registration/OnboardingAccountDetailsViewController.swift rename to Signal/src/Loki/OnboardingAccountDetailsViewController.swift index 0039e4257..5dcf840b5 100644 --- a/Signal/src/ViewControllers/Registration/OnboardingAccountDetailsViewController.swift +++ b/Signal/src/Loki/OnboardingAccountDetailsViewController.swift @@ -65,7 +65,7 @@ final class OnboardingAccountDetailsViewController : OnboardingBaseViewControlle topSpacer.autoMatch(.height, to: .height, of: bottomSpacer) } - public override func viewDidAppear(_ animated: Bool) { + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) userNameTextField.becomeFirstResponder() } diff --git a/Signal/src/ViewControllers/Registration/OnboardingKeyPairViewController.swift b/Signal/src/Loki/OnboardingKeyPairViewController.swift similarity index 99% rename from Signal/src/ViewControllers/Registration/OnboardingKeyPairViewController.swift rename to Signal/src/Loki/OnboardingKeyPairViewController.swift index 3aafac6d4..41e8afe92 100644 --- a/Signal/src/ViewControllers/Registration/OnboardingKeyPairViewController.swift +++ b/Signal/src/Loki/OnboardingKeyPairViewController.swift @@ -91,7 +91,7 @@ final class OnboardingKeyPairViewController : OnboardingBaseViewController { self.userName = userName } - override public func viewDidLoad() { + override func viewDidLoad() { super.loadView() setUpViewHierarchy() handleModeChanged() // Perform initial update diff --git a/Signal/src/Loki/Poller.swift b/Signal/src/Loki/Poller.swift index 626aaf758..26d76495e 100644 --- a/Signal/src/Loki/Poller.swift +++ b/Signal/src/Loki/Poller.swift @@ -1,6 +1,6 @@ import PromiseKit -@objc public final class Poller : NSObject { +@objc final class Poller : NSObject { private var isStarted = false private var currentJob: Promise? @@ -8,12 +8,12 @@ import PromiseKit private static let interval: TimeInterval = 30 // MARK: Initialization - @objc public static let shared = Poller() + @objc static let shared = Poller() private override init() { } // MARK: General - @objc public func startIfNeeded() { + @objc func startIfNeeded() { guard !isStarted else { return } Timer.scheduledTimer(timeInterval: Poller.interval, target: self, selector: #selector(poll), userInfo: nil, repeats: true) isStarted = true diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.h index 4d52718b5..4b73d50ef 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.h @@ -5,12 +5,14 @@ #import "ConversationViewCell.h" @class OWSMessageBubbleView; +@protocol FriendRequestViewDelegate; NS_ASSUME_NONNULL_BEGIN @interface OWSMessageCell : ConversationViewCell @property (nonatomic, readonly) OWSMessageBubbleView *messageBubbleView; +@property (nonatomic, nullable, weak) id friendRequestViewDelegate; + (NSString *)cellReuseIdentifier; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index 874ccb575..418820f96 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -19,6 +19,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) OWSMessageHeaderView *headerView; @property (nonatomic) OWSMessageBubbleView *messageBubbleView; @property (nonatomic) AvatarImageView *avatarView; +@property (nonatomic, nullable) FriendRequestView *friendRequestView; @property (nonatomic, nullable) UIImageView *sendFailureBadgeView; @property (nonatomic, nullable) NSMutableArray *viewConstraints; @@ -164,6 +165,21 @@ NS_ASSUME_NONNULL_BEGIN withInset:self.conversationStyle.gutterTrailing relation:NSLayoutRelationGreaterThanOrEqual], ]]; + + if ([self.viewItem.interaction isKindOfClass:TSIncomingMessage.class]) { + TSIncomingMessage *message = (TSIncomingMessage *)self.message; + if (YES) { // TODO: message.isFriendRequest + self.friendRequestView = [FriendRequestView new]; + self.friendRequestView.message = message; + self.friendRequestView.delegate = self.friendRequestViewDelegate; + [self.contentView addSubview:self.friendRequestView]; + [self.viewConstraints addObjectsFromArray:@[ + [self.friendRequestView autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:self.conversationStyle.gutterLeading], + [self.friendRequestView autoPinEdgeToSuperviewEdge:ALEdgeTrailing withInset:self.conversationStyle.gutterTrailing], + [self.friendRequestView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.messageBubbleView withOffset:12.f] + ]]; + } + } } else { if (self.shouldHaveSendFailureBadge) { self.sendFailureBadgeView = [UIImageView new]; @@ -339,6 +355,10 @@ NS_ASSUME_NONNULL_BEGIN if (self.shouldHaveSendFailureBadge) { cellSize.width += self.sendFailureBadgeSize + self.sendFailureBadgeSpacing; } + + if (self.friendRequestView != nil) { + cellSize.height += 118.f; // TODO: Measure dynamically + } cellSize = CGSizeCeil(cellSize); @@ -359,6 +379,9 @@ NS_ASSUME_NONNULL_BEGIN [self.headerView removeFromSuperview]; + [self.friendRequestView removeFromSuperview]; + self.friendRequestView = nil; + self.avatarView.image = nil; [self.avatarView removeFromSuperview]; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 332961209..634825a04 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -123,6 +123,7 @@ typedef enum : NSUInteger { ConversationViewCellDelegate, ConversationInputTextViewDelegate, ConversationSearchControllerDelegate, + FriendRequestViewDelegate, LongTextViewDelegate, MessageActionsDelegate, MessageDetailViewDelegate, @@ -4282,6 +4283,18 @@ typedef enum : NSUInteger { animated:YES]; } +#pragma mark - FriendRequestViewDelegate + +- (void)acceptFriendRequest:(TSIncomingMessage *)friendRequest +{ + [ThreadUtil enqueueFriendRequestAcceptMessageInThread:self.thread]; +} + +- (void)declineFriendRequest:(TSIncomingMessage *)friendRequest +{ + OWSLogDebug(@"decline friend request button pressed"); // TODO: Implement +} + #pragma mark - ConversationViewLayoutDelegate - (NSArray> *)layoutItems @@ -4567,6 +4580,7 @@ typedef enum : NSUInteger { if ([cell isKindOfClass:[OWSMessageCell class]]) { OWSMessageCell *messageCell = (OWSMessageCell *)cell; messageCell.messageBubbleView.delegate = self; + messageCell.friendRequestViewDelegate = self; } cell.conversationStyle = self.conversationStyle; diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 86451b27a..7a3787bfc 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2571,3 +2571,6 @@ "Calculating proof of work" = "Calculating proof of work"; "Failed to calculate proof of work." = "Failed to calculate proof of work."; "Share Public Key" = "Share Public Key"; +"%@ sent you a friend request" = "%@ sent you a friend request"; +"Accept" = "Accept"; +"Decline" = "Decline"; diff --git a/SignalMessaging/utils/ThreadUtil.h b/SignalMessaging/utils/ThreadUtil.h index 2ad741c1d..8e0be222d 100644 --- a/SignalMessaging/utils/ThreadUtil.h +++ b/SignalMessaging/utils/ThreadUtil.h @@ -44,6 +44,8 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Durable Message Enqueue ++ (TSOutgoingMessage *)enqueueFriendRequestAcceptMessageInThread:(TSThread *)thread; + + (TSOutgoingMessage *)enqueueMessageWithText:(NSString *)fullMessageText inThread:(TSThread *)thread quotedReplyModel:(nullable OWSQuotedReplyModel *)quotedReplyModel