Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Iroh share UI #1649

Closed
wants to merge 2 commits into from
Closed
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
8 changes: 8 additions & 0 deletions DcCore/DcCore.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
30E8F2482449C98600CE2C90 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E8F2472449C98600CE2C90 /* UIView+Extensions.swift */; };
30E8F24B2449CF6500CE2C90 /* InitialsBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E8F24A2449CF6500CE2C90 /* InitialsBadge.swift */; };
30E8F24D2449D30200CE2C90 /* DcColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E8F24C2449D30200CE2C90 /* DcColors.swift */; };
78A8733F287766FA00690A0B /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 78A8733E287766FA00690A0B /* libc++.tbd */; };
78A873412877679B00690A0B /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 78A873402877679B00690A0B /* SystemConfiguration.framework */; };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that was automatically added when updating the targets I think.

/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -69,13 +71,17 @@
30E8F2472449C98600CE2C90 /* UIView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = "<group>"; };
30E8F24A2449CF6500CE2C90 /* InitialsBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialsBadge.swift; sourceTree = "<group>"; };
30E8F24C2449D30200CE2C90 /* DcColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DcColors.swift; sourceTree = "<group>"; };
78A8733E287766FA00690A0B /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/lib/libc++.tbd"; sourceTree = DEVELOPER_DIR; };
78A873402877679B00690A0B /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
3042192E243DE0F200516852 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
78A873412877679B00690A0B /* SystemConfiguration.framework in Frameworks */,
78A8733F287766FA00690A0B /* libc++.tbd in Frameworks */,
30421959243DE6AD00516852 /* libdeltachat.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -149,6 +155,8 @@
30421957243DE61400516852 /* Frameworks */ = {
isa = PBXGroup;
children = (
78A873402877679B00690A0B /* SystemConfiguration.framework */,
78A8733E287766FA00690A0B /* libc++.tbd */,
30421958243DE61400516852 /* libdeltachat.a */,
);
name = Frameworks;
Expand Down
34 changes: 34 additions & 0 deletions DcCore/DcCore/DC/Wrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,15 @@ public class DcContext {
public func imex(what: Int32, directory: String, passphrase: String? = nil) {
dc_imex(contextPointer, what, directory, passphrase)
}

public func send_backup(directory: String, passphrase: String? = nil) -> DcBackupSender? {
guard let dcBackupSenderPointer = dc_send_backup(contextPointer, directory, passphrase) else { return nil }
return DcBackupSender(dcBackupSenderPointer)
}

public func receiveBackup(qrCode: String, passphrase: String? = nil) {
dc_receive_backup(contextPointer, qrCode, passphrase)
}

public func imexHasBackup(filePath: String) -> String? {
var file: String?
Expand Down Expand Up @@ -1402,6 +1411,31 @@ public class DcLot {
}
}

public class DcBackupSender {
private var dcBackupSenderPointer: OpaquePointer?
private var dcContextPointer: OpaquePointer?

// takes ownership of specified pointer
public init(_ dcBackupSenderPointer: OpaquePointer) {
print(">>>> 💙 init DcBackupSender")
self.dcBackupSenderPointer = dcBackupSenderPointer
}

deinit {
print(">>>> 💙 deinit DcBackupSender")
dc_backup_sender_done(dcContextPointer, dcBackupSenderPointer)
}

public func qr_code(context: DcContext) -> String? {
self.dcContextPointer = context.contextPointer
guard let cString = dc_backup_sender_qr(context.contextPointer, dcBackupSenderPointer) else { return nil }
let swiftString = String(cString: cString)
dc_str_unref(cString)
return swiftString

}
}

public class DcProvider {
private var dcProviderPointer: OpaquePointer?

Expand Down
1 change: 1 addition & 0 deletions DcCore/DcCore/DC/wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ typedef dc_msg_t dc_msg_t;
typedef dc_lot_t dc_lot_t;
typedef dc_array_t dc_array_t;
typedef dc_chatlist_t dc_chatlist_t;
typedef dc_backup_sender_t dc_backup_sender_t;

#endif /* wrapper_h */
4 changes: 4 additions & 0 deletions deltachat-ios.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
7837B64021E54DC600CDE126 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 7837B63F21E54DC600CDE126 /* .swiftlint.yml */; };
785BE16821E247F1003BE98C /* MessageInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785BE16721E247F1003BE98C /* MessageInfoViewController.swift */; };
789E879621D6CB58003ED1C5 /* QrCodeReaderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789E879521D6CB58003ED1C5 /* QrCodeReaderController.swift */; };
78A8733D2877608200690A0B /* QrBackupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A8733C2877608200690A0B /* QrBackupViewController.swift */; };
78E45E3A21D3CFBC00D4B15E /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E3921D3CFBC00D4B15E /* SettingsController.swift */; };
78E45E4421D3F14A00D4B15E /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E4321D3F14A00D4B15E /* UIImage+Extension.swift */; };
78ED838321D5379000243125 /* TextFieldCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78ED838221D5379000243125 /* TextFieldCell.swift */; };
Expand Down Expand Up @@ -409,6 +410,7 @@
785BE16721E247F1003BE98C /* MessageInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageInfoViewController.swift; sourceTree = "<group>"; };
787D6699229F2237000A7A9D /* libdeltachat.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libdeltachat.a; path = "deltachat-ios/libraries/deltachat-core-rust/target/universal/debug/libdeltachat.a"; sourceTree = "<group>"; };
789E879521D6CB58003ED1C5 /* QrCodeReaderController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QrCodeReaderController.swift; sourceTree = "<group>"; };
78A8733C2877608200690A0B /* QrBackupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QrBackupViewController.swift; sourceTree = "<group>"; };
78C7036A21D46752005D4525 /* deltachat-ios.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "deltachat-ios.entitlements"; sourceTree = "<group>"; };
78E45E3921D3CFBC00D4B15E /* SettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsController.swift; sourceTree = "<group>"; };
78E45E4321D3F14A00D4B15E /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -941,6 +943,7 @@
789E879521D6CB58003ED1C5 /* QrCodeReaderController.swift */,
AECEF03D244F2D55006C90DA /* QrPageController.swift */,
30149D9222F21129003C12B5 /* QrViewController.swift */,
78A8733C2877608200690A0B /* QrBackupViewController.swift */,
B21005DA23383664004C70C5 /* SettingsClassicViewController.swift */,
30B0ACF924AB5B99004D5E29 /* SettingsEphemeralMessageController.swift */,
78E45E3921D3CFBC00D4B15E /* SettingsController.swift */,
Expand Down Expand Up @@ -1497,6 +1500,7 @@
30653081254358B10093E196 /* QuoteView.swift in Sources */,
3067AAC62667F3FE00525036 /* ImageFormat.swift in Sources */,
30E348DF24F3F819005C93D1 /* ChatTableView.swift in Sources */,
78A8733D2877608200690A0B /* QrBackupViewController.swift in Sources */,
30EF7308252F6A3300E2C54A /* PaddingTextView.swift in Sources */,
30E348E124F53772005C93D1 /* ImageTextCell.swift in Sources */,
3008CB7624F95B6D00E6A617 /* AudioController.swift in Sources */,
Expand Down
239 changes: 239 additions & 0 deletions deltachat-ios/Controller/QrBackupViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
import Foundation
import UIKit
import DcCore
import SDWebImageSVGKitPlugin

protocol QrBackupViewControllerDelegate: NSObject {
func onBackupFinished(sender: QrBackupViewController)
func onBackupStarted()
func isPreviousBackupRunning() -> Bool
func onDismissed()
}

class QrBackupViewController: UIViewController {

private let dcContext: DcContext
private let dcAccounts: DcAccounts
private var dcBackupSender: DcBackupSender?
// this delegate is not weak on purpose: we manually remove the referece once the ViewController has
// been dismissed if no backup is in progress OR if the ongoing backup finished and the
// ViewController has been dismissed in the past
public var delegate: QrBackupViewControllerDelegate?

private lazy var qrContentView: UIImageView = {
let view = UIImageView()
view.contentMode = .scaleAspectFit
view.translatesAutoresizingMaskIntoConstraints = false
view.accessibilityHint = String.localized("scan_to_transfer")
return view
}()

private lazy var progressContainer: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 20
view.clipsToBounds = true
view.layer.borderColor = DcColors.grey50.cgColor
view.layer.borderWidth = 1
view.backgroundColor = DcColors.defaultInverseColor.withAlphaComponent(0.5)
return view
}()

private lazy var progress: UIActivityIndicatorView = {
let progress = UIActivityIndicatorView(style: .white)
progress.translatesAutoresizingMaskIntoConstraints = false
return progress
}()

private lazy var blurView: UIVisualEffectView = {
let blurEffect = UIBlurEffect(style: .light)
let view = UIVisualEffectView(effect: blurEffect)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()

private var isBackupRunning = false
private var progressObserver: NSObjectProtocol?

init(dcAccounts: DcAccounts) {
self.dcAccounts = dcAccounts
self.dcContext = dcAccounts.getSelected()
super.init(nibName: nil, bundle: nil)
}

required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - lifecycle
override func viewDidLoad() {
super.viewDidLoad()
logger.debug(">>>>>>>>>>>>>>>>> viewDidLoad")
title = String.localized("setup_new_device")
setupSubviews()
view.backgroundColor = DcColors.defaultBackgroundColor
if let delegate = delegate, !delegate.isPreviousBackupRunning() {
logger.debug(">>>> viewDidLoad no previous Backup Running starting Backup")
startBackup()
}
}

override func viewDidDisappear(_ animated: Bool) {
logger.debug(">>>> onViewDidDisappear")
delegate?.onDismissed()
if isBackupRunning {
logger.debug(">>>> stop ongoingProcess")
self.dcContext.stopOngoingProcess()
} else {
delegate = nil
dcBackupSender = nil
logger.debug(">>>> nil delegate")
}
}

public func startBackup() {
let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
if !documents.isEmpty {
self.isBackupRunning = true
logger.debug(">>>> startBackup isBackupRunning: \(isBackupRunning)")
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
guard let self = self else { return }
self.delegate?.onBackupStarted()
self.dcAccounts.stopIo()
self.dcBackupSender = self.dcContext.send_backup(directory: documents[0], passphrase: nil)
self.dcAccounts.startIo()
self.delegate?.onBackupFinished(sender: self)
DispatchQueue.main.async {
self.isBackupRunning = false
logger.debug(">>>> startBackup finishing - isBackupRunning: \(self.isBackupRunning)")
logger.debug(">>>> startBackup has parent: \(self.parent != nil)")
if self.parent == nil {
logger.debug(">>>> nil delegate")
self.delegate = nil
self.dcBackupSender = nil
} else {
let image = self.getQrImage(svg: self.dcBackupSender?.qr_code(context: self.dcContext))
self.qrContentView.image = image
self.progress.stopAnimating()
self.progressContainer.isHidden = true
}
}
}
} else {
logger.error("document directory not found")
self.dcBackupSender = nil
}
}

// MARK: - setup
private func setupSubviews() {
view.addSubview(qrContentView)
view.addSubview(progressContainer)
progressContainer.addSubview(blurView)
progressContainer.addSubview(progress)
let qrDefaultWidth = qrContentView.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor, multiplier: 0.75)
qrDefaultWidth.priority = UILayoutPriority(500)
qrDefaultWidth.isActive = true
let qrMinWidth = qrContentView.widthAnchor.constraint(lessThanOrEqualToConstant: 260)
qrMinWidth.priority = UILayoutPriority(999)
qrMinWidth.isActive = true
view.addConstraints([
qrContentView.heightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.heightAnchor, multiplier: 1.05),
qrContentView.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor),
qrContentView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
progressContainer.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor),
progressContainer.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
progressContainer.constraintHeightTo(100),
progressContainer.constraintWidthTo(100),
progress.constraintCenterXTo(progressContainer),
progress.constraintCenterYTo(progressContainer)
])
blurView.fillSuperview()
progressContainer.isHidden = false
progress.startAnimating()
}

private func getQrImage(svg: String?) -> UIImage? {
if let svg = svg {
let svgData = svg.data(using: .utf8)
return SDImageSVGKCoder.shared.decodedImage(with: svgData, options: [:])
}
return nil
}

}


/// Does a best effort attempt to trigger the local network privacy alert.
///
/// It works by sending a UDP datagram to the discard service (port 9) of every
/// IP address associated with a broadcast-capable interface. This should
/// trigger the local network privacy alert, assuming the alert hasn’t already
/// been displayed for this app.
///
/// This code takes a ‘best effort’. It handles errors by ignoring them. As
/// such, there’s guarantee that it’ll actually trigger the alert.
///
/// - note: iOS devices don’t actually run the discard service. I’m using it
/// here because I need a port to send the UDP datagram to and port 9 is
/// always going to be safe (either the discard service is running, in which
/// case it will discard the datagram, or it’s not, in which case the TCP/IP
/// stack will discard it).
///
/// There should be a proper API for this (r. 69157424).
///
/// For more background on this, see [Triggering the Local Network Privacy Alert](https://developer.apple.com/forums/thread/663768).
/// [via https://developer.apple.com/forums/thread/663768 ]
func triggerLocalNetworkPrivacyAlert() {
let sock4 = socket(AF_INET, SOCK_DGRAM, 0)
guard sock4 >= 0 else { return }
defer { close(sock4) }
let sock6 = socket(AF_INET6, SOCK_DGRAM, 0)
guard sock6 >= 0 else { return }
defer { close(sock6) }

let addresses = addressesOfDiscardServiceOnBroadcastCapableInterfaces()
var message = [UInt8]("!".utf8)
for address in addresses {
address.withUnsafeBytes { buf in
let sa = buf.baseAddress!.assumingMemoryBound(to: sockaddr.self)
let saLen = socklen_t(buf.count)
let sock = sa.pointee.sa_family == AF_INET ? sock4 : sock6
_ = sendto(sock, &message, message.count, MSG_DONTWAIT, sa, saLen)
}
}
}

/// Returns the addresses of the discard service (port 9) on every
/// broadcast-capable interface.
///
/// Each array entry is contains either a `sockaddr_in` or `sockaddr_in6`.
private func addressesOfDiscardServiceOnBroadcastCapableInterfaces() -> [Data] {
var addrList: UnsafeMutablePointer<ifaddrs>? = nil
let err = getifaddrs(&addrList)
guard err == 0, let start = addrList else { return [] }
defer { freeifaddrs(start) }
return sequence(first: start, next: { $0.pointee.ifa_next })
.compactMap { i -> Data? in
guard
(i.pointee.ifa_flags & UInt32(bitPattern: IFF_BROADCAST)) != 0,
let sa = i.pointee.ifa_addr
else { return nil }
var result = Data(UnsafeRawBufferPointer(start: sa, count: Int(sa.pointee.sa_len)))
switch CInt(sa.pointee.sa_family) {
case AF_INET:
result.withUnsafeMutableBytes { buf in
let sin = buf.baseAddress!.assumingMemoryBound(to: sockaddr_in.self)
sin.pointee.sin_port = UInt16(9).bigEndian
}
case AF_INET6:
result.withUnsafeMutableBytes { buf in
let sin6 = buf.baseAddress!.assumingMemoryBound(to: sockaddr_in6.self)
sin6.pointee.sin6_port = UInt16(9).bigEndian
}
default:
return nil
}
return result
}
}
4 changes: 3 additions & 1 deletion deltachat-ios/Controller/QrPageController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,15 @@ extension QrPageController: UIPageViewControllerDataSource, UIPageViewController
if viewController is QrViewController {
return nil
}

return QrViewController(dcContext: dcContext, qrCodeHint: qrCodeHint)
}

func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if viewController is QrViewController {
return makeQRReader()
}

return nil
}

Expand Down Expand Up @@ -211,7 +213,7 @@ extension QrPageController: QrCodeReaderDelegate {
}))
present(alert, animated: true, completion: nil)

case DC_QR_ACCOUNT:
case DC_QR_ACCOUNT, DC_QR_BACKUP:
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.appCoordinator.presentWelcomeController(accountCode: code)
}
Expand Down
Loading