Compare commits
No commits in common. "b28c8278e2a1abab8a4316f1f675c81ffba67e63" and "913170ce20b020997b472e53ba56c4b1f1a58ae0" have entirely different histories.
b28c8278e2
...
913170ce20
|
|
@ -1,9 +1,3 @@
|
|||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
|
|
@ -12,6 +6,11 @@ if (localPropertiesFile.exists()) {
|
|||
}
|
||||
}
|
||||
|
||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||
if (flutterRoot == null) {
|
||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '4'
|
||||
|
|
@ -22,16 +21,20 @@ if (flutterVersionName == null) {
|
|||
flutterVersionName = '1.0.4'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
def keystoreProperties = new Properties()
|
||||
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
|
||||
|
||||
android {
|
||||
namespace "kz.com.aman.kassa"
|
||||
compileSdkVersion 36
|
||||
ndkVersion "28.2.13676358"
|
||||
compileSdkVersion 31
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
|
|
@ -43,7 +46,7 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId "kz.com.aman.kassa"
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 35
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
|
@ -59,24 +62,29 @@ android {
|
|||
storePassword keystoreProperties['storePassword']
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
shrinkResources false
|
||||
minifyEnabled false
|
||||
useProguard false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
defaultConfig {
|
||||
minSdkVersion 24
|
||||
}
|
||||
}
|
||||
release {
|
||||
shrinkResources false
|
||||
minifyEnabled false
|
||||
useProguard false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
//coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
|
|
@ -85,8 +93,9 @@ android {
|
|||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
flutter {
|
||||
|
|
@ -94,6 +103,7 @@ flutter {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
//coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.9'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
|
|
@ -101,5 +111,15 @@ dependencies {
|
|||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.google.code.gson/gson
|
||||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
|
||||
|
||||
|
||||
//implementation 'com.android.support:multidex:2.0.1'
|
||||
//m4bank dependencies
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,24 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = '2.2.20'
|
||||
ext.kotlin_version = '1.6.10'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
|
||||
classpath 'com.android.tools.build:gradle:3.5.0'
|
||||
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // add plugin
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
|
|
@ -31,42 +37,13 @@ subprojects {
|
|||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
gradle.beforeProject { proj ->
|
||||
proj.afterEvaluate {
|
||||
if (proj.plugins.hasPlugin("com.android.library")) {
|
||||
try {
|
||||
if (!proj.android.namespace) {
|
||||
def manifestFile = proj.file("src/main/AndroidManifest.xml")
|
||||
if (manifestFile.exists()) {
|
||||
def manifest = new groovy.xml.XmlSlurper().parse(manifestFile)
|
||||
def packageName = manifest.'@package'?.toString()
|
||||
if (packageName) {
|
||||
proj.android.namespace = packageName
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
// Force Java 17 for all Android subprojects to match global Kotlin JVM target 17
|
||||
if (proj.plugins.hasPlugin("com.android.library") || proj.plugins.hasPlugin("com.android.application")) {
|
||||
try {
|
||||
proj.android.compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.layout.buildDirectory
|
||||
}
|
||||
|
||||
allprojects {
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,3 @@ org.gradle.jvmargs=-Xmx1536M
|
|||
android.enableR8=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
# This builtInKotlin flag was added automatically by Flutter migrator
|
||||
android.builtInKotlin=false
|
||||
# This newDsl flag was added automatically by Flutter migrator
|
||||
android.newDsl=false
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
|
||||
|
|
|
|||
|
|
@ -1,25 +1,15 @@
|
|||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}()
|
||||
include ':app'
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
def plugins = new Properties()
|
||||
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
|
||||
if (pluginsFile.exists()) {
|
||||
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "8.11.1" apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.2.20" apply false
|
||||
plugins.each { name, path ->
|
||||
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
|
||||
include ":$name"
|
||||
project(":$name").projectDir = pluginDirectory
|
||||
}
|
||||
|
||||
include ":app"
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
import lldb
|
||||
|
||||
def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict):
|
||||
"""Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages."""
|
||||
base = frame.register["x0"].GetValueAsAddress()
|
||||
page_len = frame.register["x1"].GetValueAsUnsigned()
|
||||
|
||||
# Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the
|
||||
# first page to see if handled it correctly. This makes diagnosing
|
||||
# misconfiguration (e.g. missing breakpoint) easier.
|
||||
data = bytearray(page_len)
|
||||
data[0:8] = b'IHELPED!'
|
||||
|
||||
error = lldb.SBError()
|
||||
frame.GetThread().GetProcess().WriteMemory(base, data, error)
|
||||
if not error.Success():
|
||||
print(f'Failed to write into {base}[+{page_len}]', error)
|
||||
return
|
||||
|
||||
def __lldb_init_module(debugger: lldb.SBDebugger, _):
|
||||
target = debugger.GetDummyTarget()
|
||||
# Caveat: must use BreakpointCreateByRegEx here and not
|
||||
# BreakpointCreateByName. For some reasons callback function does not
|
||||
# get carried over from dummy target for the later.
|
||||
bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$")
|
||||
bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__))
|
||||
bp.SetAutoContinue(True)
|
||||
print("-- LLDB integration loaded --")
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
#
|
||||
# Generated file, do not edit.
|
||||
#
|
||||
|
||||
command script import --relative-to-command-file flutter_lldb_helper.py
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
FLUTTER_ROOT=/home/rustem/development/flutter
|
||||
FLUTTER_APPLICATION_PATH=/home/rustem/PhpstormProjects/aman-kassa-flutter
|
||||
FLUTTER_FRAMEWORK_SWIFT_PACKAGE_PATH=/home/rustem/PhpstormProjects/aman-kassa-flutter/ios/Flutter/ephemeral/Packages/.packages/FlutterFramework
|
||||
COCOAPODS_PARALLEL_CODE_SIGN=true
|
||||
FLUTTER_TARGET=lib/main.dart
|
||||
FLUTTER_BUILD_DIR=build
|
||||
FLUTTER_BUILD_NAME=1.4.0
|
||||
FLUTTER_BUILD_NUMBER=45
|
||||
DART_OBFUSCATION=false
|
||||
TRACK_WIDGET_CREATION=true
|
||||
TREE_SHAKE_ICONS=false
|
||||
PACKAGE_CONFIG=.dart_tool/package_config.json
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
class BluetoothDevice {
|
||||
String? name;
|
||||
String? address;
|
||||
int? type;
|
||||
bool? connected;
|
||||
|
||||
BluetoothDevice();
|
||||
|
||||
factory BluetoothDevice.fromJson(Map<String, dynamic> json) {
|
||||
return BluetoothDevice()
|
||||
..name = json['name']
|
||||
..address = json['address']
|
||||
..type = json['type']
|
||||
..connected = json['connected'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'name': name,
|
||||
'address': address,
|
||||
'type': type,
|
||||
'connected': connected,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import 'package:aman_kassa_flutter/redux/state/user_state.dart';
|
|||
import 'package:aman_kassa_flutter/redux/store.dart';
|
||||
import 'package:aman_kassa_flutter/views/login/login_view.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:device_info/device_info.dart';
|
||||
import 'package:aman_kassa_flutter/core/models/message.dart';
|
||||
import 'package:aman_kassa_flutter/core/models/response.dart';
|
||||
import 'package:aman_kassa_flutter/core/models/smena.dart';
|
||||
|
|
|
|||
|
|
@ -1,30 +1,173 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:aman_kassa_flutter/core/base/base_service.dart';
|
||||
import 'package:aman_kassa_flutter/core/models/bluetooth_device.dart';
|
||||
import 'package:bluetooth_print/bluetooth_print.dart';
|
||||
import 'package:bluetooth_print/bluetooth_print_model.dart';
|
||||
import 'package:flutter_blue/flutter_blue.dart' as flutter_blue;
|
||||
import 'package:flutter_blue/gen/flutterblue.pb.dart' as proto;
|
||||
|
||||
// Stub implementation — bluetooth print disabled
|
||||
class BluePrintService extends BaseService {
|
||||
final BluetoothPrint _bluetoothAndr = BluetoothPrint.instance;
|
||||
flutter_blue.BluetoothDevice? _bluetoothDeviceIOS;
|
||||
final flutter_blue.FlutterBlue _bluetoothIOS =
|
||||
flutter_blue.FlutterBlue.instance;
|
||||
|
||||
final bool isAndroid = Platform.isAndroid;
|
||||
final bool isIos = Platform.isIOS;
|
||||
// final bool isAndroid = false;
|
||||
// final bool isIos = true;
|
||||
|
||||
BluetoothDevice? _device;
|
||||
|
||||
Future<void> scan() async {}
|
||||
Future<void> scan() async {
|
||||
if (isAndroid) {
|
||||
await _bluetoothAndr.startScan(timeout: Duration(seconds: 4));
|
||||
} else if (isIos) {
|
||||
await _bluetoothIOS.startScan(timeout: const Duration(seconds: 5));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stopScan() async {}
|
||||
Future<void> stopScan() async {
|
||||
if (isAndroid) {
|
||||
await _bluetoothAndr.stopScan();
|
||||
} else if (isIos) {
|
||||
await _bluetoothIOS.stopScan();
|
||||
}
|
||||
}
|
||||
|
||||
Stream<List<BluetoothDevice>> get scanResult =>
|
||||
Stream<List<BluetoothDevice>>.value([]);
|
||||
Stream<List<BluetoothDevice>> get scanResult {
|
||||
if (isAndroid)
|
||||
return _bluetoothAndr.scanResults;
|
||||
else
|
||||
return _bluetoothIOS.scanResults.asyncMap<List<BluetoothDevice>>(
|
||||
(event) =>
|
||||
Future.wait(event.toList().map((e) async => BluetoothDevice()
|
||||
..name = e.device.name
|
||||
..type = e.device.type.index
|
||||
..address = e.device.id.id)));
|
||||
}
|
||||
|
||||
Stream<bool> get isScanning => Stream<bool>.value(false);
|
||||
Stream<bool> get isScanning =>
|
||||
isAndroid ? _bluetoothAndr.isScanning : _bluetoothIOS.isScanning;
|
||||
|
||||
Stream<int> get state => Stream<int>.value(0);
|
||||
Stream<int> get state => isAndroid
|
||||
? _bluetoothAndr.state
|
||||
: _bluetoothIOS.state.asyncMap<int>((event) => event.index);
|
||||
|
||||
set device(BluetoothDevice device) => _device = device;
|
||||
|
||||
Future<bool> connect() async => false;
|
||||
Future<bool> connect() async {
|
||||
bool response = false;
|
||||
if (_device == null) {
|
||||
response = false;
|
||||
} else {
|
||||
// try {
|
||||
// await _bluetoothAndr.connect(_device!);
|
||||
// await Future.delayed(Duration(seconds: 5));
|
||||
// response = true;
|
||||
// } catch (e) {
|
||||
// print('Error connect $e');
|
||||
// response = false;
|
||||
// }
|
||||
|
||||
Future<bool> disconnect() async => true;
|
||||
try {
|
||||
if (isAndroid) {
|
||||
await _bluetoothAndr.connect(_device!);
|
||||
} else if (isIos) {
|
||||
_bluetoothDeviceIOS = flutter_blue.BluetoothDevice.fromProto(
|
||||
proto.BluetoothDevice(
|
||||
name: _device?.name ?? '',
|
||||
remoteId: _device?.address ?? '',
|
||||
type: proto.BluetoothDevice_Type.valueOf(_device?.type ?? 0),
|
||||
),
|
||||
);
|
||||
final List<flutter_blue.BluetoothDevice> connectedDevices =
|
||||
await _bluetoothIOS.connectedDevices;
|
||||
final int deviceConnectedIndex = connectedDevices
|
||||
.indexWhere((flutter_blue.BluetoothDevice bluetoothDevice) {
|
||||
return bluetoothDevice.id == _bluetoothDeviceIOS?.id;
|
||||
});
|
||||
if (deviceConnectedIndex < 0) {
|
||||
await _bluetoothDeviceIOS?.connect();
|
||||
}
|
||||
}
|
||||
|
||||
response = true;
|
||||
_device?.connected = true;
|
||||
return Future<bool>.value(response);
|
||||
} on Exception catch (error) {
|
||||
print('$runtimeType - Error $error');
|
||||
response = false;
|
||||
_device?.connected = false;
|
||||
return Future<bool>.value(response);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<bool> disconnect() async {
|
||||
bool response = false;
|
||||
try {
|
||||
if (isAndroid) {
|
||||
await _bluetoothAndr.disconnect();
|
||||
} else if (isIos) {
|
||||
await _bluetoothDeviceIOS?.disconnect();
|
||||
}
|
||||
|
||||
print('disconnected');
|
||||
response = true;
|
||||
} catch (e) {
|
||||
print('Error $e');
|
||||
response = false;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<bool> printBytes(Uint8List bytes,
|
||||
{int chunkSizeBytes = 20, int queueSleepTimeMs = 20}) async => false;
|
||||
{int chunkSizeBytes = 20, int queueSleepTimeMs = 20}) async {
|
||||
Map<String, dynamic> config = Map();
|
||||
final len = bytes.length;
|
||||
List<List<int>> chunks = [];
|
||||
for (var i = 0; i < len; i += chunkSizeBytes) {
|
||||
var end = (i + chunkSizeBytes < len) ? i + chunkSizeBytes : len;
|
||||
chunks.add(bytes.sublist(i, end));
|
||||
}
|
||||
|
||||
for (var i = 0; i < chunks.length; i += 1) {
|
||||
if (isAndroid) {
|
||||
await _printAndroid(chunks[i], config);
|
||||
} else if (isIos) {
|
||||
await _printIos(Uint8List.fromList(chunks[i]), config);
|
||||
}
|
||||
|
||||
await Future.delayed(Duration(milliseconds: queueSleepTimeMs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> _printIos(Uint8List bytes,Map<String, dynamic> config) async {
|
||||
final List<flutter_blue.BluetoothService> bluetoothServices =
|
||||
await _bluetoothDeviceIOS?.discoverServices() ??
|
||||
<flutter_blue.BluetoothService>[];
|
||||
final flutter_blue.BluetoothService bluetoothService =
|
||||
bluetoothServices.firstWhere(
|
||||
(flutter_blue.BluetoothService service) => service.isPrimary,
|
||||
);
|
||||
final flutter_blue.BluetoothCharacteristic characteristic =
|
||||
bluetoothService.characteristics.firstWhere(
|
||||
(flutter_blue.BluetoothCharacteristic bluetoothCharacteristic) =>
|
||||
bluetoothCharacteristic.properties.write,
|
||||
);
|
||||
await characteristic.write(bytes, withoutResponse: true);
|
||||
}
|
||||
|
||||
Future<void> _printAndroid(List<int> chunk ,Map<String, dynamic> config) async {
|
||||
|
||||
|
||||
await _bluetoothAndr.rawBytes(config, chunk);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,8 +106,9 @@ class MainApplication extends StatelessWidget {
|
|||
),
|
||||
builder: (context, child) => MaterialApp(
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: primaryColor).copyWith(surface: backgroundColor),
|
||||
backgroundColor: backgroundColor,
|
||||
primaryColor: primaryColor,
|
||||
accentColor: yellowColor,
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
// textTheme: GoogleFonts.latoTextTheme(
|
||||
// Theme.of(context).textTheme,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:aman_kassa_flutter/core/models/bluetooth_device.dart';
|
||||
import 'package:aman_kassa_flutter/redux/constants/setting_const.dart';
|
||||
import 'package:aman_kassa_flutter/redux/state/setting_state.dart';
|
||||
import 'package:bluetooth_print/bluetooth_print_model.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:redux/redux.dart';
|
||||
import 'package:redux_thunk/redux_thunk.dart';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:aman_kassa_flutter/core/models/bluetooth_device.dart';
|
||||
import 'package:aman_kassa_flutter/redux/constants/setting_const.dart';
|
||||
import 'package:bluetooth_print/bluetooth_print_model.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@immutable
|
||||
|
|
|
|||
|
|
@ -120,16 +120,15 @@ class _BankSettingViewState extends State<BankSettingView> {
|
|||
labelText: 'Пароль', hintText: "Введите пароль"),
|
||||
),
|
||||
verticalSpaceMedium,
|
||||
ElevatedButton(
|
||||
RaisedButton(
|
||||
onPressed: () => this._saveData(context),
|
||||
child: Text(
|
||||
'Cохранить',
|
||||
style: TextStyle(color: whiteColor, fontSize: 25.0),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 20.0),
|
||||
),
|
||||
color: primaryColor,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 5.0, horizontal: 20.0),
|
||||
),
|
||||
|
||||
],
|
||||
|
|
|
|||
|
|
@ -122,16 +122,15 @@ class _ForteSettingViewState extends State<ForteSettingView> {
|
|||
labelText: 'Пароль', hintText: "Введите пароль"),
|
||||
),
|
||||
verticalSpaceMedium,
|
||||
ElevatedButton(
|
||||
RaisedButton(
|
||||
onPressed: () => this._saveData(context),
|
||||
child: Text(
|
||||
'Cохранить',
|
||||
style: TextStyle(color: whiteColor, fontSize: 25.0),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 20.0),
|
||||
),
|
||||
color: primaryColor,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 5.0, horizontal: 20.0),
|
||||
),
|
||||
|
||||
],
|
||||
|
|
|
|||
|
|
@ -171,13 +171,13 @@ class _BankViewState extends State<BankView> {
|
|||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
ElevatedButton(
|
||||
RaisedButton(
|
||||
child: Text('Activity m4Bank'), onPressed: activity),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
Text('version: $versionValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
|
||||
ElevatedButton(
|
||||
RaisedButton(
|
||||
child: Text('Version'), onPressed: version),
|
||||
],
|
||||
),
|
||||
|
|
@ -185,7 +185,7 @@ class _BankViewState extends State<BankView> {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
Text('init: $initValue' , overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
|
||||
ElevatedButton(
|
||||
RaisedButton(
|
||||
child: Text('Init'), onPressed: initialize),
|
||||
],
|
||||
),
|
||||
|
|
@ -193,7 +193,7 @@ class _BankViewState extends State<BankView> {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
Text('connection: $connectionValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
|
||||
ElevatedButton(
|
||||
RaisedButton(
|
||||
child: Text('Connect'), onPressed: connect),
|
||||
],
|
||||
),
|
||||
|
|
@ -201,21 +201,21 @@ class _BankViewState extends State<BankView> {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
Text('auth: $authValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
|
||||
ElevatedButton(child: Text('Auth'), onPressed: auth),
|
||||
RaisedButton(child: Text('Auth'), onPressed: auth),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
Text('cancel: $cancelValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
|
||||
ElevatedButton(child: Text('Cancel'), onPressed: cancel),
|
||||
RaisedButton(child: Text('Cancel'), onPressed: cancel),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
Text('shutdown: $shutdownValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
|
||||
ElevatedButton(
|
||||
RaisedButton(
|
||||
child: Text('Shutdown'), onPressed: shutdown),
|
||||
],
|
||||
),
|
||||
|
|
@ -223,7 +223,7 @@ class _BankViewState extends State<BankView> {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
Text('transaction: $transactionValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
|
||||
ElevatedButton(
|
||||
RaisedButton(
|
||||
child: Text('Transaction'), onPressed: transaction),
|
||||
],
|
||||
),
|
||||
|
|
@ -231,7 +231,7 @@ class _BankViewState extends State<BankView> {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
Text('get: $getValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
|
||||
ElevatedButton(
|
||||
RaisedButton(
|
||||
child: Text('Get'), onPressed: get),
|
||||
],
|
||||
),
|
||||
|
|
@ -239,7 +239,7 @@ class _BankViewState extends State<BankView> {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
Text('error: $errorValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
|
||||
ElevatedButton(
|
||||
RaisedButton(
|
||||
child: Text('Error'), onPressed: error),
|
||||
],
|
||||
),
|
||||
|
|
@ -247,12 +247,12 @@ class _BankViewState extends State<BankView> {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: <Widget>[
|
||||
Text('error: $closeDayValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
|
||||
ElevatedButton(
|
||||
RaisedButton(
|
||||
child: Text('CloseDay'), onPressed: closeDay),
|
||||
],
|
||||
),
|
||||
Text('pay: $payValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
|
||||
ElevatedButton(child: Text('Payment'), onPressed: pay),
|
||||
RaisedButton(child: Text('Payment'), onPressed: pay),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -26,13 +26,12 @@ import 'package:aman_kassa_flutter/views/settings/printer/PrinterTest.dart';
|
|||
import 'package:aman_kassa_flutter/views/payment/halyk_pos_service.dart';
|
||||
import 'package:aman_kassa_flutter/widgets/fields/busy_button_icon.dart';
|
||||
import 'package:aman_kassa_flutter/widgets/loader/Dialogs.dart';
|
||||
import 'package:aman_kassa_flutter/core/models/bluetooth_device.dart';
|
||||
import 'package:bluetooth_print/bluetooth_print_model.dart';
|
||||
import 'package:esc_pos_utils/esc_pos_utils.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:vocsy_esys_flutter_share/vocsy_esys_flutter_share.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../core/models/aman_dao.dart';
|
||||
|
|
@ -402,11 +401,7 @@ class _MyFloatingActionButtonState extends State<MyFloatingActionButton> {
|
|||
|
||||
void shareFile() async {
|
||||
try {
|
||||
final bytes = base64Decode(widget.data.data!.base64Data!);
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final file = File('${tempDir.path}/aman_kassa_check.png');
|
||||
await file.writeAsBytes(bytes);
|
||||
await Share.shareXFiles([XFile(file.path, mimeType: 'image/png')], subject: 'Aman Kassa');
|
||||
await Share.file('Aman Kassa', 'aman_kassa_check.png', base64Decode(widget.data.data!.base64Data!), 'image/png');
|
||||
} catch (e) {
|
||||
print('error: $e');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class _HistoryViewState extends State<HistoryView> {
|
|||
appBar: AppBar(
|
||||
title: Text('История чеков'),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
FlatButton(
|
||||
child: Text(
|
||||
'Очистить',
|
||||
style: TextStyle(
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@ class _HomeViewState extends State<HomeView> with WidgetsBindingObserver {
|
|||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
titleSpacing: 5.0,
|
||||
brightness: Brightness.light,
|
||||
title: HeaderTitle(),
|
||||
actions: <Widget>[
|
||||
PopupMenu(
|
||||
|
|
|
|||
|
|
@ -37,8 +37,9 @@ class CalculatorTab extends StatelessWidget {
|
|||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: redColor, padding: EdgeInsets.all(16.0)),
|
||||
child: RaisedButton(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
color: redColor,
|
||||
child: Text(
|
||||
"возврат",
|
||||
style: buttonBigTitleTextStyle,
|
||||
|
|
@ -51,8 +52,9 @@ class CalculatorTab extends StatelessWidget {
|
|||
}),
|
||||
),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: greenColor, padding: EdgeInsets.all(16.0)),
|
||||
child: RaisedButton(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
color: greenColor,
|
||||
child: Text(
|
||||
"оплата",
|
||||
style: buttonBigTitleTextStyle,
|
||||
|
|
|
|||
|
|
@ -72,8 +72,9 @@ class KassaTab extends StatelessWidget {
|
|||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: primaryColor, padding: const EdgeInsets.all(8)),
|
||||
child: RaisedButton(
|
||||
padding: const EdgeInsets.all(8),
|
||||
color: primaryColor,
|
||||
child: Text(
|
||||
"Добавить",
|
||||
style: buttonBigTitleTextStyle,
|
||||
|
|
@ -87,8 +88,9 @@ class KassaTab extends StatelessWidget {
|
|||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: greenColor, padding: EdgeInsets.all(8)),
|
||||
child: RaisedButton(
|
||||
padding: EdgeInsets.all(8),
|
||||
color: greenColor,
|
||||
child: Text(
|
||||
"Каталог",
|
||||
style: buttonBigTitleTextStyle,
|
||||
|
|
@ -133,8 +135,9 @@ class KassaTab extends StatelessWidget {
|
|||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: redColor, padding: const EdgeInsets.all(8)),
|
||||
child: RaisedButton(
|
||||
padding: const EdgeInsets.all(8),
|
||||
color: redColor,
|
||||
child: Text(
|
||||
"возврат",
|
||||
style: buttonBigTitleTextStyle,
|
||||
|
|
@ -150,8 +153,9 @@ class KassaTab extends StatelessWidget {
|
|||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: greenColor, padding: const EdgeInsets.all(8)),
|
||||
child: RaisedButton(
|
||||
padding: const EdgeInsets.all(8),
|
||||
color: greenColor,
|
||||
child: Text(
|
||||
"оплата",
|
||||
style: buttonBigTitleTextStyle,
|
||||
|
|
|
|||
|
|
@ -115,12 +115,12 @@ class CatalogBottomSheet extends StatelessWidget {
|
|||
},
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
RaisedButton(
|
||||
child: Text(
|
||||
'Отмена',
|
||||
style: buttonBigTitleTextStyle,
|
||||
),
|
||||
style: ElevatedButton.styleFrom(backgroundColor: redColor),
|
||||
color: redColor,
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class _ProductAddBottomSheetState extends State<ProductAddBottomSheet> {
|
|||
child: ElevatedButton(
|
||||
onPressed: _isSearching ? null : _searchByNtin,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
primary: primaryColor,
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
),
|
||||
child: _isSearching
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ class _PaymentViewState extends State<PaymentView> {
|
|||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
brightness: Brightness.light,
|
||||
backgroundColor: whiteColor,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
|
|
|
|||
|
|
@ -343,6 +343,7 @@ class _PaymentNfcViewState extends State<PaymentNfcView> {
|
|||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
brightness: Brightness.light,
|
||||
backgroundColor: purpleColor,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class _QrViewState extends State<QrView> {
|
|||
),
|
||||
body: Container(
|
||||
child: Center(
|
||||
child: QrImageView(
|
||||
child: QrImage(
|
||||
data: widget.data.url!,
|
||||
version: QrVersions.auto,
|
||||
size: 220.0,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import 'package:aman_kassa_flutter/core/logger.dart';
|
|||
import 'package:aman_kassa_flutter/core/services/blue_print_service.dart';
|
||||
import 'package:aman_kassa_flutter/redux/actions/setting_actions.dart';
|
||||
import 'package:aman_kassa_flutter/redux/store.dart';
|
||||
import 'package:aman_kassa_flutter/core/models/bluetooth_device.dart';
|
||||
import 'package:bluetooth_print/bluetooth_print_model.dart';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart' hide Image;
|
||||
|
|
|
|||
|
|
@ -76,16 +76,15 @@ class _SettingViewState extends State<SettingView> {
|
|||
keyboardType: TextInputType.number,
|
||||
),
|
||||
verticalSpaceMedium,
|
||||
ElevatedButton(
|
||||
RaisedButton(
|
||||
onPressed: () => this._setPinCode(context),
|
||||
child: Text(
|
||||
'Cохранить настройки',
|
||||
style: TextStyle(color: whiteColor, fontSize: 20.0),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: primaryColor,
|
||||
padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 20.0),
|
||||
),
|
||||
color: primaryColor,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 5.0, horizontal: 20.0),
|
||||
),
|
||||
|
||||
],
|
||||
|
|
|
|||
|
|
@ -153,8 +153,8 @@ class ProductListItem extends StatelessWidget {
|
|||
actions: <Widget>[
|
||||
Row(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: redColor),
|
||||
RaisedButton(
|
||||
color: redColor,
|
||||
child: Text(
|
||||
'Отмена',
|
||||
style: TextStyle(fontSize: 18),
|
||||
|
|
@ -166,8 +166,8 @@ class ProductListItem extends StatelessWidget {
|
|||
SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: primaryColor),
|
||||
RaisedButton(
|
||||
color: primaryColor,
|
||||
child: Text(
|
||||
'Сохранить',
|
||||
style: TextStyle(fontSize: 18),
|
||||
|
|
|
|||
|
|
@ -23,20 +23,20 @@ class CalculatorButton extends StatelessWidget {
|
|||
// width: 1.2,
|
||||
// ),
|
||||
// ),
|
||||
child: TextButton(
|
||||
child: FlatButton(
|
||||
onPressed: () => onTap(buttonText: text),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.all(20),
|
||||
backgroundColor: buildMainColor(),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(0),
|
||||
side: BorderSide(color: Colors.black),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(fontSize: 25, fontWeight: FontWeight.w700, color: buildTextColor()),
|
||||
),
|
||||
padding: const EdgeInsets.all(20),
|
||||
highlightColor: Colors.blueGrey[100],
|
||||
splashColor: Colors.blueAccent[100],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(0),
|
||||
side: BorderSide(color: Colors.black)
|
||||
),
|
||||
color: buildMainColor(),
|
||||
)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,14 +58,14 @@ class _DialogManagerState extends State<DialogManager> {
|
|||
content: Text(request.description),
|
||||
actions: <Widget>[
|
||||
if (isConfirmationDialog)
|
||||
TextButton(
|
||||
FlatButton(
|
||||
child: Text(request.cancelTitle!),
|
||||
onPressed: () {
|
||||
_dialogService
|
||||
.dialogComplete(DialogResponse(confirmed: false));
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
FlatButton(
|
||||
child: Text(request.buttonTitle),
|
||||
onPressed: () {
|
||||
_dialogService
|
||||
|
|
@ -129,8 +129,8 @@ class _DialogManagerState extends State<DialogManager> {
|
|||
),
|
||||
actions: <Widget>[
|
||||
if (isConfirmationDialog)
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: redColor),
|
||||
RaisedButton(
|
||||
color: redColor,
|
||||
child: Text(
|
||||
request.cancelTitle!,
|
||||
style: TextStyle(fontSize: 18),
|
||||
|
|
@ -143,8 +143,8 @@ class _DialogManagerState extends State<DialogManager> {
|
|||
SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: primaryColor),
|
||||
RaisedButton(
|
||||
color: primaryColor,
|
||||
child: Text(
|
||||
request.buttonTitle,
|
||||
style: TextStyle(fontSize: 18),
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
.DS_Store
|
||||
.dart_tool/
|
||||
|
||||
.packages
|
||||
.pub/
|
||||
|
||||
build/
|
||||
ios/.generated/
|
||||
ios/Flutter/Generated.xcconfig
|
||||
ios/Runner/GeneratedPluginRegistrant.*
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: faae8bde78ac273ae380cd8a6d18b093534ec58c
|
||||
channel: master
|
||||
|
||||
project_type: package
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
## [1.0.5] - Added clear button
|
||||
## [1.0.6] - Some Changes
|
||||
## [1.0.7] - Update SDK Version
|
||||
## [1.0.8] - Update Readme
|
||||
## [2.0.0] - Update null-safety
|
||||
## [2.0.1] - Update fingerPrint image string to widget.
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
The MIT License (MIT) Copyright © 2018 Yasin Ilhan <br />
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
# Flutter Pass Code Page or Pin Code Page Package
|
||||
|
||||
This package gives you beautiful pass code page for using both android and ios.
|
||||
<br/><br/>
|
||||
|
||||
[](https://flutter.io)
|
||||
[](https://pub.dartlang.org/packages/flutter_lock_screen)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
## Demo
|
||||
|
||||
<img src="https://www.yasinilhan.com/passcode/howtouse.gif" width="320" height="600" title="Screen Shoot">
|
||||
<img src="https://www.yasinilhan.com/passcode/1.png" width="300" height="600" title="Screen Shoot">
|
||||
|
||||
## Finger Print Usage
|
||||
First, be sure you should ensure that you add the `local_auth` package as a dependency. And Please read local_auth all integration details.
|
||||
https://pub.dartlang.org/packages/local_auth
|
||||
|
||||
iOS Integration
|
||||
Note that this plugin works with both TouchID and FaceID. However, to use the latter, you need to also add:
|
||||
```
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>Why is my app authenticating using face id?</string>
|
||||
```
|
||||
to your Info.plist file. Failure to do so results in a dialog that tells the user your app has not been updated to use TouchID.
|
||||
|
||||
Android Integration
|
||||
Note that local_auth plugin requires the use of a FragmentActivity as opposed to Activity. This can be easily done by switching to use FlutterFragmentActivity as opposed to FlutterActivity in your manifest (or your own Activity class if you are extending the base class).
|
||||
|
||||
Update your project's AndroidManifest.xml file to include the USE_FINGERPRINT permissions:
|
||||
```
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.app">
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||
<manifest>
|
||||
```
|
||||
|
||||
## Usage
|
||||
It is really easy to use!
|
||||
You should ensure that you add the `flutter_lock_screen` as a dependency in your flutter project.
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
flutter_lock_screen: '^2.0.1'
|
||||
```
|
||||
Than you can use it with below example.
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:testapp/empty_page.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class PassCodeScreen extends StatefulWidget {
|
||||
PassCodeScreen({Key key, this.title}) : super(key: key);
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
_PassCodeScreenState createState() => new _PassCodeScreenState();
|
||||
}
|
||||
|
||||
class _PassCodeScreenState extends State<PassCodeScreen> {
|
||||
bool isFingerprint = false;
|
||||
|
||||
Future<Null> biometrics() async {
|
||||
final LocalAuthentication auth = new LocalAuthentication();
|
||||
bool authenticated = false;
|
||||
|
||||
try {
|
||||
authenticated = await auth.authenticateWithBiometrics(
|
||||
localizedReason: 'Scan your fingerprint to authenticate',
|
||||
useErrorDialogs: true,
|
||||
stickyAuth: false);
|
||||
} on PlatformException catch (e) {
|
||||
print(e);
|
||||
}
|
||||
if (!mounted) return;
|
||||
if (authenticated) {
|
||||
setState(() {
|
||||
isFingerprint = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var myPass = [1, 2, 3, 4];
|
||||
return LockScreen(
|
||||
title: "This is Screet ",
|
||||
passLength: myPass.length,
|
||||
bgImage: "images/pass_code_bg.jpg",
|
||||
fingerPrintImage: "images/fingerprint.png",
|
||||
showFingerPass: true,
|
||||
fingerFunction: biometrics,
|
||||
fingerVerify: isFingerprint,
|
||||
borderColor: Colors.white,
|
||||
showWrongPassDialog: true,
|
||||
wrongPassContent: "Wrong pass please try again.",
|
||||
wrongPassTitle: "Opps!",
|
||||
wrongPassCancelButtonText: "Cancel",
|
||||
passCodeVerify: (passcode) async {
|
||||
for (int i = 0; i < myPass.length; i++) {
|
||||
if (passcode[i] != myPass[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
onSuccess: () {
|
||||
Navigator.of(context).pushReplacement(
|
||||
new MaterialPageRoute(builder: (BuildContext context) {
|
||||
return EmptyPage();
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Contributing
|
||||
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
||||
|
||||
Please make sure to update tests as appropriate.
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package io.flutter.plugins;
|
||||
|
||||
import io.flutter.plugin.common.PluginRegistry;
|
||||
|
||||
/**
|
||||
* Generated file. Do not edit.
|
||||
*/
|
||||
public final class GeneratedPluginRegistrant {
|
||||
public static void registerWith(PluginRegistry registry) {
|
||||
if (alreadyRegisteredWith(registry)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean alreadyRegisteredWith(PluginRegistry registry) {
|
||||
final String key = GeneratedPluginRegistrant.class.getCanonicalName();
|
||||
if (registry.hasPlugin(key)) {
|
||||
return true;
|
||||
}
|
||||
registry.registrarFor(key);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
// Olası öznitelikler hakkında bilgi edinmek için IntelliSense kullanın.
|
||||
// Mevcut özniteliklerin açıklamalarını görmek için fare ile üzerine gelin.
|
||||
// Daha fazla bilgi için ziyaret edin: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Flutter",
|
||||
"type": "dart",
|
||||
"request": "launch",
|
||||
"program": "lib/main.dart"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
#Thu Sep 26 15:23:08 EET 2019
|
||||
gradle.version=4.10.2
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>android</name>
|
||||
<comment>Project android created by Buildship.</comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
connection.project.dir=
|
||||
eclipse.preferences.version=1
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
|
||||
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||
<classpathentry kind="output" path="bin/default"/>
|
||||
</classpath>
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>app</name>
|
||||
<comment>Project app created by Buildship.</comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
connection.project.dir=..
|
||||
eclipse.preferences.version=1
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||
if (flutterRoot == null) {
|
||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = '1'
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = '1.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.example.flutterpasscodeexample"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.flutterpasscodeexample">
|
||||
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||
In most cases you can leave this as-is, but you if you want to provide
|
||||
additional functionality it is fine to subclass or reimplement
|
||||
FlutterApplication and put your custom class here. -->
|
||||
<application
|
||||
android:label="flutter_passcode_example"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- This keeps the window background of the activity showing
|
||||
until Flutter renders its first frame. It can be removed if
|
||||
there is no splash screen (such as the default splash screen
|
||||
defined in @style/LaunchTheme). -->
|
||||
<meta-data
|
||||
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
|
||||
android:value="true" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package com.example.flutterpasscodeexample;
|
||||
|
||||
import io.flutter.app.FlutterFragmentActivity;
|
||||
import io.flutter.embedding.android.FlutterActivity;
|
||||
|
||||
public class MainActivity extends FlutterFragmentActivity {
|
||||
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package io.flutter.plugins;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import io.flutter.embedding.engine.FlutterEngine;
|
||||
|
||||
/**
|
||||
* Generated file. Do not edit.
|
||||
* This file is generated by the Flutter tool based on the
|
||||
* plugins that support the Android platform.
|
||||
*/
|
||||
@Keep
|
||||
public final class GeneratedPluginRegistrant {
|
||||
public static void registerWith(@NonNull FlutterEngine flutterEngine) {
|
||||
flutterEngine.getPlugins().add(new io.flutter.plugins.flutter_plugin_android_lifecycle.FlutterAndroidLifecyclePlugin());
|
||||
flutterEngine.getPlugins().add(new io.flutter.plugins.localauth.LocalAuthPlugin());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
Before Width: | Height: | Size: 544 B |
|
Before Width: | Height: | Size: 442 B |
|
Before Width: | Height: | Size: 721 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
Flutter draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.0'
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
android.enableR8=true
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
#Fri Jun 23 08:50:38 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
sdk.dir=/Users/yasinilhan/Library/Android/sdk
|
||||
flutter.sdk=/Users/yasinilhan/flutter
|
||||
flutter.buildMode=debug
|
||||
flutter.versionName=1.0.0
|
||||
flutter.versionCode=1
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
include ':app'
|
||||
|
||||
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
|
||||
|
||||
def plugins = new Properties()
|
||||
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
|
||||
if (pluginsFile.exists()) {
|
||||
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
|
||||
}
|
||||
|
||||
plugins.each { name, path ->
|
||||
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
|
||||
include ":$name"
|
||||
project(":$name").projectDir = pluginDirectory
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
include ':app'
|
||||
|
Before Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
|
@ -1,173 +0,0 @@
|
|||
## 0.6.3+3
|
||||
|
||||
* Update android compileSdkVersion to 29.
|
||||
|
||||
## 0.6.3+2
|
||||
|
||||
* Keep handling deprecated Android v1 classes for backward compatibility.
|
||||
|
||||
## 0.6.3+1
|
||||
|
||||
* Update package:e2e -> package:integration_test
|
||||
|
||||
## 0.6.3
|
||||
|
||||
* Increase upper range of `package:platform` constraint to allow 3.X versions.
|
||||
|
||||
## 0.6.2+4
|
||||
|
||||
* Update package:e2e reference to use the local version in the flutter/plugins
|
||||
repository.
|
||||
|
||||
## 0.6.2+3
|
||||
|
||||
* Post-v2 Android embedding cleanup.
|
||||
|
||||
## 0.6.2+2
|
||||
|
||||
* Update lower bound of dart dependency to 2.1.0.
|
||||
|
||||
## 0.6.2+1
|
||||
|
||||
* Fix CocoaPods podspec lint warnings.
|
||||
|
||||
## 0.6.2
|
||||
|
||||
* Remove Android dependencies fallback.
|
||||
* Require Flutter SDK 1.12.13+hotfix.5 or greater.
|
||||
* Fix block implicitly retains 'self' warning.
|
||||
|
||||
## 0.6.1+4
|
||||
|
||||
* Replace deprecated `getFlutterEngine` call on Android.
|
||||
|
||||
## 0.6.1+3
|
||||
|
||||
* Make the pedantic dev_dependency explicit.
|
||||
|
||||
## 0.6.1+2
|
||||
|
||||
* Support v2 embedding.
|
||||
|
||||
## 0.6.1+1
|
||||
|
||||
* Remove the deprecated `author:` field from pubspec.yaml
|
||||
* Migrate the plugin to the pubspec platforms manifest.
|
||||
* Require Flutter SDK 1.10.0 or greater.
|
||||
|
||||
## 0.6.1
|
||||
|
||||
* Added ability to stop authentication (For Android).
|
||||
|
||||
## 0.6.0+3
|
||||
|
||||
* Remove AndroidX warnings.
|
||||
|
||||
## 0.6.0+2
|
||||
|
||||
* Update and migrate iOS example project.
|
||||
* Define clang module for iOS.
|
||||
|
||||
## 0.6.0+1
|
||||
|
||||
* Update the `intl` constraint to ">=0.15.1 <0.17.0" (0.16.0 isn't really a breaking change).
|
||||
|
||||
## 0.6.0
|
||||
|
||||
* Define a new parameter for signaling that the transaction is sensitive.
|
||||
* Up the biometric version to beta01.
|
||||
* Handle no device credential error.
|
||||
|
||||
## 0.5.3
|
||||
|
||||
* Add face id detection as well by not relying on FingerprintCompat.
|
||||
|
||||
## 0.5.2+4
|
||||
|
||||
* Update README to fix syntax error.
|
||||
|
||||
## 0.5.2+3
|
||||
|
||||
* Update documentation to clarify the need for FragmentActivity.
|
||||
|
||||
## 0.5.2+2
|
||||
|
||||
* Add missing template type parameter to `invokeMethod` calls.
|
||||
* Bump minimum Flutter version to 1.5.0.
|
||||
* Replace invokeMethod with invokeMapMethod wherever necessary.
|
||||
|
||||
## 0.5.2+1
|
||||
* Use post instead of postDelayed to show the dialog onResume.
|
||||
|
||||
## 0.5.2
|
||||
* Executor thread needs to be UI thread.
|
||||
|
||||
## 0.5.1
|
||||
* Fix crash on Android versions earlier than 28.
|
||||
* [`authenticateWithBiometrics`](https://pub.dev/documentation/local_auth/latest/local_auth/LocalAuthentication/authenticateWithBiometrics.html) will not return result unless Biometric Dialog is closed.
|
||||
* Added two more error codes `LockedOut` and `PermanentlyLockedOut`.
|
||||
|
||||
## 0.5.0
|
||||
* **Breaking change**. Update the Android API to use androidx Biometric package. This gives
|
||||
the prompt the updated Material look. However, it also requires the activity to be a
|
||||
FragmentActivity. Users can switch to FlutterFragmentActivity in their main app to migrate.
|
||||
|
||||
## 0.4.0+1
|
||||
|
||||
* Log a more detailed warning at build time about the previous AndroidX
|
||||
migration.
|
||||
|
||||
## 0.4.0
|
||||
|
||||
* **Breaking change**. Migrate from the deprecated original Android Support
|
||||
Library to AndroidX. This shouldn't result in any functional changes, but it
|
||||
requires any Android apps using this plugin to [also
|
||||
migrate](https://developer.android.com/jetpack/androidx/migrate) if they're
|
||||
using the original support library.
|
||||
|
||||
## 0.3.1
|
||||
* Fix crash on Android versions earlier than 24.
|
||||
|
||||
## 0.3.0
|
||||
|
||||
* **Breaking change**. Add canCheckBiometrics and getAvailableBiometrics which leads to a new API.
|
||||
|
||||
## 0.2.1
|
||||
|
||||
* Updated Gradle tooling to match Android Studio 3.1.2.
|
||||
|
||||
## 0.2.0
|
||||
|
||||
* **Breaking change**. Set SDK constraints to match the Flutter beta release.
|
||||
|
||||
## 0.1.2
|
||||
|
||||
* Fixed Dart 2 type error.
|
||||
|
||||
## 0.1.1
|
||||
|
||||
* Simplified and upgraded Android project template to Android SDK 27.
|
||||
* Updated package description.
|
||||
|
||||
## 0.1.0
|
||||
|
||||
* **Breaking change**. Upgraded to Gradle 4.1 and Android Studio Gradle plugin
|
||||
3.0.1. Older Flutter projects need to upgrade their Gradle setup as well in
|
||||
order to use this version of the plugin. Instructions can be found
|
||||
[here](https://github.com/flutter/flutter/wiki/Updating-Flutter-projects-to-Gradle-4.1-and-Android-Studio-Gradle-plugin-3.0.1).
|
||||
|
||||
## 0.0.3
|
||||
|
||||
* Add FLT prefix to iOS types
|
||||
|
||||
## 0.0.2+1
|
||||
|
||||
* Update messaging to support Face ID.
|
||||
|
||||
## 0.0.2
|
||||
|
||||
* Support stickyAuth mode.
|
||||
|
||||
## 0.0.1
|
||||
|
||||
* Initial release of local authentication plugin.
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -1,175 +0,0 @@
|
|||
# local_auth
|
||||
|
||||
This Flutter plugin provides means to perform local, on-device authentication of
|
||||
the user.
|
||||
|
||||
This means referring to biometric authentication on iOS (Touch ID or lock code)
|
||||
and the fingerprint APIs on Android (introduced in Android 6.0).
|
||||
|
||||
## Usage in Dart
|
||||
|
||||
Import the relevant file:
|
||||
|
||||
```dart
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
```
|
||||
|
||||
To check whether there is local authentication available on this device or not, call canCheckBiometrics:
|
||||
|
||||
```dart
|
||||
bool canCheckBiometrics =
|
||||
await localAuth.canCheckBiometrics;
|
||||
```
|
||||
|
||||
Currently the following biometric types are implemented:
|
||||
|
||||
* BiometricType.face
|
||||
* BiometricType.fingerprint
|
||||
|
||||
To get a list of enrolled biometrics, call getAvailableBiometrics:
|
||||
|
||||
```dart
|
||||
List<BiometricType> availableBiometrics =
|
||||
await auth.getAvailableBiometrics();
|
||||
|
||||
if (Platform.isIOS) {
|
||||
if (availableBiometrics.contains(BiometricType.face)) {
|
||||
// Face ID.
|
||||
} else if (availableBiometrics.contains(BiometricType.fingerprint)) {
|
||||
// Touch ID.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We have default dialogs with an 'OK' button to show authentication error
|
||||
messages for the following 2 cases:
|
||||
|
||||
1. Passcode/PIN/Pattern Not Set. The user has not yet configured a passcode on
|
||||
iOS or PIN/pattern on Android.
|
||||
2. Touch ID/Fingerprint Not Enrolled. The user has not enrolled any
|
||||
fingerprints on the device.
|
||||
|
||||
Which means, if there's no fingerprint on the user's device, a dialog with
|
||||
instructions will pop up to let the user set up fingerprint. If the user clicks
|
||||
'OK' button, it will return 'false'.
|
||||
|
||||
Use the exported APIs to trigger local authentication with default dialogs:
|
||||
|
||||
```dart
|
||||
var localAuth = LocalAuthentication();
|
||||
bool didAuthenticate =
|
||||
await localAuth.authenticateWithBiometrics(
|
||||
localizedReason: 'Please authenticate to show account balance');
|
||||
```
|
||||
|
||||
If you don't want to use the default dialogs, call this API with
|
||||
'useErrorDialogs = false'. In this case, it will throw the error message back
|
||||
and you need to handle them in your dart code:
|
||||
|
||||
```dart
|
||||
bool didAuthenticate =
|
||||
await localAuth.authenticateWithBiometrics(
|
||||
localizedReason: 'Please authenticate to show account balance',
|
||||
useErrorDialogs: false);
|
||||
```
|
||||
|
||||
You can use our default dialog messages, or you can use your own messages by
|
||||
passing in IOSAuthMessages and AndroidAuthMessages:
|
||||
|
||||
```dart
|
||||
import 'package:local_auth/auth_strings.dart';
|
||||
|
||||
const iosStrings = const IOSAuthMessages(
|
||||
cancelButton: 'cancel',
|
||||
goToSettingsButton: 'settings',
|
||||
goToSettingsDescription: 'Please set up your Touch ID.',
|
||||
lockOut: 'Please reenable your Touch ID');
|
||||
await localAuth.authenticateWithBiometrics(
|
||||
localizedReason: 'Please authenticate to show account balance',
|
||||
useErrorDialogs: false,
|
||||
iOSAuthStrings: iosStrings);
|
||||
|
||||
```
|
||||
|
||||
If needed, you can manually stop authentication for android:
|
||||
|
||||
```dart
|
||||
|
||||
void _cancelAuthentication() {
|
||||
localAuth.stopAuthentication();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Exceptions
|
||||
|
||||
There are 6 types of exceptions: PasscodeNotSet, NotEnrolled, NotAvailable, OtherOperatingSystem, LockedOut and PermanentlyLockedOut.
|
||||
They are wrapped in LocalAuthenticationError class. You can
|
||||
catch the exception and handle them by different types. For example:
|
||||
|
||||
```dart
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:local_auth/error_codes.dart' as auth_error;
|
||||
|
||||
try {
|
||||
bool didAuthenticate = await local_auth.authenticateWithBiometrics(
|
||||
localizedReason: 'Please authenticate to show account balance');
|
||||
} on PlatformException catch (e) {
|
||||
if (e.code == auth_error.notAvailable) {
|
||||
// Handle this exception here.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## iOS Integration
|
||||
|
||||
Note that this plugin works with both TouchID and FaceID. However, to use the latter,
|
||||
you need to also add:
|
||||
|
||||
```xml
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>Why is my app authenticating using face id?</string>
|
||||
```
|
||||
|
||||
to your Info.plist file. Failure to do so results in a dialog that tells the user your
|
||||
app has not been updated to use TouchID.
|
||||
|
||||
|
||||
## Android Integration
|
||||
|
||||
Note that local_auth plugin requires the use of a FragmentActivity as
|
||||
opposed to Activity. This can be easily done by switching to use
|
||||
`FlutterFragmentActivity` as opposed to `FlutterActivity` in your
|
||||
manifest (or your own Activity class if you are extending the base class).
|
||||
|
||||
Update your project's `AndroidManifest.xml` file to include the
|
||||
`USE_FINGERPRINT` permissions:
|
||||
|
||||
```xml
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.app">
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||
<manifest>
|
||||
```
|
||||
|
||||
On Android, you can check only for existence of fingerprint hardware prior
|
||||
to API 29 (Android Q). Therefore, if you would like to support other biometrics
|
||||
types (such as face scanning) and you want to support SDKs lower than Q,
|
||||
*do not* call `getAvailableBiometrics`. Simply call `authenticateWithBiometrics`.
|
||||
This will return an error if there was no hardware available.
|
||||
|
||||
## Sticky Auth
|
||||
|
||||
You can set the `stickyAuth` option on the plugin to true so that plugin does not
|
||||
return failure if the app is put to background by the system. This might happen
|
||||
if the user receives a phone call before they get a chance to authenticate. With
|
||||
`stickyAuth` set to false, this would result in plugin returning failure result
|
||||
to the Dart app. If set to true, the plugin will retry authenticating when the
|
||||
app resumes.
|
||||
|
||||
## Getting Started
|
||||
|
||||
For help getting started with Flutter, view our online
|
||||
[documentation](http://flutter.io/).
|
||||
|
||||
For help on editing plugin code, view the [documentation](https://flutter.io/platform-plugins/#edit-code).
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
group 'io.flutter.plugins.localauth'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.0'
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api "androidx.core:core:1.1.0-beta01"
|
||||
api "androidx.biometric:biometric:1.0.0-beta01"
|
||||
api "androidx.fragment:fragment:1.1.0-alpha06"
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
org.gradle.jvmargs=-Xmx1536M
|
||||
|
|
@ -1 +0,0 @@
|
|||
rootProject.name = 'local_auth'
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.flutter.plugins.localauth">
|
||||
</manifest>
|
||||
|
|
@ -1,287 +0,0 @@
|
|||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
package io.flutter.plugins.localauth;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.Settings;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Authenticates the user with fingerprint and sends corresponding response back to Flutter.
|
||||
*
|
||||
* <p>One instance per call is generated to ensure readable separation of executable paths across
|
||||
* method calls.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
class AuthenticationHelper extends BiometricPrompt.AuthenticationCallback
|
||||
implements Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver {
|
||||
|
||||
/** The callback that handles the result of this authentication process. */
|
||||
interface AuthCompletionHandler {
|
||||
|
||||
/** Called when authentication was successful. */
|
||||
void onSuccess();
|
||||
|
||||
/**
|
||||
* Called when authentication failed due to user. For instance, when user cancels the auth or
|
||||
* quits the app.
|
||||
*/
|
||||
void onFailure();
|
||||
|
||||
/**
|
||||
* Called when authentication fails due to non-user related problems such as system errors,
|
||||
* phone not having a FP reader etc.
|
||||
*
|
||||
* @param code The error code to be returned to Flutter app.
|
||||
* @param error The description of the error.
|
||||
*/
|
||||
void onError(String code, String error);
|
||||
}
|
||||
|
||||
// This is null when not using v2 embedding;
|
||||
private final Lifecycle lifecycle;
|
||||
private final FragmentActivity activity;
|
||||
private final AuthCompletionHandler completionHandler;
|
||||
private final MethodCall call;
|
||||
private final BiometricPrompt.PromptInfo promptInfo;
|
||||
private final boolean isAuthSticky;
|
||||
private final UiThreadExecutor uiThreadExecutor;
|
||||
private boolean activityPaused = false;
|
||||
private BiometricPrompt biometricPrompt;
|
||||
|
||||
AuthenticationHelper(
|
||||
Lifecycle lifecycle,
|
||||
FragmentActivity activity,
|
||||
MethodCall call,
|
||||
AuthCompletionHandler completionHandler) {
|
||||
this.lifecycle = lifecycle;
|
||||
this.activity = activity;
|
||||
this.completionHandler = completionHandler;
|
||||
this.call = call;
|
||||
this.isAuthSticky = call.argument("stickyAuth");
|
||||
this.uiThreadExecutor = new UiThreadExecutor();
|
||||
this.promptInfo =
|
||||
new BiometricPrompt.PromptInfo.Builder()
|
||||
.setDescription((String) call.argument("localizedReason"))
|
||||
.setTitle((String) call.argument("signInTitle"))
|
||||
.setSubtitle((String) call.argument("fingerprintHint"))
|
||||
.setNegativeButtonText((String) call.argument("cancelButton"))
|
||||
.setConfirmationRequired((Boolean) call.argument("sensitiveTransaction"))
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Start the fingerprint listener. */
|
||||
void authenticate() {
|
||||
if (lifecycle != null) {
|
||||
lifecycle.addObserver(this);
|
||||
} else {
|
||||
activity.getApplication().registerActivityLifecycleCallbacks(this);
|
||||
}
|
||||
biometricPrompt = new BiometricPrompt(activity, uiThreadExecutor, this);
|
||||
biometricPrompt.authenticate(promptInfo);
|
||||
}
|
||||
|
||||
/** Cancels the fingerprint authentication. */
|
||||
void stopAuthentication() {
|
||||
if (biometricPrompt != null) {
|
||||
biometricPrompt.cancelAuthentication();
|
||||
biometricPrompt = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Stops the fingerprint listener. */
|
||||
private void stop() {
|
||||
if (lifecycle != null) {
|
||||
lifecycle.removeObserver(this);
|
||||
return;
|
||||
}
|
||||
activity.getApplication().unregisterActivityLifecycleCallbacks(this);
|
||||
}
|
||||
|
||||
@SuppressLint("SwitchIntDef")
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
switch (errorCode) {
|
||||
case BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL:
|
||||
completionHandler.onError(
|
||||
"PasscodeNotSet",
|
||||
"Phone not secured by PIN, pattern or password, or SIM is currently locked.");
|
||||
break;
|
||||
case BiometricPrompt.ERROR_NO_SPACE:
|
||||
case BiometricPrompt.ERROR_NO_BIOMETRICS:
|
||||
if (call.argument("useErrorDialogs")) {
|
||||
showGoToSettingsDialog();
|
||||
return;
|
||||
}
|
||||
completionHandler.onError("NotEnrolled", "No Biometrics enrolled on this device.");
|
||||
break;
|
||||
case BiometricPrompt.ERROR_HW_UNAVAILABLE:
|
||||
case BiometricPrompt.ERROR_HW_NOT_PRESENT:
|
||||
completionHandler.onError("NotAvailable", "Biometrics is not available on this device.");
|
||||
break;
|
||||
case BiometricPrompt.ERROR_LOCKOUT:
|
||||
completionHandler.onError(
|
||||
"LockedOut",
|
||||
"The operation was canceled because the API is locked out due to too many attempts. This occurs after 5 failed attempts, and lasts for 30 seconds.");
|
||||
break;
|
||||
case BiometricPrompt.ERROR_LOCKOUT_PERMANENT:
|
||||
completionHandler.onError(
|
||||
"PermanentlyLockedOut",
|
||||
"The operation was canceled because ERROR_LOCKOUT occurred too many times. Biometric authentication is disabled until the user unlocks with strong authentication (PIN/Pattern/Password)");
|
||||
break;
|
||||
case BiometricPrompt.ERROR_CANCELED:
|
||||
// If we are doing sticky auth and the activity has been paused,
|
||||
// ignore this error. We will start listening again when resumed.
|
||||
if (activityPaused && isAuthSticky) {
|
||||
return;
|
||||
} else {
|
||||
completionHandler.onFailure();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
completionHandler.onFailure();
|
||||
}
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
|
||||
completionHandler.onSuccess();
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {}
|
||||
|
||||
/**
|
||||
* If the activity is paused, we keep track because fingerprint dialog simply returns "User
|
||||
* cancelled" when the activity is paused.
|
||||
*/
|
||||
@Override
|
||||
public void onActivityPaused(Activity ignored) {
|
||||
if (isAuthSticky) {
|
||||
activityPaused = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResumed(Activity ignored) {
|
||||
if (isAuthSticky) {
|
||||
activityPaused = false;
|
||||
final BiometricPrompt prompt = new BiometricPrompt(activity, uiThreadExecutor, this);
|
||||
// When activity is resuming, we cannot show the prompt right away. We need to post it to the
|
||||
// UI queue.
|
||||
uiThreadExecutor.handler.post(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
prompt.authenticate(promptInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause(@NonNull LifecycleOwner owner) {
|
||||
onActivityPaused(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume(@NonNull LifecycleOwner owner) {
|
||||
onActivityResumed(null);
|
||||
}
|
||||
|
||||
// Suppress inflateParams lint because dialogs do not need to attach to a parent view.
|
||||
@SuppressLint("InflateParams")
|
||||
private void showGoToSettingsDialog() {
|
||||
View view = LayoutInflater.from(activity).inflate(R.layout.go_to_setting, null, false);
|
||||
TextView message = (TextView) view.findViewById(R.id.fingerprint_required);
|
||||
TextView description = (TextView) view.findViewById(R.id.go_to_setting_description);
|
||||
message.setText((String) call.argument("fingerprintRequired"));
|
||||
description.setText((String) call.argument("goToSettingDescription"));
|
||||
Context context = new ContextThemeWrapper(activity, R.style.AlertDialogCustom);
|
||||
OnClickListener goToSettingHandler =
|
||||
new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
completionHandler.onFailure();
|
||||
stop();
|
||||
activity.startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS));
|
||||
}
|
||||
};
|
||||
OnClickListener cancelHandler =
|
||||
new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
completionHandler.onFailure();
|
||||
stop();
|
||||
}
|
||||
};
|
||||
new AlertDialog.Builder(context)
|
||||
.setView(view)
|
||||
.setPositiveButton((String) call.argument("goToSetting"), goToSettingHandler)
|
||||
.setNegativeButton((String) call.argument("cancelButton"), cancelHandler)
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
}
|
||||
|
||||
// Unused methods for activity lifecycle.
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Activity activity, Bundle bundle) {}
|
||||
|
||||
@Override
|
||||
public void onActivityStarted(Activity activity) {}
|
||||
|
||||
@Override
|
||||
public void onActivityStopped(Activity activity) {}
|
||||
|
||||
@Override
|
||||
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroyed(Activity activity) {}
|
||||
|
||||
@Override
|
||||
public void onDestroy(@NonNull LifecycleOwner owner) {}
|
||||
|
||||
@Override
|
||||
public void onStop(@NonNull LifecycleOwner owner) {}
|
||||
|
||||
@Override
|
||||
public void onStart(@NonNull LifecycleOwner owner) {}
|
||||
|
||||
@Override
|
||||
public void onCreate(@NonNull LifecycleOwner owner) {}
|
||||
|
||||
private static class UiThreadExecutor implements Executor {
|
||||
final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
handler.post(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package io.flutter.plugins.localauth;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin;
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
|
||||
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
|
||||
import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
|
||||
import io.flutter.plugin.common.MethodChannel.Result;
|
||||
import io.flutter.plugins.localauth.AuthenticationHelper.AuthCompletionHandler;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Flutter plugin providing access to local authentication.
|
||||
*
|
||||
* <p>Instantiate this in an add to app scenario to gracefully handle activity and context changes.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class LocalAuthPlugin implements MethodCallHandler, FlutterPlugin, ActivityAware {
|
||||
private static final String CHANNEL_NAME = "plugins.flutter.io/local_auth";
|
||||
|
||||
private Activity activity;
|
||||
private final AtomicBoolean authInProgress = new AtomicBoolean(false);
|
||||
private AuthenticationHelper authenticationHelper;
|
||||
|
||||
// These are null when not using v2 embedding.
|
||||
private MethodChannel channel;
|
||||
private Lifecycle lifecycle;
|
||||
|
||||
/**
|
||||
* Registers a plugin with the v1 embedding api {@code io.flutter.plugin.common}.
|
||||
*
|
||||
* <p>Calling this will register the plugin with the passed registrar. However, plugins
|
||||
* initialized this way won't react to changes in activity or context.
|
||||
*
|
||||
* @param registrar attaches this plugin's {@link
|
||||
* io.flutter.plugin.common.MethodChannel.MethodCallHandler} to the registrar's {@link
|
||||
* io.flutter.plugin.common.BinaryMessenger}.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) {
|
||||
final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME);
|
||||
channel.setMethodCallHandler(new LocalAuthPlugin(registrar.activity()));
|
||||
}
|
||||
|
||||
private LocalAuthPlugin(Activity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor for LocalAuthPlugin.
|
||||
*
|
||||
* <p>Use this constructor when adding this plugin to an app with v2 embedding.
|
||||
*/
|
||||
public LocalAuthPlugin() {}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(MethodCall call, final Result result) {
|
||||
if (call.method.equals("authenticateWithBiometrics")) {
|
||||
if (authInProgress.get()) {
|
||||
// Apps should not invoke another authentication request while one is in progress,
|
||||
// so we classify this as an error condition. If we ever find a legitimate use case for
|
||||
// this, we can try to cancel the ongoing auth and start a new one but for now, not worth
|
||||
// the complexity.
|
||||
result.error("auth_in_progress", "Authentication in progress", null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (activity == null || activity.isFinishing()) {
|
||||
result.error("no_activity", "local_auth plugin requires a foreground activity", null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(activity instanceof FragmentActivity)) {
|
||||
result.error(
|
||||
"no_fragment_activity",
|
||||
"local_auth plugin requires activity to be a FragmentActivity.",
|
||||
null);
|
||||
return;
|
||||
}
|
||||
authInProgress.set(true);
|
||||
authenticationHelper =
|
||||
new AuthenticationHelper(
|
||||
lifecycle,
|
||||
(FragmentActivity) activity,
|
||||
call,
|
||||
new AuthCompletionHandler() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
if (authInProgress.compareAndSet(true, false)) {
|
||||
result.success(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
if (authInProgress.compareAndSet(true, false)) {
|
||||
result.success(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String code, String error) {
|
||||
if (authInProgress.compareAndSet(true, false)) {
|
||||
result.error(code, error, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
authenticationHelper.authenticate();
|
||||
} else if (call.method.equals("getAvailableBiometrics")) {
|
||||
try {
|
||||
if (activity == null || activity.isFinishing()) {
|
||||
result.error("no_activity", "local_auth plugin requires a foreground activity", null);
|
||||
return;
|
||||
}
|
||||
ArrayList<String> biometrics = new ArrayList<String>();
|
||||
PackageManager packageManager = activity.getPackageManager();
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
if (packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
|
||||
biometrics.add("fingerprint");
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 29) {
|
||||
if (packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
|
||||
biometrics.add("face");
|
||||
}
|
||||
if (packageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)) {
|
||||
biometrics.add("iris");
|
||||
}
|
||||
}
|
||||
result.success(biometrics);
|
||||
} catch (Exception e) {
|
||||
result.error("no_biometrics_available", e.getMessage(), null);
|
||||
}
|
||||
} else if (call.method.equals(("stopAuthentication"))) {
|
||||
stopAuthentication(result);
|
||||
} else {
|
||||
result.notImplemented();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Stops the authentication if in progress.
|
||||
*/
|
||||
private void stopAuthentication(Result result) {
|
||||
try {
|
||||
if (authenticationHelper != null && authInProgress.get()) {
|
||||
authenticationHelper.stopAuthentication();
|
||||
authenticationHelper = null;
|
||||
result.success(true);
|
||||
return;
|
||||
}
|
||||
result.success(false);
|
||||
} catch (Exception e) {
|
||||
result.success(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToEngine(FlutterPluginBinding binding) {
|
||||
channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromEngine(FlutterPluginBinding binding) {}
|
||||
|
||||
@Override
|
||||
public void onAttachedToActivity(ActivityPluginBinding binding) {
|
||||
activity = binding.getActivity();
|
||||
lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding);
|
||||
channel.setMethodCallHandler(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromActivityForConfigChanges() {
|
||||
lifecycle = null;
|
||||
activity = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
|
||||
activity = binding.getActivity();
|
||||
lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromActivity() {
|
||||
activity = null;
|
||||
lifecycle = null;
|
||||
channel.setMethodCallHandler(null);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#607D8B"/>
|
||||
<size
|
||||
android:width="40dp"
|
||||
android:height="40dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:drawable="@drawable/ic_fingerprint_white_24dp"
|
||||
android:bottom="8dp"
|
||||
android:left="8dp"
|
||||
android:right="8dp"
|
||||
android:top="8dp"/>
|
||||
</layer-list>
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/success_color"/>
|
||||
<size
|
||||
android:width="40dp"
|
||||
android:height="40dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:drawable="@drawable/ic_done_white_24dp"
|
||||
android:bottom="8dp"
|
||||
android:left="8dp"
|
||||
android:right="8dp"
|
||||
android:top="8dp"/>
|
||||
</layer-list>
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/warning_color"/>
|
||||
<size
|
||||
android:width="40dp"
|
||||
android:height="40dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:drawable="@drawable/ic_priority_high_white_24dp"
|
||||
android:bottom="8dp"
|
||||
android:left="8dp"
|
||||
android:right="8dp"
|
||||
android:top="8dp"/>
|
||||
</layer-list>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
|
||||
</vector>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M17.81,4.47c-0.08,0 -0.16,-0.02 -0.23,-0.06C15.66,3.42 14,3 12.01,3c-1.98,0 -3.86,0.47 -5.57,1.41 -0.24,0.13 -0.54,0.04 -0.68,-0.2 -0.13,-0.24 -0.04,-0.55 0.2,-0.68C7.82,2.52 9.86,2 12.01,2c2.13,0 3.99,0.47 6.03,1.52 0.25,0.13 0.34,0.43 0.21,0.67 -0.09,0.18 -0.26,0.28 -0.44,0.28zM3.5,9.72c-0.1,0 -0.2,-0.03 -0.29,-0.09 -0.23,-0.16 -0.28,-0.47 -0.12,-0.7 0.99,-1.4 2.25,-2.5 3.75,-3.27C9.98,4.04 14,4.03 17.15,5.65c1.5,0.77 2.76,1.86 3.75,3.25 0.16,0.22 0.11,0.54 -0.12,0.7 -0.23,0.16 -0.54,0.11 -0.7,-0.12 -0.9,-1.26 -2.04,-2.25 -3.39,-2.94 -2.87,-1.47 -6.54,-1.47 -9.4,0.01 -1.36,0.7 -2.5,1.7 -3.4,2.96 -0.08,0.14 -0.23,0.21 -0.39,0.21zM9.75,21.79c-0.13,0 -0.26,-0.05 -0.35,-0.15 -0.87,-0.87 -1.34,-1.43 -2.01,-2.64 -0.69,-1.23 -1.05,-2.73 -1.05,-4.34 0,-2.97 2.54,-5.39 5.66,-5.39s5.66,2.42 5.66,5.39c0,0.28 -0.22,0.5 -0.5,0.5s-0.5,-0.22 -0.5,-0.5c0,-2.42 -2.09,-4.39 -4.66,-4.39 -2.57,0 -4.66,1.97 -4.66,4.39 0,1.44 0.32,2.77 0.93,3.85 0.64,1.15 1.08,1.64 1.85,2.42 0.19,0.2 0.19,0.51 0,0.71 -0.11,0.1 -0.24,0.15 -0.37,0.15zM16.92,19.94c-1.19,0 -2.24,-0.3 -3.1,-0.89 -1.49,-1.01 -2.38,-2.65 -2.38,-4.39 0,-0.28 0.22,-0.5 0.5,-0.5s0.5,0.22 0.5,0.5c0,1.41 0.72,2.74 1.94,3.56 0.71,0.48 1.54,0.71 2.54,0.71 0.24,0 0.64,-0.03 1.04,-0.1 0.27,-0.05 0.53,0.13 0.58,0.41 0.05,0.27 -0.13,0.53 -0.41,0.58 -0.57,0.11 -1.07,0.12 -1.21,0.12zM14.91,22c-0.04,0 -0.09,-0.01 -0.13,-0.02 -1.59,-0.44 -2.63,-1.03 -3.72,-2.1 -1.4,-1.39 -2.17,-3.24 -2.17,-5.22 0,-1.62 1.38,-2.94 3.08,-2.94 1.7,0 3.08,1.32 3.08,2.94 0,1.07 0.93,1.94 2.08,1.94s2.08,-0.87 2.08,-1.94c0,-3.77 -3.25,-6.83 -7.25,-6.83 -2.84,0 -5.44,1.58 -6.61,4.03 -0.39,0.81 -0.59,1.76 -0.59,2.8 0,0.78 0.07,2.01 0.67,3.61 0.1,0.26 -0.03,0.55 -0.29,0.64 -0.26,0.1 -0.55,-0.04 -0.64,-0.29 -0.49,-1.31 -0.73,-2.61 -0.73,-3.96 0,-1.2 0.23,-2.29 0.68,-3.24 1.33,-2.79 4.28,-4.6 7.51,-4.6 4.55,0 8.25,3.51 8.25,7.83 0,1.62 -1.38,2.94 -3.08,2.94s-3.08,-1.32 -3.08,-2.94c0,-1.07 -0.93,-1.94 -2.08,-1.94s-2.08,0.87 -2.08,1.94c0,1.71 0.66,3.31 1.87,4.51 0.95,0.94 1.86,1.46 3.27,1.85 0.27,0.07 0.42,0.35 0.35,0.61 -0.05,0.23 -0.26,0.38 -0.47,0.38z"/>
|
||||
</vector>
|
||||