283 lines
10 KiB
Swift
283 lines
10 KiB
Swift
//
|
|
// TrafficPanelPresenter.swift
|
|
// PrivadoVPN
|
|
//
|
|
// Created by Juraldinio on 1/31/21.
|
|
// Copyright © 2021 Privado LLC. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
struct TrafficPanelData {
|
|
let total: UInt
|
|
let left: UInt
|
|
}
|
|
|
|
protocol TrafficPanelOutput: AnyObject {
|
|
var session: Session? { get }
|
|
var customerInput: CustomerModuleInput { get }
|
|
var trafficEmitter: Emitter<TrafficPanelData> { get }
|
|
|
|
func navigate(to url: URL)
|
|
}
|
|
|
|
// https://gitlab.com/omicronmedia/omivpn/privado-ios/-/blob/develop/iOS/Privado/Sources/Flows/ServersFlow/Submodules/Upgrade/Presenter/UpgradePresenter.swift
|
|
final class TrafficPanelPresenter {
|
|
|
|
private enum Constants {
|
|
|
|
enum LocalizedString {
|
|
static let buttonTilte = "trafficUsage.button.text"
|
|
static let lessThanOneGB = "trafficUsage.message.lessThanOneGB"
|
|
static let gigaBytesLeft = "trafficUsage.message.gigaBytesLeft"
|
|
}
|
|
}
|
|
|
|
|
|
weak var viewInput: TrafficPanelViewInput?
|
|
|
|
private weak var output: TrafficPanelOutput?
|
|
|
|
private var vpnConnectionState = VPNConnectionState.disconnected
|
|
private var currentRecord: InterfaceAddress.Record?
|
|
private var requireCleanup = false
|
|
|
|
private var trafficAllowed: UInt64?
|
|
private let trafficUsage = TrafficUsage.createTrafficUsage()
|
|
|
|
private var customerPlan: Customer.Plan?
|
|
private var actionUrl: URL?
|
|
|
|
private var currentUsage: UInt64 = 0 {
|
|
didSet {
|
|
guard currentUsage >= oldValue else {
|
|
self.trafficUsage.add(bytes: currentUsage)
|
|
return
|
|
}
|
|
self.trafficUsage.add(bytes: currentUsage - oldValue)
|
|
}
|
|
}
|
|
|
|
// MARK: - Init
|
|
|
|
init(output: TrafficPanelOutput?) {
|
|
|
|
self.output = output
|
|
|
|
if let customerInput = output?.customerInput {
|
|
|
|
customerInput.customerEmitter.addReaction { [weak self] customer -> ShouldContinueReceiveNotifications in
|
|
if let self = self
|
|
, let customer = customer {
|
|
self.handle(customer: customer)
|
|
}
|
|
return self.isExist
|
|
}
|
|
}
|
|
|
|
self.trafficUsage.bytesCountEmitter.addReaction { [weak self] bytesCount -> ShouldContinueReceiveNotifications in
|
|
if let self = self
|
|
, let plan = self.customerPlan
|
|
, plan == .freemium {
|
|
|
|
// let bytesCount: UInt64 = 4 * TrafficUsage.Unit.gigaByte
|
|
self.handleProgress(for: bytesCount)
|
|
self.handleMessage(for: bytesCount)
|
|
}
|
|
return self.isExist
|
|
}
|
|
|
|
output?.session?.vpnConnectionStateEmitter.addReaction { [weak self] state, service in
|
|
if let self = self {
|
|
self.handleVPN(service: service, change: state)
|
|
}
|
|
return self.isExist
|
|
}
|
|
|
|
output?.trafficEmitter.addReaction { [weak self] record -> ShouldContinueReceiveNotifications in
|
|
guard let self = self else { return false }
|
|
|
|
self.trafficAllowed = UInt64(record.total) * TrafficUsage.Unit.megaByte
|
|
self.trafficUsage.set(bytes: UInt64(record.total - record.left) * TrafficUsage.Unit.megaByte)
|
|
|
|
self.handleProgress(for: self.trafficUsage.get())
|
|
|
|
return true
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Private
|
|
|
|
private func handle(customer: Customer) {
|
|
|
|
self.actionUrl = customer.loginUrl.flatMap { URL(string: $0) }
|
|
self.customerPlan = customer.plan
|
|
|
|
switch customer.plan {
|
|
case .freemium:
|
|
self.trafficAllowed = UInt64(customer.trafficTotal) * TrafficUsage.Unit.megaByte
|
|
let bytesSpent = UInt64(customer.trafficTotal - customer.trafficLeft) * TrafficUsage.Unit.megaByte
|
|
self.trafficUsage.set(bytes: bytesSpent)
|
|
self.viewInput?.updateView(visibility: true)
|
|
case .overquota:
|
|
self.viewInput?.updateView(progress: 1.0)
|
|
let messageLocalized = NSLocalizedString(Constants.LocalizedString.gigaBytesLeft,
|
|
comment: " Remaining this month")
|
|
let gigabytes = "0 GB"
|
|
let attributes: [NSAttributedString.Key : NSFont] = [
|
|
NSAttributedString.Key.font : NSFont(name: PrivadoConstants.Font.bold, size: 12) ?? .boldSystemFont(ofSize: 12)
|
|
]
|
|
let messageAtrb: [NSAttributedString.Key : NSFont] = [
|
|
NSAttributedString.Key.font : NSFont(name: PrivadoConstants.Font.medium, size: 12) ?? .systemFont(ofSize: 12)
|
|
]
|
|
let messageAttributed = NSAttributedString(string: messageLocalized, attributes: messageAtrb)
|
|
let gigaBitesString = NSMutableAttributedString(string: gigabytes, attributes: attributes)
|
|
gigaBitesString.append(messageAttributed)
|
|
self.viewInput?.updateView(message: gigaBitesString)
|
|
self.viewInput?.updateView(visibility: true)
|
|
default:
|
|
self.viewInput?.updateView(visibility: false)
|
|
}
|
|
}
|
|
|
|
private func handleVPN(service: VPNService?, change state: VPNConnectionState) {
|
|
|
|
guard let service = service
|
|
, self.vpnConnectionState != state else { return }
|
|
|
|
switch state {
|
|
case .connected:
|
|
|
|
self.requireCleanup = false
|
|
|
|
if let interface = service.interface
|
|
, let record = InterfaceAddress.instance.record(with: interface)
|
|
, self.currentRecord != record {
|
|
|
|
print("TrafficCounterPresenter: \(interface)")
|
|
|
|
self.currentRecord = record
|
|
|
|
record.statistic.addReaction { [weak self] record in
|
|
|
|
// print("\(interface): \(record.received) ~ \(record.sent)")
|
|
|
|
guard let self = self else { return false }
|
|
var recordSum = record.received // + record.sent
|
|
if interface.lowercased().starts(with: "ipsec") {
|
|
recordSum /= 2
|
|
}
|
|
self.currentUsage = recordSum
|
|
return !self.requireCleanup
|
|
}
|
|
|
|
}
|
|
|
|
InterfaceAddress.instance.resume()
|
|
|
|
case .disconnected:
|
|
|
|
self.requireCleanup = true
|
|
InterfaceAddress.instance.pause()
|
|
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
private func handleProgress(for bytes: UInt64) {
|
|
|
|
if let trafficAllowed = self.trafficAllowed
|
|
, trafficAllowed != 0 {
|
|
// print("Loaded bytes: \(Double(bytes) / 1024.0 / 1024.0) and allowed: \(trafficAllowed)")
|
|
let progress = Double(bytes) / Double(trafficAllowed)
|
|
self.viewInput?.updateView(progress: progress)
|
|
}
|
|
}
|
|
|
|
private func handleMessage(for bytes: UInt64) {
|
|
|
|
guard let trafficAllowed = self.trafficAllowed else { return }
|
|
|
|
let trafficRemaining = trafficAllowed >= bytes ? trafficAllowed - bytes : 0
|
|
|
|
let message: String
|
|
let attribuedString: NSAttributedString
|
|
if trafficRemaining != 0
|
|
, trafficRemaining < TrafficUsage.Unit.gigaByte {
|
|
message = NSLocalizedString(Constants.LocalizedString.lessThanOneGB,
|
|
comment: "Less than 1 Gb remaining this month")
|
|
attribuedString = NSAttributedString(string: message)
|
|
} else {
|
|
let gigaBytesRemaining = Int((Double(trafficRemaining) / Double(TrafficUsage.Unit.gigaByte)).rounded())
|
|
let messageLocalized = NSLocalizedString(Constants.LocalizedString.gigaBytesLeft,
|
|
comment: " remaining this month")
|
|
let gigaBytesString = String(gigaBytesRemaining) + " GB"
|
|
let gigaBytesAtrb: [NSAttributedString.Key : NSFont] = [
|
|
NSAttributedString.Key.font : NSFont(name: PrivadoConstants.Font.bold, size: 12) ?? .boldSystemFont(ofSize: 12)
|
|
]
|
|
let messageAtrb: [NSAttributedString.Key : NSFont] = [
|
|
NSAttributedString.Key.font : NSFont(name: PrivadoConstants.Font.medium, size: 12) ?? .systemFont(ofSize: 12)
|
|
]
|
|
let gigaBitesString = NSMutableAttributedString(string: gigaBytesString, attributes: gigaBytesAtrb)
|
|
let string = NSMutableAttributedString(string: messageLocalized, attributes: messageAtrb)
|
|
message = String(format: messageLocalized, gigaBitesString)
|
|
gigaBitesString.append(string)
|
|
attribuedString = gigaBitesString
|
|
}
|
|
|
|
self.viewInput?.updateView(message: attribuedString)
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - TrafficPanelViewOutput
|
|
|
|
extension TrafficPanelPresenter: TrafficPanelViewOutput {
|
|
|
|
func viewIsReady(viewInput: TrafficPanelViewInput) {
|
|
|
|
if let customer = self.output?.customerInput.customer {
|
|
self.handle(customer: customer)
|
|
}
|
|
self.viewInput?.updateView(button: NSLocalizedString(Constants.LocalizedString.buttonTilte, comment: ""))
|
|
|
|
if let customer = self.output?.customerInput.customer {
|
|
switch customer.plan {
|
|
case .premium: self.viewInput?.updateView(visibility: false)
|
|
default: self.viewInput?.updateView(visibility: true)
|
|
}
|
|
} else {
|
|
self.viewInput?.updateView(visibility: false)
|
|
}
|
|
}
|
|
|
|
func viewWillDisappear(viewInput: TrafficPanelViewInput) { }
|
|
|
|
func viewButtonClicked(viewInput: TrafficPanelViewInput) {
|
|
guard let url = self.actionUrl else { return }
|
|
CometLogger.shared.event(name: PrivadoConstants.Event.Statistic.Application.upgradeButton,
|
|
attributes: [PrivadoConstants.Event.Attributes.extradata: [PrivadoConstants.Event.Attributes.screenId: PrivadoConstants.Event.Screen.trafficProgress]],
|
|
secured: nil)
|
|
self.output?.navigate(to: url)
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
|
|
final class TrafficCounterPresenter: TrafficCounterViewOutput {s
|
|
|
|
private var actionNavigateUrl: URL?
|
|
|
|
// MARK: - TrafficCounterViewOutput
|
|
|
|
func didTapUpgradeButton() {
|
|
guard let navigateUrl = self.actionNavigateUrl else { return }
|
|
self.output?.navigate(to: .navigate(url: navigateUrl))
|
|
}
|
|
}
|
|
|
|
*/
|