Skip to content

Commit cc33a3a

Browse files
save
1 parent c5e7500 commit cc33a3a

7 files changed

Lines changed: 230 additions & 3 deletions

File tree

RemoteCam/DeviceScannerViewController.swift

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import UIKit
1010
import Theater
1111
import MultipeerConnectivity
12+
import Network
1213

1314
let goToRolePickerController = "goToRolePickerController"
1415
let service: String = "RemoteCam"
@@ -47,6 +48,25 @@ public class DeviceScannerViewController: UIViewController {
4748

4849
var connectedPeers: [MCPeerID] = []
4950

51+
// Track Local Network permission state
52+
private var hasLocalNetworkPermission: Bool {
53+
get {
54+
UserDefaults.standard.bool(forKey: "hasLocalNetworkPermission")
55+
}
56+
set {
57+
UserDefaults.standard.set(newValue, forKey: "hasLocalNetworkPermission")
58+
}
59+
}
60+
61+
private var localNetworkExplicitlyDenied: Bool {
62+
get {
63+
UserDefaults.standard.bool(forKey: "localNetworkExplicitlyDenied")
64+
}
65+
set {
66+
UserDefaults.standard.set(newValue, forKey: "localNetworkExplicitlyDenied")
67+
}
68+
}
69+
5070
lazy var splash = {
5171
CoolActivityIndicator(currentController: self)
5272
}()
@@ -78,6 +98,41 @@ public class DeviceScannerViewController: UIViewController {
7898
self.setupStyle()
7999
tableView.delegate = self
80100
tableView.dataSource = self
101+
102+
// TEMPORARY: Add debugging gesture for testing
103+
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(debugDoubleTap))
104+
doubleTap.numberOfTapsRequired = 3
105+
view.addGestureRecognizer(doubleTap)
106+
}
107+
108+
@objc private func debugDoubleTap() {
109+
// Reset permission state for testing
110+
print("🔧 DEBUG: Resetting permission state for testing")
111+
hasLocalNetworkPermission = false
112+
localNetworkExplicitlyDenied = false
113+
tableView.reloadData()
114+
print("🔧 DEBUG: Permission state reset - green button should be visible")
115+
}
116+
117+
private func showLocalNetworkExplanation() {
118+
let alert = UIAlertController(
119+
title: NSLocalizedString("local_network_explanation_title", comment: "Title for Local Network explanation"),
120+
message: NSLocalizedString("local_network_explanation_message", comment: "Explanation of how Remote Shutter uses Local Network to connect devices"),
121+
preferredStyle: .alert
122+
)
123+
124+
alert.addAction(UIAlertAction(title: NSLocalizedString("Continue", comment: "Continue button"), style: .default) { _ in
125+
// Start scanning which will trigger iOS permission prompt
126+
print("🔍 DEBUG: User chose Continue - starting scan to trigger permission")
127+
self.remoteCamSession ! UICmd.StartScanning(sender: nil)
128+
})
129+
130+
alert.addAction(UIAlertAction(title: NSLocalizedString("Not Now", comment: "Not Now button"), style: .cancel) { _ in
131+
// User chose not now - just dismiss, green button stays visible
132+
print("🔍 DEBUG: User chose Not Now - dismissing")
133+
})
134+
135+
present(alert, animated: true)
81136
}
82137

83138
public override func viewWillAppear(_ animated: Bool) {
@@ -87,8 +142,25 @@ public class DeviceScannerViewController: UIViewController {
87142

88143
public override func viewDidAppear(_ animated: Bool) {
89144
super.viewDidAppear(animated)
145+
print("🔍 DEBUG: DeviceScannerViewController viewDidAppear")
146+
90147
self.remoteCamSession ! Disconnect()
91-
self.remoteCamSession ! UICmd.StartScanning(sender: nil)
148+
149+
if #available(iOS 14.0, *) {
150+
if hasLocalNetworkPermission {
151+
// User has permission - start scanning directly
152+
print("🔍 DEBUG: Permission granted - starting scan")
153+
self.remoteCamSession ! UICmd.StartScanning(sender: nil)
154+
} else {
155+
// No permission - show the UI with green button
156+
print("🔍 DEBUG: No permission - showing green button")
157+
tableView.reloadData()
158+
}
159+
} else {
160+
// iOS < 14 - no Local Network permission needed
161+
print("🔍 DEBUG: iOS < 14 - starting scan directly")
162+
self.remoteCamSession ! UICmd.StartScanning(sender: nil)
163+
}
92164
}
93165

94166
public override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
@@ -109,6 +181,7 @@ public class DeviceScannerViewController: UIViewController {
109181
tableView.reloadData()
110182
scanner.stopBrowsingForPeers()
111183
scanner.startBrowsingForPeers()
184+
print("🔍 DEBUG: Started browsing for peers")
112185
}
113186

114187
func stopScanning() {
@@ -123,7 +196,29 @@ public class DeviceScannerViewController: UIViewController {
123196
}
124197

125198
@IBAction func goToAppSettings() {
126-
goToSettings()
199+
if localNetworkExplicitlyDenied {
200+
// User explicitly denied iOS permission - show settings instructions
201+
showSettingsAlert()
202+
} else {
203+
// Show explanation of why we need access
204+
showLocalNetworkExplanation()
205+
}
206+
}
207+
208+
private func showSettingsAlert() {
209+
let alert = UIAlertController(
210+
title: NSLocalizedString("enable_local_network_access_title", comment: "Title for enable Local Network access alert"),
211+
message: NSLocalizedString("enable_local_network_access_message", comment: "Instructions for enabling Local Network access in Settings"),
212+
preferredStyle: .alert
213+
)
214+
215+
alert.addAction(UIAlertAction(title: NSLocalizedString("Open Settings", comment: "Open Settings button"), style: .default) { _ in
216+
goToSettings()
217+
})
218+
219+
alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "Cancel button"), style: .cancel))
220+
221+
present(alert, animated: true)
127222
}
128223

129224
@IBAction func shareAppLink() {
@@ -152,11 +247,14 @@ extension DeviceScannerViewController: UITableViewDataSource, UITableViewDelegat
152247
}
153248

154249
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
250+
print("🔍 DEBUG: cellForRowAt - connectedPeers.count: \(connectedPeers.count)")
155251
if (connectedPeers.count > 0) {
252+
print("🔍 DEBUG: Showing peer cell")
156253
let cell = UITableViewCell()
157254
cell.textLabel?.text = connectedPeers[indexPath.row].displayName
158255
return cell
159256
} else {
257+
print("🔍 DEBUG: Showing instructions cell")
160258
let cell = tableView.dequeueReusableCell(withIdentifier: "instructions") as! DeviceScannerPlaceholder
161259
cell.qrCode.image = qrCodeImage
162260
cell.shareButton.styleButton(
@@ -171,10 +269,15 @@ extension DeviceScannerViewController: UITableViewDataSource, UITableViewDelegat
171269
textColor: UIColor.white
172270
)
173271
cell.goToSettings.setNeedsDisplay()
272+
273+
// Show the Local Network button if no permission on iOS 14+
174274
if #available(iOS 14.0, *) {
175-
cell.goToSettings.isHidden = false
275+
let shouldShow = !hasLocalNetworkPermission
276+
cell.goToSettings.isHidden = !shouldShow
277+
print("🔍 DEBUG: Local Network button - shouldShow: \(shouldShow), hasPermission: \(hasLocalNetworkPermission), isHidden: \(cell.goToSettings.isHidden)")
176278
} else {
177279
cell.goToSettings.isHidden = true
280+
print("🔍 DEBUG: iOS < 14 - hiding Local Network button")
178281
}
179282
return cell
180283
}
@@ -196,13 +299,40 @@ extension DeviceScannerViewController: UITableViewDataSource, UITableViewDelegat
196299

197300
extension DeviceScannerViewController: MCNearbyServiceBrowserDelegate {
198301
public func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) {
302+
print("✅ DEBUG: Found peer: \(peerID.displayName) - permission granted")
199303
connectedPeers.append(peerID)
304+
305+
// If we found a peer, Local Network permission was granted
306+
if !hasLocalNetworkPermission {
307+
print("🔄 DEBUG: Setting hasLocalNetworkPermission to true")
308+
hasLocalNetworkPermission = true
309+
localNetworkExplicitlyDenied = false
310+
}
311+
200312
tableView.reloadData()
201313
}
202314

203315
public func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {
316+
print("📱 DEBUG: Lost peer: \(peerID.displayName)")
204317
connectedPeers = connectedPeers.filter { (peer) -> Bool in peer != peerID }
205318
tableView.reloadData()
206319
}
320+
321+
public func browser(_ browser: MCNearbyServiceBrowser, didNotStartBrowsingForPeers error: Error) {
322+
print("🚨 DEBUG: Browser failed to start: \(error.localizedDescription)")
323+
print("🚨 DEBUG: Error code: \((error as NSError).code)")
324+
325+
if #available(iOS 14.0, *) {
326+
// Check for kDNSServiceErr_PolicyDenied (-65571) which indicates Local Network permission denied
327+
let nsError = error as NSError
328+
if nsError.code == -65571 { // kDNSServiceErr_PolicyDenied
329+
print("🚨 DEBUG: Detected kDNSServiceErr_PolicyDenied - user explicitly denied Local Network permission")
330+
localNetworkExplicitlyDenied = true
331+
DispatchQueue.main.async {
332+
self.tableView.reloadData()
333+
}
334+
}
335+
}
336+
}
207337
}
208338

RemoteCam/Localizable.strings

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,20 @@
9393
"iAds Removed"="iAds Removed";
9494

9595
"call_to_download" = "Download Remote Shutter today %@";
96+
97+
/* Local Network Permission */
98+
"local_network_explanation_title" = "Connect to Nearby Devices";
99+
100+
"local_network_explanation_message" = "Remote Shutter allows one device to act as a camera while another controls it remotely. To discover and connect these devices, the app needs Local Network access. Without this permission, Remote Shutter cannot function.";
101+
102+
"enable_local_network_access_title" = "Enable Local Network Access";
103+
104+
"enable_local_network_access_message" = "To use Remote Shutter, please enable Local Network access in Settings > Privacy & Security > Local Network. The app cannot work without this permission.";
105+
106+
"Continue" = "Continue";
107+
108+
"Not Now" = "Not Now";
109+
110+
"Open Settings" = "Open Settings";
111+
112+
"Cancel" = "Cancel";
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* Local Network Permission - Danish */
2+
"local_network_explanation_title" = "Forbind til Enheder i Nærheden";
3+
4+
"local_network_explanation_message" = "Remote Shutter lader én enhed fungere som kamera, mens en anden styrer det på afstand. For at opdage og forbinde disse enheder har appen brug for lokal netværksadgang. Uden denne tilladelse kan Remote Shutter ikke fungere.";
5+
6+
"enable_local_network_access_title" = "Aktivér Lokal Netværksadgang";
7+
8+
"enable_local_network_access_message" = "For at bruge Remote Shutter skal du aktivere lokal netværksadgang i Indstillinger > Privatliv og sikkerhed > Lokalt netværk. Appen kan ikke fungere uden denne tilladelse.";
9+
10+
"Continue" = "Fortsæt";
11+
12+
"Not Now" = "Ikke Nu";
13+
14+
"Open Settings" = "Åbn Indstillinger";
15+
16+
"Cancel" = "Annuller";
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* Local Network Permission - English (US) */
2+
"local_network_access_required_title" = "Local Network Access Required";
3+
4+
"local_network_access_required_message" = "Remote Shutter requires Local Network access to discover nearby devices. Without this permission, the app cannot function.";
5+
6+
"enable_local_network_access_title" = "Enable Local Network Access";
7+
8+
"enable_local_network_access_message" = "To use Remote Shutter, please enable Local Network access in Settings > Privacy & Security > Local Network. The app cannot work without this permission.";
9+
10+
"Continue" = "Continue";
11+
12+
"Not Now" = "Not Now";
13+
14+
"Open Settings" = "Open Settings";
15+
16+
"Cancel" = "Cancel";
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* Local Network Permission - Spanish (Mexico) */
2+
"local_network_access_required_title" = "Acceso a Red Local Requerido";
3+
4+
"local_network_access_required_message" = "Remote Shutter requiere acceso a la red local para descubrir dispositivos cercanos. Sin este permiso, la aplicación no puede funcionar.";
5+
6+
"enable_local_network_access_title" = "Habilitar Acceso a Red Local";
7+
8+
"enable_local_network_access_message" = "Para usar Remote Shutter, por favor habilita el acceso a red local en Configuración > Privacidad y Seguridad > Red Local. La aplicación no puede funcionar sin este permiso.";
9+
10+
"Continue" = "Continuar";
11+
12+
"Not Now" = "Ahora No";
13+
14+
"Open Settings" = "Abrir Configuración";
15+
16+
"Cancel" = "Cancelar";
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* Local Network Permission - French (France) */
2+
"local_network_access_required_title" = "Accès au Réseau Local Requis";
3+
4+
"local_network_access_required_message" = "Remote Shutter nécessite l'accès au réseau local pour découvrir les appareils à proximité. Sans cette autorisation, l'application ne peut pas fonctionner.";
5+
6+
"enable_local_network_access_title" = "Activer l'Accès au Réseau Local";
7+
8+
"enable_local_network_access_message" = "Pour utiliser Remote Shutter, veuillez activer l'accès au réseau local dans Réglages > Confidentialité et sécurité > Réseau local. L'application ne peut pas fonctionner sans cette autorisation.";
9+
10+
"Continue" = "Continuer";
11+
12+
"Not Now" = "Pas Maintenant";
13+
14+
"Open Settings" = "Ouvrir les Réglages";
15+
16+
"Cancel" = "Annuler";
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* Local Network Permission - Italian */
2+
"local_network_access_required_title" = "Accesso alla Rete Locale Richiesto";
3+
4+
"local_network_access_required_message" = "Remote Shutter richiede l'accesso alla rete locale per scoprire dispositivi nelle vicinanze. Senza questa autorizzazione, l'app non può funzionare.";
5+
6+
"enable_local_network_access_title" = "Abilita Accesso alla Rete Locale";
7+
8+
"enable_local_network_access_message" = "Per utilizzare Remote Shutter, abilita l'accesso alla rete locale in Impostazioni > Privacy e sicurezza > Rete locale. L'app non può funzionare senza questa autorizzazione.";
9+
10+
"Continue" = "Continua";
11+
12+
"Not Now" = "Non Ora";
13+
14+
"Open Settings" = "Apri Impostazioni";
15+
16+
"Cancel" = "Annulla";

0 commit comments

Comments
 (0)