Files

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))
}
}
*/