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

DU/G1 switch in dev mode only. Pay IconButton

parent e3ac7fdc
No related branches found
No related tags found
No related merge requests found
......@@ -4,7 +4,7 @@
# This file should be version controlled.
version:
revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
channel: stable
project_type: app
......@@ -13,11 +13,17 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
- platform: linux
create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
- platform: macos
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
- platform: windows
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
# User provided section
......
......@@ -36,7 +36,8 @@ class PaymentCubit extends HydratedCubit<PaymentState> {
}
void sent() {
emit(state.copyWith(status: PaymentStatus.isSent));
const PaymentState newState = PaymentState();
emit(newState);
}
void notSent() {
......
......@@ -65,7 +65,7 @@ class ContactsCache {
return _box!;
}
Future<Contact> getContact(String pubKey, [bool debug = true]) async {
Future<Contact> getContact(String pubKey, [bool debug = false]) async {
Contact? cachedContact;
try {
cachedContact = await _retrieveContact(pubKey);
......
......@@ -32,11 +32,11 @@ class FirstTutorial extends Tutorial {
keyTarget: payAmountKey,
align: ContentAlign.top,
shape: ShapeLightFocus.RRect));
/* targets.add(TutorialTarget(
identify: 'paySentKey',
keyTarget: paySentKey,
align: ContentAlign.top,
shape: ShapeLightFocus.RRect)); */
targets.add(TutorialTarget(
identify: 'paySentKey',
keyTarget: paySentKey,
align: ContentAlign.top,
));
return targets;
}
}
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:toggle_switch/toggle_switch.dart';
import '../../../data/models/app_cubit.dart';
import '../../../data/models/payment_cubit.dart';
import '../../../data/models/payment_state.dart';
import '../../logger.dart';
import '../../ui_helpers.dart';
class G1PayAmountField extends StatefulWidget {
......@@ -31,48 +33,64 @@ class _G1PayAmountFieldState extends State<G1PayAmountField> {
TextPosition(offset: _controller.text.length));
}
}
final bool expertMode = context.read<AppCubit>().isExpertMode;
return Form(
key: _formKey,
child: TextFormField(
controller: _controller,
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
validator: validateDecimal,
// Disallow autocomplete
autofillHints: const <String>[],
onEditingComplete: () {},
onChanged: (String? value) {
final bool? validate = _formKey.currentState?.validate();
if (validate != null &&
value != null &&
value.isNotEmpty &&
validate) {
context.read<PaymentCubit>().selectAmount(
parseToDoubleLocalized(
locale: context.locale.toLanguageTag(),
number: value));
} else {
context.read<PaymentCubit>().selectAmount(
value == null ? null : double.tryParse(value));
}
},
decoration: InputDecoration(
labelText: tr('g1_amount'),
hintText: tr('g1_amount_hint'),
prefixIcon: Padding(
padding: const EdgeInsets.all(10.0),
child: SvgPicture.asset(
colorFilter: ColorFilter.mode(
Colors.purple.shade600, BlendMode.srcIn),
'assets/img/gbrevedot.svg',
width: 20.0,
height: 20.0,
),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
border: const OutlineInputBorder(),
),
));
validator: validateDecimal,
// Disallow autocomplete
autofillHints: const <String>[],
onEditingComplete: () {},
onChanged: (String? value) {
final bool? validate = _formKey.currentState?.validate();
if (validate != null &&
value != null &&
value.isNotEmpty &&
validate) {
context.read<PaymentCubit>().selectAmount(
parseToDoubleLocalized(
locale: context.locale.toLanguageTag(),
number: value));
} else {
context.read<PaymentCubit>().selectAmount(
value == null ? null : double.tryParse(value));
}
},
decoration: InputDecoration(
labelText: tr('g1_amount'),
hintText: tr('g1_amount_hint'),
contentPadding: const EdgeInsets.fromLTRB(16, 0, 10, 0),
border: const OutlineInputBorder(),
suffix: Padding(
padding: const EdgeInsets.only(bottom: 20),
child: ToggleSwitch(
minWidth: 40.0,
// animate: true,
radiusStyle: true,
// initialLabelIndex: 0,
cornerRadius: 20.0,
activeFgColor: Colors.white,
inactiveBgColor: Colors.grey[400],
inactiveFgColor: Colors.white,
totalSwitches: expertMode ? 2 : 1,
labels: expertMode && inDevelopment
? const <String>['Ğ1', 'DU']
: const <String>['Ğ1'],
iconSize: 30.0,
borderWidth: 2.0,
// borderColor: [Colors.blueGrey],
activeBgColors: <List<Color>>[
<Color>[Theme.of(context).primaryColor],
<Color>[Theme.of(context).primaryColor],
],
onToggle: (int? index) {
logger('switched to: $index');
},
),
))));
});
String? validateDecimal(String? value) {
......
......@@ -58,20 +58,20 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
final Response cPlusResponse = await searchCPlusUser(_searchTerm);
if (cPlusResponse.statusCode != 404) {
setState(() async {
// Add cplus users
final List<dynamic> hits = ((const JsonDecoder()
.convert(cPlusResponse.body) as Map<String, dynamic>)['hits']
as Map<String, dynamic>)['hits'] as List<dynamic>;
for (final dynamic hit in hits) {
final Contact c =
await contactFromResultSearch(hit as Map<String, dynamic>);
logger('Contact retrieved in c+ search $c');
ContactsCache().addContact(c);
// Add cplus users
final List<dynamic> hits = ((const JsonDecoder()
.convert(cPlusResponse.body) as Map<String, dynamic>)['hits']
as Map<String, dynamic>)['hits'] as List<dynamic>;
for (final dynamic hit in hits) {
final Contact c =
await contactFromResultSearch(hit as Map<String, dynamic>);
logger('Contact retrieved in c+ search $c');
ContactsCache().addContact(c);
setState(() {
_addIfNotPresent(c);
}
logger('Found: ${_results.length}');
});
});
}
logger('Found: ${_results.length}');
}
final List<Contact> wotResults = await searchWot(_searchTerm);
......@@ -82,11 +82,11 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
// retrieve extra results with c+ profile
for (final Contact wotC in wotResults) {
final Contact cachedWotProfile =
await ContactsCache().getContact(wotC.pubKey);
await ContactsCache().getContact(wotC.pubKey);
if (cachedWotProfile.name == null) {
// Users without c+ profile
final Contact cPlusProfile =
await getProfile(cachedWotProfile.pubKey, true);
await getProfile(cachedWotProfile.pubKey, true);
ContactsCache().addContact(cPlusProfile);
}
}
......@@ -120,7 +120,10 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
return Scaffold(
appBar: AppBar(
title: Text(tr('search_user_title')),
backgroundColor: Theme.of(context).colorScheme.primary,
backgroundColor: Theme
.of(context)
.colorScheme
.primary,
foregroundColor: Colors.white,
actions: <Widget>[
IconButton(
......@@ -195,32 +198,33 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
),
if (_isLoading)
const LoadingBox(simple: false)
else if (_searchTerm.isNotEmpty && _results.isEmpty && _isLoading)
const NoElements(text: 'nothing_found')
else
Expanded(
child: ListView.builder(
itemCount: _results.length,
itemBuilder: (BuildContext context, int index) {
final Contact contact = _results[index];
return FutureBuilder<Contact>(
future: ContactsCache().getContact(contact.pubKey),
builder: (BuildContext context,
AsyncSnapshot<Contact> snapshot) {
Widget widget;
if (snapshot.hasData) {
widget =
_buildItem(snapshot.data!, index, context);
} else if (snapshot.hasError) {
widget = CustomErrorWidget(snapshot.error);
} else {
// Contact without wot
widget = _buildItem(contact, index, context);
}
return widget;
});
}),
)
if (_searchTerm.isNotEmpty && _results.isEmpty && _isLoading)
const NoElements(text: 'nothing_found')
else
Expanded(
child: ListView.builder(
itemCount: _results.length,
itemBuilder: (BuildContext context, int index) {
final Contact contact = _results[index];
return FutureBuilder<Contact>(
future: ContactsCache().getContact(contact.pubKey),
builder: (BuildContext context,
AsyncSnapshot<Contact> snapshot) {
Widget widget;
if (snapshot.hasData) {
widget =
_buildItem(snapshot.data!, index, context);
} else if (snapshot.hasError) {
widget = CustomErrorWidget(snapshot.error);
} else {
// Contact without wot
widget = _buildItem(contact, index, context);
}
return widget;
});
}),
)
],
),
),
......@@ -238,9 +242,9 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
},
trailing: BlocBuilder<ContactsCubit, ContactsState>(
builder: (BuildContext context, ContactsState state) {
return ContactFavIcon(
contact: contact, contactsCubit: context.read<ContactsCubit>());
}),
return ContactFavIcon(
contact: contact, contactsCubit: context.read<ContactsCubit>());
}),
);
}
}
......@@ -10,7 +10,6 @@ import '../../logger.dart';
import '../../pay_helper.dart';
import '../../tutorial_keys.dart';
import '../../ui_helpers.dart';
import '../connectivity_widget_wrapper_wrapper.dart';
import 'g1_textfield.dart';
class PayForm extends StatefulWidget {
......@@ -23,7 +22,7 @@ class PayForm extends StatefulWidget {
class _PayFormState extends State<PayForm> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final GlobalKey<FormFieldState<String>> _formCommentKey =
GlobalKey<FormFieldState<String>>();
GlobalKey<FormFieldState<String>>();
final TextEditingController _commentController = TextEditingController();
final ValueNotifier<String> _feedbackNotifier = ValueNotifier<String>('');
......@@ -38,134 +37,143 @@ class _PayFormState extends State<PayForm> {
Widget build(BuildContext context) {
return BlocBuilder<PaymentCubit, PaymentState>(
builder: (BuildContext context, PaymentState state) {
if (state.comment != null && _commentController.text != state.comment) {
_commentController.text = state.comment;
}
if (state.comment != null &&
_commentController.text != state.comment) {
_commentController.text = state.comment;
}
if (state.amount == null || state.amount == 0) {
_feedbackNotifier.value = '';
}
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.status}' : ''));
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
G1PayAmountField(key: payAmountKey),
const SizedBox(height: 10.0),
TextFormField(
key: _formCommentKey,
inputFormatters: <TextInputFormatter>[
NoNewLineTextInputFormatter()
],
controller: _commentController,
onChanged: (String? value) {
context.read<PaymentCubit>().setComment(value ?? '');
},
decoration: InputDecoration(
labelText: tr('g1_form_pay_desc'),
hintText: tr('g1_form_pay_hint'),
border: const OutlineInputBorder(),
),
validator: (String? value) {
if (value != null && !basicEnglishCharsRegExp.hasMatch(value)) {
return tr('valid_comment');
}
return null;
},
// Disallow autocomplete
autofillHints: const <String>[],
),
const SizedBox(height: 10.0),
ConnectivityWidgetWrapperWrapper(
stacked: false,
offlineWidget: ElevatedButton(
onPressed: null,
style: payBtnStyle,
child: _buildBtn(Text(tr('offline'))),
),
child: ElevatedButton(
key: paySentKey,
onPressed: (!state.canBeSent() ||
state.amount == null ||
!_commentValidate() ||
!_weHaveBalance(context, state.amount!))
? null
: () async {
try {
await payWithRetry(
context: context,
to: state.contact!,
amount: state.amount!,
comment: state.comment);
} on RetryException {
// Here the transactions can be lost, so we must implement some manual retry use
await payWithRetry(
context: context,
to: state.contact!,
amount: state.amount!,
comment: state.comment,
useMempool: true);
if (state.amount == null || state.amount == 0) {
_feedbackNotifier.value = '';
}
final bool sentDisabled = _onPressed(state, context) == null;
final Color sentColor =
sentDisabled ? Colors.grey : Theme
.of(context)
.primaryColor;
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
const SizedBox(height: 10.0),
G1PayAmountField(key: payAmountKey),
const SizedBox(height: 10.0),
Row(children: <Widget>[
Expanded(
child: TextFormField(
key: _formCommentKey,
inputFormatters: <TextInputFormatter>[
NoNewLineTextInputFormatter()
],
controller: _commentController,
onChanged: (String? value) {
context.read<PaymentCubit>().setComment(value ?? '');
},
decoration: InputDecoration(
labelText: tr('g1_form_pay_desc'),
hintText: tr('g1_form_pay_hint'),
border: const OutlineInputBorder(),
),
validator: (String? value) {
if (value != null &&
!basicEnglishCharsRegExp.hasMatch(value)) {
return tr('valid_comment');
}
return null;
},
style: payBtnStyle,
child: _buildBtn(payBtnText),
)),
const SizedBox(height: 8),
ValueListenableBuilder<String>(
valueListenable: _feedbackNotifier,
builder: (BuildContext context, String value, Widget? child) {
if (value.isNotEmpty) {
return Row(
// Disallow autocomplete
autofillHints: const <String>[],
)),
const SizedBox(width: 5.0),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(Icons.error_outline, color: Colors.red),
const SizedBox(width: 4),
Material(
color: Colors.transparent,
child: IgnorePointer(
ignoring: sentDisabled,
child: IconTheme(
data: const IconThemeData(size: 40.0),
child: IconButton(
key: paySentKey,
tooltip: tr('g1_form_pay_send'),
icon: Icon(
Icons.send,
color: sentColor,
),
onPressed: _onPressed(state, context),
splashRadius: 20,
splashColor: Colors.white.withOpacity(0.5),
highlightColor: Colors.transparent,
),
),
),
),
Text(
capitalize(value),
style: const TextStyle(color: Colors.red),
tr('g1_form_pay_send'),
style: TextStyle(fontSize: 12, color: sentColor),
),
],
);
} else {
return const SizedBox.shrink();
}
},
),
]),
const SizedBox(height: 10.0),
ValueListenableBuilder<String>(
valueListenable: _feedbackNotifier,
builder: (BuildContext context, String value, Widget? child) {
if (value.isNotEmpty) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(Icons.error_outline, color: Colors.red),
const SizedBox(width: 4),
Text(
capitalize(value),
style: const TextStyle(color: Colors.red),
),
],
);
} else {
return const SizedBox.shrink();
}
},
),
],
),
],
),
);
});
);
});
}
Row _buildBtn(Widget payBtnText) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(Icons.send),
const SizedBox(width: 10),
payBtnText,
],
);
Future<void> Function()? _onPressed(PaymentState state,
BuildContext context) {
return (!state.canBeSent() ||
state.amount == null ||
!_commentValidate() ||
!_weHaveBalance(context, state.amount!))
? null
: () async {
try {
await payWithRetry(
context: context,
to: state.contact!,
amount: state.amount!,
comment: state.comment);
} on RetryException {
// Here the transactions can be lost, so we must implement some manual retry use
await payWithRetry(
context: context,
to: state.contact!,
amount: state.amount!,
comment: state.comment,
useMempool: true);
}
};
}
bool _commentValidate() {
final String currentComment = _commentController.value.text;
final bool val = (currentComment != null &&
basicEnglishCharsRegExp.hasMatch(currentComment)) ||
basicEnglishCharsRegExp.hasMatch(currentComment)) ||
currentComment.isEmpty;
logger('Validating comment: $val');
if (_formKey.currentState != null) {
......@@ -188,7 +196,9 @@ class _PayFormState extends State<PayForm> {
}
double getBalance(BuildContext context) =>
context.read<TransactionCubit>().balance;
context
.read<TransactionCubit>()
.balance;
}
class RetryException implements Exception {
......@@ -197,12 +207,12 @@ class RetryException implements Exception {
class NoNewLineTextInputFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
TextEditingValue formatEditUpdate(TextEditingValue oldValue,
TextEditingValue newValue) {
final int cursorPosition = newValue.selection.baseOffset;
final String newText = newValue.text.replaceAll('\n', '');
final TextSelection newSelection =
TextSelection.collapsed(offset: cursorPosition);
TextSelection.collapsed(offset: cursorPosition);
return TextEditingValue(
text: newText,
selection: newSelection,
......
......@@ -1411,6 +1411,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
toggle_switch:
dependency: "direct main"
description:
name: toggle_switch
sha256: "9e6af1f0c5a97d9de41109dc7b9e1b3bbe73417f89b10e0e44dc834fb493d4cb"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
tuple:
dependency: "direct main"
description:
......
......@@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.1.5
version: 0.1.6-SNAPSHOT
environment:
sdk: ">=2.17.1 <3.0.0"
......@@ -87,6 +87,7 @@ dependencies:
connectivity_wrapper: ^1.1.3
rxdart: ^0.27.7
fast_image_resizer: ^0.0.2
toggle_switch: ^2.1.0
dev_dependencies:
flutter_test:
......
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