Compare commits

..

2 Commits

Author SHA1 Message Date
Rustem b28c8278e2 flutter upgrade 2026-06-10 13:21:40 +05:00
Rustem e1b6f7c2cb flutter upgrade 2026-06-10 13:21:12 +05:00
322 changed files with 98582 additions and 592 deletions

View File

@ -1,3 +1,9 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties() def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties') def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) { if (localPropertiesFile.exists()) {
@ -6,11 +12,6 @@ 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') def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) { if (flutterVersionCode == null) {
flutterVersionCode = '4' flutterVersionCode = '4'
@ -21,20 +22,16 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0.4' 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 keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties') def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) { if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
} }
android { android {
compileSdkVersion 31 namespace "kz.com.aman.kassa"
compileSdkVersion 36
ndkVersion "28.2.13676358"
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
@ -46,7 +43,7 @@ android {
defaultConfig { defaultConfig {
applicationId "kz.com.aman.kassa" applicationId "kz.com.aman.kassa"
minSdkVersion 21 minSdkVersion flutter.minSdkVersion
targetSdkVersion 35 targetSdkVersion 35
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
@ -62,29 +59,24 @@ android {
storePassword keystoreProperties['storePassword'] storePassword keystoreProperties['storePassword']
} }
} }
buildTypes { buildTypes {
debug { debug {
shrinkResources false shrinkResources false
minifyEnabled false minifyEnabled false
useProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
defaultConfig {
minSdkVersion 24
}
} }
release { release {
shrinkResources false shrinkResources false
minifyEnabled false minifyEnabled false
useProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release signingConfig signingConfigs.release
} }
} }
compileOptions { compileOptions {
//coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_17
sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_1_8
} }
packagingOptions { packagingOptions {
@ -93,9 +85,8 @@ android {
} }
kotlinOptions { kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString() jvmTarget = JavaVersion.VERSION_17.toString()
} }
} }
flutter { flutter {
@ -103,7 +94,6 @@ flutter {
} }
dependencies { dependencies {
//coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.9'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
@ -111,15 +101,5 @@ dependencies {
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.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 group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
//implementation 'com.android.support:multidex:2.0.1'
//m4bank dependencies
} }

View File

@ -1,24 +1,18 @@
buildscript { buildscript {
ext.kotlin_version = '1.6.10' ext.kotlin_version = '2.2.20'
repositories { repositories {
google() google()
jcenter()
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.5.0' classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // add plugin
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }
allprojects { allprojects {
repositories { repositories {
google() google()
jcenter()
mavenCentral() mavenCentral()
} }
@ -37,13 +31,42 @@ subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
} }
task clean(type: Delete) { gradle.beforeProject { proj ->
delete rootProject.buildDir proj.afterEvaluate {
} if (proj.plugins.hasPlugin("com.android.library")) {
try {
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { if (!proj.android.namespace) {
kotlinOptions { def manifestFile = proj.file("src/main/AndroidManifest.xml")
jvmTarget = "1.8" 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) {}
}
} }
} }
tasks.register("clean", Delete) {
delete rootProject.layout.buildDirectory
}
allprojects {
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
jvmTarget = "17"
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -2,3 +2,8 @@ org.gradle.jvmargs=-Xmx1536M
android.enableR8=true android.enableR8=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=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

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-all.zip

View File

@ -1,15 +1,25 @@
include ':app' 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
}()
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
def plugins = new Properties() repositories {
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') google()
if (pluginsFile.exists()) { mavenCentral()
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } gradlePluginPortal()
}
} }
plugins.each { name, path -> plugins {
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() id "dev.flutter.flutter-plugin-loader" version "1.0.0"
include ":$name" id "com.android.application" version "8.11.1" apply false
project(":$name").projectDir = pluginDirectory id "org.jetbrains.kotlin.android" version "2.2.20" apply false
} }
include ":app"

View File

@ -0,0 +1,32 @@
#
# 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 --")

View File

@ -0,0 +1,5 @@
#
# Generated file, do not edit.
#
command script import --relative-to-command-file flutter_lldb_helper.py

View File

@ -0,0 +1,12 @@
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

View File

@ -0,0 +1,25 @@
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,
};
}
}

View File

@ -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/redux/store.dart';
import 'package:aman_kassa_flutter/views/login/login_view.dart'; import 'package:aman_kassa_flutter/views/login/login_view.dart';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:device_info/device_info.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:aman_kassa_flutter/core/models/message.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/response.dart';
import 'package:aman_kassa_flutter/core/models/smena.dart'; import 'package:aman_kassa_flutter/core/models/smena.dart';

View File

@ -1,173 +1,30 @@
import 'dart:io'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:aman_kassa_flutter/core/base/base_service.dart'; import 'package:aman_kassa_flutter/core/base/base_service.dart';
import 'package:bluetooth_print/bluetooth_print.dart'; import 'package:aman_kassa_flutter/core/models/bluetooth_device.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 { 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; 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>> get scanResult =>
if (isAndroid) Stream<List<BluetoothDevice>>.value([]);
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> get isScanning => Stream<bool>.value(false);
isAndroid ? _bluetoothAndr.isScanning : _bluetoothIOS.isScanning;
Stream<int> get state => isAndroid Stream<int> get state => Stream<int>.value(0);
? _bluetoothAndr.state
: _bluetoothIOS.state.asyncMap<int>((event) => event.index);
set device(BluetoothDevice device) => _device = device; set device(BluetoothDevice device) => _device = device;
Future<bool> connect() async { Future<bool> connect() async => false;
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;
// }
try { Future<bool> disconnect() async => true;
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, Future<bool> printBytes(Uint8List bytes,
{int chunkSizeBytes = 20, int queueSleepTimeMs = 20}) async { {int chunkSizeBytes = 20, int queueSleepTimeMs = 20}) async => false;
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);
}
} }

View File

@ -106,9 +106,8 @@ class MainApplication extends StatelessWidget {
), ),
builder: (context, child) => MaterialApp( builder: (context, child) => MaterialApp(
theme: ThemeData( theme: ThemeData(
backgroundColor: backgroundColor, colorScheme: ColorScheme.fromSeed(seedColor: primaryColor).copyWith(surface: backgroundColor),
primaryColor: primaryColor, primaryColor: primaryColor,
accentColor: yellowColor,
scaffoldBackgroundColor: Colors.white, scaffoldBackgroundColor: Colors.white,
// textTheme: GoogleFonts.latoTextTheme( // textTheme: GoogleFonts.latoTextTheme(
// Theme.of(context).textTheme, // Theme.of(context).textTheme,

View File

@ -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/constants/setting_const.dart';
import 'package:aman_kassa_flutter/redux/state/setting_state.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:meta/meta.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:redux_thunk/redux_thunk.dart'; import 'package:redux_thunk/redux_thunk.dart';

View File

@ -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:aman_kassa_flutter/redux/constants/setting_const.dart';
import 'package:bluetooth_print/bluetooth_print_model.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
@immutable @immutable

View File

@ -120,15 +120,16 @@ class _BankSettingViewState extends State<BankSettingView> {
labelText: 'Пароль', hintText: "Введите пароль"), labelText: 'Пароль', hintText: "Введите пароль"),
), ),
verticalSpaceMedium, verticalSpaceMedium,
RaisedButton( ElevatedButton(
onPressed: () => this._saveData(context), onPressed: () => this._saveData(context),
child: Text( child: Text(
'Cохранить', 'Cохранить',
style: TextStyle(color: whiteColor, fontSize: 25.0), style: TextStyle(color: whiteColor, fontSize: 25.0),
), ),
color: primaryColor, style: ElevatedButton.styleFrom(
padding: backgroundColor: primaryColor,
const EdgeInsets.symmetric(vertical: 5.0, horizontal: 20.0), padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 20.0),
),
), ),
], ],

View File

@ -122,15 +122,16 @@ class _ForteSettingViewState extends State<ForteSettingView> {
labelText: 'Пароль', hintText: "Введите пароль"), labelText: 'Пароль', hintText: "Введите пароль"),
), ),
verticalSpaceMedium, verticalSpaceMedium,
RaisedButton( ElevatedButton(
onPressed: () => this._saveData(context), onPressed: () => this._saveData(context),
child: Text( child: Text(
'Cохранить', 'Cохранить',
style: TextStyle(color: whiteColor, fontSize: 25.0), style: TextStyle(color: whiteColor, fontSize: 25.0),
), ),
color: primaryColor, style: ElevatedButton.styleFrom(
padding: backgroundColor: primaryColor,
const EdgeInsets.symmetric(vertical: 5.0, horizontal: 20.0), padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 20.0),
),
), ),
], ],

View File

@ -171,13 +171,13 @@ class _BankViewState extends State<BankView> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
RaisedButton( ElevatedButton(
child: Text('Activity m4Bank'), onPressed: activity), child: Text('Activity m4Bank'), onPressed: activity),
Column( Column(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[ children: <Widget>[
Text('version: $versionValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false), Text('version: $versionValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
RaisedButton( ElevatedButton(
child: Text('Version'), onPressed: version), child: Text('Version'), onPressed: version),
], ],
), ),
@ -185,7 +185,7 @@ class _BankViewState extends State<BankView> {
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[ children: <Widget>[
Text('init: $initValue' , overflow: TextOverflow.clip, maxLines: 2, softWrap: false), Text('init: $initValue' , overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
RaisedButton( ElevatedButton(
child: Text('Init'), onPressed: initialize), child: Text('Init'), onPressed: initialize),
], ],
), ),
@ -193,7 +193,7 @@ class _BankViewState extends State<BankView> {
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[ children: <Widget>[
Text('connection: $connectionValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false), Text('connection: $connectionValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
RaisedButton( ElevatedButton(
child: Text('Connect'), onPressed: connect), child: Text('Connect'), onPressed: connect),
], ],
), ),
@ -201,21 +201,21 @@ class _BankViewState extends State<BankView> {
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[ children: <Widget>[
Text('auth: $authValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false), Text('auth: $authValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
RaisedButton(child: Text('Auth'), onPressed: auth), ElevatedButton(child: Text('Auth'), onPressed: auth),
], ],
), ),
Column( Column(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[ children: <Widget>[
Text('cancel: $cancelValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false), Text('cancel: $cancelValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
RaisedButton(child: Text('Cancel'), onPressed: cancel), ElevatedButton(child: Text('Cancel'), onPressed: cancel),
], ],
), ),
Column( Column(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[ children: <Widget>[
Text('shutdown: $shutdownValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false), Text('shutdown: $shutdownValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
RaisedButton( ElevatedButton(
child: Text('Shutdown'), onPressed: shutdown), child: Text('Shutdown'), onPressed: shutdown),
], ],
), ),
@ -223,7 +223,7 @@ class _BankViewState extends State<BankView> {
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[ children: <Widget>[
Text('transaction: $transactionValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false), Text('transaction: $transactionValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
RaisedButton( ElevatedButton(
child: Text('Transaction'), onPressed: transaction), child: Text('Transaction'), onPressed: transaction),
], ],
), ),
@ -231,7 +231,7 @@ class _BankViewState extends State<BankView> {
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[ children: <Widget>[
Text('get: $getValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false), Text('get: $getValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
RaisedButton( ElevatedButton(
child: Text('Get'), onPressed: get), child: Text('Get'), onPressed: get),
], ],
), ),
@ -239,7 +239,7 @@ class _BankViewState extends State<BankView> {
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[ children: <Widget>[
Text('error: $errorValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false), Text('error: $errorValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
RaisedButton( ElevatedButton(
child: Text('Error'), onPressed: error), child: Text('Error'), onPressed: error),
], ],
), ),
@ -247,12 +247,12 @@ class _BankViewState extends State<BankView> {
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[ children: <Widget>[
Text('error: $closeDayValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false), Text('error: $closeDayValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
RaisedButton( ElevatedButton(
child: Text('CloseDay'), onPressed: closeDay), child: Text('CloseDay'), onPressed: closeDay),
], ],
), ),
Text('pay: $payValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false), Text('pay: $payValue', overflow: TextOverflow.clip, maxLines: 2, softWrap: false),
RaisedButton(child: Text('Payment'), onPressed: pay), ElevatedButton(child: Text('Payment'), onPressed: pay),
], ],
), ),
), ),

View File

@ -26,12 +26,13 @@ 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/views/payment/halyk_pos_service.dart';
import 'package:aman_kassa_flutter/widgets/fields/busy_button_icon.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/widgets/loader/Dialogs.dart';
import 'package:bluetooth_print/bluetooth_print_model.dart'; import 'package:aman_kassa_flutter/core/models/bluetooth_device.dart';
import 'package:esc_pos_utils/esc_pos_utils.dart'; import 'package:esc_pos_utils/esc_pos_utils.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:vocsy_esys_flutter_share/vocsy_esys_flutter_share.dart'; import 'package:share_plus/share_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../../core/models/aman_dao.dart'; import '../../core/models/aman_dao.dart';
@ -401,7 +402,11 @@ class _MyFloatingActionButtonState extends State<MyFloatingActionButton> {
void shareFile() async { void shareFile() async {
try { try {
await Share.file('Aman Kassa', 'aman_kassa_check.png', base64Decode(widget.data.data!.base64Data!), 'image/png'); 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');
} catch (e) { } catch (e) {
print('error: $e'); print('error: $e');
} }

View File

@ -47,7 +47,7 @@ class _HistoryViewState extends State<HistoryView> {
appBar: AppBar( appBar: AppBar(
title: Text('История чеков'), title: Text('История чеков'),
actions: <Widget>[ actions: <Widget>[
FlatButton( TextButton(
child: Text( child: Text(
'Очистить', 'Очистить',
style: TextStyle( style: TextStyle(

View File

@ -168,7 +168,6 @@ class _HomeViewState extends State<HomeView> with WidgetsBindingObserver {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
titleSpacing: 5.0, titleSpacing: 5.0,
brightness: Brightness.light,
title: HeaderTitle(), title: HeaderTitle(),
actions: <Widget>[ actions: <Widget>[
PopupMenu( PopupMenu(

View File

@ -37,9 +37,8 @@ class CalculatorTab extends StatelessWidget {
Row( Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: RaisedButton( child: ElevatedButton(
padding: EdgeInsets.all(16.0), style: ElevatedButton.styleFrom(backgroundColor: redColor, padding: EdgeInsets.all(16.0)),
color: redColor,
child: Text( child: Text(
"возврат", "возврат",
style: buttonBigTitleTextStyle, style: buttonBigTitleTextStyle,
@ -52,9 +51,8 @@ class CalculatorTab extends StatelessWidget {
}), }),
), ),
Expanded( Expanded(
child: RaisedButton( child: ElevatedButton(
padding: EdgeInsets.all(16.0), style: ElevatedButton.styleFrom(backgroundColor: greenColor, padding: EdgeInsets.all(16.0)),
color: greenColor,
child: Text( child: Text(
"оплата", "оплата",
style: buttonBigTitleTextStyle, style: buttonBigTitleTextStyle,

View File

@ -72,9 +72,8 @@ class KassaTab extends StatelessWidget {
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.all(4.0), padding: const EdgeInsets.all(4.0),
child: RaisedButton( child: ElevatedButton(
padding: const EdgeInsets.all(8), style: ElevatedButton.styleFrom(backgroundColor: primaryColor, padding: const EdgeInsets.all(8)),
color: primaryColor,
child: Text( child: Text(
"Добавить", "Добавить",
style: buttonBigTitleTextStyle, style: buttonBigTitleTextStyle,
@ -88,9 +87,8 @@ class KassaTab extends StatelessWidget {
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.all(4.0), padding: const EdgeInsets.all(4.0),
child: RaisedButton( child: ElevatedButton(
padding: EdgeInsets.all(8), style: ElevatedButton.styleFrom(backgroundColor: greenColor, padding: EdgeInsets.all(8)),
color: greenColor,
child: Text( child: Text(
"Каталог", "Каталог",
style: buttonBigTitleTextStyle, style: buttonBigTitleTextStyle,
@ -135,9 +133,8 @@ class KassaTab extends StatelessWidget {
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.all(4.0), padding: const EdgeInsets.all(4.0),
child: RaisedButton( child: ElevatedButton(
padding: const EdgeInsets.all(8), style: ElevatedButton.styleFrom(backgroundColor: redColor, padding: const EdgeInsets.all(8)),
color: redColor,
child: Text( child: Text(
"возврат", "возврат",
style: buttonBigTitleTextStyle, style: buttonBigTitleTextStyle,
@ -153,9 +150,8 @@ class KassaTab extends StatelessWidget {
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.all(4.0), padding: const EdgeInsets.all(4.0),
child: RaisedButton( child: ElevatedButton(
padding: const EdgeInsets.all(8), style: ElevatedButton.styleFrom(backgroundColor: greenColor, padding: const EdgeInsets.all(8)),
color: greenColor,
child: Text( child: Text(
"оплата", "оплата",
style: buttonBigTitleTextStyle, style: buttonBigTitleTextStyle,

View File

@ -115,12 +115,12 @@ class CatalogBottomSheet extends StatelessWidget {
}, },
), ),
), ),
RaisedButton( ElevatedButton(
child: Text( child: Text(
'Отмена', 'Отмена',
style: buttonBigTitleTextStyle, style: buttonBigTitleTextStyle,
), ),
color: redColor, style: ElevatedButton.styleFrom(backgroundColor: redColor),
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);
}), }),

View File

@ -90,7 +90,7 @@ class _ProductAddBottomSheetState extends State<ProductAddBottomSheet> {
child: ElevatedButton( child: ElevatedButton(
onPressed: _isSearching ? null : _searchByNtin, onPressed: _isSearching ? null : _searchByNtin,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
primary: primaryColor, backgroundColor: primaryColor,
padding: EdgeInsets.symmetric(horizontal: 12), padding: EdgeInsets.symmetric(horizontal: 12),
), ),
child: _isSearching child: _isSearching

View File

@ -108,7 +108,6 @@ class _PaymentViewState extends State<PaymentView> {
}, },
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
brightness: Brightness.light,
backgroundColor: whiteColor, backgroundColor: whiteColor,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(

View File

@ -343,7 +343,6 @@ class _PaymentNfcViewState extends State<PaymentNfcView> {
}, },
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
brightness: Brightness.light,
backgroundColor: purpleColor, backgroundColor: purpleColor,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(

View File

@ -26,7 +26,7 @@ class _QrViewState extends State<QrView> {
), ),
body: Container( body: Container(
child: Center( child: Center(
child: QrImage( child: QrImageView(
data: widget.data.url!, data: widget.data.url!,
version: QrVersions.auto, version: QrVersions.auto,
size: 220.0, size: 220.0,

View File

@ -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/core/services/blue_print_service.dart';
import 'package:aman_kassa_flutter/redux/actions/setting_actions.dart'; import 'package:aman_kassa_flutter/redux/actions/setting_actions.dart';
import 'package:aman_kassa_flutter/redux/store.dart'; import 'package:aman_kassa_flutter/redux/store.dart';
import 'package:bluetooth_print/bluetooth_print_model.dart'; import 'package:aman_kassa_flutter/core/models/bluetooth_device.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' hide Image; import 'package:flutter/material.dart' hide Image;

View File

@ -76,15 +76,16 @@ class _SettingViewState extends State<SettingView> {
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
), ),
verticalSpaceMedium, verticalSpaceMedium,
RaisedButton( ElevatedButton(
onPressed: () => this._setPinCode(context), onPressed: () => this._setPinCode(context),
child: Text( child: Text(
'Cохранить настройки', 'Cохранить настройки',
style: TextStyle(color: whiteColor, fontSize: 20.0), style: TextStyle(color: whiteColor, fontSize: 20.0),
), ),
color: primaryColor, style: ElevatedButton.styleFrom(
padding: backgroundColor: primaryColor,
const EdgeInsets.symmetric(vertical: 5.0, horizontal: 20.0), padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 20.0),
),
), ),
], ],

View File

@ -153,8 +153,8 @@ class ProductListItem extends StatelessWidget {
actions: <Widget>[ actions: <Widget>[
Row( Row(
children: [ children: [
RaisedButton( ElevatedButton(
color: redColor, style: ElevatedButton.styleFrom(backgroundColor: redColor),
child: Text( child: Text(
'Отмена', 'Отмена',
style: TextStyle(fontSize: 18), style: TextStyle(fontSize: 18),
@ -166,8 +166,8 @@ class ProductListItem extends StatelessWidget {
SizedBox( SizedBox(
width: 5, width: 5,
), ),
RaisedButton( ElevatedButton(
color: primaryColor, style: ElevatedButton.styleFrom(backgroundColor: primaryColor),
child: Text( child: Text(
'Сохранить', 'Сохранить',
style: TextStyle(fontSize: 18), style: TextStyle(fontSize: 18),

View File

@ -23,20 +23,20 @@ class CalculatorButton extends StatelessWidget {
// width: 1.2, // width: 1.2,
// ), // ),
// ), // ),
child: FlatButton( child: TextButton(
onPressed: () => onTap(buttonText: text), 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( child: Text(
text, text,
style: TextStyle(fontSize: 25, fontWeight: FontWeight.w700, color: buildTextColor()), 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(),
))); )));
} }

View File

@ -58,14 +58,14 @@ class _DialogManagerState extends State<DialogManager> {
content: Text(request.description), content: Text(request.description),
actions: <Widget>[ actions: <Widget>[
if (isConfirmationDialog) if (isConfirmationDialog)
FlatButton( TextButton(
child: Text(request.cancelTitle!), child: Text(request.cancelTitle!),
onPressed: () { onPressed: () {
_dialogService _dialogService
.dialogComplete(DialogResponse(confirmed: false)); .dialogComplete(DialogResponse(confirmed: false));
}, },
), ),
FlatButton( TextButton(
child: Text(request.buttonTitle), child: Text(request.buttonTitle),
onPressed: () { onPressed: () {
_dialogService _dialogService
@ -129,8 +129,8 @@ class _DialogManagerState extends State<DialogManager> {
), ),
actions: <Widget>[ actions: <Widget>[
if (isConfirmationDialog) if (isConfirmationDialog)
RaisedButton( ElevatedButton(
color: redColor, style: ElevatedButton.styleFrom(backgroundColor: redColor),
child: Text( child: Text(
request.cancelTitle!, request.cancelTitle!,
style: TextStyle(fontSize: 18), style: TextStyle(fontSize: 18),
@ -143,8 +143,8 @@ class _DialogManagerState extends State<DialogManager> {
SizedBox( SizedBox(
width: 5, width: 5,
), ),
RaisedButton( ElevatedButton(
color: primaryColor, style: ElevatedButton.styleFrom(backgroundColor: primaryColor),
child: Text( child: Text(
request.buttonTitle, request.buttonTitle,
style: TextStyle(fontSize: 18), style: TextStyle(fontSize: 18),

View File

@ -0,0 +1,10 @@
.DS_Store
.dart_tool/
.packages
.pub/
build/
ios/.generated/
ios/Flutter/Generated.xcconfig
ios/Runner/GeneratedPluginRegistrant.*

View File

@ -0,0 +1,10 @@
# 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

View File

@ -0,0 +1,6 @@
## [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.

View File

@ -0,0 +1,6 @@
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.

View File

@ -0,0 +1,127 @@
# 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/>
[![flutter platform](https://img.shields.io/badge/Platform-Flutter-yellow.svg)](https://flutter.io)
[![pub package](https://img.shields.io/pub/v/flutter_lock_screen.svg)](https://pub.dartlang.org/packages/flutter_lock_screen)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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.

View File

@ -0,0 +1,23 @@
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;
}
}

View File

@ -0,0 +1,14 @@
{
// 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"
},
]
}

View File

@ -0,0 +1,2 @@
#Thu Sep 26 15:23:08 EET 2019
gradle.version=4.10.2

View File

@ -0,0 +1,17 @@
<?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>

View File

@ -0,0 +1,2 @@
connection.project.dir=
eclipse.preferences.version=1

View File

@ -0,0 +1,6 @@
<?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>

View File

@ -0,0 +1,23 @@
<?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>

View File

@ -0,0 +1,2 @@
connection.project.dir=..
eclipse.preferences.version=1

View File

@ -0,0 +1,59 @@
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 {
}

View File

@ -0,0 +1,42 @@
<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>

View File

@ -0,0 +1,8 @@
package com.example.flutterpasscodeexample;
import io.flutter.app.FlutterFragmentActivity;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterFragmentActivity {
}

View File

@ -0,0 +1,19 @@
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());
}
}

View File

@ -0,0 +1,12 @@
<?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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,8 @@
<?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>

View File

@ -0,0 +1,29 @@
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
}

View File

@ -0,0 +1,4 @@
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true

View File

@ -0,0 +1,6 @@
#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

View File

@ -0,0 +1,160 @@
#!/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 "$@"

View File

@ -0,0 +1,90 @@
@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

View File

@ -0,0 +1,5 @@
sdk.dir=/Users/yasinilhan/Library/Android/sdk
flutter.sdk=/Users/yasinilhan/flutter
flutter.buildMode=debug
flutter.versionName=1.0.0
flutter.versionCode=1

View File

@ -0,0 +1,15 @@
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
}

View File

@ -0,0 +1 @@
include ':app'

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -0,0 +1,173 @@
## 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.

View File

@ -0,0 +1,25 @@
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.

View File

@ -0,0 +1,175 @@
# 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).

View File

@ -0,0 +1,43 @@
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'
}

View File

@ -0,0 +1 @@
org.gradle.jvmargs=-Xmx1536M

View File

@ -0,0 +1 @@
rootProject.name = 'local_auth'

View File

@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.flutter.plugins.localauth">
</manifest>

View File

@ -0,0 +1,287 @@
// 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);
}
}
}

View File

@ -0,0 +1,203 @@
// 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);
}
}

View File

@ -0,0 +1,16 @@
<?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>

View File

@ -0,0 +1,16 @@
<?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>

View File

@ -0,0 +1,16 @@
<?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>

View File

@ -0,0 +1,9 @@
<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>

View File

@ -0,0 +1,9 @@
<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>

Some files were not shown because too many files have changed in this diff Show More