Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions LocalPackages/PIALibrary/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ let package = Package(
.package(url: "git@github.com:pia-foss/mobile-ios-openvpn.git", branch: "master"),
.package(url: "git@github.com:pia-foss/mobile-ios-wireguard.git", revision: "bf7b4258d9c9279c6051bba0b7a73ca7d5f9547e"),
.package(url: "https://github.com/hkellaway/Gloss.git", from: "3.1.0"),
.package(url: "https://github.com/airbnb/lottie-ios.git", from: "3.4.1"),
.package(url: "https://github.com/huri000/SwiftEntryKit.git", from: "1.0.3"),
.package(url: "https://github.com/Orderella/PopupDialog.git", branch: "master"),
.package(url: "https://github.com/SwiftyBeaver/SwiftyBeaver.git", from: "1.9.0"),
Expand All @@ -46,7 +45,6 @@ let package = Package(
"Alamofire",
.product(name: "PopupDialog", package: "PopupDialog", condition: .when(platforms: [.iOS])),
.product(name: "SwiftEntryKit", package: "SwiftEntryKit", condition: .when(platforms: [.iOS])),
.product(name: "Lottie", package: "lottie-ios"),
.product(name: "Reachability", package: "Reachability.swift"),
.product(name: "PIAKPI", package: "mobile-ios-releases-kpi"),
.product(name: "PIACSI", package: "mobile-ios-releases-csi"),
Expand Down
8 changes: 0 additions & 8 deletions PIA VPN-tvOS/Resources/Licences.plist
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,6 @@
<key>LicenseURL</key>
<string>https://raw.githubusercontent.com/huri000/SwiftEntryKit/master/LICENSE</string>
</dict>
<dict>
<key>Name</key>
<string>lottie-ios</string>
<key>Copyright</key>
<string>Copyright 2018 Airbnb, Inc.</string>
<key>LicenseURL</key>
<string>https://raw.githubusercontent.com/airbnb/lottie-ios/master/LICENSE</string>
</dict>
<dict>
<key>Name</key>
<string>nanopb</string>
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 6 additions & 14 deletions PIA VPN/Resources/Components.plist
Original file line number Diff line number Diff line change
Expand Up @@ -145,28 +145,20 @@
</dict>
<dict>
<key>Name</key>
<string>lottie-ios</string>
<string>nanopb</string>
<key>Copyright</key>
<string>Copyright 2018 Airbnb, Inc.</string>
<string>Copyright (c) 2011 Petteri Aimonen</string>
<key>LicenseURL</key>
<string>https://raw.githubusercontent.com/airbnb/lottie-ios/master/LICENSE</string>
<string>https://raw.githubusercontent.com/nanopb/nanopb/master/LICENSE.txt</string>
</dict>
<dict>
<key>Name</key>
<string>nanopb</string>
<string>Popover</string>
<key>Copyright</key>
<string>Copyright (c) 2011 Petteri Aimonen</string>
<string>© 2020 corin8823</string>
<key>LicenseURL</key>
<string>https://raw.githubusercontent.com/nanopb/nanopb/master/LICENSE.txt</string>
<string>https://raw.githubusercontent.com/corin8823/Popover/master/LICENSE</string>
</dict>
<dict>
<key>Name</key>
<string>Popover</string>
<key>Copyright</key>
<string>© 2020 corin8823</string>
<key>LicenseURL</key>
<string>https://raw.githubusercontent.com/corin8823/Popover/master/LICENSE</string>
</dict>
</array>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "pia-spinner.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion PIA VPN/Resources/Lottie/pia-spinner.json

This file was deleted.

169 changes: 44 additions & 125 deletions PIA VPN/UI/AutolayoutViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
//

import UIKit
import Lottie
import PIALibrary
/// Declares a generic, dismissable modal controller.
public protocol ModalController: AnyObject {
Expand Down Expand Up @@ -72,9 +71,13 @@ open class AutolayoutViewController: UIViewController, ModalController, Restylab
open var status: ViewControllerStatus = .initial {
didSet { reloadFormElements() }
}


/// Loading view controller for showing animations
private var loadingViewController: LoadingViewController?

deinit {
NotificationCenter.default.removeObserver(self)
loadingViewController?.view.removeFromSuperview()
}

/// :nodoc:
Expand Down Expand Up @@ -221,139 +224,55 @@ open class AutolayoutViewController: UIViewController, ModalController, Restylab
}

extension AutolayoutViewController: AnimatingLoadingDelegate {

private struct LottieRepos {
static var graphLoad: AnimationView?
static var containerView: UIView?
}

var graphLoad: AnimationView? {
get {
return objc_getAssociatedObject(self, &LottieRepos.graphLoad) as? AnimationView
}
set {
if let unwrappedValue = newValue {
objc_setAssociatedObject(self, &LottieRepos.graphLoad, unwrappedValue as AnimationView?, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}

var containerView: UIView? {
get {
return LottieRepos.containerView
}
set {
if let unwrappedValue = newValue {
LottieRepos.containerView = unwrappedValue
}
}
}

@objc public func showLoadingAnimation() {
if graphLoad == nil {
containerView = UIView(frame: UIScreen.main.bounds)
containerView?.backgroundColor = Theme.current.palette.appearance == .dark ?
UIColor.black.withAlphaComponent(0.72) :
UIColor.piaGrey1.withAlphaComponent(0.75)
graphLoad = AnimationView(name: "pia-spinner")
}
addLoadingAnimation()
}

private func addLoadingAnimation() {
graphLoad?.loopMode = .loop
if let graphLoad = graphLoad,
let containerView = containerView {
if let key = self.navigationController?.view {
key.addSubview(containerView)
key.addSubview(graphLoad)
}
setLoadingConstraints()
graphLoad.play()
// If already showing, keep it
guard loadingViewController == nil else { return }

guard
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first
else {
return
}
}

@objc public func hideLoadingAnimation() {
graphLoad?.stop()
graphLoad?.removeFromSuperview()
containerView?.removeFromSuperview()
}

private func setLoadingConstraints() {
if let graphLoad = graphLoad,
let keyView = self.navigationController?.view,
let containerView = containerView {

containerView.translatesAutoresizingMaskIntoConstraints = false
graphLoad.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint(item: containerView,
attribute: .left,
relatedBy: .equal,
toItem: keyView,
attribute: .left,
multiplier: 1.0,
constant: 0.0).isActive = true
// Lock UI
window.isUserInteractionEnabled = false

NSLayoutConstraint(item: containerView,
attribute: .right,
relatedBy: .equal,
toItem: keyView,
attribute: .right,
multiplier: 1.0,
constant: 0.0).isActive = true
let loadingVC = LoadingViewController()
loadingVC.view.translatesAutoresizingMaskIntoConstraints = false
self.loadingViewController = loadingVC

NSLayoutConstraint(item: containerView,
attribute: .top,
relatedBy: .equal,
toItem: keyView,
attribute: .top,
multiplier: 1.0,
constant: 0.0).isActive = true
window.addSubview(loadingVC.view)

NSLayoutConstraint(item: containerView,
attribute: .bottom,
relatedBy: .equal,
toItem: keyView,
attribute: .bottom,
multiplier: 1.0,
constant: 0.0).isActive = true
setupLoadingConstraints(
loadingView: loadingVC.view,
in: window
)
}

NSLayoutConstraint(item: graphLoad,
attribute: .centerX,
relatedBy: .equal,
toItem: containerView,
attribute: .centerX,
multiplier: 1.0,
constant: 0.0).isActive = true

NSLayoutConstraint(item: graphLoad,
attribute: .centerY,
relatedBy: .equal,
toItem: containerView,
attribute: .centerY,
multiplier: 1.0,
constant: 0.0).isActive = true

let lottieWidth = UIScreen.main.bounds.width/4
@objc public func hideLoadingAnimation() {
// Unlock UI
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
let window = windowScene?.windows.first
window?.isUserInteractionEnabled = true

NSLayoutConstraint(item: graphLoad,
attribute: .width,
relatedBy: .equal,
toItem: nil,
attribute: .width,
multiplier: 1.0,
constant: lottieWidth).isActive = true

NSLayoutConstraint(item: graphLoad,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .height,
multiplier: 1.0,
constant: lottieWidth).isActive = true
loadingViewController?.view.removeFromSuperview()
loadingViewController = nil
}

}
private func setupLoadingConstraints(
loadingView: UIView,
in parentView: UIView
) {
// Loading view centered in parent with fixed size
let loadingSize = UIScreen.main.bounds.width / 4
NSLayoutConstraint.activate([
loadingView.centerXAnchor.constraint(equalTo: parentView.centerXAnchor),
loadingView.centerYAnchor.constraint(equalTo: parentView.centerYAnchor),
loadingView.widthAnchor.constraint(equalToConstant: loadingSize),
loadingView.heightAnchor.constraint(equalToConstant: loadingSize)
])
}

}
60 changes: 60 additions & 0 deletions PIA VPN/UI/Loading/LoadingView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// LoadingView.swift
// PIA VPN
//
// Created by Diego Trevisan on 16.12.25.
// Copyright © 2025 Private Internet Access, Inc.
//
// This file is part of the Private Internet Access iOS Client.
//
// The Private Internet Access iOS Client is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any later version.
//
// The Private Internet Access iOS Client is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
// details.
//
// You should have received a copy of the GNU General Public License along with the Private
// Internet Access iOS Client. If not, see <https://www.gnu.org/licenses/>.
//

import SwiftUI
import UIKit

struct LoadingView: View {
@State private var isRotating = false

var body: some View {
Image(asset: Asset.Ui.piaSpinner)
.resizable()
.aspectRatio(contentMode: .fit)
.rotationEffect(.degrees(isRotating ? 360 : 0))
.animation(
.linear(duration: 1.0)
.repeatForever(autoreverses: false),
value: isRotating
)
.onAppear {
isRotating = true
}
}
}

/// UIKit wrapper for LoadingView
final class LoadingViewController: UIHostingController<LoadingView> {
init() {
super.init(rootView: LoadingView())
view.backgroundColor = .clear
}

@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

#Preview {
LoadingView()
}
1 change: 1 addition & 0 deletions PIA VPN/UI/Shared/SwiftGen+Assets.swift
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ internal enum Asset {
internal static let imageWalkthrough2 = ImageAsset(name: "image-walkthrough-2")
internal static let imageWalkthrough3 = ImageAsset(name: "image-walkthrough-3")
internal static let navLogo = ImageAsset(name: "nav-logo")
internal static let piaSpinner = ImageAsset(name: "pia-spinner")
internal static let qrCode = ImageAsset(name: "qr-code")
}
}
Expand Down
Loading