Configure iPhone Git commit identity (#76)
This commit is contained in:
@@ -712,6 +712,7 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
private let authentication: MobileAuthentication?
|
||||
private let keyTransfer = try? mobileKeyTransfer()
|
||||
private var state: MobileAuthenticationState?
|
||||
private var gitIdentity: MobileGitIdentity?
|
||||
private var preferenceTask: Task<Void, Never>?
|
||||
private var loadTask: Task<Void, Never>?
|
||||
private var loadGeneration = 0
|
||||
@@ -753,14 +754,14 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
}
|
||||
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
page.state == .ready ? 3 : 0
|
||||
page.state == .ready ? 4 : 0
|
||||
}
|
||||
|
||||
override func tableView(
|
||||
_ tableView: UITableView,
|
||||
numberOfRowsInSection section: Int
|
||||
) -> Int {
|
||||
section == 0 ? 2 : (section == 1 ? 1 : 2)
|
||||
section == 1 ? 2 : (section == 3 ? 2 : 1)
|
||||
}
|
||||
|
||||
override func tableView(
|
||||
@@ -768,8 +769,9 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
titleForHeaderInSection section: Int
|
||||
) -> String? {
|
||||
switch section {
|
||||
case 0: "GPG Key Transfer"
|
||||
case 1: "Secure Unlock"
|
||||
case 0: "Git Commit Identity"
|
||||
case 1: "GPG Key Transfer"
|
||||
case 2: "Secure Unlock"
|
||||
default: "Authentication Session"
|
||||
}
|
||||
}
|
||||
@@ -779,9 +781,12 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
titleForFooterInSection section: Int
|
||||
) -> String? {
|
||||
if section == 0 {
|
||||
return "Scan or display ASCII-armored GPG keys. Private-key transfers require explicit confirmation and passphrase validation."
|
||||
return "Used as the author for commits created by the iPhone app."
|
||||
}
|
||||
if section == 1 {
|
||||
return "Scan or display ASCII-armored GPG keys. Private-key transfers require explicit confirmation and passphrase validation."
|
||||
}
|
||||
if section == 2 {
|
||||
return "When enabled, the GPG passphrase is device-only, requires a device passcode, and is invalidated when enrolled biometrics change."
|
||||
}
|
||||
return "Manual lock and inactivity expiry immediately revoke the shared Rust authentication lease."
|
||||
@@ -794,6 +799,13 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
|
||||
var content = cell.defaultContentConfiguration()
|
||||
if indexPath.section == 0 {
|
||||
content.image = UIImage(systemName: "person.crop.circle")
|
||||
content.text = gitIdentity?.name ?? "Git Commit Identity"
|
||||
content.secondaryText = gitIdentity?.email ?? "Set the commit author name and email"
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
cell.isUserInteractionEnabled = authentication != nil
|
||||
cell.contentView.alpha = authentication == nil ? 0.45 : 1
|
||||
} else if indexPath.section == 1 {
|
||||
let importing = indexPath.row == 0
|
||||
content.image = UIImage(systemName: importing ? "qrcode.viewfinder" : "qrcode")
|
||||
content.text = importing ? "Import GPG Key" : "Export GPG Key"
|
||||
@@ -803,7 +815,7 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
cell.isUserInteractionEnabled = keyTransfer != nil
|
||||
cell.contentView.alpha = keyTransfer == nil ? 0.45 : 1
|
||||
} else if indexPath.section == 1 {
|
||||
} else if indexPath.section == 2 {
|
||||
content.image = UIImage(systemName: "faceid")
|
||||
content.text = "Biometric Unlock"
|
||||
content.secondaryText = state?.biometricUnlockEnabled == true
|
||||
@@ -841,6 +853,10 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
if indexPath.section == 0 {
|
||||
editGitIdentity()
|
||||
return
|
||||
}
|
||||
if indexPath.section == 1 {
|
||||
guard let keyTransfer else { return }
|
||||
if indexPath.row == 0 {
|
||||
requestKeyScanner(keyTransfer)
|
||||
@@ -852,7 +868,7 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
}
|
||||
return
|
||||
}
|
||||
guard indexPath.section == 2, indexPath.row == 1, let authentication else { return }
|
||||
guard indexPath.section == 3, indexPath.row == 1, let authentication else { return }
|
||||
do {
|
||||
try authentication.manualLock()
|
||||
refreshState()
|
||||
@@ -970,6 +986,81 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
private func editGitIdentity() {
|
||||
guard let authentication else {
|
||||
presentAuthenticationFailure(.unavailable)
|
||||
return
|
||||
}
|
||||
let alert = UIAlertController(
|
||||
title: "Git Commit Identity",
|
||||
message: "Enter the author used for commits created on this iPhone.",
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addTextField { [gitIdentity] field in
|
||||
field.placeholder = "Name"
|
||||
field.text = gitIdentity?.name
|
||||
field.textContentType = .name
|
||||
field.autocapitalizationType = .words
|
||||
field.clearButtonMode = .whileEditing
|
||||
}
|
||||
alert.addTextField { [gitIdentity] field in
|
||||
field.placeholder = "Email"
|
||||
field.text = gitIdentity?.email
|
||||
field.textContentType = .emailAddress
|
||||
field.keyboardType = .emailAddress
|
||||
field.autocapitalizationType = .none
|
||||
field.autocorrectionType = .no
|
||||
field.clearButtonMode = .whileEditing
|
||||
}
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||
alert.addAction(UIAlertAction(title: "Save", style: .default) { [weak self, weak alert] _ in
|
||||
guard
|
||||
let self,
|
||||
let fields = alert?.textFields,
|
||||
fields.count == 2
|
||||
else { return }
|
||||
saveGitIdentity(
|
||||
authentication,
|
||||
name: fields[0].text ?? "",
|
||||
email: fields[1].text ?? ""
|
||||
)
|
||||
})
|
||||
present(alert, animated: true)
|
||||
}
|
||||
|
||||
private func saveGitIdentity(
|
||||
_ authentication: MobileAuthentication,
|
||||
name: String,
|
||||
email: String
|
||||
) {
|
||||
preferenceTask?.cancel()
|
||||
preferenceTask = Task { [weak self] in
|
||||
let result = await Task.detached(priority: .userInitiated) {
|
||||
do {
|
||||
return Result<MobileGitIdentity, AuthenticationFailure>.success(
|
||||
try authentication.setGitIdentity(name: name, email: email)
|
||||
)
|
||||
} catch let error as MobileAuthenticationFfiError {
|
||||
return .failure(AuthenticationFailure(error))
|
||||
} catch {
|
||||
return .failure(.unexpected)
|
||||
}
|
||||
}.value
|
||||
guard !Task.isCancelled, let self else { return }
|
||||
switch result {
|
||||
case let .success(identity):
|
||||
gitIdentity = identity
|
||||
tableView.reloadSections(IndexSet(integer: 0), with: .automatic)
|
||||
UIAccessibility.post(
|
||||
notification: .announcement,
|
||||
argument: "Git commit identity updated"
|
||||
)
|
||||
case let .failure(failure):
|
||||
presentAuthenticationFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func authenticationDidChange() {
|
||||
refreshState()
|
||||
}
|
||||
@@ -1014,6 +1105,7 @@ private final class PreferencesViewController: UITableViewController, MobileTabR
|
||||
|
||||
private func refreshState() {
|
||||
state = try? authentication?.state()
|
||||
gitIdentity = try? authentication?.gitIdentity()
|
||||
tableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user