Implement basic onion request UI
This commit is contained in:
parent
8fb5e7102f
commit
00f5cebdef
|
@ -592,6 +592,8 @@
|
||||||
B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; };
|
B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; };
|
||||||
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; };
|
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; };
|
||||||
B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */; };
|
B8783E9E23EB948D00404FB8 /* UILabel+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */; };
|
||||||
|
B879D449247E1BE300DB3608 /* PathVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B879D448247E1BE300DB3608 /* PathVC.swift */; };
|
||||||
|
B879D44B247E1D9200DB3608 /* PathStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B879D44A247E1D9200DB3608 /* PathStatusView.swift */; };
|
||||||
B885D5F4233491AB00EE0D8E /* DeviceLinkingModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */; };
|
B885D5F4233491AB00EE0D8E /* DeviceLinkingModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */; };
|
||||||
B886B4A72398B23E00211ABE /* QRCodeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A62398B23E00211ABE /* QRCodeVC.swift */; };
|
B886B4A72398B23E00211ABE /* QRCodeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A62398B23E00211ABE /* QRCodeVC.swift */; };
|
||||||
B886B4A92398BA1500211ABE /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A82398BA1500211ABE /* QRCode.swift */; };
|
B886B4A92398BA1500211ABE /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B886B4A82398BA1500211ABE /* QRCode.swift */; };
|
||||||
|
@ -1462,6 +1464,8 @@
|
||||||
B86BD08323399ACF000F5AE3 /* Modal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modal.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>"; };
|
B86BD08523399CEF000F5AE3 /* SeedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedModal.swift; sourceTree = "<group>"; };
|
||||||
B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Interaction.swift"; sourceTree = "<group>"; };
|
B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Interaction.swift"; sourceTree = "<group>"; };
|
||||||
|
B879D448247E1BE300DB3608 /* PathVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathVC.swift; sourceTree = "<group>"; };
|
||||||
|
B879D44A247E1D9200DB3608 /* PathStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathStatusView.swift; sourceTree = "<group>"; };
|
||||||
B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLinkingModal.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>"; };
|
B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraints.swift"; sourceTree = "<group>"; };
|
||||||
B886B4A62398B23E00211ABE /* QRCodeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeVC.swift; sourceTree = "<group>"; };
|
B886B4A62398B23E00211ABE /* QRCodeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeVC.swift; sourceTree = "<group>"; };
|
||||||
|
@ -2871,6 +2875,7 @@
|
||||||
B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */,
|
B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */,
|
||||||
B8B26C90234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift */,
|
B8B26C90234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift */,
|
||||||
B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */,
|
B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */,
|
||||||
|
B879D44A247E1D9200DB3608 /* PathStatusView.swift */,
|
||||||
C353F8F8244809150011121A /* PNOptionView.swift */,
|
C353F8F8244809150011121A /* PNOptionView.swift */,
|
||||||
B8BB82B02390C37000BA5194 /* SearchBar.swift */,
|
B8BB82B02390C37000BA5194 /* SearchBar.swift */,
|
||||||
B85357BE23A1AE0800AAF6CD /* SeedReminderView.swift */,
|
B85357BE23A1AE0800AAF6CD /* SeedReminderView.swift */,
|
||||||
|
@ -2917,6 +2922,7 @@
|
||||||
B8CCF63623961D6D0091D419 /* NewPrivateChatVC.swift */,
|
B8CCF63623961D6D0091D419 /* NewPrivateChatVC.swift */,
|
||||||
B894D0742339EDCF00B4D94D /* NukeDataModal.swift */,
|
B894D0742339EDCF00B4D94D /* NukeDataModal.swift */,
|
||||||
C3DFFAC723E970080058DAF8 /* OpenGroupSuggestionSheet.swift */,
|
C3DFFAC723E970080058DAF8 /* OpenGroupSuggestionSheet.swift */,
|
||||||
|
B879D448247E1BE300DB3608 /* PathVC.swift */,
|
||||||
C353F8F6244808E90011121A /* PNModeSheet.swift */,
|
C353F8F6244808E90011121A /* PNModeSheet.swift */,
|
||||||
C3548F0524456447009433A8 /* PNModeVC.swift */,
|
C3548F0524456447009433A8 /* PNModeVC.swift */,
|
||||||
B886B4A62398B23E00211ABE /* QRCodeVC.swift */,
|
B886B4A62398B23E00211ABE /* QRCodeVC.swift */,
|
||||||
|
@ -4010,6 +4016,7 @@
|
||||||
341F2C0F1F2B8AE700D07D6B /* DebugUIMisc.m in Sources */,
|
341F2C0F1F2B8AE700D07D6B /* DebugUIMisc.m in Sources */,
|
||||||
B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */,
|
B893063F2383961A005EAA8E /* ScanQRCodeWrapperVC.swift in Sources */,
|
||||||
B83F2B86240C7B8F000A54AB /* NewConversationButtonSet.swift in Sources */,
|
B83F2B86240C7B8F000A54AB /* NewConversationButtonSet.swift in Sources */,
|
||||||
|
B879D449247E1BE300DB3608 /* PathVC.swift in Sources */,
|
||||||
340FC8AF204DAC8D007AEB0F /* OWSLinkDeviceViewController.m in Sources */,
|
340FC8AF204DAC8D007AEB0F /* OWSLinkDeviceViewController.m in Sources */,
|
||||||
34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */,
|
34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */,
|
||||||
454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */,
|
454A84042059C787008B8C75 /* MediaTileViewController.swift in Sources */,
|
||||||
|
@ -4146,6 +4153,7 @@
|
||||||
34D8C0281ED3673300188D7C /* DebugUITableViewController.m in Sources */,
|
34D8C0281ED3673300188D7C /* DebugUITableViewController.m in Sources */,
|
||||||
45F32C222057297A00A300D5 /* MediaDetailViewController.m in Sources */,
|
45F32C222057297A00A300D5 /* MediaDetailViewController.m in Sources */,
|
||||||
B8B26C8F234D629C004ED98C /* MentionCandidateSelectionView.swift in Sources */,
|
B8B26C8F234D629C004ED98C /* MentionCandidateSelectionView.swift in Sources */,
|
||||||
|
B879D44B247E1D9200DB3608 /* PathStatusView.swift in Sources */,
|
||||||
34B3F8851E8DF1700035BE1A /* NewGroupViewController.m in Sources */,
|
34B3F8851E8DF1700035BE1A /* NewGroupViewController.m in Sources */,
|
||||||
B8CCF63F23975CFB0091D419 /* JoinPublicChatVC.swift in Sources */,
|
B8CCF63F23975CFB0091D419 /* JoinPublicChatVC.swift in Sources */,
|
||||||
34ABC0E421DD20C500ED9469 /* ConversationMessageMapping.swift in Sources */,
|
34ABC0E421DD20C500ED9469 /* ConversationMessageMapping.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
|
||||||
|
final class PathStatusView : UIView {
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
setUpViewHierarchy()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
setUpViewHierarchy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setUpViewHierarchy() {
|
||||||
|
backgroundColor = Colors.accent
|
||||||
|
let size = Values.pathStatusViewSize
|
||||||
|
layer.cornerRadius = size / 2
|
||||||
|
setGlow(to: size, with: Colors.accent, animated: 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -299,6 +299,18 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
||||||
profilePictureView.pin(.trailing, to: .trailing, of: profilePictureViewContainer)
|
profilePictureView.pin(.trailing, to: .trailing, of: profilePictureViewContainer)
|
||||||
profilePictureView.pin(.bottom, to: .bottom, of: profilePictureViewContainer)
|
profilePictureView.pin(.bottom, to: .bottom, of: profilePictureViewContainer)
|
||||||
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: profilePictureViewContainer)
|
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: profilePictureViewContainer)
|
||||||
|
let pathStatusViewContainer = UIView()
|
||||||
|
let pathStatusViewContainerSize = Values.verySmallProfilePictureSize // Match the profile picture view
|
||||||
|
pathStatusViewContainer.set(.width, to: pathStatusViewContainerSize)
|
||||||
|
pathStatusViewContainer.set(.height, to: pathStatusViewContainerSize)
|
||||||
|
let pathStatusView = PathStatusView()
|
||||||
|
pathStatusView.set(.width, to: Values.pathStatusViewSize)
|
||||||
|
pathStatusView.set(.height, to: Values.pathStatusViewSize)
|
||||||
|
pathStatusViewContainer.addSubview(pathStatusView)
|
||||||
|
pathStatusView.center(.horizontal, in: pathStatusViewContainer)
|
||||||
|
pathStatusView.center(.vertical, in: pathStatusViewContainer)
|
||||||
|
pathStatusViewContainer.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(showPath)))
|
||||||
|
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: pathStatusViewContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Interaction
|
// MARK: Interaction
|
||||||
|
@ -398,6 +410,12 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol
|
||||||
present(navigationController, animated: true, completion: nil)
|
present(navigationController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func showPath() {
|
||||||
|
let pathVC = PathVC()
|
||||||
|
let navigationController = OWSNavigationController(rootViewController: pathVC)
|
||||||
|
present(navigationController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
@objc func joinOpenGroup() {
|
@objc func joinOpenGroup() {
|
||||||
let joinPublicChatVC = JoinPublicChatVC()
|
let joinPublicChatVC = JoinPublicChatVC()
|
||||||
let navigationController = OWSNavigationController(rootViewController: joinPublicChatVC)
|
let navigationController = OWSNavigationController(rootViewController: joinPublicChatVC)
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
|
||||||
|
final class PathVC : BaseVC {
|
||||||
|
|
||||||
|
// MARK: Lifecycle
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
// Set gradient background
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
let gradient = Gradients.defaultLokiBackground
|
||||||
|
view.setGradient(gradient)
|
||||||
|
// Set up navigation bar
|
||||||
|
let navigationBar = navigationController!.navigationBar
|
||||||
|
navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
|
||||||
|
navigationBar.shadowImage = UIImage()
|
||||||
|
navigationBar.isTranslucent = false
|
||||||
|
navigationBar.barTintColor = Colors.navigationBarBackground
|
||||||
|
// Set up close button
|
||||||
|
let closeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "X"), style: .plain, target: self, action: #selector(close))
|
||||||
|
closeButton.tintColor = Colors.text
|
||||||
|
navigationItem.leftBarButtonItem = closeButton
|
||||||
|
// Customize title
|
||||||
|
let titleLabel = UILabel()
|
||||||
|
titleLabel.text = NSLocalizedString("Path", comment: "")
|
||||||
|
titleLabel.textColor = Colors.text
|
||||||
|
titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
|
||||||
|
navigationItem.titleView = titleLabel
|
||||||
|
// Set up explanation label
|
||||||
|
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.numberOfLines = 0
|
||||||
|
explanationLabel.textAlignment = .center
|
||||||
|
explanationLabel.lineBreakMode = .byWordWrapping
|
||||||
|
view.addSubview(explanationLabel)
|
||||||
|
explanationLabel.pin(.leading, to: .leading, of: view, withInset: Values.largeSpacing)
|
||||||
|
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 {
|
||||||
|
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 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
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getPathRow(forSnode snode: LokiAPITarget, at location: LineView.Location) -> UIStackView {
|
||||||
|
let lineView = LineView(location: location)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if let colonIndex = snodeDescription.lastIndex(of: ":") {
|
||||||
|
snodeDescription = String(snodeDescription[snodeDescription.startIndex..<colonIndex])
|
||||||
|
}
|
||||||
|
snodeLabel.text = snodeDescription
|
||||||
|
snodeLabel.lineBreakMode = .byTruncatingTail
|
||||||
|
let stackView = UIStackView(arrangedSubviews: [ lineView, snodeLabel ])
|
||||||
|
stackView.axis = .horizontal
|
||||||
|
stackView.spacing = Values.largeSpacing
|
||||||
|
return stackView
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Interaction
|
||||||
|
@objc private func close() {
|
||||||
|
dismiss(animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Line View
|
||||||
|
private final class LineView : UIView {
|
||||||
|
private let location: Location
|
||||||
|
|
||||||
|
enum Location {
|
||||||
|
case top, middle, bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
init(location: Location) {
|
||||||
|
self.location = location
|
||||||
|
super.init(frame: CGRect.zero)
|
||||||
|
setUpViewHierarchy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
preconditionFailure("Use init(location:) instead.")
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
preconditionFailure("Use init(location:) instead.")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setUpViewHierarchy() {
|
||||||
|
set(.height, to: Values.pathRowHeight)
|
||||||
|
let lineView = UIView()
|
||||||
|
lineView.set(.width, to: Values.pathRowLineThickness)
|
||||||
|
lineView.backgroundColor = Colors.text
|
||||||
|
addSubview(lineView)
|
||||||
|
lineView.center(.horizontal, in: self)
|
||||||
|
switch location {
|
||||||
|
case .top: lineView.topAnchor.constraint(equalTo: centerYAnchor).isActive = true
|
||||||
|
case .middle, .bottom: lineView.pin(.top, to: .top, of: self)
|
||||||
|
}
|
||||||
|
switch location {
|
||||||
|
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
|
||||||
|
addSubview(dotView)
|
||||||
|
dotView.center(in: self)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2829,3 +2829,5 @@
|
||||||
"Please check your internet connection and try again" = "Please check your internet connection and try again";
|
"Please check your internet connection and try again" = "Please check your internet connection and try again";
|
||||||
"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";
|
||||||
|
"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.";
|
||||||
|
|
|
@ -47,6 +47,10 @@ public final class Values : NSObject {
|
||||||
@objc public static let messageBubbleCornerRadius: CGFloat = 10
|
@objc public static let messageBubbleCornerRadius: CGFloat = 10
|
||||||
@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 pathRowLineThickness = CGFloat(1)
|
||||||
|
@objc public static let pathRowDotSize = CGFloat(8)
|
||||||
|
@objc public static let pathRowHeight = CGFloat(72)
|
||||||
|
|
||||||
// MARK: - Distances
|
// MARK: - Distances
|
||||||
@objc public static let verySmallSpacing = CGFloat(4)
|
@objc public static let verySmallSpacing = CGFloat(4)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
/// Either a service node or another client if P2P is enabled.
|
/// Either a service node or another client if P2P is enabled.
|
||||||
internal final class LokiAPITarget : NSObject, NSCoding {
|
public final class LokiAPITarget : NSObject, NSCoding {
|
||||||
internal let address: String
|
internal let address: String
|
||||||
internal let port: UInt16
|
internal let port: UInt16
|
||||||
internal let publicKeySet: KeySet?
|
internal let publicKeySet: KeySet?
|
||||||
|
@ -27,7 +27,7 @@ internal final class LokiAPITarget : NSObject, NSCoding {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Coding
|
// MARK: Coding
|
||||||
internal init?(coder: NSCoder) {
|
public init?(coder: NSCoder) {
|
||||||
address = coder.decodeObject(forKey: "address") as! String
|
address = coder.decodeObject(forKey: "address") as! String
|
||||||
port = coder.decodeObject(forKey: "port") as! UInt16
|
port = coder.decodeObject(forKey: "port") as! UInt16
|
||||||
if let idKey = coder.decodeObject(forKey: "idKey") as? String, let encryptionKey = coder.decodeObject(forKey: "encryptionKey") as? String {
|
if let idKey = coder.decodeObject(forKey: "idKey") as? String, let encryptionKey = coder.decodeObject(forKey: "encryptionKey") as? String {
|
||||||
|
@ -38,7 +38,7 @@ internal final class LokiAPITarget : NSObject, NSCoding {
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal func encode(with coder: NSCoder) {
|
public func encode(with coder: NSCoder) {
|
||||||
coder.encode(address, forKey: "address")
|
coder.encode(address, forKey: "address")
|
||||||
coder.encode(port, forKey: "port")
|
coder.encode(port, forKey: "port")
|
||||||
if let keySet = publicKeySet {
|
if let keySet = publicKeySet {
|
||||||
|
@ -48,16 +48,16 @@ internal final class LokiAPITarget : NSObject, NSCoding {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Equality
|
// MARK: Equality
|
||||||
override internal func isEqual(_ other: Any?) -> Bool {
|
override public func isEqual(_ other: Any?) -> Bool {
|
||||||
guard let other = other as? LokiAPITarget else { return false }
|
guard let other = other as? LokiAPITarget else { return false }
|
||||||
return address == other.address && port == other.port
|
return address == other.address && port == other.port
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Hashing
|
// MARK: Hashing
|
||||||
override internal var hash: Int { // Override NSObject.hash and not Hashable.hashValue or Hashable.hash(into:)
|
override public var hash: Int { // Override NSObject.hash and not Hashable.hashValue or Hashable.hash(into:)
|
||||||
return address.hashValue ^ port.hashValue
|
return address.hashValue ^ port.hashValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Description
|
// MARK: Description
|
||||||
override internal var description: String { return "\(address):\(port)" }
|
override public var description: String { return "\(address):\(port)" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@ import CryptoSwift
|
||||||
import PromiseKit
|
import PromiseKit
|
||||||
|
|
||||||
/// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information.
|
/// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information.
|
||||||
internal enum OnionRequestAPI {
|
public enum OnionRequestAPI {
|
||||||
/// - Note: Must only be modified from `LokiAPI.workQueue`.
|
/// - Note: Must only be modified from `LokiAPI.workQueue`.
|
||||||
private static var guardSnodes: Set<LokiAPITarget> = []
|
private static var guardSnodes: Set<LokiAPITarget> = []
|
||||||
/// - Note: Must only be modified from `LokiAPI.workQueue`.
|
/// - Note: Must only be modified from `LokiAPI.workQueue`.
|
||||||
private static var paths: Set<Path> = []
|
public static var paths: [Path] = []
|
||||||
|
|
||||||
private static var snodePool: Set<LokiAPITarget> {
|
private static var snodePool: Set<LokiAPITarget> {
|
||||||
let unreliableSnodes = Set(LokiAPI.failureCount.keys)
|
let unreliableSnodes = Set(LokiAPI.failureCount.keys)
|
||||||
|
@ -42,7 +42,7 @@ internal enum OnionRequestAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Path
|
// MARK: Path
|
||||||
internal typealias Path = [LokiAPITarget]
|
public typealias Path = [LokiAPITarget]
|
||||||
|
|
||||||
// MARK: Onion Building Result
|
// MARK: Onion Building Result
|
||||||
private typealias OnionBuildingResult = (guardSnode: LokiAPITarget, finalEncryptionResult: EncryptionResult, targetSnodeSymmetricKey: Data)
|
private typealias OnionBuildingResult = (guardSnode: LokiAPITarget, finalEncryptionResult: EncryptionResult, targetSnodeSymmetricKey: Data)
|
||||||
|
@ -54,7 +54,7 @@ internal enum OnionRequestAPI {
|
||||||
let queue = DispatchQueue.global() // No need to block the work queue for this
|
let queue = DispatchQueue.global() // No need to block the work queue for this
|
||||||
queue.async {
|
queue.async {
|
||||||
let url = "\(snode.address):\(snode.port)/get_stats/v1"
|
let url = "\(snode.address):\(snode.port)/get_stats/v1"
|
||||||
let timeout: TimeInterval = 6 // Use a shorter timeout for testing
|
let timeout: TimeInterval = 3 // Use a shorter timeout for testing
|
||||||
HTTP.execute(.get, url, timeout: timeout).done(on: queue) { rawResponse in
|
HTTP.execute(.get, url, timeout: timeout).done(on: queue) { rawResponse in
|
||||||
guard let json = rawResponse as? JSON, let version = json["version"] as? String else { return seal.reject(Error.missingSnodeVersion) }
|
guard let json = rawResponse as? JSON, let version = json["version"] as? String else { return seal.reject(Error.missingSnodeVersion) }
|
||||||
if version >= "2.0.0" {
|
if version >= "2.0.0" {
|
||||||
|
@ -100,15 +100,15 @@ internal enum OnionRequestAPI {
|
||||||
|
|
||||||
/// Builds and returns `pathCount` paths. The returned promise errors out with `Error.insufficientSnodes`
|
/// Builds and returns `pathCount` paths. The returned promise errors out with `Error.insufficientSnodes`
|
||||||
/// if not enough (reliable) snodes are available.
|
/// if not enough (reliable) snodes are available.
|
||||||
private static func buildPaths() -> Promise<Set<Path>> {
|
private static func buildPaths() -> Promise<[Path]> {
|
||||||
print("[Loki] [Onion Request API] Building onion request paths.")
|
print("[Loki] [Onion Request API] Building onion request paths.")
|
||||||
return LokiAPI.getRandomSnode().then(on: LokiAPI.workQueue) { _ -> Promise<Set<Path>> in // Just used to populate the snode pool
|
return LokiAPI.getRandomSnode().then(on: LokiAPI.workQueue) { _ -> Promise<[Path]> in // Just used to populate the snode pool
|
||||||
return getGuardSnodes().map(on: LokiAPI.workQueue) { guardSnodes in
|
return getGuardSnodes().map(on: LokiAPI.workQueue) { guardSnodes in
|
||||||
var unusedSnodes = snodePool.subtracting(guardSnodes)
|
var unusedSnodes = snodePool.subtracting(guardSnodes)
|
||||||
let pathSnodeCount = guardSnodeCount * pathSize - guardSnodeCount
|
let pathSnodeCount = guardSnodeCount * pathSize - guardSnodeCount
|
||||||
guard unusedSnodes.count >= pathSnodeCount else { throw Error.insufficientSnodes }
|
guard unusedSnodes.count >= pathSnodeCount else { throw Error.insufficientSnodes }
|
||||||
// Don't test path snodes as this would reveal the user's IP to them
|
// Don't test path snodes as this would reveal the user's IP to them
|
||||||
return Set(guardSnodes.map { guardSnode in
|
return guardSnodes.map { guardSnode in
|
||||||
let result = [ guardSnode ] + (0..<(pathSize - 1)).map { _ in
|
let result = [ guardSnode ] + (0..<(pathSize - 1)).map { _ in
|
||||||
// randomElement() uses the system's default random generator, which is cryptographically secure
|
// randomElement() uses the system's default random generator, which is cryptographically secure
|
||||||
let pathSnode = unusedSnodes.randomElement()! // Safe because of the pathSnodeCount check above
|
let pathSnode = unusedSnodes.randomElement()! // Safe because of the pathSnodeCount check above
|
||||||
|
@ -117,7 +117,7 @@ internal enum OnionRequestAPI {
|
||||||
}
|
}
|
||||||
print("[Loki] [Onion Request API] Built new onion request path: \(result.prettifiedDescription).")
|
print("[Loki] [Onion Request API] Built new onion request path: \(result.prettifiedDescription).")
|
||||||
return result
|
return result
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue