Fix bugs reports in forum; change Sentry mode

This commit is contained in:
poka 2021-02-18 04:42:57 +01:00
parent 2e10ee0743
commit bde1028c70
13 changed files with 203 additions and 92 deletions

View File

@ -49,6 +49,7 @@ android {
targetSdkVersion 29 targetSdkVersion 29
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
multiDexEnabled true
} }
signingConfigs { signingConfigs {

BIN
assets/qrcode-scan.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -12,45 +12,84 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:catcher/catcher.dart';
// import 'dart:io';
// import 'package:flutter_logs/flutter_logs.dart';
// import 'package:downloads_path_provider/downloads_path_provider.dart';
final bool enableSentry = true; final bool enableSentry = true;
// Future<String> getJsonEndpoints() {
// return rootBundle.loadString('config/gva_endpoints.json');
// }
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); try {
HomeProvider _homeProvider = HomeProvider(); WidgetsFlutterBinding.ensureInitialized();
await _homeProvider.getAppPath();
await _homeProvider.createDefaultAvatar();
appVersion = await _homeProvider.getAppVersion();
prefs = await SharedPreferences.getInstance();
final HiveStore _store =
await HiveStore.open(path: '${appPath.path}/gqlCache');
// Get a valid GVA endpoint // var downloadsDirectory = DownloadsPathProvider.downloadsDirectory;
endPointGVA = await _homeProvider.getValidEndpoint(); // File logFile = File(downloadsDirectory.toString() + '/gecko.log');
if (kReleaseMode && enableSentry) { // await FlutterLogs.initLogs(
await SentryFlutter.init( // logLevelsEnabled: [
(options) { // LogLevel.INFO,
options.dsn = // LogLevel.WARNING,
'https://c09587b46eaa42e8b9fda28d838ed180@o496840.ingest.sentry.io/5572110'; // LogLevel.ERROR,
}, // LogLevel.SEVERE
appRunner: () => runApp(Gecko(endPointGVA, _store)), // ],
); // timeStampFormat: TimeStampFormat.TIME_FORMAT_READABLE,
} else { // directoryStructure: DirectoryStructure.FOR_EVENT,
print('Debug mode enabled: No sentry alerte'); // logTypesEnabled: ["Locations", "APIs"],
// logFileExtension: LogFileExtension.LOG,
// logsWriteDirectoryName: downloadsDirectory.toString(),
// logsExportDirectoryName: downloadsDirectory.toString());
runApp(Gecko(endPointGVA, _store)); HomeProvider _homeProvider = HomeProvider();
await _homeProvider.getAppPath();
await _homeProvider.createDefaultAvatar();
appVersion = await _homeProvider.getAppVersion();
prefs = await SharedPreferences.getInstance();
final HiveStore _store =
await HiveStore.open(path: '${appPath.path}/gqlCache');
// Get a valid GVA endpoint
endPointGVA = await _homeProvider.getValidEndpoint();
if (kReleaseMode && enableSentry) {
CatcherOptions debugOptions = CatcherOptions(DialogReportMode(), [
SentryHandler(SentryClient(SentryOptions(
dsn:
"https://c09587b46eaa42e8b9fda28d838ed180@o496840.ingest.sentry.io/5572110")))
]);
// CatcherOptions releaseOptions = CatcherOptions(NotificationReportMode(), [
// EmailManualHandler(["poka@p2p.legal"])
// ]);
Catcher(
rootWidget: Gecko(endPointGVA, _store), debugConfig: debugOptions);
// await SentryFlutter.init(
// (options) {
// options.dsn =
// 'https://c09587b46eaa42e8b9fda28d838ed180@o496840.ingest.sentry.io/5572110';
// },
// appRunner: () => runApp(Gecko(endPointGVA, _store)),
// );
} else {
print('Debug mode enabled: No sentry alerte');
runApp(Gecko(endPointGVA, _store));
}
} catch (e, stack) {
print(e);
if (kReleaseMode) {
await Sentry.captureException(
e,
stackTrace: stack,
);
}
} }
} }
// ignore: must_be_immutable
class Gecko extends StatelessWidget { class Gecko extends StatelessWidget {
Gecko(this.randomEndpoint, this._store); Gecko(this.randomEndpoint, this._store);
final String randomEndpoint; final String randomEndpoint;
@ -68,8 +107,8 @@ class Gecko extends StatelessWidget {
link: _httpLink, link: _httpLink,
), ),
); );
DubpRust.setup(); DubpRust.setup();
return MultiProvider( return MultiProvider(
providers: [ providers: [
// Provider(create: (context) => HistoryProvider()), // Provider(create: (context) => HistoryProvider()),
@ -84,6 +123,7 @@ class Gecko extends StatelessWidget {
child: GraphQLProvider( child: GraphQLProvider(
client: _client, client: _client,
child: MaterialApp( child: MaterialApp(
navigatorKey: Catcher.navigatorKey,
title: 'Ğecko', title: 'Ğecko',
theme: ThemeData( theme: ThemeData(
primaryColor: Color(0xffFFD58D), primaryColor: Color(0xffFFD58D),

View File

@ -7,7 +7,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:gecko/globals.dart'; import 'package:gecko/globals.dart';
import 'package:sentry_flutter/sentry_flutter.dart' as sentry;
import 'package:pdf/pdf.dart'; import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw; import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart'; import 'package:printing/printing.dart';
@ -81,6 +80,9 @@ class GenerateWalletsProvider with ChangeNotifier {
.writeAsString('$nbrWallet:$_name:$_derivationNbr:$_pubkey'); .writeAsString('$nbrWallet:$_name:$_derivationNbr:$_pubkey');
} }
print('CODE PIN :::');
print(wallet.pin);
Navigator.pop(context, true); Navigator.pop(context, true);
return _name; return _name;
@ -147,14 +149,8 @@ class GenerateWalletsProvider with ChangeNotifier {
this.actualWallet = await generateWallet(this.generatedMnemonic); this.actualWallet = await generateWallet(this.generatedMnemonic);
walletIsGenerated = true; walletIsGenerated = true;
// notifyListeners(); // notifyListeners();
} catch (e, stack) { } catch (e) {
print(e); print(e);
if (kReleaseMode) {
await sentry.Sentry.captureException(
e,
stackTrace: stack,
);
}
} }
// await checkIfWalletExist(); // await checkIfWalletExist();
return generatedMnemonic; return generatedMnemonic;
@ -167,14 +163,8 @@ class GenerateWalletsProvider with ChangeNotifier {
mnemonic: generatedMnemonic, mnemonic: generatedMnemonic,
secretCodeType: SecretCodeType.letters, secretCodeType: SecretCodeType.letters,
walletType: WalletType.bip32Ed25519); walletType: WalletType.bip32Ed25519);
} catch (e, stack) { } catch (e) {
print(e); print(e);
if (kReleaseMode) {
await sentry.Sentry.captureException(
e,
stackTrace: stack,
);
}
} }
mnemonicController.text = generatedMnemonic; mnemonicController.text = generatedMnemonic;
@ -243,6 +233,8 @@ class GenerateWalletsProvider with ChangeNotifier {
salt: _cesiumID, password: _cesiumPWD); salt: _cesiumID, password: _cesiumPWD);
cesiumPubkey.text = _walletPubkey; cesiumPubkey.text = _walletPubkey;
pin.text = actualWallet.pin;
isPinChanged = true;
print(_walletPubkey); print(_walletPubkey);
} }
@ -274,13 +266,24 @@ class GenerateWalletsProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void showPinIfEmpty() { void resetImportView() {
if (!isPinChanged) { cesiumID.text = '';
changePinCode(reload: true); cesiumPWD.text = '';
isPinChanged = true; cesiumPubkey.text = '';
} pin.text = '';
canImport = false;
isPinChanged = false;
isCesiumIDVisible = false;
isCesiumPWDVisible = false;
actualWallet = null;
notifyListeners();
} }
// void makeError() {
// var tata = File(appPath.path + '/ddfhjftjfg');
// tata.readAsLinesSync();
// }
void reloadBuild() { void reloadBuild() {
notifyListeners(); notifyListeners();
} }

View File

@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:gecko/globals.dart'; import 'package:gecko/globals.dart';
import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:sentry/sentry.dart' as sentry;
import 'package:qrscan/qrscan.dart' as scanner; import 'package:qrscan/qrscan.dart' as scanner;
import 'dart:math'; import 'dart:math';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@ -28,14 +27,8 @@ class HistoryProvider with ChangeNotifier {
String barcode; String barcode;
try { try {
barcode = await scanner.scan(); barcode = await scanner.scan();
} catch (e, stack) { } catch (e) {
print(e); print(e);
if (kReleaseMode) {
await sentry.Sentry.captureException(
e,
stackTrace: stack,
);
}
return 'false'; return 'false';
} }
if (barcode != null) { if (barcode != null) {
@ -63,7 +56,6 @@ class HistoryProvider with ChangeNotifier {
getShortPubkey(pubkey); getShortPubkey(pubkey);
this.outputPubkey.text = pubkey; this.outputPubkey.text = pubkey;
print(pubkeyShort);
isHistoryScreen = false; isHistoryScreen = false;
historySwitchButtun = "Voir l'historique"; historySwitchButtun = "Voir l'historique";

View File

@ -6,6 +6,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'dart:async'; import 'dart:async';
import 'package:gecko/globals.dart'; import 'package:gecko/globals.dart';
import 'package:gecko/screens/history.dart';
import 'package:gecko/screens/myWallets/walletsHome.dart';
import 'package:package_info/package_info.dart'; import 'package:package_info/package_info.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@ -15,6 +17,8 @@ class HomeProvider with ChangeNotifier {
Icon searchIcon = Icon(Icons.search); Icon searchIcon = Icon(Icons.search);
final TextEditingController searchQuery = new TextEditingController(); final TextEditingController searchQuery = new TextEditingController();
Widget appBarTitle = Text('Ğecko', style: TextStyle(color: Colors.grey[850])); Widget appBarTitle = Text('Ğecko', style: TextStyle(color: Colors.grey[850]));
List currentTab = [HistoryScreen(), WalletsHome()];
get currentIndex => _currentIndex; get currentIndex => _currentIndex;

View File

@ -48,7 +48,9 @@ class HistoryScreen extends StatelessWidget with ChangeNotifier {
child: Container( child: Container(
height: 40.0, height: 40.0,
width: 40.0, width: 40.0,
child: Image.asset('images/scanner.png')), child: Padding(
padding: EdgeInsets.symmetric(horizontal: 3),
child: Image.asset('assets/qrcode-scan.png'))),
backgroundColor: Color( backgroundColor: Color(
0xffEFEFBF), //Color(0xffFFD68E), //Color.fromARGB(500, 204, 255, 255), 0xffEFEFBF), //Color(0xffFFD68E), //Color.fromARGB(500, 204, 255, 255),
), ),
@ -174,7 +176,9 @@ class HistoryScreen extends StatelessWidget with ChangeNotifier {
text: _historyProvider.pubkey)); text: _historyProvider.pubkey));
_historyProvider.snackCopyKey(context); _historyProvider.snackCopyKey(context);
}, },
child: Text(_historyProvider.pubkeyShort, child: Text(
_historyProvider
.getShortPubkey(_historyProvider.pubkey),
style: TextStyle( style: TextStyle(
fontSize: 22, fontSize: 22,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,

View File

@ -1,17 +1,12 @@
import 'package:gecko/globals.dart'; import 'package:gecko/globals.dart';
import 'package:gecko/models/history.dart'; import 'package:gecko/models/history.dart';
import 'package:gecko/models/home.dart'; import 'package:gecko/models/home.dart';
import 'package:gecko/screens/history.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:ui'; import 'dart:ui';
import 'package:gecko/screens/myWallets/walletsHome.dart';
import 'package:gecko/screens/settings.dart'; import 'package:gecko/screens/settings.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
// ignore: must_be_immutable
class HomeScreen extends StatelessWidget { class HomeScreen extends StatelessWidget {
var currentTab = [HistoryScreen(), WalletsHome()];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
HomeProvider _homeProvider = Provider.of<HomeProvider>(context); HomeProvider _homeProvider = Provider.of<HomeProvider>(context);
@ -116,7 +111,7 @@ class HomeScreen extends StatelessWidget {
backgroundColor: Color(0xffFFD58D), backgroundColor: Color(0xffFFD58D),
), ),
backgroundColor: Color(0xffF9F9F1), backgroundColor: Color(0xffF9F9F1),
body: currentTab[_homeProvider.currentIndex], body: _homeProvider.currentTab[_homeProvider.currentIndex],
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: BottomNavigationBar(
backgroundColor: Color(0xffFFD58D), backgroundColor: Color(0xffFFD58D),
fixedColor: Colors.grey[850], fixedColor: Colors.grey[850],

View File

@ -21,6 +21,8 @@ class GenerateWalletsScreen extends StatelessWidget {
GenerateWalletsProvider _generateWalletProvider = GenerateWalletsProvider _generateWalletProvider =
Provider.of<GenerateWalletsProvider>(context); Provider.of<GenerateWalletsProvider>(context);
_generateWalletProvider.generateMnemonic(); _generateWalletProvider.generateMnemonic();
// _generateWalletProvider.makeError();
print('IS GENERATED ? : ' + print('IS GENERATED ? : ' +
_generateWalletProvider.walletIsGenerated.toString()); _generateWalletProvider.walletIsGenerated.toString());
return Scaffold( return Scaffold(

View File

@ -18,19 +18,9 @@ class ImportWalletScreen extends StatelessWidget {
WalletOptionsProvider _walletOptions = WalletOptionsProvider _walletOptions =
Provider.of<WalletOptionsProvider>(context); Provider.of<WalletOptionsProvider>(context);
_generateWalletProvider.showPinIfEmpty();
return WillPopScope( return WillPopScope(
onWillPop: () { onWillPop: () {
_generateWalletProvider.cesiumID.text = ''; _generateWalletProvider.resetImportView();
_generateWalletProvider.cesiumPWD.text = '';
_generateWalletProvider.cesiumPubkey.text = '';
_generateWalletProvider.pin.text = '';
_generateWalletProvider.canImport = false;
_generateWalletProvider.isPinChanged = false;
_generateWalletProvider.isCesiumIDVisible = false;
_generateWalletProvider.isCesiumPWDVisible = false;
_generateWalletProvider.reloadBuild();
return Future<bool>.value(true); return Future<bool>.value(true);
}, },
child: Scaffold( child: Scaffold(
@ -38,15 +28,7 @@ class ImportWalletScreen extends StatelessWidget {
leading: IconButton( leading: IconButton(
icon: Icon(Icons.arrow_back, color: Colors.black), icon: Icon(Icons.arrow_back, color: Colors.black),
onPressed: () { onPressed: () {
_generateWalletProvider.cesiumID.text = ''; _generateWalletProvider.resetImportView();
_generateWalletProvider.cesiumPWD.text = '';
_generateWalletProvider.cesiumPubkey.text = '';
_generateWalletProvider.pin.text = '';
_generateWalletProvider.canImport = false;
_generateWalletProvider.isPinChanged = false;
_generateWalletProvider.isCesiumIDVisible = false;
_generateWalletProvider.isCesiumPWDVisible = false;
_generateWalletProvider.reloadBuild();
Navigator.of(context).pop(); Navigator.of(context).pop();
}), }),
title: SizedBox( title: SizedBox(
@ -195,6 +177,7 @@ class ImportWalletScreen extends StatelessWidget {
.cesiumPWD.text) .cesiumPWD.text)
.then((value) { .then((value) {
_myWalletProvider.rebuildWidget(); _myWalletProvider.rebuildWidget();
_generateWalletProvider.resetImportView();
}); });
} }
: null, : null,

View File

@ -9,6 +9,7 @@ import 'package:provider/provider.dart';
// ignore: must_be_immutable // ignore: must_be_immutable
class WalletsHome extends StatelessWidget { class WalletsHome extends StatelessWidget {
final _derivationKey = GlobalKey<FormState>(); final _derivationKey = GlobalKey<FormState>();
int firstWalletDerivation;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -20,9 +21,14 @@ class WalletsHome extends StatelessWidget {
myWalletProvider.listWallets = myWalletProvider.getAllWalletsNames(); myWalletProvider.listWallets = myWalletProvider.getAllWalletsNames();
final bool isWalletsExists = myWalletProvider.checkIfWalletExist(); final bool isWalletsExists = myWalletProvider.checkIfWalletExist();
if (myWalletProvider.listWallets != '') {
firstWalletDerivation =
int.parse(myWalletProvider.listWallets.split('\n')[0].split(':')[2]);
}
return Scaffold( return Scaffold(
floatingActionButton: Visibility( floatingActionButton: Visibility(
visible: (isWalletsExists), visible: (isWalletsExists && firstWalletDerivation != -1),
child: Container( child: Container(
height: 80.0, height: 80.0,
width: 80.0, width: 80.0,

View File

@ -36,6 +36,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0-nullsafety.1" version: "2.1.0-nullsafety.1"
catcher:
dependency: "direct main"
description:
name: catcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.1"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -106,6 +113,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.5" version: "2.1.5"
device_info:
dependency: transitive
description:
name: device_info
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
device_info_platform_interface:
dependency: transitive
description:
name: device_info_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
dio:
dependency: transitive
description:
name: dio
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.10"
dubp: dubp:
dependency: "direct main" dependency: "direct main"
description: description:
@ -160,6 +188,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.8.1" version: "0.8.1"
flutter_logs:
dependency: "direct main"
description:
name: flutter_logs
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
flutter_mailer:
dependency: transitive
description:
name: flutter_mailer
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
@ -177,6 +219,13 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
fluttertoast:
dependency: transitive
description:
name: fluttertoast
url: "https://pub.dartlang.org"
source: hosted
version: "7.1.6"
globbing: globbing:
dependency: transitive dependency: transitive
description: description:
@ -317,6 +366,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.2" version: "0.6.2"
logger:
dependency: "direct main"
description:
name: logger
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.4"
logging:
dependency: transitive
description:
name: logging
url: "https://pub.dartlang.org"
source: hosted
version: "0.11.4"
mailer:
dependency: transitive
description:
name: mailer
url: "https://pub.dartlang.org"
source: hosted
version: "3.3.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -331,6 +401,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0-nullsafety.3" version: "1.3.0-nullsafety.3"
mime:
dependency: transitive
description:
name: mime
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.7"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -500,19 +577,19 @@ packages:
source: hosted source: hosted
version: "0.24.1" version: "0.24.1"
sentry: sentry:
dependency: transitive dependency: "direct main"
description: description:
name: sentry name: sentry
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.1" version: "4.0.4"
sentry_flutter: sentry_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: sentry_flutter name: sentry_flutter
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.1" version: "4.0.4"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -5,7 +5,7 @@ description: A new Flutter project.
# pub.dev using `pub publish`. This is preferred for private packages. # pub.dev using `pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 0.0.1+6 version: 0.0.1+8
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: ">=2.7.0 <3.0.0"
@ -23,7 +23,6 @@ dependencies:
graphql_flutter: ^4.0.0 #^3.1.0 graphql_flutter: ^4.0.0 #^3.1.0
provider: ^4.3.2+3 provider: ^4.3.2+3
truncate: ^2.1.2 truncate: ^2.1.2
sentry_flutter: ^4.0.1
path_provider: ^1.6.24 path_provider: ^1.6.24
pin_code_fields: ^6.0.2 pin_code_fields: ^6.0.2
http: ^0.12.2 http: ^0.12.2
@ -34,7 +33,11 @@ dependencies:
sync_http: ^0.2.0 sync_http: ^0.2.0
crypto: ^2.1.5 crypto: ^2.1.5
fast_base58: fast_base58:
logger: ^0.9.4
flutter_logs: ^2.1.3
sentry: ^4.0.4
sentry_flutter: ^4.0.4
catcher: ^0.4.1
flutter_icons: flutter_icons:
android: "ic_launcher" android: "ic_launcher"
@ -57,3 +60,4 @@ flutter:
- assets/ - assets/
- assets/OpenSans-Regular.ttf - assets/OpenSans-Regular.ttf
- assets/icon_user.png - assets/icon_user.png
- assets/qrcode-scan.png