Prettify onion request UI

This commit is contained in:
nielsandriesse 2020-05-28 09:52:13 +10:00
parent 302d7ad09a
commit 112ff20c73
8 changed files with 214 additions and 113 deletions

View File

@ -623,6 +623,8 @@
B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; };
B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.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 */; };
C353F8F7244808E90011121A /* PNModeSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C353F8F6244808E90011121A /* PNModeSheet.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>"; };
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; };
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>"; };
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>"; };
@ -2896,7 +2900,9 @@
B886B4A82398BA1500211ABE /* QRCode.swift */,
B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */,
B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */,
C31A6C59247F214E001123EF /* UIView+Glow.swift */,
C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */,
C31A6C5B247F2CF3001123EF /* CGRect+Utilities.swift */,
);
path = Utilities;
sourceTree = "<group>";
@ -4203,6 +4209,7 @@
34D1F0841F8678AA0066283D /* ConversationInputToolbar.m in Sources */,
457F671B20746193000EABCD /* QuotedReplyPreview.swift in Sources */,
34A6C28021E503E700B5B12E /* OWSImagePickerController.swift in Sources */,
C31A6C5C247F2CF3001123EF /* CGRect+Utilities.swift in Sources */,
4C21D5D6223A9DC500EF8A77 /* UIAlerts+iOS9.m in Sources */,
34DBF004206BD5A500025978 /* OWSBubbleView.m in Sources */,
3496957021A301A100DCFE74 /* OWSBackupIO.m in Sources */,
@ -4217,6 +4224,7 @@
34BECE2B1F74C12700D7438D /* DebugUIStress.m in Sources */,
340FC8B9204DAC8D007AEB0F /* UpdateGroupViewController.m in Sources */,
B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */,
C31A6C5A247F214E001123EF /* UIView+Glow.swift in Sources */,
3448E1662215B313004B052E /* OnboardingCaptchaViewController.swift in Sources */,
4574A5D61DD6704700C6B692 /* CallService.swift in Sources */,
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */,

View File

@ -158,7 +158,9 @@ final class NewConversationButtonSet : UIView {
self.layoutIfNeeded()
button.frame = frame
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
}
}
@ -183,7 +185,8 @@ final class NewConversationButtonSet : UIView {
button.frame = frame
button.layer.cornerRadius = size / 2
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
}
}
@ -208,8 +211,7 @@ private final class NewConversationButton : UIImageView {
private let icon: UIImage
var widthConstraint: NSLayoutConstraint!
var heightConstraint: NSLayoutConstraint!
// Initialization
init(isMainButton: Bool, icon: UIImage) {
self.isMainButton = isMainButton
self.icon = icon
@ -230,7 +232,8 @@ private final class NewConversationButton : UIImageView {
let size = Values.newConversationButtonCollapsedSize
layer.cornerRadius = size / 2
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
let iconColor = (isMainButton && isLightMode) ? UIColor.white : Colors.text
image = icon.asTintedImage(color: iconColor)!
@ -238,31 +241,6 @@ private final class NewConversationButton : UIImageView {
widthConstraint = set(.width, 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
@ -306,13 +284,3 @@ private extension CGPoint {
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)
}
}

View File

@ -15,31 +15,8 @@ final class PathStatusView : UIView {
backgroundColor = Colors.accent
let size = Values.pathStatusViewSize
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
}
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
}
}

View 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)
}
}

View 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
}
}

View File

@ -28,7 +28,7 @@ final class PathVC : BaseVC {
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text.withAlphaComponent(Values.unimportantElementOpacity)
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.textAlignment = .center
explanationLabel.lineBreakMode = .byWordWrapping
@ -37,84 +37,128 @@ final class PathVC : BaseVC {
explanationLabel.pin(.top, to: .top, of: view, withInset: Values.mediumSpacing)
explanationLabel.pin(.trailing, to: .trailing, of: view, withInset: -Values.largeSpacing)
// 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
}
let rows: [UIStackView]
switch mainPath.count {
case 1: return // TODO: Do we want to handle this case?
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 dotAnimationRepeatInterval = (Double(mainPath.count) + 2) * 0.5
let snodeRows = mainPath.enumerated().reversed().map { index, snode in
getPathRow(snode: snode, location: .middle, dotAnimationStartDelay: (Double(index) + 1) * 0.5, dotAnimationRepeatInterval: dotAnimationRepeatInterval)
}
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)
pathStackView.axis = .vertical
view.addSubview(pathStackView)
pathStackView.pin(.top, to: .bottom, of: explanationLabel, withInset: Values.largeSpacing)
pathStackView.center(.horizontal, in: view)
pathStackView.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: Values.largeSpacing).isActive = true
view.trailingAnchor.constraint(greaterThanOrEqualTo: pathStackView.trailingAnchor, constant: Values.largeSpacing).isActive = true
let pathStackViewContainer = UIView()
pathStackViewContainer.addSubview(pathStackView)
pathStackView.pin([ UIView.VerticalEdge.top, UIView.VerticalEdge.bottom ], to: pathStackViewContainer)
pathStackView.center(in: pathStackViewContainer)
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 {
let lineView = LineView(location: location)
private func getPathRow(title: String, subtitle: String?, location: LineView.Location, dotAnimationStartDelay: Double, dotAnimationRepeatInterval: Double) -> UIStackView {
let lineView = LineView(location: location, dotAnimationStartDelay: dotAnimationStartDelay, dotAnimationRepeatInterval: dotAnimationRepeatInterval)
lineView.set(.width, to: Values.pathRowDotSize)
let snodeLabel = UILabel()
snodeLabel.textColor = Colors.text
snodeLabel.font = .systemFont(ofSize: Values.mediumFontSize)
var snodeDescription = snode.description
if snodeDescription.hasPrefix("https://") {
snodeDescription.removeFirst(8)
lineView.set(.height, to: Values.pathRowHeight)
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .systemFont(ofSize: Values.mediumFontSize)
titleLabel.text = title
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: ":") {
snodeDescription = String(snodeDescription[snodeDescription.startIndex..<colonIndex])
}
snodeLabel.text = snodeDescription
snodeLabel.lineBreakMode = .byTruncatingTail
let stackView = UIStackView(arrangedSubviews: [ lineView, snodeLabel ])
let stackView = UIStackView(arrangedSubviews: [ lineView, titleStackView ])
stackView.axis = .horizontal
stackView.spacing = Values.largeSpacing
stackView.alignment = .center
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
@objc private func close() {
dismiss(animated: true, completion: nil)
}
@objc private func rebuildPath() {
// TODO: Implement
}
}
// MARK: Line View
private final class LineView : UIView {
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 {
case top, middle, bottom
}
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) {
init(location: Location, dotAnimationStartDelay: Double, dotAnimationRepeatInterval: Double) {
self.location = location
self.dotAnimationStartDelay = dotAnimationStartDelay
self.dotAnimationRepeatInterval = dotAnimationRepeatInterval
super.init(frame: CGRect.zero)
setUpViewHierarchy()
}
override init(frame: CGRect) {
preconditionFailure("Use init(location:) instead.")
preconditionFailure("Use init(location:dotAnimationStartDelay:dotAnimationRepeatInterval:) instead.")
}
required init?(coder: NSCoder) {
preconditionFailure("Use init(location:) instead.")
preconditionFailure("Use init(location:dotAnimationStartDelay:dotAnimationRepeatInterval:) instead.")
}
private func setUpViewHierarchy() {
set(.height, to: Values.pathRowHeight)
let lineView = UIView()
lineView.set(.width, to: Values.pathRowLineThickness)
lineView.backgroundColor = Colors.text
@ -128,13 +172,56 @@ private final class LineView : UIView {
case .top, .middle: lineView.pin(.bottom, to: .bottom, of: self)
case .bottom: lineView.bottomAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
let dotView = UIView()
let dotSize = Values.pathRowDotSize
dotView.set(.width, to: dotSize)
dotView.set(.height, to: dotSize)
dotView.layer.cornerRadius = dotSize / 2
dotView.backgroundColor = Colors.text
dotViewWidthConstraint = dotView.set(.width, to: dotSize)
dotViewHeightConstraint = dotView.set(.height, to: dotSize)
addSubview(dotView)
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
}
}
}

View File

@ -2834,4 +2834,8 @@
"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.";
"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";

View File

@ -48,8 +48,9 @@ public final class Values : NSObject {
@objc public static let progressBarThickness: CGFloat = 2
@objc public static let pnOptionCornerRadius = 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 pathRowExpandedDotSize = CGFloat(16)
@objc public static let pathRowHeight = CGFloat(72)
// MARK: - Distances