mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Prettify onion request UI
This commit is contained in:
parent
302d7ad09a
commit
112ff20c73
|
@ -623,6 +623,8 @@
|
||||||
B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; };
|
B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; };
|
||||||
B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; };
|
B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; };
|
||||||
BFF3FB9730634F37D25903F4 /* Pods_Signal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D17BB5C25D615AB49813100C /* Pods_Signal.framework */; };
|
BFF3FB9730634F37D25903F4 /* Pods_Signal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D17BB5C25D615AB49813100C /* Pods_Signal.framework */; };
|
||||||
|
C31A6C5A247F214E001123EF /* UIView+Glow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31A6C59247F214E001123EF /* UIView+Glow.swift */; };
|
||||||
|
C31A6C5C247F2CF3001123EF /* CGRect+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31A6C5B247F2CF3001123EF /* CGRect+Utilities.swift */; };
|
||||||
C34C8F7423A7830B00D82669 /* SpaceMono-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C34C8F7323A7830A00D82669 /* SpaceMono-Bold.ttf */; };
|
C34C8F7423A7830B00D82669 /* SpaceMono-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C34C8F7323A7830A00D82669 /* SpaceMono-Bold.ttf */; };
|
||||||
C353F8F7244808E90011121A /* PNModeSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C353F8F6244808E90011121A /* PNModeSheet.swift */; };
|
C353F8F7244808E90011121A /* PNModeSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C353F8F6244808E90011121A /* PNModeSheet.swift */; };
|
||||||
C353F8F9244809150011121A /* PNOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C353F8F8244809150011121A /* PNOptionView.swift */; };
|
C353F8F9244809150011121A /* PNOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C353F8F8244809150011121A /* PNOptionView.swift */; };
|
||||||
|
@ -1499,6 +1501,8 @@
|
||||||
B97940251832BD2400BD66CB /* UIUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIUtil.h; sourceTree = "<group>"; };
|
B97940251832BD2400BD66CB /* UIUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIUtil.h; sourceTree = "<group>"; };
|
||||||
B97940261832BD2400BD66CB /* UIUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIUtil.m; sourceTree = "<group>"; };
|
B97940261832BD2400BD66CB /* UIUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIUtil.m; sourceTree = "<group>"; };
|
||||||
B9EB5ABC1884C002007CBB57 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; };
|
B9EB5ABC1884C002007CBB57 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; };
|
||||||
|
C31A6C59247F214E001123EF /* UIView+Glow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Glow.swift"; sourceTree = "<group>"; };
|
||||||
|
C31A6C5B247F2CF3001123EF /* CGRect+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGRect+Utilities.swift"; sourceTree = "<group>"; };
|
||||||
C34C8F7323A7830A00D82669 /* SpaceMono-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpaceMono-Bold.ttf"; sourceTree = "<group>"; };
|
C34C8F7323A7830A00D82669 /* SpaceMono-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpaceMono-Bold.ttf"; sourceTree = "<group>"; };
|
||||||
C353F8F6244808E90011121A /* PNModeSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNModeSheet.swift; sourceTree = "<group>"; };
|
C353F8F6244808E90011121A /* PNModeSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNModeSheet.swift; sourceTree = "<group>"; };
|
||||||
C353F8F8244809150011121A /* PNOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNOptionView.swift; sourceTree = "<group>"; };
|
C353F8F8244809150011121A /* PNOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNOptionView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -2896,7 +2900,9 @@
|
||||||
B886B4A82398BA1500211ABE /* QRCode.swift */,
|
B886B4A82398BA1500211ABE /* QRCode.swift */,
|
||||||
B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */,
|
B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */,
|
||||||
B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */,
|
B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */,
|
||||||
|
C31A6C59247F214E001123EF /* UIView+Glow.swift */,
|
||||||
C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */,
|
C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */,
|
||||||
|
C31A6C5B247F2CF3001123EF /* CGRect+Utilities.swift */,
|
||||||
);
|
);
|
||||||
path = Utilities;
|
path = Utilities;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -4203,6 +4209,7 @@
|
||||||
34D1F0841F8678AA0066283D /* ConversationInputToolbar.m in Sources */,
|
34D1F0841F8678AA0066283D /* ConversationInputToolbar.m in Sources */,
|
||||||
457F671B20746193000EABCD /* QuotedReplyPreview.swift in Sources */,
|
457F671B20746193000EABCD /* QuotedReplyPreview.swift in Sources */,
|
||||||
34A6C28021E503E700B5B12E /* OWSImagePickerController.swift in Sources */,
|
34A6C28021E503E700B5B12E /* OWSImagePickerController.swift in Sources */,
|
||||||
|
C31A6C5C247F2CF3001123EF /* CGRect+Utilities.swift in Sources */,
|
||||||
4C21D5D6223A9DC500EF8A77 /* UIAlerts+iOS9.m in Sources */,
|
4C21D5D6223A9DC500EF8A77 /* UIAlerts+iOS9.m in Sources */,
|
||||||
34DBF004206BD5A500025978 /* OWSBubbleView.m in Sources */,
|
34DBF004206BD5A500025978 /* OWSBubbleView.m in Sources */,
|
||||||
3496957021A301A100DCFE74 /* OWSBackupIO.m in Sources */,
|
3496957021A301A100DCFE74 /* OWSBackupIO.m in Sources */,
|
||||||
|
@ -4217,6 +4224,7 @@
|
||||||
34BECE2B1F74C12700D7438D /* DebugUIStress.m in Sources */,
|
34BECE2B1F74C12700D7438D /* DebugUIStress.m in Sources */,
|
||||||
340FC8B9204DAC8D007AEB0F /* UpdateGroupViewController.m in Sources */,
|
340FC8B9204DAC8D007AEB0F /* UpdateGroupViewController.m in Sources */,
|
||||||
B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */,
|
B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */,
|
||||||
|
C31A6C5A247F214E001123EF /* UIView+Glow.swift in Sources */,
|
||||||
3448E1662215B313004B052E /* OnboardingCaptchaViewController.swift in Sources */,
|
3448E1662215B313004B052E /* OnboardingCaptchaViewController.swift in Sources */,
|
||||||
4574A5D61DD6704700C6B692 /* CallService.swift in Sources */,
|
4574A5D61DD6704700C6B692 /* CallService.swift in Sources */,
|
||||||
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */,
|
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */,
|
||||||
|
|
|
@ -158,7 +158,9 @@ final class NewConversationButtonSet : UIView {
|
||||||
self.layoutIfNeeded()
|
self.layoutIfNeeded()
|
||||||
button.frame = frame
|
button.frame = frame
|
||||||
button.layer.cornerRadius = size / 2
|
button.layer.cornerRadius = size / 2
|
||||||
button.setGlow(to: size, with: Colors.newConversationButtonShadow, animated: true)
|
let glowColor = Colors.newConversationButtonShadow
|
||||||
|
let glowConfiguration = UIView.CircularGlowConfiguration(size: size, color: glowColor, isAnimated: true, radius: isLightMode ? 4 : 6)
|
||||||
|
button.setCircularGlow(with: glowConfiguration)
|
||||||
button.backgroundColor = Colors.accent
|
button.backgroundColor = Colors.accent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,7 +185,8 @@ final class NewConversationButtonSet : UIView {
|
||||||
button.frame = frame
|
button.frame = frame
|
||||||
button.layer.cornerRadius = size / 2
|
button.layer.cornerRadius = size / 2
|
||||||
let glowColor = isLightMode ? UIColor.black.withAlphaComponent(0.4) : UIColor.black
|
let glowColor = isLightMode ? UIColor.black.withAlphaComponent(0.4) : UIColor.black
|
||||||
button.setGlow(to: size, with: glowColor, animated: true)
|
let glowConfiguration = UIView.CircularGlowConfiguration(size: size, color: glowColor, isAnimated: true, radius: isLightMode ? 4 : 6)
|
||||||
|
button.setCircularGlow(with: glowConfiguration)
|
||||||
button.backgroundColor = Colors.newConversationButtonCollapsedBackground
|
button.backgroundColor = Colors.newConversationButtonCollapsedBackground
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,7 +212,6 @@ private final class NewConversationButton : UIImageView {
|
||||||
var widthConstraint: NSLayoutConstraint!
|
var widthConstraint: NSLayoutConstraint!
|
||||||
var heightConstraint: NSLayoutConstraint!
|
var heightConstraint: NSLayoutConstraint!
|
||||||
|
|
||||||
// Initialization
|
|
||||||
init(isMainButton: Bool, icon: UIImage) {
|
init(isMainButton: Bool, icon: UIImage) {
|
||||||
self.isMainButton = isMainButton
|
self.isMainButton = isMainButton
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
|
@ -230,7 +232,8 @@ private final class NewConversationButton : UIImageView {
|
||||||
let size = Values.newConversationButtonCollapsedSize
|
let size = Values.newConversationButtonCollapsedSize
|
||||||
layer.cornerRadius = size / 2
|
layer.cornerRadius = size / 2
|
||||||
let glowColor = isMainButton ? Colors.newConversationButtonShadow : (isLightMode ? UIColor.black.withAlphaComponent(0.4) : UIColor.black)
|
let glowColor = isMainButton ? Colors.newConversationButtonShadow : (isLightMode ? UIColor.black.withAlphaComponent(0.4) : UIColor.black)
|
||||||
setGlow(to: size, with: glowColor, animated: false)
|
let glowConfiguration = UIView.CircularGlowConfiguration(size: size, color: glowColor, isAnimated: false, radius: isLightMode ? 4 : 6)
|
||||||
|
setCircularGlow(with: glowConfiguration)
|
||||||
layer.masksToBounds = false
|
layer.masksToBounds = false
|
||||||
let iconColor = (isMainButton && isLightMode) ? UIColor.white : Colors.text
|
let iconColor = (isMainButton && isLightMode) ? UIColor.white : Colors.text
|
||||||
image = icon.asTintedImage(color: iconColor)!
|
image = icon.asTintedImage(color: iconColor)!
|
||||||
|
@ -238,31 +241,6 @@ private final class NewConversationButton : UIImageView {
|
||||||
widthConstraint = set(.width, to: size)
|
widthConstraint = set(.width, to: size)
|
||||||
heightConstraint = set(.height, to: size)
|
heightConstraint = set(.height, to: size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// General
|
|
||||||
func setGlow(to size: CGFloat, with color: UIColor, animated isAnimated: Bool) {
|
|
||||||
let newPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: size, height: size))).cgPath
|
|
||||||
if isAnimated {
|
|
||||||
let pathAnimation = CABasicAnimation(keyPath: "shadowPath")
|
|
||||||
pathAnimation.fromValue = layer.shadowPath
|
|
||||||
pathAnimation.toValue = newPath
|
|
||||||
pathAnimation.duration = 0.25
|
|
||||||
layer.add(pathAnimation, forKey: pathAnimation.keyPath)
|
|
||||||
}
|
|
||||||
layer.shadowPath = newPath
|
|
||||||
let newColor = color.cgColor
|
|
||||||
if isAnimated {
|
|
||||||
let colorAnimation = CABasicAnimation(keyPath: "shadowColor")
|
|
||||||
colorAnimation.fromValue = layer.shadowColor
|
|
||||||
colorAnimation.toValue = newColor
|
|
||||||
colorAnimation.duration = 0.25
|
|
||||||
layer.add(colorAnimation, forKey: colorAnimation.keyPath)
|
|
||||||
}
|
|
||||||
layer.shadowColor = newColor
|
|
||||||
layer.shadowOffset = CGSize(width: 0, height: 0.8)
|
|
||||||
layer.shadowOpacity = isLightMode ? 0.4 : 1
|
|
||||||
layer.shadowRadius = isLightMode ? 4 : 6
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Convenience
|
// MARK: Convenience
|
||||||
|
@ -306,13 +284,3 @@ private extension CGPoint {
|
||||||
return sqrt(pow(self.x - otherPoint.x, 2) + pow(self.y - otherPoint.y, 2))
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -15,31 +15,8 @@ final class PathStatusView : UIView {
|
||||||
backgroundColor = Colors.accent
|
backgroundColor = Colors.accent
|
||||||
let size = Values.pathStatusViewSize
|
let size = Values.pathStatusViewSize
|
||||||
layer.cornerRadius = size / 2
|
layer.cornerRadius = size / 2
|
||||||
setGlow(to: size, with: Colors.accent, animated: false)
|
let glowConfiguration = UIView.CircularGlowConfiguration(size: size, color: Colors.accent, isAnimated: false, radius: isLightMode ? 6 : 8)
|
||||||
|
setCircularGlow(with: glowConfiguration)
|
||||||
layer.masksToBounds = false
|
layer.masksToBounds = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func setGlow(to size: CGFloat, with color: UIColor, animated isAnimated: Bool) {
|
|
||||||
let newPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: size, height: size))).cgPath
|
|
||||||
if isAnimated {
|
|
||||||
let pathAnimation = CABasicAnimation(keyPath: "shadowPath")
|
|
||||||
pathAnimation.fromValue = layer.shadowPath
|
|
||||||
pathAnimation.toValue = newPath
|
|
||||||
pathAnimation.duration = 0.25
|
|
||||||
layer.add(pathAnimation, forKey: pathAnimation.keyPath)
|
|
||||||
}
|
|
||||||
layer.shadowPath = newPath
|
|
||||||
let newColor = color.cgColor
|
|
||||||
if isAnimated {
|
|
||||||
let colorAnimation = CABasicAnimation(keyPath: "shadowColor")
|
|
||||||
colorAnimation.fromValue = layer.shadowColor
|
|
||||||
colorAnimation.toValue = newColor
|
|
||||||
colorAnimation.duration = 0.25
|
|
||||||
layer.add(colorAnimation, forKey: colorAnimation.keyPath)
|
|
||||||
}
|
|
||||||
layer.shadowColor = newColor
|
|
||||||
layer.shadowOffset = CGSize(width: 0, height: 0.8)
|
|
||||||
layer.shadowOpacity = isLightMode ? 0.4 : 1
|
|
||||||
layer.shadowRadius = isLightMode ? 6 : 8
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
10
Signal/src/Loki/Utilities/CGRect+Utilities.swift
Normal file
10
Signal/src/Loki/Utilities/CGRect+Utilities.swift
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
46
Signal/src/Loki/Utilities/UIView+Glow.swift
Normal file
46
Signal/src/Loki/Utilities/UIView+Glow.swift
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
|
||||||
|
extension UIView {
|
||||||
|
|
||||||
|
struct CircularGlowConfiguration {
|
||||||
|
let size: CGFloat
|
||||||
|
let color: UIColor
|
||||||
|
let isAnimated: Bool
|
||||||
|
let offset: CGSize
|
||||||
|
let opacity: Float
|
||||||
|
let radius: CGFloat
|
||||||
|
|
||||||
|
init(size: CGFloat, color: UIColor, isAnimated: Bool, offset: CGSize = CGSize(width: 0, height: 0.8), opacity: Float = isLightMode ? 0.4 : 1, radius: CGFloat) {
|
||||||
|
self.size = size
|
||||||
|
self.color = color
|
||||||
|
self.isAnimated = isAnimated
|
||||||
|
self.offset = offset
|
||||||
|
self.opacity = opacity
|
||||||
|
self.radius = radius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCircularGlow(with configuration: CircularGlowConfiguration) {
|
||||||
|
let newSize = configuration.size
|
||||||
|
let newPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint.zero, size: CGSize(width: newSize, height: newSize))).cgPath
|
||||||
|
if configuration.isAnimated {
|
||||||
|
let pathAnimation = CABasicAnimation(keyPath: "shadowPath")
|
||||||
|
pathAnimation.fromValue = layer.shadowPath
|
||||||
|
pathAnimation.toValue = newPath
|
||||||
|
pathAnimation.duration = 0.25
|
||||||
|
layer.add(pathAnimation, forKey: pathAnimation.keyPath)
|
||||||
|
}
|
||||||
|
layer.shadowPath = newPath
|
||||||
|
let newColor = configuration.color.cgColor
|
||||||
|
if configuration.isAnimated {
|
||||||
|
let colorAnimation = CABasicAnimation(keyPath: "shadowColor")
|
||||||
|
colorAnimation.fromValue = layer.shadowColor
|
||||||
|
colorAnimation.toValue = newColor
|
||||||
|
colorAnimation.duration = 0.25
|
||||||
|
layer.add(colorAnimation, forKey: colorAnimation.keyPath)
|
||||||
|
}
|
||||||
|
layer.shadowColor = newColor
|
||||||
|
layer.shadowOffset = configuration.offset
|
||||||
|
layer.shadowOpacity = configuration.opacity
|
||||||
|
layer.shadowRadius = configuration.radius
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ final class PathVC : BaseVC {
|
||||||
let explanationLabel = UILabel()
|
let explanationLabel = UILabel()
|
||||||
explanationLabel.textColor = Colors.text.withAlphaComponent(Values.unimportantElementOpacity)
|
explanationLabel.textColor = Colors.text.withAlphaComponent(Values.unimportantElementOpacity)
|
||||||
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
|
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
|
||||||
explanationLabel.text = NSLocalizedString("Session hides your IP by onion routing your messages through Session's decentralized Service Node network. The Service Nodes currently being used for this are shown below.", comment: "")
|
explanationLabel.text = NSLocalizedString("Session hides your IP by routing your messages through several Service Nodes in Session's decentralized Service Node network before sending them to their destination. The Service Nodes currently being used by your device are shown below.", comment: "")
|
||||||
explanationLabel.numberOfLines = 0
|
explanationLabel.numberOfLines = 0
|
||||||
explanationLabel.textAlignment = .center
|
explanationLabel.textAlignment = .center
|
||||||
explanationLabel.lineBreakMode = .byWordWrapping
|
explanationLabel.lineBreakMode = .byWordWrapping
|
||||||
|
@ -37,84 +37,128 @@ final class PathVC : BaseVC {
|
||||||
explanationLabel.pin(.top, to: .top, of: view, withInset: Values.mediumSpacing)
|
explanationLabel.pin(.top, to: .top, of: view, withInset: Values.mediumSpacing)
|
||||||
explanationLabel.pin(.trailing, to: .trailing, of: view, withInset: -Values.largeSpacing)
|
explanationLabel.pin(.trailing, to: .trailing, of: view, withInset: -Values.largeSpacing)
|
||||||
// Set up path stack view
|
// Set up path stack view
|
||||||
guard var mainPath = OnionRequestAPI.paths.first else {
|
guard let mainPath = OnionRequestAPI.paths.first else {
|
||||||
return close() // TODO: Show path establishing UI
|
return close() // TODO: Show path establishing UI
|
||||||
}
|
}
|
||||||
let rows: [UIStackView]
|
let dotAnimationRepeatInterval = (Double(mainPath.count) + 2) * 0.5
|
||||||
switch mainPath.count {
|
let snodeRows = mainPath.enumerated().reversed().map { index, snode in
|
||||||
case 1: return // TODO: Do we want to handle this case?
|
getPathRow(snode: snode, location: .middle, dotAnimationStartDelay: (Double(index) + 1) * 0.5, dotAnimationRepeatInterval: dotAnimationRepeatInterval)
|
||||||
case 2:
|
|
||||||
let topPathRow = getPathRow(forSnode: mainPath[1], at: .top)
|
|
||||||
let bottomPathRow = getPathRow(forSnode: mainPath[0], at: .bottom)
|
|
||||||
rows = [ topPathRow, bottomPathRow ]
|
|
||||||
default:
|
|
||||||
let topPathRow = getPathRow(forSnode: mainPath.removeLast(), at: .top)
|
|
||||||
let bottomPathRow = getPathRow(forSnode: mainPath.removeFirst(), at: .bottom)
|
|
||||||
let middlePathRows = mainPath.map {
|
|
||||||
getPathRow(forSnode: $0, at: .middle)
|
|
||||||
}
|
|
||||||
rows = [ topPathRow ] + middlePathRows + [ bottomPathRow ]
|
|
||||||
}
|
}
|
||||||
|
let destinationRow = getPathRow(title: NSLocalizedString("Destination", comment: ""), subtitle: nil, location: .top, dotAnimationStartDelay: (Double(mainPath.count) + 1) * 0.5, dotAnimationRepeatInterval: dotAnimationRepeatInterval)
|
||||||
|
let youRow = getPathRow(title: NSLocalizedString("You", comment: ""), subtitle: nil, location: .bottom, dotAnimationStartDelay: 0, dotAnimationRepeatInterval: dotAnimationRepeatInterval)
|
||||||
|
let rows = [ destinationRow ] + snodeRows + [ youRow ]
|
||||||
let pathStackView = UIStackView(arrangedSubviews: rows)
|
let pathStackView = UIStackView(arrangedSubviews: rows)
|
||||||
pathStackView.axis = .vertical
|
pathStackView.axis = .vertical
|
||||||
view.addSubview(pathStackView)
|
let pathStackViewContainer = UIView()
|
||||||
pathStackView.pin(.top, to: .bottom, of: explanationLabel, withInset: Values.largeSpacing)
|
pathStackViewContainer.addSubview(pathStackView)
|
||||||
pathStackView.center(.horizontal, in: view)
|
pathStackView.pin([ UIView.VerticalEdge.top, UIView.VerticalEdge.bottom ], to: pathStackViewContainer)
|
||||||
pathStackView.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: Values.largeSpacing).isActive = true
|
pathStackView.center(in: pathStackViewContainer)
|
||||||
view.trailingAnchor.constraint(greaterThanOrEqualTo: pathStackView.trailingAnchor, constant: Values.largeSpacing).isActive = true
|
pathStackView.leadingAnchor.constraint(greaterThanOrEqualTo: pathStackViewContainer.leadingAnchor).isActive = true
|
||||||
|
pathStackViewContainer.trailingAnchor.constraint(greaterThanOrEqualTo: pathStackView.trailingAnchor).isActive = true
|
||||||
|
// Set up rebuild path button
|
||||||
|
let rebuildPathButton = Button(style: .prominentOutline, size: .large)
|
||||||
|
rebuildPathButton.setTitle(NSLocalizedString("Rebuild Path", comment: ""), for: UIControl.State.normal)
|
||||||
|
rebuildPathButton.addTarget(self, action: #selector(rebuildPath), for: UIControl.Event.touchUpInside)
|
||||||
|
let rebuildPathButtonContainer = UIView()
|
||||||
|
rebuildPathButtonContainer.addSubview(rebuildPathButton)
|
||||||
|
rebuildPathButton.pin(.leading, to: .leading, of: rebuildPathButtonContainer, withInset: 80)
|
||||||
|
rebuildPathButton.pin(.top, to: .top, of: rebuildPathButtonContainer)
|
||||||
|
rebuildPathButtonContainer.pin(.trailing, to: .trailing, of: rebuildPathButton, withInset: 80)
|
||||||
|
rebuildPathButtonContainer.pin(.bottom, to: .bottom, of: rebuildPathButton)
|
||||||
|
// Set up main stack view
|
||||||
|
let mainStackView = UIStackView(arrangedSubviews: [ explanationLabel, UIView.spacer(withHeight: Values.mediumSpacing), pathStackViewContainer, UIView.vStretchingSpacer(), rebuildPathButtonContainer ])
|
||||||
|
mainStackView.axis = .vertical
|
||||||
|
mainStackView.alignment = .fill
|
||||||
|
mainStackView.layoutMargins = UIEdgeInsets(top: Values.largeSpacing, left: Values.largeSpacing, bottom: Values.largeSpacing, right: Values.largeSpacing)
|
||||||
|
mainStackView.isLayoutMarginsRelativeArrangement = true
|
||||||
|
view.addSubview(mainStackView)
|
||||||
|
mainStackView.pin(to: view)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getPathRow(forSnode snode: LokiAPITarget, at location: LineView.Location) -> UIStackView {
|
private func getPathRow(title: String, subtitle: String?, location: LineView.Location, dotAnimationStartDelay: Double, dotAnimationRepeatInterval: Double) -> UIStackView {
|
||||||
let lineView = LineView(location: location)
|
let lineView = LineView(location: location, dotAnimationStartDelay: dotAnimationStartDelay, dotAnimationRepeatInterval: dotAnimationRepeatInterval)
|
||||||
lineView.set(.width, to: Values.pathRowDotSize)
|
lineView.set(.width, to: Values.pathRowDotSize)
|
||||||
let snodeLabel = UILabel()
|
lineView.set(.height, to: Values.pathRowHeight)
|
||||||
snodeLabel.textColor = Colors.text
|
let titleLabel = UILabel()
|
||||||
snodeLabel.font = .systemFont(ofSize: Values.mediumFontSize)
|
titleLabel.textColor = Colors.text
|
||||||
var snodeDescription = snode.description
|
titleLabel.font = .systemFont(ofSize: Values.mediumFontSize)
|
||||||
if snodeDescription.hasPrefix("https://") {
|
titleLabel.text = title
|
||||||
snodeDescription.removeFirst(8)
|
titleLabel.lineBreakMode = .byTruncatingTail
|
||||||
|
let titleStackView = UIStackView(arrangedSubviews: [ titleLabel ])
|
||||||
|
titleStackView.axis = .vertical
|
||||||
|
if let subtitle = subtitle {
|
||||||
|
let subtitleLabel = UILabel()
|
||||||
|
subtitleLabel.textColor = Colors.text
|
||||||
|
subtitleLabel.font = .systemFont(ofSize: Values.verySmallFontSize)
|
||||||
|
subtitleLabel.text = subtitle
|
||||||
|
subtitleLabel.lineBreakMode = .byTruncatingTail
|
||||||
|
titleStackView.addArrangedSubview(subtitleLabel)
|
||||||
}
|
}
|
||||||
if let colonIndex = snodeDescription.lastIndex(of: ":") {
|
let stackView = UIStackView(arrangedSubviews: [ lineView, titleStackView ])
|
||||||
snodeDescription = String(snodeDescription[snodeDescription.startIndex..<colonIndex])
|
|
||||||
}
|
|
||||||
snodeLabel.text = snodeDescription
|
|
||||||
snodeLabel.lineBreakMode = .byTruncatingTail
|
|
||||||
let stackView = UIStackView(arrangedSubviews: [ lineView, snodeLabel ])
|
|
||||||
stackView.axis = .horizontal
|
stackView.axis = .horizontal
|
||||||
stackView.spacing = Values.largeSpacing
|
stackView.spacing = Values.largeSpacing
|
||||||
|
stackView.alignment = .center
|
||||||
return stackView
|
return stackView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func getPathRow(snode: LokiAPITarget, location: LineView.Location, dotAnimationStartDelay: Double, dotAnimationRepeatInterval: Double) -> UIStackView {
|
||||||
|
var snodeIP = snode.description
|
||||||
|
if snodeIP.hasPrefix("https://") { snodeIP.removeFirst(8) }
|
||||||
|
if let colonIndex = snodeIP.lastIndex(of: ":") {
|
||||||
|
snodeIP = String(snodeIP[snodeIP.startIndex..<colonIndex])
|
||||||
|
}
|
||||||
|
return getPathRow(title: NSLocalizedString("Service Node", comment: ""), subtitle: snodeIP, location: location, dotAnimationStartDelay: dotAnimationStartDelay, dotAnimationRepeatInterval: dotAnimationRepeatInterval)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Interaction
|
// MARK: Interaction
|
||||||
@objc private func close() {
|
@objc private func close() {
|
||||||
dismiss(animated: true, completion: nil)
|
dismiss(animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func rebuildPath() {
|
||||||
|
// TODO: Implement
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Line View
|
// MARK: Line View
|
||||||
private final class LineView : UIView {
|
private final class LineView : UIView {
|
||||||
private let location: Location
|
private let location: Location
|
||||||
|
private let dotAnimationStartDelay: Double
|
||||||
|
private let dotAnimationRepeatInterval: Double
|
||||||
|
private var dotViewWidthConstraint: NSLayoutConstraint!
|
||||||
|
private var dotViewHeightConstraint: NSLayoutConstraint!
|
||||||
|
private var dotViewAnimationTimer: Timer!
|
||||||
|
|
||||||
enum Location {
|
enum Location {
|
||||||
case top, middle, bottom
|
case top, middle, bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
init(location: Location) {
|
private lazy var dotView: UIView = {
|
||||||
|
let result = UIView()
|
||||||
|
result.layer.cornerRadius = Values.pathRowDotSize / 2
|
||||||
|
let glowConfiguration = UIView.CircularGlowConfiguration(size: Values.pathRowDotSize, color: Colors.accent, isAnimated: true, radius: isLightMode ? 2 : 4)
|
||||||
|
result.setCircularGlow(with: glowConfiguration)
|
||||||
|
result.backgroundColor = Colors.accent
|
||||||
|
return result
|
||||||
|
}()
|
||||||
|
|
||||||
|
init(location: Location, dotAnimationStartDelay: Double, dotAnimationRepeatInterval: Double) {
|
||||||
self.location = location
|
self.location = location
|
||||||
|
self.dotAnimationStartDelay = dotAnimationStartDelay
|
||||||
|
self.dotAnimationRepeatInterval = dotAnimationRepeatInterval
|
||||||
super.init(frame: CGRect.zero)
|
super.init(frame: CGRect.zero)
|
||||||
setUpViewHierarchy()
|
setUpViewHierarchy()
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
preconditionFailure("Use init(location:) instead.")
|
preconditionFailure("Use init(location:dotAnimationStartDelay:dotAnimationRepeatInterval:) instead.")
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
preconditionFailure("Use init(location:) instead.")
|
preconditionFailure("Use init(location:dotAnimationStartDelay:dotAnimationRepeatInterval:) instead.")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setUpViewHierarchy() {
|
private func setUpViewHierarchy() {
|
||||||
set(.height, to: Values.pathRowHeight)
|
|
||||||
let lineView = UIView()
|
let lineView = UIView()
|
||||||
lineView.set(.width, to: Values.pathRowLineThickness)
|
lineView.set(.width, to: Values.pathRowLineThickness)
|
||||||
lineView.backgroundColor = Colors.text
|
lineView.backgroundColor = Colors.text
|
||||||
|
@ -128,13 +172,56 @@ private final class LineView : UIView {
|
||||||
case .top, .middle: lineView.pin(.bottom, to: .bottom, of: self)
|
case .top, .middle: lineView.pin(.bottom, to: .bottom, of: self)
|
||||||
case .bottom: lineView.bottomAnchor.constraint(equalTo: centerYAnchor).isActive = true
|
case .bottom: lineView.bottomAnchor.constraint(equalTo: centerYAnchor).isActive = true
|
||||||
}
|
}
|
||||||
let dotView = UIView()
|
|
||||||
let dotSize = Values.pathRowDotSize
|
let dotSize = Values.pathRowDotSize
|
||||||
dotView.set(.width, to: dotSize)
|
dotViewWidthConstraint = dotView.set(.width, to: dotSize)
|
||||||
dotView.set(.height, to: dotSize)
|
dotViewHeightConstraint = dotView.set(.height, to: dotSize)
|
||||||
dotView.layer.cornerRadius = dotSize / 2
|
|
||||||
dotView.backgroundColor = Colors.text
|
|
||||||
addSubview(dotView)
|
addSubview(dotView)
|
||||||
dotView.center(in: self)
|
dotView.center(in: self)
|
||||||
|
Timer.scheduledTimer(withTimeInterval: dotAnimationStartDelay, repeats: false) { [weak self] _ in
|
||||||
|
guard let strongSelf = self else { return }
|
||||||
|
strongSelf.animate()
|
||||||
|
strongSelf.dotViewAnimationTimer = Timer.scheduledTimer(withTimeInterval: strongSelf.dotAnimationRepeatInterval, repeats: true) { _ in
|
||||||
|
guard let strongSelf = self else { return }
|
||||||
|
strongSelf.animate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
dotViewAnimationTimer?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func animate() {
|
||||||
|
expandDot()
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { [weak self] _ in
|
||||||
|
self?.collapseDot()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func expandDot() {
|
||||||
|
let newSize = Values.pathRowExpandedDotSize
|
||||||
|
let newGlowRadius: CGFloat = isLightMode ? 6 : 8
|
||||||
|
updateDotView(size: newSize, glowRadius: newGlowRadius)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func collapseDot() {
|
||||||
|
let newSize = Values.pathRowDotSize
|
||||||
|
let newGlowRadius: CGFloat = isLightMode ? 2 : 4
|
||||||
|
updateDotView(size: newSize, glowRadius: newGlowRadius)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateDotView(size: CGFloat, glowRadius: CGFloat) {
|
||||||
|
let frame = CGRect(center: dotView.center, size: CGSize(width: size, height: size))
|
||||||
|
dotViewWidthConstraint.constant = size
|
||||||
|
dotViewHeightConstraint.constant = size
|
||||||
|
UIView.animate(withDuration: 0.25) {
|
||||||
|
self.layoutIfNeeded()
|
||||||
|
self.dotView.frame = frame
|
||||||
|
self.dotView.layer.cornerRadius = size / 2
|
||||||
|
let glowColor = Colors.accent
|
||||||
|
let glowConfiguration = UIView.CircularGlowConfiguration(size: size, color: glowColor, isAnimated: true, radius: glowRadius)
|
||||||
|
self.dotView.setCircularGlow(with: glowConfiguration)
|
||||||
|
self.dotView.backgroundColor = Colors.accent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2834,4 +2834,8 @@
|
||||||
"Authorizing Device Link" = "Authorizing Device Link";
|
"Authorizing Device Link" = "Authorizing Device Link";
|
||||||
"Please wait while the device link is created. This can take up to a minute." = "Please wait while the device link is created. This can take up to a minute.";
|
"Please wait while the device link is created. This can take up to a minute." = "Please wait while the device link is created. This can take up to a minute.";
|
||||||
"Path" = "Path";
|
"Path" = "Path";
|
||||||
"Session hides your IP by onion routing your messages through Session's decentralized Service Node network. The Service Nodes currently being used for this are shown below." = "Session hides your IP by onion routing your messages through Session's decentralized Service Node network. The Service Nodes currently being used for this are shown below.";
|
"Session hides your IP by routing your messages through several Service Nodes in Session's decentralized Service Node network before sending them to their destination. The Service Nodes currently being used by your device are shown below." = "Session hides your IP by routing your messages through several Service Nodes in Session's decentralized Service Node network before sending them to their destination. The Service Nodes currently being used by your device are shown below.";
|
||||||
|
"Service Node" = "Service Node";
|
||||||
|
"You" = "You";
|
||||||
|
"Destination" = "Destination";
|
||||||
|
"Rebuild Path" = "Rebuild Path";
|
||||||
|
|
|
@ -48,8 +48,9 @@ public final class Values : NSObject {
|
||||||
@objc public static let progressBarThickness: CGFloat = 2
|
@objc public static let progressBarThickness: CGFloat = 2
|
||||||
@objc public static let pnOptionCornerRadius = CGFloat(8)
|
@objc public static let pnOptionCornerRadius = CGFloat(8)
|
||||||
@objc public static let pathStatusViewSize = CGFloat(8)
|
@objc public static let pathStatusViewSize = CGFloat(8)
|
||||||
@objc public static let pathRowLineThickness = CGFloat(1)
|
@objc public static var pathRowLineThickness: CGFloat { return 1 / UIScreen.main.scale }
|
||||||
@objc public static let pathRowDotSize = CGFloat(8)
|
@objc public static let pathRowDotSize = CGFloat(8)
|
||||||
|
@objc public static let pathRowExpandedDotSize = CGFloat(16)
|
||||||
@objc public static let pathRowHeight = CGFloat(72)
|
@objc public static let pathRowHeight = CGFloat(72)
|
||||||
|
|
||||||
// MARK: - Distances
|
// MARK: - Distances
|
||||||
|
|
Loading…
Reference in a new issue