Skip to content

Commit 15c0e29

Browse files
Custom UI for Fingerprint Auth #5
1 parent 32fd5b5 commit 15c0e29

13 files changed

+321
-109
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright 2017 Eddy Verbruggen
3+
Copyright 2018 Eddy Verbruggen
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,30 @@ class MyClass {
6969
Note that on the iOS simulator this will just `resolve()`.
7070

7171
```js
72-
fingerprintAuth.verifyFingerprint({
73-
title: 'Android title', // optional title (used only on Android)
74-
message: 'Scan yer finger', // optional (used on both platforms) - for FaceID on iOS see the notes about NSFaceIDUsageDescription
75-
authenticationValidityDuration: 10 // optional (used on Android, default 5)
76-
}).then(
77-
function() {
78-
console.log("Fingerprint was OK");
79-
},
80-
function() {
81-
console.log("Fingerprint NOT OK");
82-
}
83-
)
72+
fingerprintAuth.verifyFingerprint(
73+
{
74+
title: 'Android title', // optional title (used only on Android)
75+
message: 'Scan yer finger', // optional (used on both platforms) - for FaceID on iOS see the notes about NSFaceIDUsageDescription
76+
authenticationValidityDuration: 10, // optional (used on Android, default 5)
77+
useCustomAndroidUI: false // set to true to use a different authentication screen (see below)
78+
})
79+
.then(() => console.log("Biometric ID OK"))
80+
.catch(err => console.log(`Biometric ID NOT OK: ${JSON.stringify(err)}`));
8481
```
8582

83+
#### A nicer UX/UI on Android (`useCustomAndroidUI: true`)
84+
The default authentication screen on Android is a standalone screen that (depending on the exact Android version) looks kinda 'uninteresting'. So with version 6.0.0 this plugin added the ability to override the default screen and offer an iOS popover style which you can activate by passing in `useCustomAndroidUI: true` in the function above.
85+
86+
##### Mandatory change
87+
To be able to use this screen, a change to `App_Resources/Android/AndroidManifest.xml` is required as our NativeScript activity needs to extend AppCompatActivity (note that in the future this may become the default for NativeScript apps).
88+
89+
To do so, open the file and replace `<activity android:name="com.tns.NativeScriptActivity"` by `<activity android:name="org.nativescript.fingerprintplugin.AppCompatActivity"`.
90+
91+
Note that if you forget this and set `useCustomAndroidUI: true` the plugin will `reject` the Promise with a relevant error message.
92+
93+
##### Optional change
94+
If you want to override the default texts of this popover screen, then drop a file `App_Resources/Android/values/strings.xml` in your project and override the properties you like. See the demo app for an example.
95+
8696
### `verifyFingerprintWithCustomFallback` (iOS only, falls back to `verifyFingerprint` on Android)
8797
Instead of falling back to the default Passcode UI of iOS you can roll your own.
8898
Just show that when the error callback is invoked.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<resources>
2+
<string name="app_name">Fingerprint</string>
3+
<string name="title_activity_kimera">Fingerprint</string>
4+
5+
<string name="cancel">Cancel</string>
6+
<string name="use_password">Use password</string>
7+
<string name="ok">OK</string>
8+
<string name="password_description">Enter your password to continue</string>
9+
<string name="new_fingerprint_enrolled_description">A new fingerprint was added to this device, so your password is required.</string>
10+
11+
<string name="use_fingerprint_in_future">Use fingerprint in the future</string>
12+
<string name="use_fingerprint_to_authenticate_key" >use_fingerprint_to_authenticate_key</string>
13+
<!--<string name="fingerprint_description">Confirm fingerprint 2 continue</string>-->
14+
<string name="fingerprint_hint">Touch da sensor</string>
15+
16+
<!-- Error messages -->
17+
<string name="fingerprint_auth_not_available_msg"><![CDATA[\"Secure lock screen hasn\'t set up.\\n\" + \"Go to \'Settings -> Security -> Fingerprint\' to set up a fingerprint\"]]></string>
18+
<string name="fingerprint_auth_not_enrolled_msg">Go to \'Settings -> Security -> Fingerprint\' and register at least one fingerprint</string>
19+
<string name="fingerprint_not_initialised_error_msg">Fingerprint not initialised</string>
20+
<string name="fingerprint_not_available_error_msg">Fingerprint not available</string>
21+
<string name="fingerprint_not_recognized_error_msg">Fingerprint not recognized</string>
22+
<string name="warning_password_empty">Password can\'t be empty</string>
23+
</resources>

demo/app/app.css

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
background-color: #F4F4F4;
33
}
44

5+
label {
6+
font-size: 14;
7+
}
8+
59
.tab-content {
610
color: #808080;
711
padding: 20;
@@ -14,15 +18,14 @@
1418
}
1519

1620
.status {
21+
border-radius: 8;
1722
font-size: 16;
18-
margin: 14;
19-
padding: 8;
20-
color: white;
21-
background-color: brown;
22-
}
23-
24-
label {
25-
font-size: 14;
23+
margin: 20;
24+
padding: 18 12;
25+
color: #444444;
26+
background-color: #cccccc;
27+
border-color: #aaaaaa;
28+
border-width: 1px;
2629
}
2730

2831
button {

demo/app/main-page.xml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<TabViewItem.view>
66
<ScrollView>
77
<StackLayout class="tab-content">
8-
<Label text="{{ status }}" class="status" textWrap="true" style="text-align: center" width="100%"/>
8+
<Label text="{{ status }}" class="status" textWrap="true" style="text-align: center"/>
99
<Label text="Checking availability" class="title"/>
1010
<Button text="available?" tap="{{ doCheckAvailable }}" class="button" />
1111

@@ -15,7 +15,13 @@
1515
<Label text="Scanning the fingerprint / face" class="title"/>
1616
<Label text="When scanning fails or is not possible, you can either use the built-in passcode fallback or handle it yourself (custom fallback)." textWrap="true"/>
1717
<Button text="verify with passcode fallback" tap="{{ doVerifyFingerprint }}" class="button" />
18-
<Button text="verify with custom fallback" tap="{{ doVerifyFingerprintWithCustomFallback }}" class="button" />
18+
<iOS>
19+
<Button text="verify with custom fallback" tap="{{ doVerifyFingerprintWithCustomFallback }}" class="button" />
20+
</iOS>
21+
<Android>
22+
<Label text="Note that this will fail if you previously cancelled authentication with the button above. Try reinstalling the app if funny things happen." textWrap="true"/>
23+
<Button text="verify with custom UI" tap="{{ doVerifyFingerprintWithCustomUI }}" class="button" />
24+
</Android>
1925
</StackLayout>
2026
</ScrollView>
2127
</TabViewItem.view>

demo/app/main-view-model.ts

Lines changed: 57 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { Observable } from "tns-core-modules/data/observable";
21
import { alert } from "tns-core-modules/ui/dialogs";
3-
import { FingerprintAuth } from "nativescript-fingerprint-auth";
4-
import { BiometricIDAvailableResult } from "../../src/fingerprint-auth.common";
2+
import { Observable } from "tns-core-modules/data/observable";
3+
import { FingerprintAuth, BiometricIDAvailableResult } from "nativescript-fingerprint-auth";
54

65
export class HelloWorldModel extends Observable {
76
private fingerprintAuth: FingerprintAuth;
8-
public status: string = 'STATUS';
7+
public status: string = "Tap a button..";
98

109
constructor() {
1110
super();
@@ -14,64 +13,71 @@ export class HelloWorldModel extends Observable {
1413

1514
public doCheckAvailable(): void {
1615
this.fingerprintAuth.available().then(
17-
(result: BiometricIDAvailableResult) => {
18-
console.log("available result: " + JSON.stringify(result));
19-
this.set('status', "Biometric ID available? - " + (result.any ? (result.face ? "Face" : "Touch") : "NO"));
20-
}
21-
);
16+
(result: BiometricIDAvailableResult) => {
17+
console.log("available result: " + JSON.stringify(result));
18+
this.set('status', "Biometric ID available? - " + (result.any ? (result.face ? "Face" : "Touch") : "NO"));
19+
});
2220
}
2321

2422
public doCheckFingerprintsChanged(): void {
2523
this.fingerprintAuth.didFingerprintDatabaseChange().then(
26-
(changed: boolean) => {
27-
this.set('status', "Biometric ID changed? - " + (changed ? "YES" : "NO"));
28-
}
29-
);
24+
(changed: boolean) => {
25+
this.set('status', "Biometric ID changed? - " + (changed ? "YES" : "NO"));
26+
});
3027
}
3128

3229
public doVerifyFingerprint(): void {
33-
this.fingerprintAuth.verifyFingerprint({
34-
message: 'Scan yer finger', // optional
35-
authenticationValidityDuration: 10 // Android
36-
}).then(
37-
() => {
38-
this.set('status', "Biometric ID OK");
39-
alert({
40-
title: "Biometric ID / passcode OK",
41-
okButtonText: "Sweet"
30+
this.fingerprintAuth.verifyFingerprint(
31+
{
32+
message: 'Scan yer finger', // optional
33+
authenticationValidityDuration: 10 // Android
34+
})
35+
.then(() => {
36+
alert({
37+
title: "Biometric ID / passcode OK",
38+
okButtonText: "Sweet"
39+
});
40+
})
41+
.catch(err => {
42+
alert({
43+
title: "Biometric ID NOT OK / canceled",
44+
message: JSON.stringify(err),
45+
okButtonText: "Mmkay"
46+
});
4247
});
43-
},
44-
err => {
45-
this.set('status', "Biometric ID NOT OK: " + err);
46-
alert({
47-
title: "Biometric ID NOT OK / canceled",
48-
okButtonText: "Mmkay"
49-
});
50-
}
51-
);
48+
}
49+
50+
public doVerifyFingerprintWithCustomUI(): void {
51+
this.fingerprintAuth.verifyFingerprint(
52+
{
53+
message: 'Scan yer finger', // optional
54+
useCustomAndroidUI: true // Android
55+
})
56+
.then(() => this.set('status', "Biometric ID OK"))
57+
.catch(err => this.set('status', `Biometric ID NOT OK: " + ${JSON.stringify(err)}`));
5258
}
5359

5460
public doVerifyFingerprintWithCustomFallback(): void {
55-
this.fingerprintAuth.verifyFingerprintWithCustomFallback({
56-
message: 'Scan yer finger', // optional
57-
fallbackMessage: 'Enter PIN', // optional
58-
authenticationValidityDuration: 10 // Android
59-
}).then(
60-
() => {
61-
this.set('status', "Biometric ID OK");
62-
alert({
63-
title: "Biometric ID OK",
64-
okButtonText: "Sweet"
65-
});
66-
},
67-
error => {
68-
this.set('status', "Biometric ID NOT OK: " + JSON.stringify(error));
69-
alert({
70-
title: "Biometric ID NOT OK",
71-
message: (error.code === -3 ? "Show custom fallback" : error.message),
72-
okButtonText: "Mmkay"
61+
this.fingerprintAuth.verifyFingerprintWithCustomFallback(
62+
{
63+
message: 'Scan yer finger', // optional
64+
fallbackMessage: 'Enter PIN', // optional
65+
authenticationValidityDuration: 10 // Android
66+
})
67+
.then(() => {
68+
this.set('status', "Biometric ID OK");
69+
alert({
70+
title: "Biometric ID OK",
71+
okButtonText: "Sweet"
72+
});
73+
})
74+
.catch(error => {
75+
this.set('status', "Biometric ID NOT OK: " + JSON.stringify(error));
76+
alert({
77+
title: "Biometric ID NOT OK",
78+
message: (error.code === -3 ? "Show custom fallback" : error.message),
79+
okButtonText: "Mmkay"
80+
});
7381
});
74-
}
75-
);
7682
}
7783
}

src/appcompat-activity.android.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { setActivityCallbacks, AndroidActivityCallbacks } from "tns-core-modules/ui/frame";
2+
3+
@JavaProxy("org.nativescript.fingerprintplugin.AppCompatActivity")
4+
class Activity extends android.support.v7.app.AppCompatActivity {
5+
private _callbacks: AndroidActivityCallbacks;
6+
7+
protected onCreate(savedInstanceState: android.os.Bundle): void {
8+
if (!this._callbacks) {
9+
setActivityCallbacks(this);
10+
}
11+
12+
this._callbacks.onCreate(this, savedInstanceState, super.onCreate);
13+
}
14+
15+
protected onSaveInstanceState(outState: android.os.Bundle): void {
16+
this._callbacks.onSaveInstanceState(this, outState, super.onSaveInstanceState);
17+
}
18+
19+
protected onStart(): void {
20+
this._callbacks.onStart(this, super.onStart);
21+
}
22+
23+
protected onStop(): void {
24+
this._callbacks.onStop(this, super.onStop);
25+
}
26+
27+
protected onDestroy(): void {
28+
this._callbacks.onDestroy(this, super.onDestroy);
29+
}
30+
31+
public onBackPressed(): void {
32+
this._callbacks.onBackPressed(this, super.onBackPressed);
33+
}
34+
35+
public onRequestPermissionsResult(requestCode: number, permissions: Array<String>, grantResults: Array<number>): void {
36+
this._callbacks.onRequestPermissionsResult(this, requestCode, permissions, grantResults, undefined);
37+
}
38+
39+
protected onActivityResult(requestCode: number, resultCode: number, data: android.content.Intent): void {
40+
this._callbacks.onActivityResult(this, requestCode, resultCode, data, super.onActivityResult);
41+
}
42+
}

0 commit comments

Comments
 (0)