authme_ekyc_sdk 2.8.19
authme_ekyc_sdk: ^2.8.19 copied to clipboard
A Flutter package of ekyc of authme.
example/lib/main.dart
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:authme_ekyc_sdk/authme_ui_config.dart';
import 'package:authme_ekyc_sdk/ekyc_feature.dart';
import 'package:authme_ekyc_sdk/ekyc_locale.dart';
import 'package:authme_ekyc_sdk/ekyc_sdk_plugin_bridge.dart';
import 'package:authme_ekyc_sdk_example/widget/color_setting_panel.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter AuthMe eKYC SDK Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const AuthMeHomePage(),
);
}
}
class AuthMeHomePage extends StatefulWidget {
const AuthMeHomePage({super.key});
@override
State<AuthMeHomePage> createState() => _AuthMeHomePageState();
}
class FullScreenDisplayPage extends StatelessWidget {
final Map<String, dynamic> data;
final Map<String, String> images;
const FullScreenDisplayPage({
Key? key,
required this.data,
required this.images,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Details'),
backgroundColor: Colors.black,
),
body: Column(
children: [
Expanded(
child: ListView(
children: [
...data.entries.map((entry) {
return ListTile(
title: Text(entry.key),
subtitle: Text(entry.value.toString()),
);
}).toList(),
const Divider(),
if (images['frontCropImage'] != null)
Image.network(
images['frontCropImage']!,
fit: BoxFit.cover,
height: 150,
),
if (images['backCropImage'] != null)
Image.network(
images['backCropImage']!,
fit: BoxFit.cover,
height: 150,
),
],
),
),
],
),
);
}
}
class _AuthMeHomePageState extends State<AuthMeHomePage> {
final Color _buttonColor = const Color(0xFF00C1B6);
final Color _defaultButtonColor = const Color(0xFFFFFFFF);
String? _token;
bool _isLoading = false;
final AuthmeSdk _authMeSdk = AuthmeSdk();
late StreamSubscription _resultSubscription;
String _name = "";
var timestamp = DateTime.now().millisecondsSinceEpoch;
bool _isInitialized = false;
AuthmeLocale _selectedLanguage = AuthmeLocale.zhHant;
bool _needConfirm = true;
bool _isResultPageDisplayable = true;
bool _isResultEditable = true;
ActionButtonConfig? _actionButtonConfig;
ActionHintConfig? _actionHintConfig;
CloseHintConfig? _closeHintConfig;
BackHintConfig? _backHintConfig;
StepButtonConfig? _stepButtonConfig;
bool _showStatement = true;
@override
void initState() {
super.initState();
_resultSubscription = _authMeSdk.resultStream.listen(
(result) {
if (result.containsKey('result')) {
final resultMap = result['result'];
if (resultMap is Map) {
// 取得主要資料層
final data = resultMap['data'];
// 取得圖片層
final images = resultMap['images'];
if (data is Map) {
// 只修改主要資料層
Map<String, dynamic> modifications = Map.from(data);
// 修改需要更新的部分
modifications['address'] = 'new address';
// 確認結果時只傳送主要資料層的修改
_authMeSdk.confirmScanResult(modifications).then((_) {
log('Scan result confirmed:$modifications');
}).catchError((error) {
log('Failed to confirm scan result: $error');
});
// 如果需要處理圖片,可以另外處理
if (images != null && images is Map<String, dynamic>) {
final frontCropImage = images['frontCropImage'];
final frontImage = images['frontImage'];
final backCropImage = images['backCropImage'];
final backImage = images['backImage'];
// 將圖片轉換為 Uint8List
final List<Uint8List> imageBytes = [
frontCropImage,
frontImage,
backCropImage,
backImage,
]
.map((base64) => base64Decode(base64!.split(',').last))
.toList();
// 處理圖片...
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ListView.builder(
padding: const EdgeInsets.all(10),
itemCount: imageBytes.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
// 圖片圓角
child: Image.memory(
imageBytes[index],
fit: BoxFit.cover, // 圖片適應方式
height: 200, // 設置圖片高度
width: double.infinity, // 設置圖片寬度
),
),
);
},
)));
}
}
}
} else if (result.containsKey('error')) {
// 處理錯誤
log("錯誤: ${result['error']}");
}
},
onError: (error) {
log("Stream error: $error");
},
);
}
String normalizeBase64(String base64String) {
return base64String.replaceAll('\n', '');
}
String fixBase64(String base64String) {
while (base64String.length % 4 != 0) {
base64String += '=';
}
return base64String;
}
Future<void> _fetchToken() async {
setState(() {
_isLoading = true;
});
try {
final response = await http.post(
Uri.parse('https://auth.authme.com/connect/token'),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: {
'client_id': '8BAFC189407549C2B451F58FB5136BCC',
'client_secret': '8BAFC189407549C2B451F58FB5136BCC',
'grant_type': 'client_credentials',
'scope':
'AuthmeServices customer_id:FlutterTest$timestamp event_name:default',
},
);
log(response.toString());
if (response.statusCode == 200) {
final Map<String, dynamic> jsonResponse = json.decode(response.body);
setState(() {
_token = jsonResponse['access_token'];
_isLoading = false;
});
_authMeSdk.refreshToken(_token!);
} else {
throw Exception('Failed to load token');
}
} catch (e) {
setState(() {
_isLoading = false;
});
print('Error fetching token: $e');
}
}
Future<void> initSDK() async {
try {
var serverURL = "https://api.authme.com";
var activateToken =
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmZWF0dXJlcyI6WyJvcmdfbmFtZT1hdXRobWUtd2ViZGVtbyJdLCJleHAiOjE3ODM5MDA4MDAsImlzcyI6ImF1dGhtZS5jb20iLCJuYmYiOjE3NjgxNzYwMDB9.kXWkLXlDSMVkyD2gHF4I77DuHkwEAl18b09H92NvYbzvk2P0013r5W9j40O1mOjZ91J3-XDjQd186mkqwOig_a-95Cz5xYAEse2A_nbsnO6cOS-NUWwD3LQL38mmKMulM0EerHiJwJfo7hXjSwwHyB416uWMGVxgPG6azsAO6gR8iycAzKI8eUFKZWA4Lvfa-sXwtUTSdSJMqNW9icSeGH7LUV2f2aUHi3i75y7GSVUH1wsEeVHOmsNFuzqH8f8innhzHKh2H0ICAyBxqnUbkooi4aiztL-muRD6eK6Nn2OTnqAeXw1390YUpTIKZ7Ya7V7WVw_-yLkr7Q1-DWIiXg";
var activateCertificate =
"-----BEGIN CERTIFICATE-----\nMIIEhjCCA26gAwIBAgIUAOZ+C1CekSjJ38NsRKS+qCvW8ZAwDQYJKoZIhvcNAQEL\nBQAwNzELMAkGA1UEBhMCVFcxDzANBgNVBAoTBkF1dGhtZTEXMBUGA1UEAwwOKi4o\nYXV0aG1lKS5jb20wHhcNMjUwNTA2MDgzODUzWhcNMzIwNzA5MDI0NzA2WjAzMQsw\nCQYDVQQGEwJUVzEPMA0GA1UEChMGQXV0aG1lMRMwEQYDVQQDEwphdXRobWUuY29t\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsCTrKRpifJosF9gAyd5I\nmU9Ay3uEVWHrx122ks+JBm9u+1oVtH+AefTK+VRyS0kQ+vrddbSD6gwHPU6zXWV7\n3ZQxv+Kb/6lSZT2q0zhE2yU/QS1bSMwRLJ2CVS5YOoP5kmV/sj4RUHSSGtJHc0VG\nCPvPcsGrN17spXEjAjAHWZRFlXnQ37goQdP/EXwFU/1dIcViDSUzgeQjYItfTDTQ\nJQWffSE4goss4+WolnjcZvMORI1zr4kvecYDbOthhlngmsUykOFkEXI4udVRwzZq\n/qeznv0tSlgDBy0Mi8KkIgMqMZM70WSbL9t2AuW/m0+JWYemVNdKuFDWi3oKz/17\nvQIDAQABo4IBjDCCAYgwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUF\nBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFFXxdO+tPGt85NoCnjDlz8+s3tEa\nMB8GA1UdIwQYMBaAFMVSKq0CAmAnU4FngTexJCHZnjwfMIGNBggrBgEFBQcBAQSB\ngDB+MHwGCCsGAQUFBzAChnBodHRwOi8vcHJpdmF0ZWNhLWNvbnRlbnQtNjJjY2E3\nOGItMDAwMC0yMTA1LWE3ZjEtZDRmNTQ3ZjgzZTc0LnN0b3JhZ2UuZ29vZ2xlYXBp\ncy5jb20vZDdjNjY2MDY0NWMwNTM5NTE1ZTIvY2EuY3J0MIGCBgNVHR8EezB5MHeg\ndaBzhnFodHRwOi8vcHJpdmF0ZWNhLWNvbnRlbnQtNjJjY2E3OGItMDAwMC0yMTA1\nLWE3ZjEtZDRmNTQ3ZjgzZTc0LnN0b3JhZ2UuZ29vZ2xlYXBpcy5jb20vZDdjNjY2\nMDY0NWMwNTM5NTE1ZTIvY3JsLmNybDANBgkqhkiG9w0BAQsFAAOCAQEAOR3gAZ1f\nQL3DVWAEFlH9gyKgOht74+xX/N3qmVzAl6TzEwwy70uxURbzItTbfPhsjBdeLcWj\n0jQ4SnedX4NJzck+lctq4UXFZ38cthUxKNX77Of5nuzpqpbrpCGxE3cLC+4u7F8g\n49AMFMopxDWeUgbdxDGoFgBH0jRs5fQYNfX2zrNklyxb2e9cZsOsMRbIsjBub5th\nlquYVZUe1BL9p9T5FrY+nKJLuB9RoXcRNCTyLClTFoK4e7v42qq5QhaFVIZoAPSw\nRByCxKsisPr0+G4h8Z0+edETImjiW+Fh+wJnAcMYqxfGD/J5LtSiYM5L9qkd0rP0\nUMpQuhXM1tAGxA==\n-----END CERTIFICATE-----\n\n";
final uiConfig = AuthMeUIConfig(
actionButton: _actionButtonConfig,
actionHint: _actionHintConfig,
closeHint: _closeHintConfig,
backHint: _backHintConfig,
stepButton: _stepButtonConfig,
showStatement: _showStatement,
);
await _authMeSdk.initialize(
serverURL,
activateToken,
activateCertificate,
showStatement: _showStatement,
);
_authMeSdk.setLocale(AuthmeLocale.zhHant);
} on PlatformException catch (e) {
// 捕捉 PlatformException 並進行處理
log("Failed to initialize AuthMe SDK: ${e.message}");
// 顯示錯誤訊息給用戶,或進一步處理錯誤
} catch (e) {
// 捕捉其他潛在的例外
log("An unexpected error occurred: ${e.runtimeType}");
}
}
void _startAuthme() async {
try {
await _fetchToken();
if (_token != null) {
await _authMeSdk.startFeature(
_token!,
AuthmeFeature.TWID,
needConfirm: _needConfirm,
isResultPageDisplayable: _isResultPageDisplayable,
isResultEditable: _isResultEditable,
requestCode: 999,
captureTimeout: 0,
);
} else {
log('Token 不可用');
}
} on PlatformException catch (e) {
// 捕捉 PlatformException 並進行處理
log("Failed to start AuthMe SDK: ${e.message}");
// 顯示錯誤訊息給用戶,或進一步處理錯誤
} catch (e) {
// 捕捉其他潛在的例外
log("An unexpected error occurred: ${e.runtimeType}");
}
}
void _startAuthmeLiveness() async {
try {
_fetchToken();
if (_token != null) {
await _authMeSdk.startFeature(
_token!,
AuthmeFeature.Liveness,
needConfirm: _needConfirm,
isResultPageDisplayable: _isResultPageDisplayable,
isResultEditable: _isResultEditable,
);
} else {
log('Token 不可用');
}
} on PlatformException catch (e) {
// 捕捉 PlatformException 並進行處理
log("Failed to start Authme Liveness: ${e.message}");
// 顯示錯誤訊息給用戶,或進一步處理錯誤
} catch (e) {
// 捕捉其他潛在的例外
log("An unexpected error occurred: ${e.runtimeType}");
}
}
void _startAuthmePassport() async {
_fetchToken();
if (_token != null) {
await _authMeSdk.startFeature(
_token!,
AuthmeFeature.Passport,
needConfirm: _needConfirm,
isResultPageDisplayable: _isResultPageDisplayable,
isResultEditable: _isResultEditable,
);
} else {
print('Token 不可用');
}
}
void _startAuthmePassportNFC() async {
_fetchToken();
if (_token != null) {
await _authMeSdk.startFeature(
_token!,
AuthmeFeature.NFCPassport,
needConfirm: _needConfirm,
isResultPageDisplayable: _isResultPageDisplayable,
isResultEditable: _isResultEditable,
);
} else {
print('Token 不可用');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text("Flutter AuthMe eKYC SDK Example"),
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_colorPanel(),
const SizedBox(height: 20),
_buildSettingsCard(),
const SizedBox(height: 20),
_buildFeaturesCard(),
],
),
),
),
);
}
Widget _colorPanel() {
if (Platform.isIOS) {
return Card(
margin: EdgeInsets.zero,
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
ColorSettingsPanel(
actionButtonConfig: _actionButtonConfig,
actionHintConfig: _actionHintConfig,
closeHintConfig: _closeHintConfig,
backHintConfig: _backHintConfig,
stepButtonConfig: _stepButtonConfig,
showStatement: _showStatement,
isInitialized: _isInitialized,
onShowStatementChanged: (value) {
setState(() => _showStatement = value);
},
onActionButtonChanged: _updateActionButton,
onActionHintChanged: _updateActionHint,
onCloseHintChanged: _updateCloseHint,
onBackHintChanged: _updateBackHint,
onStepButtonChanged: _updateStepButton,
// 新增回調處理
onApplyConfig: () {
_authMeSdk.setUIConfig(AuthMeUIConfig(
actionButton: _actionButtonConfig,
actionHint: _actionHintConfig,
closeHint: _closeHintConfig,
backHint: _backHintConfig,
stepButton: _stepButtonConfig,
showStatement: _showStatement));
// 可選:顯示成功提示
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('UI 設定已更新'),
backgroundColor: Colors.green,
),
);
},
),
],
),
),
);
} else {
return const SizedBox(height: 0);
}
}
Widget _buildSettingsCard() {
return Card(
margin: EdgeInsets.zero,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('設置', style: Theme.of(context).textTheme.titleLarge),
_isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const SizedBox(),
],
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'語言設定',
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 8),
DropdownButtonFormField<AuthmeLocale>(
value: _selectedLanguage,
decoration: const InputDecoration(
contentPadding:
EdgeInsets.symmetric(horizontal: 12, vertical: 8),
border: OutlineInputBorder(),
),
items: AuthmeLocale.values.map((language) {
return DropdownMenuItem(
value: language,
child: Text(language.displayName),
);
}).toList(),
onChanged: (AuthmeLocale? value) {
if (value != null) {
setState(() {
_selectedLanguage = value;
});
_updateLanguage(value);
}
},
),
],
),
),
SwitchListTile(
title: const Text('需要確認'),
value: _needConfirm,
onChanged: (bool value) {
setState(() => _needConfirm = value);
},
),
SwitchListTile(
title: const Text('顯示結果頁面'),
value: _isResultPageDisplayable,
onChanged: (bool value) {
setState(() => _isResultPageDisplayable = value);
},
),
SwitchListTile(
title: const Text('允許編輯結果'),
value: _isResultEditable,
onChanged: (bool value) {
setState(() => _isResultEditable = value);
},
),
const SizedBox(height: 8),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: _isInitialized
? null
: () async {
setState(() {
_isInitialized = true;
});
try {
initSDK();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('SDK 初始化成功'),
backgroundColor: Colors.green,
),
);
} catch (e) {
setState(() {
_isInitialized = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('初始化失敗: $e'),
backgroundColor: Colors.red,
),
);
}
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: _buttonColor,
disabledBackgroundColor: _buttonColor.withOpacity(0.6),
),
child: const Text(
"初始化 SDK",
style: TextStyle(color: Colors.white),
),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton(
onPressed: _isInitialized && _token == null
? () => _fetchToken()
: null,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: _buttonColor,
disabledBackgroundColor: _buttonColor.withOpacity(0.6),
),
child: const Text(
"Get Token",
style: TextStyle(color: Colors.white),
),
),
),
],
),
],
),
),
);
}
Widget _buildFeaturesCard() {
return Card(
margin: EdgeInsets.zero,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('功能', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 16),
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 2.5,
children: [
_buildFeatureButton('Start AuthMe', _startAuthme),
_buildFeatureButton(
'Start AuthMe Liveness', _startAuthmeLiveness),
_buildFeatureButton(
'Start Authme Passport', _startAuthmePassport),
_buildFeatureButton(
'Start Authme Passport NFC', _startAuthmePassportNFC),
],
),
if (_name.isNotEmpty) ...[
const SizedBox(height: 16),
Text(
"Name: $_name",
style: const TextStyle(fontSize: 24),
),
],
],
),
),
);
}
Widget _buildFeatureButton(String title, VoidCallback onPressed) {
final bool isEnabled = _isInitialized && _token != null;
final txtColor = isEnabled ? Colors.black : Colors.grey;
return ElevatedButton(
onPressed: isEnabled ? onPressed : null,
style: ElevatedButton.styleFrom(
backgroundColor: _defaultButtonColor,
disabledBackgroundColor: _defaultButtonColor,
),
child: Text(
title,
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: txtColor),
),
);
}
Future<void> _updateLanguage(AuthmeLocale language) async {
try {
await _authMeSdk.setLocale(language);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('語言設定已更新'),
backgroundColor: Colors.green,
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('更新語言失敗: ${e.toString()}'),
backgroundColor: Colors.red,
),
);
} finally {}
}
void _updateActionButton({Color? backgroundColor, Color? contentColor}) {
setState(() {
_actionButtonConfig = ActionButtonConfig(
backgroundColor: backgroundColor ??
_actionButtonConfig?.backgroundColor ??
Colors.blue,
contentColor:
contentColor ?? _actionButtonConfig?.contentColor ?? Colors.white,
);
});
}
void _updateActionHint({Color? backgroundColor, Color? contentColor}) {
setState(() {
_actionHintConfig = ActionHintConfig(
backgroundColor: backgroundColor ??
_actionHintConfig?.backgroundColor ??
Colors.white,
contentColor:
contentColor ?? _actionHintConfig?.contentColor ?? Colors.black,
);
});
}
void _updateCloseHint({Color? backgroundColor, Color? contentColor}) {
setState(() {
_closeHintConfig = CloseHintConfig(
backgroundColor: backgroundColor ??
_closeHintConfig?.backgroundColor ??
Colors.white,
contentColor:
contentColor ?? _closeHintConfig?.contentColor ?? Colors.black,
);
});
}
void _updateBackHint({Color? contentColor}) {
setState(() {
_backHintConfig = BackHintConfig(
contentColor:
contentColor ?? _backHintConfig?.contentColor ?? Colors.black,
);
});
}
void _updateStepButton({Color? backgroundColor, Color? contentColor}) {
setState(() {
_stepButtonConfig = StepButtonConfig(
backgroundColor: backgroundColor ??
_stepButtonConfig?.backgroundColor ??
Colors.blue,
contentColor:
contentColor ?? _stepButtonConfig?.contentColor ?? Colors.white,
);
});
}
@override
void dispose() {
_resultSubscription.cancel();
_authMeSdk.dispose();
super.dispose();
}
}