diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 77980091e..20a976c65 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -615,6 +615,8 @@ B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; }; BFF3FB9730634F37D25903F4 /* Pods_Signal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D17BB5C25D615AB49813100C /* Pods_Signal.framework */; }; C34C8F7423A7830B00D82669 /* SpaceMono-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C34C8F7323A7830A00D82669 /* SpaceMono-Bold.ttf */; }; + C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */; }; + C3DFFAC823E970080058DAF8 /* OpenGroupSuggestionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC723E970080058DAF8 /* OpenGroupSuggestionSheet.swift */; }; CC875800737563D6891B741D /* Pods_SignalTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 748A5CAEDD7C919FC64C6807 /* Pods_SignalTests.framework */; }; D202868116DBE0E7009068E9 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2AEACDB16C426DA00C364C0 /* CFNetwork.framework */; }; D202868216DBE0F4009068E9 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFD16BB0B480006F3AB /* SystemConfiguration.framework */; }; @@ -1458,6 +1460,8 @@ B97940261832BD2400BD66CB /* UIUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIUtil.m; sourceTree = ""; }; B9EB5ABC1884C002007CBB57 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; C34C8F7323A7830A00D82669 /* SpaceMono-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpaceMono-Bold.ttf"; sourceTree = ""; }; + C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sheet.swift; sourceTree = ""; }; + C3DFFAC723E970080058DAF8 /* OpenGroupSuggestionSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupSuggestionSheet.swift; sourceTree = ""; }; D17BB5C25D615AB49813100C /* Pods_Signal.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Signal.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; }; D2179CFD16BB0B480006F3AB /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; @@ -2810,6 +2814,7 @@ B80C6B582384C4E700FDBC8B /* DeviceNameModal.swift */, B80C6B5A2384C7F900FDBC8B /* DeviceNameModalDelegate.swift */, B82B408D239DC00D00A248E7 /* DisplayNameVC.swift */, + B88847BB23E10BC6009836D2 /* GroupMembersVC.swift */, B8BB82A4238F627000BA5194 /* HomeVC.swift */, B8CCF63E23975CFB0091D419 /* JoinPublicChatVC.swift */, B82B40872399EB0E00A248E7 /* LandingVC.swift */, @@ -2817,9 +2822,9 @@ B85357C623A1FB5100AAF6CD /* LinkDeviceVCDelegate.swift */, B86BD08323399ACF000F5AE3 /* Modal.swift */, B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */, - B88847BB23E10BC6009836D2 /* GroupMembersVC.swift */, B8CCF63623961D6D0091D419 /* NewPrivateChatVC.swift */, B894D0742339EDCF00B4D94D /* NukeDataModal.swift */, + C3DFFAC723E970080058DAF8 /* OpenGroupSuggestionSheet.swift */, B886B4A62398B23E00211ABE /* QRCodeVC.swift */, B82B408B239A068800A248E7 /* RegisterVC.swift */, B82B408F239DD75000A248E7 /* RestoreVC.swift */, @@ -2827,6 +2832,7 @@ B86BD08523399CEF000F5AE3 /* SeedModal.swift */, B85357C223A1BD1200AAF6CD /* SeedVC.swift */, B8CCF6422397711F0091D419 /* SettingsVC.swift */, + C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -3922,6 +3928,7 @@ 346129991FD1E4DA00532771 /* SignalApp.m in Sources */, 3496957121A301A100DCFE74 /* OWSBackupImportJob.m in Sources */, 34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */, + C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */, 343A65951FC47D5E000477A1 /* DebugUISyncMessages.m in Sources */, 45C0DC1E1E69011F00E04C47 /* UIStoryboard+OWS.swift in Sources */, 452ECA4D1E087E7200E2F016 /* MessageFetcherJob.swift in Sources */, @@ -4046,6 +4053,7 @@ B8BB82BE2394D4CE00BA5194 /* Fonts.swift in Sources */, 3496957321A301A100DCFE74 /* OWSBackupJob.m in Sources */, 340FC8B3204DAC8D007AEB0F /* AppSettingsViewController.m in Sources */, + C3DFFAC823E970080058DAF8 /* OpenGroupSuggestionSheet.swift in Sources */, 34C4E2572118957600BEA353 /* OWSWebRTCDataProtos.pb.swift in Sources */, B894D0752339EDCF00B4D94D /* NukeDataModal.swift in Sources */, 346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */, diff --git a/Signal/Images.xcassets/Loki V2/ChatBubbles.imageset/ChatBubbles.pdf b/Signal/Images.xcassets/Loki V2/ChatBubbles.imageset/ChatBubbles.pdf new file mode 100644 index 000000000..e63801c4c Binary files /dev/null and b/Signal/Images.xcassets/Loki V2/ChatBubbles.imageset/ChatBubbles.pdf differ diff --git a/Signal/Images.xcassets/Loki V2/ChatBubbles.imageset/Contents.json b/Signal/Images.xcassets/Loki V2/ChatBubbles.imageset/Contents.json new file mode 100644 index 000000000..0cd7e6164 --- /dev/null +++ b/Signal/Images.xcassets/Loki V2/ChatBubbles.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ChatBubbles.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/src/Loki/Style Guide/Values.swift b/Signal/src/Loki/Style Guide/Values.swift index c1dca9530..587f55a10 100644 --- a/Signal/src/Loki/Style Guide/Values.swift +++ b/Signal/src/Loki/Style Guide/Values.swift @@ -57,8 +57,8 @@ final class Values : NSObject { @objc static let onboardingButtonBottomOffset = isSmallScreen ? CGFloat(52) : CGFloat(72) // MARK: - Animation Values - @objc static let fakeChatStartDelay: TimeInterval = 2 + @objc static let fakeChatStartDelay: TimeInterval = 1.5 @objc static let fakeChatAnimationDuration: TimeInterval = 0.4 - @objc static let fakeChatDelay: TimeInterval = 2.5 + @objc static let fakeChatDelay: TimeInterval = 2 @objc static let fakeChatMessagePopAnimationStartScale: CGFloat = 0.6 } diff --git a/Signal/src/Loki/View Controllers/DeviceNameModal.swift b/Signal/src/Loki/View Controllers/DeviceNameModal.swift index 704594c38..7e9bd7ded 100644 --- a/Signal/src/Loki/View Controllers/DeviceNameModal.swift +++ b/Signal/src/Loki/View Controllers/DeviceNameModal.swift @@ -57,7 +57,7 @@ final class DeviceNameModal : Modal { buttonStackView.axis = .horizontal buttonStackView.spacing = Values.mediumSpacing buttonStackView.distribution = .fillEqually - // Stack view + // Set up main stack view let stackView = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, nameTextField, buttonStackView ]) stackView.axis = .vertical stackView.spacing = Values.largeSpacing diff --git a/Signal/src/Loki/View Controllers/HomeVC.swift b/Signal/src/Loki/View Controllers/HomeVC.swift index dda5d9294..a1d8171d9 100644 --- a/Signal/src/Loki/View Controllers/HomeVC.swift +++ b/Signal/src/Loki/View Controllers/HomeVC.swift @@ -145,6 +145,13 @@ final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegat override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) isViewVisible = true + let hasSeenOpenGroupSuggestionSheet = UserDefaults.standard.bool(forKey: "hasSeenOpenGroupSuggestionSheet") + if !hasSeenOpenGroupSuggestionSheet { + let openGroupSuggestionSheet = OpenGroupSuggestionSheet() + openGroupSuggestionSheet.modalPresentationStyle = .overFullScreen + openGroupSuggestionSheet.modalTransitionStyle = .crossDissolve + present(openGroupSuggestionSheet, animated: true, completion: nil) + } } override func viewWillDisappear(_ animated: Bool) { diff --git a/Signal/src/Loki/View Controllers/Modal.swift b/Signal/src/Loki/View Controllers/Modal.swift index f6e94b653..e69a28049 100644 --- a/Signal/src/Loki/View Controllers/Modal.swift +++ b/Signal/src/Loki/View Controllers/Modal.swift @@ -1,6 +1,6 @@ @objc(LKModal) -internal class Modal : UIViewController { +class Modal : UIViewController { private(set) var verticalCenteringConstraint: NSLayoutConstraint! // MARK: Settings diff --git a/Signal/src/Loki/View Controllers/OpenGroupSuggestionSheet.swift b/Signal/src/Loki/View Controllers/OpenGroupSuggestionSheet.swift new file mode 100644 index 000000000..09cce552d --- /dev/null +++ b/Signal/src/Loki/View Controllers/OpenGroupSuggestionSheet.swift @@ -0,0 +1,77 @@ + +final class OpenGroupSuggestionSheet : Sheet { + + override func populateContentView() { + // Set up image view + let imageView = UIImageView(image: #imageLiteral(resourceName: "ChatBubbles")) + // Set up title label + let titleLabel = UILabel() + titleLabel.textColor = Colors.text + titleLabel.font = .boldSystemFont(ofSize: Values.largeFontSize) + titleLabel.text = NSLocalizedString("No messages yet", comment: "") + titleLabel.numberOfLines = 0 + titleLabel.lineBreakMode = .byWordWrapping + titleLabel.textAlignment = .center + // Set up top explanation label + let topExplanationLabel = UILabel() + topExplanationLabel.textColor = Colors.text + topExplanationLabel.font = .systemFont(ofSize: Values.mediumFontSize) + topExplanationLabel.text = NSLocalizedString("Would you like to join the Session Public Chat?", comment: "") + topExplanationLabel.numberOfLines = 0 + topExplanationLabel.textAlignment = .center + topExplanationLabel.lineBreakMode = .byWordWrapping + // Set up join button + let joinButton = Button(style: .prominentOutline, size: .medium) + joinButton.set(.width, to: 240) + joinButton.setTitle(NSLocalizedString("Join Public Chat", comment: ""), for: UIControl.State.normal) + joinButton.addTarget(self, action: #selector(joinSessionPublicChat), for: UIControl.Event.touchUpInside) + // Set up dismiss button + let dismissButton = Button(style: .regular, size: .medium) + dismissButton.set(.width, to: 240) + dismissButton.setTitle(NSLocalizedString("No, thank you", comment: ""), for: UIControl.State.normal) + dismissButton.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside) + // Set up bottom explanation label + let bottomExplanationLabel = UILabel() + bottomExplanationLabel.textColor = Colors.text.withAlphaComponent(Values.unimportantElementOpacity) + bottomExplanationLabel.font = .systemFont(ofSize: Values.verySmallFontSize) + bottomExplanationLabel.text = NSLocalizedString("Open groups can be joined by anyone and do not provide full metadata protection", comment: "") + bottomExplanationLabel.numberOfLines = 0 + bottomExplanationLabel.textAlignment = .center + bottomExplanationLabel.lineBreakMode = .byWordWrapping + // Set up button stack view + let bottomStackView = UIStackView(arrangedSubviews: [ joinButton, dismissButton, bottomExplanationLabel ]) + bottomStackView.axis = .vertical + bottomStackView.spacing = Values.mediumSpacing + bottomStackView.alignment = .fill + // Set up main stack view + let stackView = UIStackView(arrangedSubviews: [ imageView, titleLabel, topExplanationLabel, bottomStackView ]) + stackView.axis = .vertical + stackView.spacing = Values.largeSpacing + stackView.alignment = .center + // Set up constraints + contentView.addSubview(stackView) + stackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing) + stackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing) + contentView.pin(.trailing, to: .trailing, of: stackView, withInset: Values.largeSpacing) + contentView.pin(.bottom, to: .bottom, of: stackView, withInset: Values.veryLargeSpacing + overshoot) + } + + @objc private func joinSessionPublicChat() { + // TODO: Duplicate of the code in JoinPublicChatVC + let channelID: UInt64 = 1 + let url = "https://chat.getsession.org" + let displayName = OWSProfileManager.shared().localProfileName() + // TODO: Profile picture & profile key + let _ = LokiPublicChatManager.shared.addChat(server: url, channel: channelID).done(on: .main) { _ in + let _ = LokiPublicChatAPI.getMessages(for: channelID, on: url) + let _ = LokiPublicChatAPI.setDisplayName(to: displayName, on: url) + let _ = LokiPublicChatAPI.join(channelID, on: url) + } + close() + } + + override func close() { + UserDefaults.standard.set(true, forKey: "hasSeenOpenGroupSuggestionSheet") + super.close() + } +} diff --git a/Signal/src/Loki/View Controllers/Sheet.swift b/Signal/src/Loki/View Controllers/Sheet.swift new file mode 100644 index 000000000..b7c96713d --- /dev/null +++ b/Signal/src/Loki/View Controllers/Sheet.swift @@ -0,0 +1,71 @@ + +class Sheet : UIViewController { + private(set) var bottomConstraint: NSLayoutConstraint! + + // MARK: Settings + let overshoot: CGFloat = 40 + override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } + + // MARK: Components + lazy var contentView: UIView = { + let result = UIView() + result.backgroundColor = Colors.modalBackground + result.layer.cornerRadius = 24 + result.layer.masksToBounds = false + result.layer.borderColor = Colors.modalBorder.cgColor + result.layer.borderWidth = Values.borderThickness + result.layer.shadowColor = UIColor.black.cgColor + result.layer.shadowRadius = 8 + result.layer.shadowOpacity = 0.64 + return result + }() + + // MARK: Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = UIColor(hex: 0x000000).withAlphaComponent(Values.modalBackgroundOpacity) + let swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(close)) + swipeGestureRecognizer.direction = .down + view.addGestureRecognizer(swipeGestureRecognizer) + setUpViewHierarchy() + } + + private func setUpViewHierarchy() { + view.addSubview(contentView) + contentView.pin(.leading, to: .leading, of: view, withInset: -Values.borderThickness) + contentView.pin(.trailing, to: .trailing, of: view, withInset: Values.borderThickness) + bottomConstraint = contentView.pin(.bottom, to: .bottom, of: view, withInset: overshoot) + populateContentView() + } + + /// To be overridden by subclasses. + func populateContentView() { + preconditionFailure("populateContentView() is abstract and must be overridden.") + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + // TODO: Animate +// bottomConstraint.constant = contentView.height() +// view.layoutIfNeeded() +// bottomConstraint.constant = overshoot +// UIView.animate(withDuration: 0.25) { +// self.view.layoutIfNeeded() +// } + } + + // MARK: Interaction + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + let touch = touches.first! + let location = touch.location(in: view) + if contentView.frame.contains(location) { + super.touchesBegan(touches, with: event) + } else { + close() + } + } + + @objc func close() { + dismiss(animated: true, completion: nil) + } +} diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 928ddf32d..f53bfc71c 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2804,3 +2804,7 @@ "The ability to add members to a closed group is coming soon." = "The ability to add members to a closed group is coming soon."; "A closed group cannot have more than 10 members" = "A closed group cannot have more than 10 members"; "Closed groups are end-to-end encrypted group chats for up to 10 members. They provide the same privacy protections as one-on-one sessions." = "Closed groups are end-to-end encrypted group chats for up to 10 members. They provide the same privacy protections as one-on-one sessions."; +"No messages yet" = "No messages yet"; +"Would you like to join the Session Public Chat?" = "Would you like to join the Session Public Chat?"; +"Join Public Chat" = "Join Public Chat"; +"No, thank you" = "No, thank you";