Skip to content
Open
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
4 changes: 4 additions & 0 deletions ios/Plugin.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
34ECC0E425BE199F00881175 /* Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34ECC0E125BE199F00881175 /* Conversion.swift */; };
34ECC0E525BE199F00881175 /* DeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34ECC0E225BE199F00881175 /* DeviceManager.swift */; };
34ECC2D725C0BAEC00881175 /* ConversionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34ECC2D625C0BAEC00881175 /* ConversionTests.swift */; };
36EC9FF52D50C166009750C9 /* DeviceListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36EC9FF42D50C166009750C9 /* DeviceListView.swift */; };
50ADFF92201F53D600D50D53 /* Plugin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50ADFF88201F53D600D50D53 /* Plugin.framework */; };
50ADFF97201F53D600D50D53 /* PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ADFF96201F53D600D50D53 /* PluginTests.swift */; };
50ADFF99201F53D600D50D53 /* Plugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 50ADFF8B201F53D600D50D53 /* Plugin.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand All @@ -40,6 +41,7 @@
34ECC0E125BE199F00881175 /* Conversion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Conversion.swift; sourceTree = "<group>"; };
34ECC0E225BE199F00881175 /* DeviceManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceManager.swift; sourceTree = "<group>"; };
34ECC2D625C0BAEC00881175 /* ConversionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversionTests.swift; sourceTree = "<group>"; };
36EC9FF42D50C166009750C9 /* DeviceListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceListView.swift; sourceTree = "<group>"; };
3B2A61DA5A1F2DD4F959604D /* Pods_Plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; };
50ADFF88201F53D600D50D53 /* Plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; };
50ADFF8B201F53D600D50D53 /* Plugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Plugin.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -106,6 +108,7 @@
34E541D52962E2EA007544B1 /* Logging.swift */,
34ECC0E125BE199F00881175 /* Conversion.swift */,
34ECC0E025BE199F00881175 /* Device.swift */,
36EC9FF42D50C166009750C9 /* DeviceListView.swift */,
34ECC0E225BE199F00881175 /* DeviceManager.swift */,
50E1A94720377CB70090CE1A /* Plugin.swift */,
50ADFF8B201F53D600D50D53 /* Plugin.h */,
Expand Down Expand Up @@ -326,6 +329,7 @@
34ECC0E425BE199F00881175 /* Conversion.swift in Sources */,
50E1A94820377CB70090CE1A /* Plugin.swift in Sources */,
34ECC0E525BE199F00881175 /* DeviceManager.swift in Sources */,
36EC9FF52D50C166009750C9 /* DeviceListView.swift in Sources */,
50ADFFA82020EE4F00D50D53 /* Plugin.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
121 changes: 121 additions & 0 deletions ios/Plugin/DeviceListView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import UIKit

class DeviceListView: UIViewController, UITableViewDelegate, UITableViewDataSource {

private let tableView = UITableView()
private let cancelButton = UIButton(type: .system)
private let titleLabel = UILabel()
private let progressIndication = UIActivityIndicatorView()
private var onCancel: (() -> Void)?
private var devices: [(name: String, action: () -> Void)] = []

override func viewDidLoad() {
super.viewDidLoad()
isModalInPresentation = true // don't allow drag to dismiss
setupUI()
addBlurBackground()
}

func setTitle(_ title: String?) {
titleLabel.text = title
}

func setCancelButton(_ title: String?, action: @escaping () -> Void) {

Choose a reason for hiding this comment

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

action is never used here i think

Copy link
Collaborator

Choose a reason for hiding this comment

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

@talaviram any chance you've got some time to double-check this comment?

Copy link
Author

Choose a reason for hiding this comment

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

Hey, sorry. been busy :)

@PAFriedrichMueller thank you for taking the time and discovering the missing call.

I've pushed a commit that can be squashed/fixup (so it'll be easier to review).
I've simply forgot to pass the action closure properly (oops).
Let me know if it's working as expected now.

cancelButton.setTitle(title, for: .normal)
self.onCancel = action
}

func addItem(_ name: String, action: @escaping () -> Void) {
devices.append((name, action))
tableView.reloadData()
}

private func addBlurBackground() {
let blurEffect = UIBlurEffect(style: .systemChromeMaterial)
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.frame = view.bounds
blurView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.insertSubview(blurView, at: 0)
}

private func setupUI() {
titleLabel.textAlignment = .center
titleLabel.font = UIFont.boldSystemFont(ofSize: 22)

let titleStack = UIStackView(arrangedSubviews: [titleLabel, progressIndication])
titleStack.axis = .horizontal
titleStack.alignment = .center
titleStack.distribution = .fill
titleStack.spacing = 8
titleStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(titleStack)

progressIndication.translatesAutoresizingMaskIntoConstraints = false
progressIndication.startAnimating()
progressIndication.hidesWhenStopped = true
progressIndication.setContentHuggingPriority(.required, for: .horizontal)
NSLayoutConstraint.activate([
progressIndication.widthAnchor.constraint(equalToConstant: 22),
progressIndication.heightAnchor.constraint(equalToConstant: 22)
])

tableView.backgroundColor = .clear
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
view.addSubview(tableView)

// Cancel Button
cancelButton.setTitleColor(.systemRed, for: .normal)
cancelButton.addTarget(self, action: #selector(dismissPopover), for: .touchUpInside)
cancelButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(cancelButton)

NSLayoutConstraint.activate([
titleStack.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
titleStack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30),
titleStack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30),
titleStack.heightAnchor.constraint(equalToConstant: 30),

// TableView
tableView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
tableView.bottomAnchor.constraint(equalTo: cancelButton.topAnchor, constant: -10),

// Cancel Button at the Bottom
cancelButton.leadingAnchor.constraint(equalTo: view.leadingAnchor),
cancelButton.trailingAnchor.constraint(equalTo: view.trailingAnchor),
cancelButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
cancelButton.heightAnchor.constraint(equalToConstant: 40)
])
}

@objc private func dismissPopover() {
onCancel?()
dismiss(animated: true)
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return devices.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = devices[indexPath.row].name
cell.textLabel?.textColor = .systemBlue
cell.textLabel?.backgroundColor = .clear
cell.backgroundColor = .clear
cell.contentView.backgroundColor = .clear
cell.textLabel?.textAlignment = .center
cell.selectionStyle = .default
return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
devices[indexPath.row].action() // Execute closure action
dismiss(animated: true) // Optionally close the popover
}
}
23 changes: 14 additions & 9 deletions ios/Plugin/DeviceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ class DeviceManager: NSObject, CBCentralManagerDelegate {
private var stateReceiver: StateReceiver?
private var timeoutMap = [String: DispatchWorkItem]()
private var stopScanWorkItem: DispatchWorkItem?
private var alertController: UIAlertController?
private var deviceListView: DeviceListView?
private var popoverController: UIPopoverPresentationController?
private var discoveredDevices = [String: Device]()
private var deviceNameFilter: String?
private var deviceNamePrefixFilter: String?
Expand Down Expand Up @@ -129,9 +130,9 @@ class DeviceManager: NSObject, CBCentralManagerDelegate {
self.stopScanWorkItem = nil
DispatchQueue.main.async { [weak self] in
if self?.discoveredDevices.count == 0 {
self?.alertController?.title = self?.displayStrings["noDeviceFound"]
self?.deviceListView?.setTitle (self?.displayStrings["noDeviceFound"])
} else {
self?.alertController?.title = self?.displayStrings["availableDevices"]
self?.deviceListView?.setTitle (self?.displayStrings["availableDevices"])
}
}
}
Expand Down Expand Up @@ -168,11 +169,11 @@ class DeviceManager: NSObject, CBCentralManagerDelegate {

if shouldShowDeviceList {
DispatchQueue.main.async { [weak self] in
self?.alertController?.addAction(UIAlertAction(title: device.getName() ?? "Unknown", style: UIAlertAction.Style.default, handler: { (_) in
self?.deviceListView?.addItem(device.getName() ?? "Unknown", action: {
log("Selected device")
self?.stopScan()
self?.resolve("startScanning", device.getId())
}))
})
}
} else {
if self.scanResultCallback != nil {
Expand All @@ -183,13 +184,17 @@ class DeviceManager: NSObject, CBCentralManagerDelegate {

func showDeviceList() {
DispatchQueue.main.async { [weak self] in
self?.alertController = UIAlertController(title: self?.displayStrings["scanning"], message: nil, preferredStyle: UIAlertController.Style.alert)
self?.alertController?.addAction(UIAlertAction(title: self?.displayStrings["cancel"], style: UIAlertAction.Style.cancel, handler: { (_) in
self?.deviceListView = DeviceListView()
if #available(macCatalyst 15.0, iOS 15.0, *) {
self?.deviceListView?.sheetPresentationController?.detents = [.medium()]
}
self?.viewController?.present((self?.deviceListView)!, animated: true, completion: nil)
self?.deviceListView?.setTitle(self?.displayStrings["scanning"])
self?.deviceListView?.setCancelButton(self?.displayStrings["cancel"], action: {
log("Cancelled request device.")
self?.stopScan()
self?.reject("startScanning", "requestDevice cancelled.")
}))
self?.viewController?.present((self?.alertController)!, animated: true, completion: nil)
})
}
}

Expand Down