Skip to content

Commit

Permalink
Custom UI for Fingerprint Auth #5
Browse files Browse the repository at this point in the history
  • Loading branch information
EddyVerbruggen committed Jan 30, 2018
1 parent 32fd5b5 commit 15c0e29
Show file tree
Hide file tree
Showing 13 changed files with 321 additions and 109 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright 2017 Eddy Verbruggen
Copyright 2018 Eddy Verbruggen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
34 changes: 22 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,30 @@ class MyClass {
Note that on the iOS simulator this will just `resolve()`.

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

#### A nicer UX/UI on Android (`useCustomAndroidUI: true`)
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.

##### Mandatory change
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).

To do so, open the file and replace `<activity android:name="com.tns.NativeScriptActivity"` by `<activity android:name="org.nativescript.fingerprintplugin.AppCompatActivity"`.

Note that if you forget this and set `useCustomAndroidUI: true` the plugin will `reject` the Promise with a relevant error message.

##### Optional change
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.

### `verifyFingerprintWithCustomFallback` (iOS only, falls back to `verifyFingerprint` on Android)
Instead of falling back to the default Passcode UI of iOS you can roll your own.
Just show that when the error callback is invoked.
Expand Down
23 changes: 23 additions & 0 deletions demo/app/App_Resources/Android/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<resources>
<string name="app_name">Fingerprint</string>
<string name="title_activity_kimera">Fingerprint</string>

<string name="cancel">Cancel</string>
<string name="use_password">Use password</string>
<string name="ok">OK</string>
<string name="password_description">Enter your password to continue</string>
<string name="new_fingerprint_enrolled_description">A new fingerprint was added to this device, so your password is required.</string>

<string name="use_fingerprint_in_future">Use fingerprint in the future</string>
<string name="use_fingerprint_to_authenticate_key" >use_fingerprint_to_authenticate_key</string>
<!--<string name="fingerprint_description">Confirm fingerprint 2 continue</string>-->
<string name="fingerprint_hint">Touch da sensor</string>

<!-- Error messages -->
<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>
<string name="fingerprint_auth_not_enrolled_msg">Go to \'Settings -> Security -> Fingerprint\' and register at least one fingerprint</string>
<string name="fingerprint_not_initialised_error_msg">Fingerprint not initialised</string>
<string name="fingerprint_not_available_error_msg">Fingerprint not available</string>
<string name="fingerprint_not_recognized_error_msg">Fingerprint not recognized</string>
<string name="warning_password_empty">Password can\'t be empty</string>
</resources>
19 changes: 11 additions & 8 deletions demo/app/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
background-color: #F4F4F4;
}

label {
font-size: 14;
}

.tab-content {
color: #808080;
padding: 20;
Expand All @@ -14,15 +18,14 @@
}

.status {
border-radius: 8;
font-size: 16;
margin: 14;
padding: 8;
color: white;
background-color: brown;
}

label {
font-size: 14;
margin: 20;
padding: 18 12;
color: #444444;
background-color: #cccccc;
border-color: #aaaaaa;
border-width: 1px;
}

button {
Expand Down
10 changes: 8 additions & 2 deletions demo/app/main-page.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<TabViewItem.view>
<ScrollView>
<StackLayout class="tab-content">
<Label text="{{ status }}" class="status" textWrap="true" style="text-align: center" width="100%"/>
<Label text="{{ status }}" class="status" textWrap="true" style="text-align: center"/>
<Label text="Checking availability" class="title"/>
<Button text="available?" tap="{{ doCheckAvailable }}" class="button" />

Expand All @@ -15,7 +15,13 @@
<Label text="Scanning the fingerprint / face" class="title"/>
<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"/>
<Button text="verify with passcode fallback" tap="{{ doVerifyFingerprint }}" class="button" />
<Button text="verify with custom fallback" tap="{{ doVerifyFingerprintWithCustomFallback }}" class="button" />
<iOS>
<Button text="verify with custom fallback" tap="{{ doVerifyFingerprintWithCustomFallback }}" class="button" />
</iOS>
<Android>
<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"/>
<Button text="verify with custom UI" tap="{{ doVerifyFingerprintWithCustomUI }}" class="button" />
</Android>
</StackLayout>
</ScrollView>
</TabViewItem.view>
Expand Down
108 changes: 57 additions & 51 deletions demo/app/main-view-model.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Observable } from "tns-core-modules/data/observable";
import { alert } from "tns-core-modules/ui/dialogs";
import { FingerprintAuth } from "nativescript-fingerprint-auth";
import { BiometricIDAvailableResult } from "../../src/fingerprint-auth.common";
import { Observable } from "tns-core-modules/data/observable";
import { FingerprintAuth, BiometricIDAvailableResult } from "nativescript-fingerprint-auth";

export class HelloWorldModel extends Observable {
private fingerprintAuth: FingerprintAuth;
public status: string = 'STATUS';
public status: string = "Tap a button..";

constructor() {
super();
Expand All @@ -14,64 +13,71 @@ export class HelloWorldModel extends Observable {

public doCheckAvailable(): void {
this.fingerprintAuth.available().then(
(result: BiometricIDAvailableResult) => {
console.log("available result: " + JSON.stringify(result));
this.set('status', "Biometric ID available? - " + (result.any ? (result.face ? "Face" : "Touch") : "NO"));
}
);
(result: BiometricIDAvailableResult) => {
console.log("available result: " + JSON.stringify(result));
this.set('status', "Biometric ID available? - " + (result.any ? (result.face ? "Face" : "Touch") : "NO"));
});
}

public doCheckFingerprintsChanged(): void {
this.fingerprintAuth.didFingerprintDatabaseChange().then(
(changed: boolean) => {
this.set('status', "Biometric ID changed? - " + (changed ? "YES" : "NO"));
}
);
(changed: boolean) => {
this.set('status', "Biometric ID changed? - " + (changed ? "YES" : "NO"));
});
}

public doVerifyFingerprint(): void {
this.fingerprintAuth.verifyFingerprint({
message: 'Scan yer finger', // optional
authenticationValidityDuration: 10 // Android
}).then(
() => {
this.set('status', "Biometric ID OK");
alert({
title: "Biometric ID / passcode OK",
okButtonText: "Sweet"
this.fingerprintAuth.verifyFingerprint(
{
message: 'Scan yer finger', // optional
authenticationValidityDuration: 10 // Android
})
.then(() => {
alert({
title: "Biometric ID / passcode OK",
okButtonText: "Sweet"
});
})
.catch(err => {
alert({
title: "Biometric ID NOT OK / canceled",
message: JSON.stringify(err),
okButtonText: "Mmkay"
});
});
},
err => {
this.set('status', "Biometric ID NOT OK: " + err);
alert({
title: "Biometric ID NOT OK / canceled",
okButtonText: "Mmkay"
});
}
);
}

public doVerifyFingerprintWithCustomUI(): void {
this.fingerprintAuth.verifyFingerprint(
{
message: 'Scan yer finger', // optional
useCustomAndroidUI: true // Android
})
.then(() => this.set('status', "Biometric ID OK"))
.catch(err => this.set('status', `Biometric ID NOT OK: " + ${JSON.stringify(err)}`));
}

public doVerifyFingerprintWithCustomFallback(): void {
this.fingerprintAuth.verifyFingerprintWithCustomFallback({
message: 'Scan yer finger', // optional
fallbackMessage: 'Enter PIN', // optional
authenticationValidityDuration: 10 // Android
}).then(
() => {
this.set('status', "Biometric ID OK");
alert({
title: "Biometric ID OK",
okButtonText: "Sweet"
});
},
error => {
this.set('status', "Biometric ID NOT OK: " + JSON.stringify(error));
alert({
title: "Biometric ID NOT OK",
message: (error.code === -3 ? "Show custom fallback" : error.message),
okButtonText: "Mmkay"
this.fingerprintAuth.verifyFingerprintWithCustomFallback(
{
message: 'Scan yer finger', // optional
fallbackMessage: 'Enter PIN', // optional
authenticationValidityDuration: 10 // Android
})
.then(() => {
this.set('status', "Biometric ID OK");
alert({
title: "Biometric ID OK",
okButtonText: "Sweet"
});
})
.catch(error => {
this.set('status', "Biometric ID NOT OK: " + JSON.stringify(error));
alert({
title: "Biometric ID NOT OK",
message: (error.code === -3 ? "Show custom fallback" : error.message),
okButtonText: "Mmkay"
});
});
}
);
}
}
42 changes: 42 additions & 0 deletions src/appcompat-activity.android.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { setActivityCallbacks, AndroidActivityCallbacks } from "tns-core-modules/ui/frame";

@JavaProxy("org.nativescript.fingerprintplugin.AppCompatActivity")
class Activity extends android.support.v7.app.AppCompatActivity {
private _callbacks: AndroidActivityCallbacks;

protected onCreate(savedInstanceState: android.os.Bundle): void {
if (!this._callbacks) {
setActivityCallbacks(this);
}

this._callbacks.onCreate(this, savedInstanceState, super.onCreate);
}

protected onSaveInstanceState(outState: android.os.Bundle): void {
this._callbacks.onSaveInstanceState(this, outState, super.onSaveInstanceState);
}

protected onStart(): void {
this._callbacks.onStart(this, super.onStart);
}

protected onStop(): void {
this._callbacks.onStop(this, super.onStop);
}

protected onDestroy(): void {
this._callbacks.onDestroy(this, super.onDestroy);
}

public onBackPressed(): void {
this._callbacks.onBackPressed(this, super.onBackPressed);
}

public onRequestPermissionsResult(requestCode: number, permissions: Array<String>, grantResults: Array<number>): void {
this._callbacks.onRequestPermissionsResult(this, requestCode, permissions, grantResults, undefined);
}

protected onActivityResult(requestCode: number, resultCode: number, data: android.content.Intent): void {
this._callbacks.onActivityResult(this, requestCode, resultCode, data, super.onActivityResult);
}
}
Loading

0 comments on commit 15c0e29

Please sign in to comment.