...
 
Commits (3)
......@@ -22,7 +22,9 @@
3462310BCEBE60064FFEAD44 /* LoginFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34623449A1A720282A3FDFB1 /* LoginFormView.swift */; };
346231915DEFA12AF6D9E6D7 /* Finder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346232C3CDAD43E90850DE0C /* Finder.swift */; };
346231A4CF7C146517804E3E /* UIViewControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346231B868431D297A92BCE4 /* UIViewControllerExtensions.swift */; };
346231D603502E539E31981F /* FetchDevicesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3462335B5CA27018C97277DB /* FetchDevicesViewController.swift */; };
3462329608A712B08B2B2D16 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 346232C1883114ED52C3EC79 /* Localizable.strings */; };
3462339DEB9E50496B27FC3D /* FetchDevicesActivityLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34623947AA0AEE5D28629A97 /* FetchDevicesActivityLabel.swift */; };
346233E940096F260271D62F /* CredentialStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3462356CABE6D90619DFF3AA /* CredentialStorage.swift */; };
34623660D0CA503D46C59F38 /* LoginActivityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3462368C3F5C9037087EB2EB /* LoginActivityViewController.swift */; };
34623719535AC5DFA0AA8046 /* LoginButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346231A808B927C844103FE7 /* LoginButton.swift */; };
......@@ -77,6 +79,7 @@
346231C91A0CAF8D190AEFCC /* DataExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataExtensions.swift; sourceTree = "<group>"; };
346232C1883114ED52C3EC79 /* Localizable.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
346232C3CDAD43E90850DE0C /* Finder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Finder.swift; sourceTree = "<group>"; };
3462335B5CA27018C97277DB /* FetchDevicesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchDevicesViewController.swift; sourceTree = "<group>"; };
3462337A2DF5BD57BAE79801 /* LoginFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginFormViewController.swift; sourceTree = "<group>"; };
3462337FE8087EDD1E2ADD51 /* AlertOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertOperation.swift; sourceTree = "<group>"; };
34623449A1A720282A3FDFB1 /* LoginFormView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginFormView.swift; sourceTree = "<group>"; };
......@@ -85,6 +88,7 @@
346236E6FAA30FE227D3EC4A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = "<group>"; };
3462387A088990A789000D3E /* LoginOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginOperation.swift; sourceTree = "<group>"; };
346238AFC195BB86E6E78FAA /* Device.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Device.swift; sourceTree = "<group>"; };
34623947AA0AEE5D28629A97 /* FetchDevicesActivityLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchDevicesActivityLabel.swift; sourceTree = "<group>"; };
34623C6A38693F986BDD6BF7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
34623CF89CC53EAD8E6AFF57 /* UserDefaultsExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtensions.swift; sourceTree = "<group>"; };
34623D9300CB347BEE58DE3A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
......@@ -152,6 +156,7 @@
34623DABD4F30772CD1C85D2 /* Resources */,
346232AF6EF6A058D52A1353 /* Login Activity */,
3462356CABE6D90619DFF3AA /* CredentialStorage.swift */,
34623E368565A16E2D2B10E4 /* Device List */,
);
path = Finder;
sourceTree = "<group>";
......@@ -199,6 +204,15 @@
name = Resources;
sourceTree = "<group>";
};
34623E368565A16E2D2B10E4 /* Device List */ = {
isa = PBXGroup;
children = (
3462335B5CA27018C97277DB /* FetchDevicesViewController.swift */,
34623947AA0AEE5D28629A97 /* FetchDevicesActivityLabel.swift */,
);
path = "Device List";
sourceTree = "<group>";
};
34623E72BF059440EC49C8AC /* Networking */ = {
isa = PBXGroup;
children = (
......@@ -363,6 +377,8 @@
34623660D0CA503D46C59F38 /* LoginActivityViewController.swift in Sources */,
34623F9DFD5BFB0CF0840063 /* UserDefaultsExtensions.swift in Sources */,
346233E940096F260271D62F /* CredentialStorage.swift in Sources */,
346231D603502E539E31981F /* FetchDevicesViewController.swift in Sources */,
3462339DEB9E50496B27FC3D /* FetchDevicesActivityLabel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......
......@@ -21,7 +21,9 @@ class AppViewController: UIViewController {
switch success {
case true:
fatalError("success")
DispatchQueue.main.async {
appViewController.transition(to: FetchDevicesViewController())
}
case false:
DispatchQueue.main.async {
let loginViewController = appViewController.newLoginFormViewController()
......
// Created by Geoff Pado on 4/8/18.
// Copyright © 2018 Cocoatype, LLC. All rights reserved.
import UIKit
class FetchDevicesActivityLabel: UILabel {
init() {
super.init(frame: .zero)
font = UIFont.preferredFont(forTextStyle: .footnote)
translatesAutoresizingMaskIntoConstraints = false
}
// MARK: Boilerplate
@available(*, unavailable)
required init(coder: NSCoder) {
type(of: self).notImplementedInit()
}
}
// Created by Geoff Pado on 4/8/18.
// Copyright © 2018 Cocoatype, LLC. All rights reserved.
import Intents
import UIKit
class FetchDevicesViewController: UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
}
override func loadView() {
super.loadView()
view.backgroundColor = .white
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
let activityLabel = FetchDevicesActivityLabel()
activityLabel.text = FetchDevicesViewController.fetchingText
let stackView = UIStackView(arrangedSubviews: [activityIndicator, activityLabel])
stackView.alignment = .center
stackView.axis = .vertical
stackView.distribution = .equalSpacing
stackView.spacing = 8
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
self.activityIndicator = activityIndicator
self.activityLabel = activityLabel
NSLayoutConstraint.activate([
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let fetchOperation = FetchDevicesOperation()
fetchOperation.completionBlock = { [weak self, weak fetchOperation] in
DispatchQueue.main.async { [weak self] in
self?.activityIndicator.stopAnimating()
}
// handle failed fetching
guard let devices = fetchOperation?.devices else {
DispatchQueue.main.async { [weak self] in
self?.activityLabel.text = FetchDevicesViewController.fetchFailedText
}
return
}
// update activity status
DispatchQueue.main.async {
self?.activityLabel.text = FetchDevicesViewController.fetchSuccessfulText
}
// save device names to vocabulary
let deviceNameSet = NSOrderedSet(array: devices.map { INSpeakableString(spokenPhrase: $0.name) })
INVocabulary.shared().setVocabulary(deviceNameSet, of: .contactName)
}
operationQueue.addOperation(fetchOperation)
activityIndicator.startAnimating()
}
// MARK: Boilerplate
private weak var activityIndicator: UIActivityIndicatorView!
private weak var activityLabel: UILabel!
private let operationQueue: OperationQueue = {
let operationQueue = OperationQueue()
operationQueue.qualityOfService = .userInitiated
return operationQueue
}()
private static let fetchingText = NSLocalizedString("FetchDevicesViewController.fetchingText", comment: "Text displayed while fetching the list of devices")
private static let fetchFailedText = NSLocalizedString("FetchDevicesViewController.fetchFailedText", comment: "Text displayed after failing to fetch the list of devices")
private static let fetchSuccessfulText = NSLocalizedString("FetchDevicesViewController.fetchSuccessfulText", comment: "Text displayed after successfully fetching the list of devices")
@available(*, unavailable)
required init(coder: NSCoder) {
type(of: self).notImplementedInit()
}
}
/**
* FetchDevicesViewController.swift
*/
// Text displayed while fetching the list of devices
"FetchDevicesViewController.fetchingText" = "Updating device list…";
// Text displayed after failing to fetch the list of devices
"FetchDevicesViewController.fetchFailedText" = "Updating failed.";
// Text displayed after successfully fetching the list of devices
"FetchDevicesViewController.fetchSuccessfulText" = "Device list up to date.";
/**
* LoginView.swift
*/
......
......@@ -31,7 +31,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling {
}
func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
guard let alertDeviceName = intent.recipients?.first?.personHandle?.value else { return }
guard let alertDeviceName = intent.recipients?.first?.displayName else { return }
let finder = Finder()
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
......
......@@ -11,19 +11,19 @@ The user interface for Finder is currently a bit of a hack job. Here are the ste
3. Tap the "Log In" button.
3. Dismiss any two-factor authentication prompts that appear.
4. If logging in was unsuccessful, the app will return to the login form.
5. If logging in was successful, the app will crash. Yes, that's a success. Hush.
5. If logging in was successful, the app will begin updating the device list.
At this point, you can now use Siri from either the device or HomePod to trigger Find my iPhone. The following command works best:
Once the device list finishes updating, you can now use Siri from either the device or HomePod to trigger Find my iPhone. The following command is simplest:
> Hey Siri, send a message using Finder.
This will trigger a "play sound" alert on the device that is set up to handle Personal Requests from HomePod, or the active device from any other iOS device.
This will trigger a "play sound" alert on the device that is set up to handle Personal Requests from HomePod, or the active device from any other iOS device (though this has dubious utility).
The following command is also available, but currently doesn't work very well:
The following command is also available:
> Hey Siri, send a message to `<device name>` using Finder.
This allows you to send an alert to any device associated with your iCloud account. However, the detection of device names isn't very robust, and so getting the correct device (or any device) is tricky.
This allows you to send an alert to any device associated with your iCloud account.
## License
......