diff --git a/assets/translations/en.json b/assets/translations/en.json index a18421a5d5eb5cec54ae1b90f368a985c2ca6721..3f82a1f7f9c526da08e91903828e89b0f17514af 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -116,7 +116,7 @@ "form_contact_name": "Name", "form_contact_name_validation": "Please enter a name", "form_contact_notes": "Notes", - "long_press_to_edit": "Tap and hold to edit", + "long_press_to_edit": "Slide to select this contact. Tap and hold to edit", "form_contact_pub_key": "Public key", "payment_error": "Payment error", "payment_error_desc": "Oops! the payment failed. More details: {error}", diff --git a/assets/translations/es.json b/assets/translations/es.json index 36e8edf66fc112fc4231b4692657f6a5da303967..1a75136ed07d42ff37aa13b38a197a265ab141ff 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -115,7 +115,7 @@ "form_contact_name": "Nombre", "form_contact_name_validation": "Por favor, ingresa un nombre", "form_contact_notes": "Notas", - "long_press_to_edit": "Mantén pulsado para editar", + "long_press_to_edit": "Desliza para seleccionar este contacto. Mantén pulsado para editar", "form_contact_pub_key": "Clave pública", "reloading_nodes": "Refrescando nodos del tipo {type}", "payment_error": "Error en el pago", diff --git a/lib/g1/api.dart b/lib/g1/api.dart index 92eabfa82720731c2a68c4bf3ee16000b196695c..5ebe962864471879fcf98ce4c4bf7b3de9d9ef61 100644 --- a/lib/g1/api.dart +++ b/lib/g1/api.dart @@ -5,6 +5,7 @@ import 'package:durt/durt.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:http/http.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:universal_html/html.dart' show window; import '../data/models/contact.dart'; @@ -280,6 +281,7 @@ Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async { logger( 'Fetched ${lNodes.length} ${type.name} nodes ordered by latency (first: ${lNodes.first.url})'); } catch (e, stacktrace) { + await Sentry.captureException(e, stackTrace: stacktrace); logger('General error in fetch ${type.name} nodes: $e'); logger(stacktrace); // rethrow; @@ -324,6 +326,7 @@ Future<List<Node>> _fetchNodes(NodeType type) async { logger( 'Fetched ${lNodes.length} ${type.name} nodes ordered by latency (first: ${lNodes.first.url})'); } catch (e, stacktrace) { + await Sentry.captureException(e, stackTrace: stacktrace); logger('General error in fetch ${type.name}: $e'); logger(stacktrace); } @@ -472,6 +475,7 @@ Future<String> pay( ? eCause[eCause.length > 1 ? 1 : 0].split(',')[0] : 'Transaction failed for unknown reason'; } catch (e, stacktrace) { + await Sentry.captureException(e, stackTrace: stacktrace); logger(e); logger(stacktrace); return "Something didn't work as expected ($e)"; @@ -534,7 +538,8 @@ Future<T?> gvaFunctionWrapper<T>( final T? result = await specificFunction(gva); return result; } - } catch (e) { + } catch (e, stacktrace) { + await Sentry.captureException(e, stackTrace: stacktrace); logger('Error trying ${node.url} $e'); logger('Increasing node errors of ${node.url} (${node.errors})'); NodeManager() diff --git a/lib/g1/g1_helper.dart b/lib/g1/g1_helper.dart index 8340eb54568fcfb07f6142812e58112d943fa221..2f18f2f65814eb6fa6174b7699bc17cf57a827ca 100644 --- a/lib/g1/g1_helper.dart +++ b/lib/g1/g1_helper.dart @@ -5,6 +5,7 @@ import 'dart:typed_data'; import 'package:durt/durt.dart'; import 'package:encrypt/encrypt.dart' as encrypt; import 'package:encrypt/encrypt.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import '../data/models/contact.dart'; import '../data/models/payment_state.dart'; @@ -89,7 +90,8 @@ String? parseHost(String endpointUnParsed) { } final String endpoint = '$protocol://$host$port$path'.trim(); return endpoint; - } catch (e) { + } catch (e, stacktrace) { + Sentry.captureException(e, stackTrace: stacktrace); // Don't do this here or tests will fail // logger('Cannot parse endpoint $endpointUnParsed'); return null; diff --git a/lib/main.dart b/lib/main.dart index 1ee100ccbdad6806d3f8c0ef0f93b277d8596550..ea0111ba94613867b99f23e774ccd457aa911961 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,11 +11,11 @@ import 'package:hive_flutter/hive_flutter.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:introduction_screen/introduction_screen.dart'; import 'package:once/once.dart'; -import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; import 'package:responsive_framework/responsive_wrapper.dart'; import 'package:responsive_framework/utils/scroll_behavior.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_logging/sentry_logging.dart'; import 'app_bloc_observer.dart'; import 'config/theme.dart'; @@ -107,21 +107,37 @@ void main() async { ), ); - final PackageInfo packageInfo = await PackageInfo.fromPlatform(); - - final String version = packageInfo.version; - logger('G1nkgo version: $version'); - if (kReleaseMode) { // Only use sentry in production await SentryFlutter.init(( SentryFlutterOptions options, ) { - options.release = version; - options.environment = 'production'; - options.beforeSend = (SentryEvent event, {dynamic hint}) { - return event; - }; + options.tracesSampleRate = 1.0; + options.reportPackages = false; + // options.addInAppInclude('sentry_flutter_example'); + options.considerInAppFramesByDefault = false; + // options.attachThreads = true; + // options.enableWindowMetricBreadcrumbs = true; + options.addIntegration(LoggingIntegration()); + options.sendDefaultPii = true; + options.reportSilentFlutterErrors = true; + // options.attachScreenshot = true; + // options.screenshotQuality = SentryScreenshotQuality.low; + // This fails: + // options.attachViewHierarchy = true; + // We can enable Sentry debug logging during development. This is likely + // going to log too much for your app, but can be useful when figuring out + // configuration issues, e.g. finding out why your events are not uploaded. + options.debug = false; + + options.maxRequestBodySize = MaxRequestBodySize.always; + options.maxResponseBodySize = MaxResponseBodySize.always; + + // options.release = version; + // options.environment = 'production'; + // options.beforeSend = (SentryEvent event, {dynamic hint}) { + // return event; + //}; options.dsn = "${dotenv.env['SENTRY_DSN']}"; // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring. diff --git a/lib/ui/contacts_cache.dart b/lib/ui/contacts_cache.dart index ae4fd8a081827c196552ee54140fb7904134e0a7..6b700057740bb7b049b6e1171a2aec1b3e5795c8 100644 --- a/lib/ui/contacts_cache.dart +++ b/lib/ui/contacts_cache.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import '../data/models/contact.dart'; import '../g1/api.dart'; @@ -42,7 +43,8 @@ class ContactsCache { Contact? cachedContact; try { cachedContact = await _retrieveContact(pubKey); - } catch (e) { + } catch (e, stackTrace) { + await Sentry.captureException(e, stackTrace: stackTrace); logger('Error while retrieving contact from cache: $e, $pubKey'); } @@ -78,13 +80,13 @@ class ContactsCache { _pendingRequests.remove(pubKey); return cachedContact; - } catch (e) { + } catch (e, stackTrace) { // Send error to listeners for (final Completer<Contact> completer in _pendingRequests[pubKey]!) { completer.completeError(e); } _pendingRequests.remove(pubKey); - + await Sentry.captureException(e, stackTrace: stackTrace); rethrow; } } @@ -123,7 +125,7 @@ class ContactsCache { if (record != null) { final Map<String, dynamic> typedRecord = - Map<String, dynamic>.from(record as Map<String, dynamic>); + Map<String, dynamic>.from(record as Map<dynamic, dynamic>); final DateTime timestamp = DateTime.parse(typedRecord['timestamp'] as String); final bool before = DateTime.now().isBefore(timestamp.add(duration)); diff --git a/lib/ui/ui_helpers.dart b/lib/ui/ui_helpers.dart index 6354d97d8b33619dcad4e5fd86b8ed8472df23d5..d0f905c89071e610b02740cecaffc7f7b4c93d07 100644 --- a/lib/ui/ui_helpers.dart +++ b/lib/ui/ui_helpers.dart @@ -182,7 +182,7 @@ ListTile contactToListItem(Contact contact, int index, BuildContext context, contact.subtitle != null ? Text(contact.subtitle!) : null; return ListTile( title: Text(title), - subtitle: subtitle, + subtitle: subtitle ?? Container(), tileColor: tileColor(index, context), onTap: onTap, onLongPress: onLongPress, @@ -203,3 +203,5 @@ bool inDevelopment() => !inProduction(); bool onlyInProduction() => kReleaseMode; bool inProduction() => onlyInProduction(); + +String assets(String str) => (kIsWeb && kReleaseMode) ? 'assets/$str' : str; diff --git a/lib/ui/widgets/card_drawer.dart b/lib/ui/widgets/card_drawer.dart index 01d6e9f12480907473cf0584e3cd343adece62e0..6fe4a51f0f566ef8c47d3c74c4fb110ff08d191f 100644 --- a/lib/ui/widgets/card_drawer.dart +++ b/lib/ui/widgets/card_drawer.dart @@ -2,9 +2,11 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import '../../data/models/cesium_card.dart'; import '../../shared_prefs.dart'; +import '../ui_helpers.dart'; class CardDrawer extends StatelessWidget { const CardDrawer({super.key}); @@ -12,8 +14,8 @@ class CardDrawer extends StatelessWidget { @override Widget build(BuildContext context) { final List<CesiumCard> cards = SharedPreferencesHelper().cesiumCards; - const ImageIcon g1nkgoIcon = ImageIcon( - AssetImage('img/favicon.png'), + final ImageIcon g1nkgoIcon = ImageIcon( + AssetImage(assets('img/favicon.png')), size: 24, ); return FutureBuilder<PackageInfo>( @@ -29,11 +31,14 @@ class CardDrawer extends StatelessWidget { ), */ child: Column( children: <Widget>[ - Image.asset( - 'assets/img/logo.png', - fit: BoxFit.scaleDown, - height: 80.0, - ), + GestureDetector( + onTap: () => tryCatch(), + onLongPress: () => tryCatch(), + child: Image.asset( + 'assets/img/logo.png', + fit: BoxFit.scaleDown, + height: 80.0, + )), // const SizedBox(height: 20.0), /* Text(tr('app_name'), style: const TextStyle( @@ -56,6 +61,7 @@ class CardDrawer extends StatelessWidget { ), ), ), + if (kReleaseMode) Expanded(child: Container()), if (!kReleaseMode) Expanded( child: Container( @@ -84,8 +90,7 @@ class CardDrawer extends StatelessWidget { AboutListTile( icon: g1nkgoIcon, applicationName: tr('app_name'), - applicationVersion: - 'Version: ${snapshot.data!.version} build: ${snapshot.data!.buildNumber}', + applicationVersion: 'Version: ${snapshot.data!.version}', applicationIcon: g1nkgoIcon, applicationLegalese: '© 2023-${DateTime.now().year} Comunes Association, under AGPLv3', @@ -102,3 +107,11 @@ class CardDrawer extends StatelessWidget { ); } } + +Future<void> tryCatch() async { + try { + throw StateError('Testing sentry with try catch'); + } catch (error, stackTrace) { + await Sentry.captureException(error, stackTrace: stackTrace); + } +} diff --git a/lib/ui/widgets/fifth_screen/import_dialog.dart b/lib/ui/widgets/fifth_screen/import_dialog.dart index 473c642f0ac939b1c719f1c254b298639638c669..aa7be351a9ad4a198c53cc8b356ec4341f9b49e8 100644 --- a/lib/ui/widgets/fifth_screen/import_dialog.dart +++ b/lib/ui/widgets/fifth_screen/import_dialog.dart @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:pattern_lock/pattern_lock.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:universal_html/html.dart' as html; import '../../../data/models/transaction_cubit.dart'; @@ -92,13 +93,15 @@ class _ImportDialogState extends State<ImportDialog> { return; } Navigator.of(context).pop(true); - } catch (e) { + } catch (e, stacktrace) { context.replaceSnackbar( content: Text( tr('wrong_pattern'), style: const TextStyle(color: Colors.red), ), ); + await Sentry.captureException(e, + stackTrace: stacktrace); } }, ), @@ -141,8 +144,9 @@ class _ImportDialogState extends State<ImportDialog> { logger(jsonString); } completer.complete(jsonString); - } catch (e) { + } catch (e, stacktrace) { logger('Error importing wallet $e'); + await Sentry.captureException(e, stackTrace: stacktrace); } }); return completer.future; diff --git a/lib/ui/widgets/fourth_screen/transaction_item.dart b/lib/ui/widgets/fourth_screen/transaction_item.dart index 298a497c19acb72b61b2aea5d156ebce6461cd3f..905aafa7e3a843aef6c26d687011a00b8284548d 100644 --- a/lib/ui/widgets/fourth_screen/transaction_item.dart +++ b/lib/ui/widgets/fourth_screen/transaction_item.dart @@ -75,7 +75,7 @@ class TransactionListItem extends StatelessWidget { final ContactsCubit contactsCubit = context.read<ContactsCubit>(); return Slidable( // Specify a key if the Slidable is dismissible. - key: const ValueKey<int>(0), + key: ValueKey<int>(index), // The end action pane is the one at the right or the bottom side. endActionPane: ActionPane( motion: const ScrollMotion(), diff --git a/lib/ui/widgets/second_screen/card_terminal.dart b/lib/ui/widgets/second_screen/card_terminal.dart index 2ba30940084f103d97e00675ff0c7f301036efde..5626545b6999bf19b83dc374f1a82e657a8d27f5 100644 --- a/lib/ui/widgets/second_screen/card_terminal.dart +++ b/lib/ui/widgets/second_screen/card_terminal.dart @@ -181,6 +181,6 @@ Future<void> vibrateIfPossible() async { Vibration.vibrate(duration: 1000); } } catch (e) { - // ok we tryied... + // ok we tried... } } diff --git a/lib/ui/widgets/third_screen/contacts_page.dart b/lib/ui/widgets/third_screen/contacts_page.dart index 69a6298a2f753107c9759d9d998ad67322c7e6e4..9aadfa91482ee0f46f41b4b2caf41fcd5f1fb2e8 100644 --- a/lib/ui/widgets/third_screen/contacts_page.dart +++ b/lib/ui/widgets/third_screen/contacts_page.dart @@ -57,6 +57,7 @@ class _ContactsPageState extends State<ContactsPage> { _contactsCubit.filterContacts(query); }, ), + const SizedBox(height: 20), if (state.filteredContacts.isEmpty) const NoElements(text: 'no_contacts') else @@ -67,7 +68,7 @@ class _ContactsPageState extends State<ContactsPage> { final Contact contact = state.filteredContacts[index]; return Slidable( // Specify a key if the Slidable is dismissible. - key: const ValueKey<int>(0), + key: ValueKey<int>(index), // The start action pane is the one at the left or the top side. startActionPane: ActionPane( diff --git a/pubspec.lock b/pubspec.lock index 69547726991a7d5d2ad70e16cd45171d9d4f0277..4d42a6cb187b3515d4d5b2fc19d45b0b9950acf1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1104,10 +1104,10 @@ packages: dependency: transitive description: name: sentry - sha256: faecda9087cd6d1c2b95bd5e16cce0adeef2e9aa34b8016b150b314be7b5c642 + sha256: "144d96d63c23dfd6bba991ba2154279d4e7db5e325767f8fe0f466293da86890" url: "https://pub.dev" source: hosted - version: "7.2.0" + version: "7.4.0" sentry_dart_plugin: dependency: "direct main" description: @@ -1120,10 +1120,18 @@ packages: dependency: "direct main" description: name: sentry_flutter - sha256: "038607c578d2601c63ced78a503c23d7b25f24bd3c24492cadfc47bc9cbb6636" + sha256: d60384b6dd61c461f3891b96ad7f0293397c3afc6ea4a129c12e92a27651e000 url: "https://pub.dev" source: hosted - version: "7.2.0" + version: "7.4.0" + sentry_logging: + dependency: "direct main" + description: + name: sentry_logging + sha256: "5a28f0b3070872c772d9b371106345a8680ced5b8394502125dd532cd0935fdd" + url: "https://pub.dev" + source: hosted + version: "7.4.0" share_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index bd3d9e6be00e594633e710434d03f201d53fe0f8..7566a71e7e2fed05bc3c355d638a553d444ebc09 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -66,6 +66,7 @@ dependencies: package_info_plus: ^3.0.3 share_plus: ^6.3.1 sentry_dart_plugin: ^1.1.0 + sentry_logging: ^7.4.0 dev_dependencies: flutter_test: