Skip to content
Merged
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
5 changes: 4 additions & 1 deletion Android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->

<uses-permission android:name="android.permission.VIBRATE"/>

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Expand All @@ -34,6 +34,9 @@
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />

<!-- permissions needed for the Biometric Authentication playground (SkipKit) -->
<uses-permission android:name="android.permission.USE_BIOMETRIC" />

<queries>
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
Expand Down
87 changes: 87 additions & 0 deletions Sources/Showcase/BiometricAuthenticationPlayground.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2023–2026 Skip
import SwiftUI
import SkipKit

/// This component uses the `SkipKit` module from https://source.skip.tools/skip-kit
struct BiometricAuthenticationPlayground: View {
@State var authenticationType = BiometricAuthentication.authenticationType
@State var result = "Not authenticated"
@State var authenticationAttempt = 0

private var canAuthenticate: Bool { BiometricAuthentication.canAuthenticate }

private var resultColor: Color {
switch self.result {
case "Authenticated": .green
case "Cancelled": .orange
case "Failed", "Unavailable": .red
default:
.secondary
}
}

var body: some View {
List {
Section("Availability") {
LabeledContent("Type",
value: {
switch self.authenticationType {
case .fingerprint: "Fingerprint"
case .facialRecognition: "Facial Recognition"
case .unspecified: "Unspecified Biometric"
case .none: "None"
}
}()
)
LabeledContent("Can Authenticate") {
Text(self.canAuthenticate ? "Yes" : "No")
.foregroundStyle(self.canAuthenticate ? Color.green : Color.red)
}
}

if self.canAuthenticate {
Section("Authentication") {
Button("Authenticate") { self.authenticate() }

Text(self.result)
.foregroundStyle(self.resultColor)
}
}
}
.onAppear { self.refresh() }
.onDisappear { self.cancelAuthentication() }
.toolbar {
PlaygroundSourceLink(file: "BiometricAuthenticationPlayground.swift")
}
}

private func authenticate() {
self.authenticationAttempt += 1
let authenticationAttempt = self.authenticationAttempt
self.result = "Authenticating..."

BiometricAuthentication.authenticate(
localizedReason: "Authenticate with biometrics"
) { authenticationResult in
guard authenticationAttempt == self.authenticationAttempt else {
return
}

self.result = switch authenticationResult {
case .success: "Authenticated"
case .cancelled: "Cancelled"
case .failed: "Failed"
case .unavailable: "Unavailable"
}
self.refresh()
}
}

private func refresh() {
self.authenticationType = BiometricAuthentication.authenticationType
}

private func cancelAuthentication() {
self.authenticationAttempt += 1
}
}
5 changes: 5 additions & 0 deletions Sources/Showcase/PlaygroundListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ enum PlaygroundType: CaseIterable, View {
case animation
case audio
case background
case biometricAuthentication
case blendMode
case blur
case border
Expand Down Expand Up @@ -113,6 +114,8 @@ enum PlaygroundType: CaseIterable, View {
return LocalizedStringResource("Audio", comment: "Title of Audio playground")
case .background:
return LocalizedStringResource("Background", comment: "Title of Background playground")
case .biometricAuthentication:
return LocalizedStringResource("Biometric Authentication", comment: "Title of Biometric Authentication playground")
case .blendMode:
return LocalizedStringResource("BlendMode", comment: "Title of BlendMode playground")
case .blur:
Expand Down Expand Up @@ -316,6 +319,8 @@ enum PlaygroundType: CaseIterable, View {
#endif
case .background:
BackgroundPlayground()
case .biometricAuthentication:
BiometricAuthenticationPlayground()
case .blendMode:
BlendModePlayground()
case .blur:
Expand Down
56 changes: 55 additions & 1 deletion Sources/Showcase/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -1002,12 +1002,30 @@
},
"AsyncImage" : {

},
"Authenticate" : {

},
"Authenticate with biometrics" : {

},
"Authenticated" : {

},
"Authenticating..." : {

},
"Authentication" : {

},
"Audio" : {
"comment" : "Title of Audio playground"
},
"AudioPlayground is Lite-only until ported to SkipAV" : {

},
"Availability" : {

},
"B" : {

Expand Down Expand Up @@ -1077,6 +1095,9 @@
},
"Binding View" : {

},
"Biometric Authentication" : {
"comment" : "Title of Biometric Authentication playground"
},
"BipBop" : {

Expand Down Expand Up @@ -1182,6 +1203,12 @@
},
"Cancel" : {

},
"Cancelled" : {

},
"Can Authenticate" : {

},
"Capsule" : {

Expand Down Expand Up @@ -1673,6 +1700,12 @@
},
"F2" : {

},
"Facial Recognition" : {

},
"Failed" : {

},
"Favorite" : {

Expand Down Expand Up @@ -1700,6 +1733,9 @@
},
"fill(.red)\n .stroke(.green, lineWidth: 10)" : {

},
"Fingerprint" : {

},
"First" : {

Expand Down Expand Up @@ -2647,6 +2683,9 @@
},
"NO" : {

},
"No" : {

},
"No badge (count 0)" : {

Expand All @@ -2665,6 +2704,12 @@
},
"Normal" : {

},
"None" : {

},
"Not authenticated" : {

},
"Not supported on macOS" : {

Expand Down Expand Up @@ -4225,6 +4270,9 @@
},
"Title Only" : {

},
"Type" : {

},
"To:" : {

Expand Down Expand Up @@ -4306,6 +4354,9 @@
},
"Unavailable" : {

},
"Unspecified Biometric" : {

},
"Underline" : {

Expand Down Expand Up @@ -4719,6 +4770,9 @@
},
"Yellow" : {

},
"Yes" : {

},
"YES" : {

Expand All @@ -4734,4 +4788,4 @@
}
},
"version" : "1.0"
}
}
Loading