Merge pull request #130 from loki-project/ui

Various UI Improvements
pull/132/head
gmbnt 5 years ago committed by GitHub
commit e2b40028d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -573,6 +573,8 @@
B82B408E239DC00D00A248E7 /* DisplayNameVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B408D239DC00D00A248E7 /* DisplayNameVC.swift */; };
B82B4090239DD75000A248E7 /* RestoreVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B408F239DD75000A248E7 /* RestoreVC.swift */; };
B82B4094239DF15900A248E7 /* ConversationTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82B4093239DF15900A248E7 /* ConversationTitleView.swift */; };
B83F2B86240C7B8F000A54AB /* NewConversationButtonSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */; };
B83F2B88240CB75A000A54AB /* UIImage+Scaling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */; };
B846365B22B7418B00AF1514 /* Identicon+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */; };
B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B84664F4235022F30083A1CD /* MentionUtilities.swift */; };
B847570323D5698100759540 /* LokiPushNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B847570223D5698100759540 /* LokiPushNotificationManager.swift */; };
@ -587,7 +589,6 @@
B8544E3523D5201400299F14 /* UIView+Constraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */; };
B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; };
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; };
B8783E9C23EB8DDE00404FB8 /* GeneralUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8783E9B23EB8DDE00404FB8 /* GeneralUtilities.swift */; };
B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */; };
B885D5F4233491AB00EE0D8E /* DeviceLinkingModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */; };
B886B4A72398B23E00211ABE /* QRCodeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A62398B23E00211ABE /* QRCodeVC.swift */; };
@ -1416,6 +1417,8 @@
B82B408D239DC00D00A248E7 /* DisplayNameVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayNameVC.swift; sourceTree = "<group>"; };
B82B408F239DD75000A248E7 /* RestoreVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreVC.swift; sourceTree = "<group>"; };
B82B4093239DF15900A248E7 /* ConversationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationTitleView.swift; sourceTree = "<group>"; };
B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationButtonSet.swift; sourceTree = "<group>"; };
B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Scaling.swift"; sourceTree = "<group>"; };
B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identicon+ObjC.swift"; sourceTree = "<group>"; };
B84664F4235022F30083A1CD /* MentionUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionUtilities.swift; sourceTree = "<group>"; };
B847570023D568EB00759540 /* SignalServiceKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SignalServiceKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -1429,7 +1432,6 @@
B8544E3223D50E4900299F14 /* AppearanceUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceUtilities.swift; sourceTree = "<group>"; };
B86BD08323399ACF000F5AE3 /* Modal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modal.swift; sourceTree = "<group>"; };
B86BD08523399CEF000F5AE3 /* SeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedModal.swift; sourceTree = "<group>"; };
B8783E9B23EB8DDE00404FB8 /* GeneralUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralUtilities.swift; sourceTree = "<group>"; };
B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Interaction.swift"; sourceTree = "<group>"; };
B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLinkingModal.swift; sourceTree = "<group>"; };
B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraints.swift"; sourceTree = "<group>"; };
@ -2786,6 +2788,7 @@
B8162F0422892C5F00D46544 /* FriendRequestViewDelegate.swift */,
B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */,
B8B26C90234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift */,
B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */,
B8BB82B02390C37000BA5194 /* SearchBar.swift */,
B85357BE23A1AE0800AAF6CD /* SeedReminderView.swift */,
B85357C023A1B81900AAF6CD /* SeedReminderViewDelegate.swift */,
@ -2802,11 +2805,11 @@
children = (
B8544E3223D50E4900299F14 /* AppearanceUtilities.swift */,
B8544E3023D16CA500299F14 /* DeviceUtilities.swift */,
B8783E9B23EB8DDE00404FB8 /* GeneralUtilities.swift */,
B847570223D5698100759540 /* LokiPushNotificationManager.swift */,
B84664F4235022F30083A1CD /* MentionUtilities.swift */,
B886B4A82398BA1500211ABE /* QRCode.swift */,
B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */,
B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */,
);
path = Utilities;
sourceTree = "<group>";
@ -3846,6 +3849,7 @@
340FC8B8204DAC8D007AEB0F /* AddToGroupViewController.m in Sources */,
341F2C0F1F2B8AE700D07D6B /* DebugUIMisc.m in Sources */,
B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */,
B83F2B86240C7B8F000A54AB /* NewConversationButtonSet.swift in Sources */,
340FC8AF204DAC8D007AEB0F /* OWSLinkDeviceViewController.m in Sources */,
34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */,
454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */,
@ -3883,6 +3887,7 @@
B885D5F4233491AB00EE0D8E /* DeviceLinkingModal.swift in Sources */,
45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */,
451166C01FD86B98000739BA /* AccountManager.swift in Sources */,
B83F2B88240CB75A000A54AB /* UIImage+Scaling.swift in Sources */,
3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */,
4C2F454F214C00E1004871FF /* AvatarTableViewCell.swift in Sources */,
346E9D5421B040B700562252 /* RegistrationController.swift in Sources */,
@ -3900,7 +3905,6 @@
3403B95D20EA9527001A1F44 /* OWSContactShareButtonsView.m in Sources */,
34B0796D1FCF46B100E248C2 /* MainAppContext.m in Sources */,
34E3EF101EFC2684007F6822 /* DebugUIPage.m in Sources */,
B8783E9C23EB8DDE00404FB8 /* GeneralUtilities.swift in Sources */,
B80C6B572384A56D00FDBC8B /* DeviceLinksVC.swift in Sources */,
34A8B3512190A40E00218A25 /* MediaAlbumCellView.swift in Sources */,
34D1F0AE1F867BFC0066283D /* OWSMessageCell.m in Sources */,

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "Group.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "Message.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "Plus.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

@ -5,7 +5,7 @@
<key>BuildDetails</key>
<dict>
<key>CarthageVersion</key>
<string>0.33.0</string>
<string>0.34.0</string>
<key>OSXVersion</key>
<string>10.15.3</string>
<key>WebRTCCommit</key>

@ -884,7 +884,7 @@ static NSTimeInterval launchStartedAt;
return;
}
[SignalApp.sharedApp.homeViewController createPrivateChat];
[SignalApp.sharedApp.homeViewController createNewPrivateChat];
completionHandler(YES);
}];

@ -63,7 +63,7 @@ final class Button : UIButton {
layer.borderColor = borderColor.cgColor
layer.borderWidth = Values.borderThickness
let fontSize = (size == .small) ? Values.smallFontSize : Values.mediumFontSize
titleLabel!.font = Fonts.spaceMono(ofSize: fontSize)
titleLabel!.font = .boldSystemFont(ofSize: fontSize)
setTitleColor(textColor, for: UIControl.State.normal)
}
}

@ -42,7 +42,9 @@ final class ConversationCell : UITableViewCell {
private lazy var statusIndicatorView: UIImageView = {
let result = UIImageView()
result.contentMode = .center
result.contentMode = .scaleAspectFit
result.layer.cornerRadius = Values.conversationCellStatusIndicatorSize / 2
result.layer.masksToBounds = true
return result
}()
@ -167,6 +169,7 @@ final class ConversationCell : UITableViewCell {
typingIndicatorView.isHidden = true
typingIndicatorView.stopAnimation()
}
statusIndicatorView.backgroundColor = nil
let lastMessage = threadViewModel.lastMessageForInbox
if let lastMessage = lastMessage as? TSOutgoingMessage {
let image: UIImage
@ -174,7 +177,9 @@ final class ConversationCell : UITableViewCell {
switch status {
case .calculatingPoW, .uploading, .sending: image = #imageLiteral(resourceName: "CircleDotDotDot")
case .sent, .skipped, .delivered: image = #imageLiteral(resourceName: "CircleCheck")
case .read: image = #imageLiteral(resourceName: "FilledCircleCheck")
case .read:
statusIndicatorView.backgroundColor = .white
image = #imageLiteral(resourceName: "FilledCircleCheck")
case .failed: image = #imageLiteral(resourceName: "message_status_failed").asTintedImage(color: Colors.text)!
}
statusIndicatorView.image = image

@ -0,0 +1,310 @@
final class NewConversationButtonSet : UIView {
private var isUserDragging = false
private var horizontalButtonConstraints: [NewConversationButton:NSLayoutConstraint] = [:]
private var verticalButtonConstraints: [NewConversationButton:NSLayoutConstraint] = [:]
private var expandedButton: NewConversationButton?
var delegate: NewConversationButtonSetDelegate?
// MARK: Settings
private let spacing = Values.largeSpacing
private let iconSize = CGFloat(24)
private let maxDragDistance = CGFloat(56)
private let dragMargin = CGFloat(16)
// MARK: Components
private lazy var mainButton = NewConversationButton(isMainButton: true, icon: #imageLiteral(resourceName: "Plus").scaled(to: CGSize(width: iconSize, height: iconSize)))
private lazy var createNewPrivateChatButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Message").scaled(to: CGSize(width: iconSize, height: iconSize)))
private lazy var createNewClosedGroupButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Group").scaled(to: CGSize(width: iconSize, height: iconSize)))
private lazy var joinOpenGroupButton = NewConversationButton(isMainButton: false, icon: #imageLiteral(resourceName: "Globe").scaled(to: CGSize(width: iconSize, height: iconSize)))
// MARK: Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setUpViewHierarchy()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setUpViewHierarchy()
}
private func setUpViewHierarchy() {
let inset = (Values.newConversationButtonExpandedSize - Values.newConversationButtonCollapsedSize) / 2
addSubview(joinOpenGroupButton)
horizontalButtonConstraints[joinOpenGroupButton] = joinOpenGroupButton.pin(.left, to: .left, of: self, withInset: inset)
verticalButtonConstraints[joinOpenGroupButton] = joinOpenGroupButton.pin(.bottom, to: .bottom, of: self, withInset: -inset)
addSubview(createNewPrivateChatButton)
createNewPrivateChatButton.center(.horizontal, in: self)
verticalButtonConstraints[createNewPrivateChatButton] = createNewPrivateChatButton.pin(.top, to: .top, of: self, withInset: inset)
addSubview(createNewClosedGroupButton)
horizontalButtonConstraints[createNewClosedGroupButton] = createNewClosedGroupButton.pin(.right, to: .right, of: self, withInset: -inset)
verticalButtonConstraints[createNewClosedGroupButton] = createNewClosedGroupButton.pin(.bottom, to: .bottom, of: self, withInset: -inset)
addSubview(mainButton)
mainButton.center(.horizontal, in: self)
mainButton.pin(.bottom, to: .bottom, of: self)
let width = 3 * Values.newConversationButtonExpandedSize + 2 * spacing
set(.width, to: width)
let height = 2 * Values.newConversationButtonExpandedSize + spacing
set(.height, to: height)
collapse(withAnimation: false)
isUserInteractionEnabled = true
let mainButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleMainButtonTapped))
mainButton.addGestureRecognizer(mainButtonTapGestureRecognizer)
let joinOpenGroupButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleJoinOpenGroupButtonTapped))
joinOpenGroupButton.addGestureRecognizer(joinOpenGroupButtonTapGestureRecognizer)
let createNewPrivateChatButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleCreateNewPrivateChatButtonTapped))
createNewPrivateChatButton.addGestureRecognizer(createNewPrivateChatButtonTapGestureRecognizer)
let createNewClosedGroupButtonTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleCreateNewClosedGroupButtonTapped))
createNewClosedGroupButton.addGestureRecognizer(createNewClosedGroupButtonTapGestureRecognizer)
}
// MARK: Interaction
@objc private func handleMainButtonTapped() { expand(isUserDragging: false) }
@objc private func handleJoinOpenGroupButtonTapped() { delegate?.joinOpenGroup() }
@objc private func handleCreateNewPrivateChatButtonTapped() { delegate?.createNewPrivateChat() }
@objc private func handleCreateNewClosedGroupButtonTapped() { delegate?.createNewClosedGroup() }
private func expand(isUserDragging: Bool) {
let buttons = [ joinOpenGroupButton, createNewPrivateChatButton, createNewClosedGroupButton ]
UIView.animate(withDuration: 0.25, animations: {
buttons.forEach { $0.alpha = 1 }
let inset = (Values.newConversationButtonExpandedSize - Values.newConversationButtonCollapsedSize) / 2
let size = Values.newConversationButtonCollapsedSize
self.joinOpenGroupButton.frame = CGRect(origin: CGPoint(x: inset, y: self.height() - size - inset), size: CGSize(width: size, height: size))
self.createNewPrivateChatButton.frame = CGRect(center: CGPoint(x: self.bounds.center.x, y: inset + size / 2), size: CGSize(width: size, height: size))
self.createNewClosedGroupButton.frame = CGRect(origin: CGPoint(x: self.width() - size - inset, y: self.height() - size - inset), size: CGSize(width: size, height: size))
}, completion: { _ in
self.isUserDragging = isUserDragging
})
}
private func collapse(withAnimation isAnimated: Bool) {
isUserDragging = false
let buttons = [ joinOpenGroupButton, createNewPrivateChatButton, createNewClosedGroupButton ]
UIView.animate(withDuration: isAnimated ? 0.25 : 0) {
buttons.forEach { button in
button.alpha = 0
let size = Values.newConversationButtonCollapsedSize
button.frame = CGRect(center: self.mainButton.center, size: CGSize(width: size, height: size))
}
}
}
private func reset() {
let mainButtonLocationInSelfCoordinates = CGPoint(x: width() / 2, y: height() - Values.newConversationButtonExpandedSize / 2)
let mainButtonSize = mainButton.frame.size
UIView.animate(withDuration: 0.25) {
self.mainButton.frame = CGRect(center: mainButtonLocationInSelfCoordinates, size: mainButtonSize)
self.mainButton.alpha = 1
}
if let expandedButton = expandedButton { collapse(expandedButton) }
expandedButton = nil
Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { _ in
self.collapse(withAnimation: true)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first, mainButton.contains(touch), !isUserDragging else { return }
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
expand(isUserDragging: true)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first, isUserDragging else { return }
let mainButtonSize = mainButton.frame.size
let mainButtonLocationInSelfCoordinates = CGPoint(x: width() / 2, y: height() - Values.newConversationButtonExpandedSize / 2)
let touchLocationInSelfCoordinates = touch.location(in: self)
mainButton.frame = CGRect(center: touchLocationInSelfCoordinates, size: mainButtonSize)
mainButton.alpha = 1 - (touchLocationInSelfCoordinates.distance(to: mainButtonLocationInSelfCoordinates) / maxDragDistance)
let buttons = [ joinOpenGroupButton, createNewPrivateChatButton, createNewClosedGroupButton ]
let buttonToExpand = buttons.first { button in
var hasUserDraggedBeyondButton = false
if button == joinOpenGroupButton && touch.isLeft(of: joinOpenGroupButton, with: dragMargin) { hasUserDraggedBeyondButton = true }
if button == createNewPrivateChatButton && touch.isAbove(createNewPrivateChatButton, with: dragMargin) { hasUserDraggedBeyondButton = true }
if button == createNewClosedGroupButton && touch.isRight(of: createNewClosedGroupButton, with: dragMargin) { hasUserDraggedBeyondButton = true }
return button.contains(touch) || hasUserDraggedBeyondButton
}
if let buttonToExpand = buttonToExpand {
guard buttonToExpand != expandedButton else { return }
if let expandedButton = expandedButton { collapse(expandedButton) }
expand(buttonToExpand)
expandedButton = buttonToExpand
} else {
if let expandedButton = expandedButton { collapse(expandedButton) }
expandedButton = nil
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first, isUserDragging else { return }
if joinOpenGroupButton.contains(touch) || touch.isLeft(of: joinOpenGroupButton, with: dragMargin) { delegate?.joinOpenGroup() }
else if createNewPrivateChatButton.contains(touch) || touch.isAbove(createNewPrivateChatButton, with: dragMargin) { delegate?.createNewPrivateChat() }
else if createNewClosedGroupButton.contains(touch) || touch.isRight(of: createNewClosedGroupButton, with: dragMargin) { delegate?.createNewClosedGroup() }
reset()
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
guard isUserDragging else { return }
reset()
}
private func expand(_ button: NewConversationButton) {
if let horizontalConstraint = horizontalButtonConstraints[button] { horizontalConstraint.constant = 0 }
if let verticalConstraint = verticalButtonConstraints[button] { verticalConstraint.constant = 0 }
let size = Values.newConversationButtonExpandedSize
let frame = CGRect(center: button.center, size: CGSize(width: size, height: size))
button.widthConstraint.constant = size
button.heightConstraint.constant = size
UIView.animate(withDuration: 0.25) {
self.layoutIfNeeded()
button.frame = frame
button.layer.cornerRadius = size / 2
button.addGlow(ofSize: size)
button.backgroundColor = Colors.accent
}
}
private func collapse(_ button: NewConversationButton) {
let inset = (Values.newConversationButtonExpandedSize - Values.newConversationButtonCollapsedSize) / 2
if joinOpenGroupButton == expandedButton {
horizontalButtonConstraints[joinOpenGroupButton]!.constant = inset
verticalButtonConstraints[joinOpenGroupButton]!.constant = -inset
} else if createNewPrivateChatButton == expandedButton {
verticalButtonConstraints[createNewPrivateChatButton]!.constant = inset
} else if createNewClosedGroupButton == expandedButton {
horizontalButtonConstraints[createNewClosedGroupButton]!.constant = -inset
verticalButtonConstraints[createNewClosedGroupButton]!.constant = -inset
}
let size = Values.newConversationButtonCollapsedSize
let frame = CGRect(center: button.center, size: CGSize(width: size, height: size))
button.widthConstraint.constant = size
button.heightConstraint.constant = size
UIView.animate(withDuration: 0.25) {
self.layoutIfNeeded()
button.frame = frame
button.layer.cornerRadius = size / 2
button.removeGlow()
button.backgroundColor = Colors.newConversationButtonCollapsedBackground
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !bounds.contains(point), isUserDragging { collapse(withAnimation: true) }
return super.hitTest(point, with: event)
}
}
// MARK: Delegate
protocol NewConversationButtonSetDelegate {
func joinOpenGroup()
func createNewPrivateChat()
func createNewClosedGroup()
}
// MARK: Button
private final class NewConversationButton : UIImageView {
private let isMainButton: Bool
private let icon: UIImage
var widthConstraint: NSLayoutConstraint!
var heightConstraint: NSLayoutConstraint!
// Initialization
init(isMainButton: Bool, icon: UIImage) {
self.isMainButton = isMainButton
self.icon = icon
super.init(frame: CGRect.zero)
setUpViewHierarchy()
}
override init(frame: CGRect) {
preconditionFailure("Use init(isMainButton:) instead.")
}
required init?(coder: NSCoder) {
preconditionFailure("Use init(isMainButton:) instead.")
}
private func setUpViewHierarchy() {
backgroundColor = isMainButton ? Colors.accent : Colors.newConversationButtonCollapsedBackground
let size = isMainButton ? Values.newConversationButtonExpandedSize : Values.newConversationButtonCollapsedSize
layer.cornerRadius = size / 2
if isMainButton { addGlow(ofSize: size) }
layer.masksToBounds = false
image = icon
contentMode = .center
widthConstraint = set(.width, to: size)
heightConstraint = set(.height, to: size)
}
// General
func addGlow(ofSize size: CGFloat) {
layer.shadowPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: size, height: size))).cgPath
layer.shadowColor = Colors.newConversationButtonShadow.cgColor
layer.shadowOffset = CGSize(width: 0, height: 0.8)
layer.shadowOpacity = 1
layer.shadowRadius = 6
}
func removeGlow() {
layer.shadowPath = nil
layer.shadowColor = nil
layer.shadowOffset = CGSize.zero
layer.shadowOpacity = 0
layer.shadowRadius = 0
}
}
// MARK: Convenience
private extension UIView {
func contains(_ touch: UITouch) -> Bool {
return bounds.contains(touch.location(in: self))
}
}
private extension UITouch {
func isLeft(of view: UIView, with margin: CGFloat = 0) -> Bool {
return isContainedVertically(in: view, with: margin) && location(in: view).x < view.bounds.minX
}
func isAbove(_ view: UIView, with margin: CGFloat = 0) -> Bool {
return isContainedHorizontally(in: view, with: margin) && location(in: view).y < view.bounds.minY
}
func isRight(of view: UIView, with margin: CGFloat = 0) -> Bool {
return isContainedVertically(in: view, with: margin) && location(in: view).x > view.bounds.maxX
}
func isBelow(_ view: UIView, with margin: CGFloat = 0) -> Bool {
return isContainedHorizontally(in: view, with: margin) && location(in: view).y > view.bounds.maxY
}
private func isContainedHorizontally(in view: UIView, with margin: CGFloat = 0) -> Bool {
return ((view.bounds.minX - margin)...(view.bounds.maxX + margin)) ~= location(in: view).x
}
private func isContainedVertically(in view: UIView, with margin: CGFloat = 0) -> Bool {
return ((view.bounds.minY - margin)...(view.bounds.maxY + margin)) ~= location(in: view).y
}
}
private extension CGPoint {
func distance(to otherPoint: CGPoint) -> CGFloat {
return sqrt(pow(self.x - otherPoint.x, 2) + pow(self.y - otherPoint.y, 2))
}
}
private extension CGRect {
init(center: CGPoint, size: CGSize) {
let originX = center.x - size.width / 2
let originY = center.y - size.height / 2
let origin = CGPoint(x: originX, y: originY)
self.init(origin: origin, size: size)
}
}

@ -35,4 +35,5 @@ final class Colors : NSObject {
@objc static let composeViewTextFieldBackground = UIColor(hex: 0x141414)
@objc static let receivedMessageBackground = UIColor(hex: 0x222325)
@objc static let sentMessageBackground = UIColor(hex: 0x3F4146)
@objc static let newConversationButtonCollapsedBackground = UIColor(hex: 0x1F1F1F)
}

@ -26,5 +26,6 @@ final class Gradient : NSObject {
@objc(LKGradients)
final class Gradients : NSObject {
@objc static let defaultLokiBackground = Gradient(start: UIColor(hex: 0x171717), end: UIColor(hex:0x121212))
@objc static let defaultLokiBackground = Gradient(start: UIColor(hex: 0x171717), end: UIColor(hex: 0x121212))
@objc static let transparentToBlack75 = Gradient(start: UIColor(red: 0, green: 0, blue: 0, alpha: 0), end: UIColor(red: 0, green: 0, blue: 0, alpha: 0.75))
}

@ -30,7 +30,8 @@ final class Values : NSObject {
@objc static let borderThickness = CGFloat(1)
@objc static let conversationCellStatusIndicatorSize = CGFloat(14)
@objc static let searchBarHeight = CGFloat(36)
@objc static let newConversationButtonSize = CGFloat(45)
@objc static let newConversationButtonCollapsedSize = CGFloat(48)
@objc static let newConversationButtonExpandedSize = CGFloat(60)
@objc static let textFieldHeight = isSmallScreen ? CGFloat(48) : CGFloat(80)
@objc static let textFieldCornerRadius = CGFloat(8)
@objc static let separatorLabelHeight = CGFloat(24)

@ -1,29 +0,0 @@
@objc(LKGeneralUtilities)
final class GeneralUtilities : NSObject {
private override init() { }
@objc static func getSessionPublicChatNotice() -> String {
return """
Welcome to the Session public chat! In order for this forum to be a fun environment, full of robust and constructive discussion and inclusive of everyone, please read and follow the rules below.
1. Please Keep Talk Relevant to Topic and Add Value to the Discussion.
(No Referral Links, Spamming, Off Topic Discussion)
2. You don't have to love everyone, but be civil.
(No Baiting, Excessively Partisan Arguments, Threats, and so on. Use common sense.)
3. Do not be a shill.
Comparison and criticism is reasonable, but blatant shilling of anything you work for, work on, or own is not.
4. Don't post explicit content - be it excessively offensive language, sexual, or violent. Any form of bigotry including racism, sexism, transphobia, homophobia, ableism, fatphobia, classism will NOT be tolerated.
If you break these rules, youll be warned by an admin. If your behaviour doesnt improve, you will be removed from the public chat. Admins reserve the right to remove anyone violating these rules.
We want to keep this group a pleasant and supportive space for everyone.
If you experience any anti-social behaviour or have an issue with these rules, please contact an admin.
"""
}
}

@ -0,0 +1,17 @@
extension UIImage {
func scaled(to size: CGSize) -> UIImage {
var rect = CGRect.zero
let aspectRatio = min(size.width / self.size.width, size.height / self.size.height)
rect.size.width = self.size.width * aspectRatio
rect.size.height = self.size.height * aspectRatio
rect.origin.x = (size.width - rect.size.width) / 2
rect.origin.y = (size.height - rect.size.height) / 2
UIGraphicsBeginImageContextWithOptions(size, false, 0)
draw(in: rect)
let result = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return result
}
}

@ -24,7 +24,7 @@ final class DeviceLinksVC : UIViewController, UITableViewDataSource, UITableView
explanationLabel.lineBreakMode = .byWordWrapping
explanationLabel.textAlignment = .center
explanationLabel.text = NSLocalizedString("You haven't linked any devices yet", comment: "")
let linkNewDeviceButton = Button(style: .prominentOutline, size: .medium)
let linkNewDeviceButton = Button(style: .prominentOutline, size: .large)
linkNewDeviceButton.setTitle(NSLocalizedString("Link a Device", comment: ""), for: UIControl.State.normal)
linkNewDeviceButton.addTarget(self, action: #selector(linkNewDevice), for: UIControl.Event.touchUpInside)
linkNewDeviceButton.set(.width, to: 160)

@ -1,5 +1,5 @@
final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate, UIViewControllerPreviewingDelegate, SeedReminderViewDelegate {
final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate, UIViewControllerPreviewingDelegate, NewConversationButtonSetDelegate, SeedReminderViewDelegate {
private var threadViewModelCache: [String:ThreadViewModel] = [:]
private var isObservingDatabase = true
private var isViewVisible = false { didSet { updateIsObservingDatabase() } }
@ -45,23 +45,17 @@ final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegat
return result
}()
private lazy var newConversationButton: UIButton = {
let result = UIButton()
result.setTitle("+", for: UIControl.State.normal)
result.titleLabel!.font = .systemFont(ofSize: 35)
result.setTitleColor(UIColor(hex: 0x121212), for: UIControl.State.normal)
result.titleEdgeInsets = UIEdgeInsets(top: 0, left: 1, bottom: 4, right: 0) // Slight adjustment to make the plus exactly centered
result.backgroundColor = Colors.accent
let size = Values.newConversationButtonSize
result.layer.cornerRadius = size / 2
result.layer.shadowPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: size, height: size))).cgPath
result.layer.shadowColor = Colors.newConversationButtonShadow.cgColor
result.layer.shadowOffset = CGSize(width: 0, height: 0.8)
result.layer.shadowOpacity = 1
result.layer.shadowRadius = 6
result.layer.masksToBounds = false
result.set(.width, to: size)
result.set(.height, to: size)
private lazy var newConversationButtonSet: NewConversationButtonSet = {
let result = NewConversationButtonSet()
result.delegate = self
return result
}()
private lazy var fadeView: UIView = {
let result = UIView()
let gradient = Gradients.transparentToBlack75
result.setGradient(gradient)
result.isUserInteractionEnabled = false
return result
}()
@ -109,15 +103,16 @@ final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegat
}
tableView.pin(.trailing, to: .trailing, of: view)
tableView.pin(.bottom, to: .bottom, of: view)
view.addSubview(fadeView)
fadeView.pin(to: view)
// Set up search bar
// tableView.tableHeaderView = searchBar
// searchBar.sizeToFit()
// tableView.contentOffset = CGPoint(x: 0, y: searchBar.frame.height)
// Set up new conversation button
newConversationButton.addTarget(self, action: #selector(createPrivateChat), for: UIControl.Event.touchUpInside)
view.addSubview(newConversationButton)
newConversationButton.center(.horizontal, in: view)
newConversationButton.pin(.bottom, to: .bottom, of: view, withInset: -Values.newConversationButtonBottomOffset) // Negative due to how the constraint is set up
// Set up new conversation button set
view.addSubview(newConversationButtonSet)
newConversationButtonSet.center(.horizontal, in: view)
newConversationButtonSet.pin(.bottom, to: .bottom, of: view, withInset: -Values.newConversationButtonBottomOffset) // Negative due to how the constraint is set up
// Set up previewing
if (traitCollection.forceTouchCapability == .available) {
registerForPreviewing(with: self, sourceView: tableView)
@ -261,17 +256,6 @@ final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegat
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings))
profilePictureView.addGestureRecognizer(tapGestureRecognizer)
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: profilePictureView)
let newClosedGroupButton = UIButton(type: .custom)
newClosedGroupButton.setImage(#imageLiteral(resourceName: "btnGroup--white"), for: UIControl.State.normal)
newClosedGroupButton.addTarget(self, action: #selector(createClosedGroup), for: UIControl.Event.touchUpInside)
newClosedGroupButton.tintColor = Colors.text
let joinPublicChatButton = UIButton(type: .custom)
joinPublicChatButton.setImage(#imageLiteral(resourceName: "Globe"), for: UIControl.State.normal)
joinPublicChatButton.addTarget(self, action: #selector(joinPublicChat), for: UIControl.Event.touchUpInside)
joinPublicChatButton.tintColor = Colors.text
let buttonStackView = UIStackView(arrangedSubviews: [ newClosedGroupButton, joinPublicChatButton ])
buttonStackView.axis = .horizontal
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: buttonStackView)
}
// MARK: Interaction
@ -371,21 +355,21 @@ final class HomeVC : UIViewController, UITableViewDataSource, UITableViewDelegat
present(navigationController, animated: true, completion: nil)
}
@objc private func joinPublicChat() {
@objc func joinOpenGroup() {
let joinPublicChatVC = JoinPublicChatVC()
let navigationController = OWSNavigationController(rootViewController: joinPublicChatVC)
present(navigationController, animated: true, completion: nil)
}
@objc private func createClosedGroup() {
let newClosedGroupVC = NewClosedGroupVC()
let navigationController = OWSNavigationController(rootViewController: newClosedGroupVC)
@objc func createNewPrivateChat() {
let newPrivateChatVC = NewPrivateChatVC()
let navigationController = OWSNavigationController(rootViewController: newPrivateChatVC)
present(navigationController, animated: true, completion: nil)
}
@objc func createPrivateChat() {
let newPrivateChatVC = NewPrivateChatVC()
let navigationController = OWSNavigationController(rootViewController: newPrivateChatVC)
@objc func createNewClosedGroup() {
let newClosedGroupVC = NewClosedGroupVC()
let navigationController = OWSNavigationController(rootViewController: newClosedGroupVC)
present(navigationController, animated: true, completion: nil)
}

@ -65,7 +65,8 @@ final class NewClosedGroupVC : UIViewController, UITableViewDataSource, UITableV
let titleLabel = UILabel()
titleLabel.text = NSLocalizedString("New Closed Group", comment: "")
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
let titleLabelFontSize = isSmallScreen ? Values.largeFontSize : Values.veryLargeFontSize
titleLabel.font = .boldSystemFont(ofSize: titleLabelFontSize)
navigationItem.titleView = titleLabel
// Set up content
if !contacts.isEmpty {
@ -104,9 +105,9 @@ final class NewClosedGroupVC : UIViewController, UITableViewDataSource, UITableV
explanationLabel.lineBreakMode = .byWordWrapping
explanationLabel.textAlignment = .center
explanationLabel.text = NSLocalizedString("You don't have any contacts yet", comment: "")
let createNewPrivateChatButton = Button(style: .prominentOutline, size: .medium)
let createNewPrivateChatButton = Button(style: .prominentOutline, size: .large)
createNewPrivateChatButton.setTitle(NSLocalizedString("Start a Session", comment: ""), for: UIControl.State.normal)
createNewPrivateChatButton.addTarget(self, action: #selector(createPrivateChat), for: UIControl.Event.touchUpInside)
createNewPrivateChatButton.addTarget(self, action: #selector(createNewPrivateChat), for: UIControl.Event.touchUpInside)
createNewPrivateChatButton.set(.width, to: 160)
let stackView = UIStackView(arrangedSubviews: [ explanationLabel, createNewPrivateChatButton ])
stackView.axis = .vertical
@ -200,9 +201,9 @@ final class NewClosedGroupVC : UIViewController, UITableViewDataSource, UITableV
}
}
@objc private func createPrivateChat() {
@objc private func createNewPrivateChat() {
presentingViewController?.dismiss(animated: true, completion: nil)
SignalApp.shared().homeViewController!.createPrivateChat()
SignalApp.shared().homeViewController!.createNewPrivateChat()
}
}

@ -71,7 +71,8 @@ final class SeedVC : UIViewController {
let navigationBarTitleLabel = UILabel()
navigationBarTitleLabel.text = NSLocalizedString("Your Recovery Phrase", comment: "")
navigationBarTitleLabel.textColor = Colors.text
navigationBarTitleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
let titleLabelFontSize = isSmallScreen ? Values.largeFontSize : Values.veryLargeFontSize
navigationBarTitleLabel.font = .boldSystemFont(ofSize: titleLabelFontSize)
navigationItem.titleView = navigationBarTitleLabel
// Set up navigation bar buttons
let closeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "X"), style: .plain, target: self, action: #selector(close))
@ -145,7 +146,7 @@ final class SeedVC : UIViewController {
let mainStackView = UIStackView(arrangedSubviews: [ topSpacer, topStackViewContainer, bottomSpacer, copyButtonContainer ])
mainStackView.axis = .vertical
mainStackView.alignment = .fill
mainStackView.layoutMargins = UIEdgeInsets(top: 0, leading: 0, bottom: isSmallScreen ? Values.smallSpacing : Values.mediumSpacing, trailing: 0)
mainStackView.layoutMargins = UIEdgeInsets(top: 0, leading: 0, bottom: Values.mediumSpacing, trailing: 0)
mainStackView.isLayoutMarginsRelativeArrangement = true
view.addSubview(mainStackView)
mainStackView.pin(.leading, to: .leading, of: view)

@ -1440,14 +1440,6 @@ typedef enum : NSUInteger {
[self updateInputToolbarLayout];
[self ensureScrollDownButton];
NSUserDefaults *userDefaults = NSUserDefaults.standardUserDefaults;
if ([@"Session Public Chat" isEqual:self.thread.name] && ![userDefaults boolForKey:@"hasSeenSessionPublicChatNotice"]) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"The Rules" message:[LKGeneralUtilities getSessionPublicChatNotice] preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
[userDefaults setBool:YES forKey:@"hasSeenSessionPublicChatNotice"];
}
}
// `viewWillDisappear` is called whenever the view *starts* to disappear,

Loading…
Cancel
Save