Skip to content
Snippets Groups Projects
Commit ce5c5a64 authored by vjrj's avatar vjrj
Browse files

Merge branch 'master' into weblate

parents 1aea4856 b9b7f886
No related branches found
No related tags found
No related merge requests found
......@@ -10,7 +10,6 @@
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
<application
android:preserveLegacyExternalStorage="true"
android:requestLegacyExternalStorage="true"
android:icon="@mipmap/ic_launcher"
android:label="Ginkgo">
......
......@@ -124,5 +124,6 @@
"error_installing_desktop": "Error installing Ğ1nkgo in your Desktop: (error: {error})",
"install_desktop": "Install Ğ1nkgo in Desktop",
"import_failed": "Wallet import failed",
"select_file_to_import": "Select the wallet backup"
"select_file_to_import": "Select the wallet backup",
"You can't send money to yourself.": "You can't send money to yourself."
}
......@@ -31,11 +31,11 @@
"no_internet": "Sin conexión a internet",
"skip": "Omitir",
"start": "Comenzar",
"offline": "¡Está desconectad@!",
"offline": "¡Estás desconectad@!",
"online_terminal": "En línea",
"offline_terminal": "Fuera de línea",
"show_qr_to_client": "Muestre su clave pública a su clientæ",
"show_qr_to_client_amount": "Muestre este QR con esa cantidad a otro monedero Ğ1nkgo",
"show_qr_to_client": "Muestra su clave pública a su clientæ",
"show_qr_to_client_amount": "Muestra este QR con esa cantidad a otro monedero Ğ1nkgo",
"keys_tooltip": "Las claves públicas y privadas en Ğ1 y Duniter son como un sistema de cerradura y llave, donde la clave pública actúa como la cerradura que puede ser abierta por cualquiera que tenga la clave privada correspondiente, proporcionando una forma segura de autenticar y verificar transacciones",
"card_validity": "Validez",
"card_validity_tooltip": "Tenga en cuenta que este monedero solo es accesible mientras utiliza este navegador y este dispositivo específico. Si borra o restablece el navegador, perderá el acceso a este monedero y los fondos almacenados en el.",
......@@ -124,5 +124,6 @@
"error_installing_desktop": "Error al instalar Ğ1nkgo en Desktop (error: {error})",
"install_desktop": "Instalar Ğ1nkgo en tu Desktop",
"import_failed": "Error al importar monedero",
"select_file_to_import": "Selecciona el monedero guadado"
"select_file_to_import": "Selecciona el monedero guadado",
"You can't send money to yourself.": "No puedes enviarte dinero a ti mismo/a."
}
......@@ -544,8 +544,9 @@ Future<T?> gvaFunctionWrapper<T>(
final T? result = await specificFunction(gva);
return result;
}
} catch (e, stacktrace) {
await Sentry.captureException(e, stackTrace: stacktrace);
} catch (e) {
await Sentry.captureMessage(
'Error trying to use gva node ${node.url} $e');
logger('Error trying ${node.url} $e');
logger('Increasing node errors of ${node.url} (${node.errors})');
NodeManager()
......
......@@ -33,7 +33,7 @@ TransactionsAndBalanceState transactionParser(String txData) {
balance = balance -= amount;
}
final DateTime txDate =
DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
DateTime.fromMillisecondsSinceEpoch(timestamp * 1000, isUtc: true);
/* if (!kReleaseMode) {
logger('Timestamp: $timestamp');
logger('Fecha: $txDate');
......
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
......@@ -22,13 +23,18 @@ class ContactsCache {
Box<dynamic>? _box;
Future<void> init() async {
if (kIsWeb) {
_box = await Hive.openBox(_boxName);
} else {
final Directory appDocDir = await getApplicationDocumentsDirectory();
final String appDocPath = appDocDir.path;
_box = await Hive.openBox(_boxName, path: appDocPath);
try {
if (kIsWeb) {
_box = await Hive.openBox(_boxName);
} else {
final Directory appDocDir = await getApplicationDocumentsDirectory();
final String appDocPath = appDocDir.path;
_box = await Hive.openBox(_boxName, path: appDocPath);
}
} catch (e) {
logger('Error opening Hive: $e');
}
_box ??= _MemoryFallbackBox<Contact>();
}
Future<void> dispose() async {
......@@ -37,9 +43,9 @@ class ContactsCache {
static ContactsCache? _instance;
final Map<String, List<Completer<Contact>>> _pendingRequests =
<String, List<Completer<Contact>>>{};
<String, List<Completer<Contact>>>{};
static Duration duration =
kReleaseMode ? const Duration(days: 3) : const Duration(hours: 5);
kReleaseMode ? const Duration(days: 3) : const Duration(hours: 5);
final String _boxName = 'contacts_cache';
......@@ -135,9 +141,9 @@ class ContactsCache {
if (record != null) {
final Map<String, dynamic> typedRecord =
Map<String, dynamic>.from(record as Map<dynamic, dynamic>);
Map<String, dynamic>.from(record as Map<dynamic, dynamic>);
final DateTime timestamp =
DateTime.parse(typedRecord['timestamp'] as String);
DateTime.parse(typedRecord['timestamp'] as String);
final bool before = DateTime.now().isBefore(timestamp.add(duration));
if (before) {
final Contact contact = Contact.fromJson(
......@@ -148,3 +154,147 @@ class ContactsCache {
return null;
}
}
class _MemoryFallbackBox<E> extends Box<E> {
final Map<String, dynamic> _storage = HashMap<String, dynamic>();
@override
String get name => '_memory_fallback_box';
@override
bool get isOpen => true;
@override
String? get path => null;
@override
bool get lazy => false;
@override
Iterable<dynamic> get keys => _storage.keys;
@override
int get length => _storage.length;
@override
bool get isEmpty => _storage.isEmpty;
@override
bool get isNotEmpty => _storage.isNotEmpty;
@override
dynamic keyAt(int index) {
return _storage.keys.elementAt(index);
}
@override
Stream<BoxEvent> watch({dynamic key}) {
throw UnimplementedError('watch() is not supported in _MemoryFallbackBox');
}
@override
bool containsKey(dynamic key) {
return _storage.containsKey(key);
}
@override
Future<void> put(dynamic key, E value) async {
_storage[key as String] = value;
}
@override
Future<void> putAt(int index, E value) async {
_storage[_storage.keys.elementAt(index)] = value;
}
@override
Future<void> putAll(Map<dynamic, E> entries) async {
_storage.addAll(entries as Map<String, dynamic>);
}
@override
Future<int> add(E value) async {
throw UnimplementedError('add() is not supported in _MemoryFallbackBox');
}
@override
Future<Iterable<int>> addAll(Iterable<E> values) async {
throw UnimplementedError('addAll() is not supported in _MemoryFallbackBox');
}
@override
Future<void> delete(dynamic key) async {
_storage.remove(key);
}
@override
Future<void> deleteAt(int index) async {
_storage.remove(_storage.keys.elementAt(index));
}
@override
Future<void> deleteAll(Iterable<dynamic> keys) async {
// ignore: prefer_foreach
for (final dynamic key in keys) {
_storage.remove(key);
}
}
@override
Future<void> compact() async {}
@override
Future<int> clear() async {
final int count = _storage.length;
_storage.clear();
return count;
}
@override
Future<void> close() async {}
@override
Future<void> deleteFromDisk() async {}
@override
Future<void> flush() async {}
@override
E? get(dynamic key, {E? defaultValue}) {
return _storage.containsKey(key) ? _storage[key] as E : defaultValue;
}
@override
E? getAt(int index) {
return _storage.values.elementAt(index) as E?;
}
@override
Map<dynamic, E> toMap() {
return Map<dynamic, E>.from(_storage);
}
@override
Iterable<E> get values => _storage.values.cast<E>();
@override
Iterable<E> valuesBetween({dynamic startKey, dynamic endKey}) {
if (startKey == null && endKey == null) {
return values;
}
final int startIndex = startKey != null ? _storage.keys.toList().indexOf(
startKey as String) : 0;
final int endIndex = endKey != null ? _storage.keys.toList().indexOf(
endKey as String) : _storage.length - 1;
if (startIndex < 0 || endIndex < 0) {
throw ArgumentError('Start key or end key not found in the box.');
}
return _storage.values.skip(startIndex)
.take(endIndex - startIndex + 1)
.cast<E>();
}
}
import 'package:connectivity_wrapper/connectivity_wrapper.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
......@@ -35,6 +36,20 @@ class _PayFormState extends State<PayForm> {
if (state.comment != null && _commentController.text != state.comment) {
_commentController.text = state.comment;
}
final ButtonStyle payBtnStyle = ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 25),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
foregroundColor: Colors.white,
backgroundColor: Theme.of(context).colorScheme.primary,
textStyle: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
);
final Widget payBtnText = Text(tr('g1_form_pay_send') +
(!kReleaseMode ? ' ${state.amount} ${state.comment}' : ''));
return Form(
key: _formKey,
child: Column(
......@@ -70,51 +85,47 @@ class _PayFormState extends State<PayForm> {
},
),
const SizedBox(height: 10.0),
ElevatedButton(
onPressed: (!state.canBeSent() ||
state.amount == null ||
!_commentValidate() ||
!_weHaveBalance(context, state.amount!))
? null
: () async {
try {
await payWithRetry(context, state, false);
} on RetryException {
// Here the transactions can be lost, so we must implement some manual retry use
await payWithRetry(context, state, true);
}
},
style: ElevatedButton.styleFrom(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 25),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
ConnectivityWidgetWrapper(
stacked: false,
offlineWidget: ElevatedButton(
onPressed: null,
style: payBtnStyle,
child: _buildBtn(Text(tr('offline'))),
),
foregroundColor: Colors.white,
backgroundColor: Theme.of(context).colorScheme.primary,
textStyle: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(Icons.send),
const SizedBox(width: 10),
Text(tr('g1_form_pay_send') +
(!kReleaseMode
? ' ${state.amount} ${state.comment}'
: '')),
],
),
)
child: ElevatedButton(
onPressed: (!state.canBeSent() ||
state.amount == null ||
!_commentValidate() ||
!_weHaveBalance(context, state.amount!))
? null
: () async {
try {
await payWithRetry(context, state, false);
} on RetryException {
// Here the transactions can be lost, so we must implement some manual retry use
await payWithRetry(context, state, true);
}
},
style: payBtnStyle,
child: _buildBtn(payBtnText),
))
],
),
);
});
}
Row _buildBtn(Widget payBtnText) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(Icons.send),
const SizedBox(width: 10),
payBtnText,
],
);
}
bool _commentValidate() {
final bool? val = _formKey.currentState?.validate();
logger('Validating comment: $val');
......
......@@ -197,7 +197,7 @@ ListTile contactToListItem(Contact contact, int index, BuildContext context,
trailing: trailing);
}
bool showShare() => onlyInDevelopment;
bool showShare() => onlyInDevelopment || !kIsWeb;
bool get onlyInDevelopment => !inProduction;
......@@ -207,7 +207,10 @@ bool get onlyInProduction => kReleaseMode;
bool get inProduction => onlyInProduction;
String assets(String str) => (kIsWeb && kReleaseMode) ? 'assets/$str' : str;
String assets(String str) =>
(kIsWeb && kReleaseMode) || (!kIsWeb && Platform.isAndroid)
? 'assets/$str'
: str;
Future<Directory?> getAppSpecificExternalFilesDirectory(
[bool ext = false]) async {
......
......@@ -148,10 +148,11 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
if (_results.length == 1 && pay != null) {
final Contact contact = _results[0];
paymentCubit.selectUser(contact, pay.amount);
} else if (pay!.amount != null) {
paymentCubit.selectKeyAmount(pay.contact!, pay.amount!);
} else {
paymentCubit.selectKey(pay.contact);
if (pay.amount != null) {
paymentCubit.selectKeyAmount(contact, pay.amount!);
} else {
paymentCubit.selectKey(contact);
}
}
if (!mounted) {
return;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment