mirror of https://github.com/oxen-io/session-ios
Implement basic onion request UI
parent
8fb5e7102f
commit
00f5cebdef
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue