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

Cesium plus username support (wip)

parent 7a4a41b2
No related branches found
No related tags found
No related merge requests found
......@@ -31,7 +31,7 @@ final String currency = currencyDotEnv.isEmpty ? 'g1' : currencyDotEnv;
Future<String> getTxHistory(String publicKey) async {
final Response response =
await requestWithRetry(NodeType.duniter, '/tx/history/$publicKey');
await requestWithRetry(NodeType.duniter, '/tx/history/$publicKey');
if (response.statusCode == 200) {
return response.body;
} else {
......@@ -59,7 +59,7 @@ Future<Response> searchCPlusUser(String searchTerm) async {
'/user/profile/_search?q=title:$searchTermLower OR issuer:$searchTerm OR title:$searchTermCapitalized OR title:$searchTerm';
final Response response =
await requestCPlusWithRetry(query, retryWith404: false);
await requestCPlusWithRetry(query, retryWith404: false);
return response;
}
......@@ -71,12 +71,12 @@ Future<Contact> getProfile(String pubKeyRaw,
'/user/profile/$pubKey',
retryWith404: false);
final Map<String, dynamic> result =
const JsonDecoder().convert(cPlusResponse.body) as Map<String, dynamic>;
const JsonDecoder().convert(cPlusResponse.body) as Map<String, dynamic>;
if (result['found'] == false) {
return Contact(pubKey: pubKey);
}
final Map<String, dynamic> profile =
const JsonDecoder().convert(cPlusResponse.body) as Map<String, dynamic>;
const JsonDecoder().convert(cPlusResponse.body) as Map<String, dynamic>;
final Contact c = await contactFromResultSearch(profile);
if (!onlyCPlusProfile) {
// This penalize the gva rate limit
......@@ -117,7 +117,7 @@ Future<List<Contact>> searchWot(String searchTermRaw) async {
final List<Contact> contacts = <Contact>[];
if (response.statusCode == HttpStatus.ok) {
final Map<String, dynamic> data =
json.decode(response.body) as Map<String, dynamic>;
json.decode(response.body) as Map<String, dynamic>;
final List<dynamic> results = data['results'] as List<dynamic>;
// logger('Returning wot results ${results.length}');
if (results.isNotEmpty) {
......@@ -144,11 +144,11 @@ Future<Contact> getWot(Contact contact) async {
// Will be better to analyze the 404 response (to detect faulty node)
if (response.statusCode == HttpStatus.ok) {
final Map<String, dynamic> data =
json.decode(response.body) as Map<String, dynamic>;
json.decode(response.body) as Map<String, dynamic>;
final List<dynamic> results = data['results'] as List<dynamic>;
if (results.isNotEmpty) {
final List<dynamic> uids =
(results[0] as Map<String, dynamic>)['uids'] as List<dynamic>;
(results[0] as Map<String, dynamic>)['uids'] as List<dynamic>;
if (uids.isNotEmpty) {
// ignore: avoid_dynamic_calls
return contact.copyWith(nick: uids[0]!['uid'] as String);
......@@ -161,14 +161,14 @@ Future<Contact> getWot(Contact contact) async {
@Deprecated('use getProfile')
Future<String> _getDataImageFromKey(String publicKey) async {
final Response response =
await requestCPlusWithRetry('/user/profile/$publicKey');
await requestCPlusWithRetry('/user/profile/$publicKey');
if (response.statusCode == HttpStatus.ok) {
final Map<String, dynamic> data =
json.decode(response.body) as Map<String, dynamic>;
json.decode(response.body) as Map<String, dynamic>;
final Map<String, dynamic> source = data['_source'] as Map<String, dynamic>;
if (source.containsKey('avatar')) {
final Map<String, dynamic> avatarData =
source['avatar'] as Map<String, dynamic>;
source['avatar'] as Map<String, dynamic>;
if (avatarData.containsKey('_content')) {
final String content = avatarData['_content'] as String;
return 'data:image/png;base64,$content';
......@@ -263,18 +263,16 @@ Future<void> _fetchGvaNodes({bool force = false}) async {
NodeManager().loading = false;
}
int nodesWorking(NodeType type) =>
NodeManager()
.nodeList(type)
.where((Node n) => n.errors < NodeManager.maxNodeErrors)
.toList()
.length;
int nodesWorking(NodeType type) => NodeManager()
.nodeList(type)
.where((Node n) => n.errors < NodeManager.maxNodeErrors)
.toList()
.length;
List<Node> nodesWorkingList(NodeType type) =>
NodeManager()
.nodeList(type)
.where((Node n) => n.errors < NodeManager.maxNodeErrors)
.toList();
List<Node> nodesWorkingList(NodeType type) => NodeManager()
.nodeList(type)
.where((Node n) => n.errors < NodeManager.maxNodeErrors)
.toList();
Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
final List<Node> lNodes = <Node>[];
......@@ -286,14 +284,14 @@ Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
final Response response = await getPeers();
if (response.statusCode == 200) {
final Map<String, dynamic> peerList =
jsonDecode(response.body) as Map<String, dynamic>;
jsonDecode(response.body) as Map<String, dynamic>;
final List<dynamic> peers = (peerList['peers'] as List<dynamic>)
.where((dynamic peer) =>
(peer as Map<String, dynamic>)['currency'] == currency)
(peer as Map<String, dynamic>)['currency'] == currency)
.where(
(dynamic peer) => (peer as Map<String, dynamic>)['version'] == 10)
.where((dynamic peer) =>
(peer as Map<String, dynamic>)['status'] == 'UP')
(peer as Map<String, dynamic>)['status'] == 'UP')
.toList();
// reorder peer list
peers.shuffle();
......@@ -301,7 +299,7 @@ Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
final Map<String, dynamic> peer = peerR as Map<String, dynamic>;
if (peer['endpoints'] != null) {
final List<String> endpoints =
List<String>.from(peer['endpoints'] as List<dynamic>);
List<String>.from(peer['endpoints'] as List<dynamic>);
for (int j = 0; j < endpoints.length; j++) {
if (endpoints[j].startsWith(apyType)) {
final String endpointUnParsed = endpoints[j];
......@@ -313,9 +311,7 @@ Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
final NodeCheck nodeCheck = await _pingNode(endpoint, type);
final Duration latency = nodeCheck.latency;
logger(
'Evaluating node: $endpoint, latency ${latency
.inMicroseconds} currentBlock: ${nodeCheck
.currentBlock}');
'Evaluating node: $endpoint, latency ${latency.inMicroseconds} currentBlock: ${nodeCheck.currentBlock}');
final Node node = Node(
url: endpoint,
latency: latency.inMicroseconds,
......@@ -348,8 +344,7 @@ Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
}
}
logger(
'Fetched ${lNodes.length} ${type.name} nodes ordered by latency ${lNodes
.isNotEmpty ? '(first: ${lNodes.first.url})' : '(zero nodes)'}');
'Fetched ${lNodes.length} ${type.name} nodes ordered by latency ${lNodes.isNotEmpty ? '(first: ${lNodes.first.url})' : '(zero nodes)'}');
} catch (e, stacktrace) {
await Sentry.captureException(e, stackTrace: stacktrace);
logger('General error in fetch ${type.name} nodes: $e');
......@@ -401,8 +396,7 @@ Future<List<Node>> _fetchNodes(NodeType type) async {
}
logger(
'Fetched ${lNodes.length} ${type
.name} nodes ordered by latency (first: ${lNodes.first.url})');
'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');
......@@ -419,8 +413,7 @@ Future<NodeCheck> _pingNode(String node, NodeType type) async {
int currentBlock = 0;
Duration latency;
try {
final Stopwatch stopwatch = Stopwatch()
..start();
final Stopwatch stopwatch = Stopwatch()..start();
if (type == NodeType.duniter) {
final Response response = await http
.get(Uri.parse('$node/blockchain/current'))
......@@ -429,7 +422,7 @@ Future<NodeCheck> _pingNode(String node, NodeType type) async {
latency = stopwatch.elapsed;
if (response.statusCode == 200) {
final Map<String, dynamic> json =
jsonDecode(response.body) as Map<String, dynamic>;
jsonDecode(response.body) as Map<String, dynamic>;
currentBlock = json['number'] as int;
} else {
latency = wrongNodeDuration;
......@@ -438,7 +431,7 @@ Future<NodeCheck> _pingNode(String node, NodeType type) async {
// see: http://g1.data.e-is.pro/network/peering
await http
.get(Uri.parse('$node/network/peering'))
// Decrease http timeout during ping
// Decrease http timeout during ping
.timeout(timeout);
stopwatch.stop();
latency = stopwatch.elapsed;
......@@ -454,8 +447,7 @@ Future<NodeCheck> _pingNode(String node, NodeType type) async {
latency = balance >= 0 ? stopwatch.elapsed : wrongNodeDuration;
}
logger(
'Ping tested in node $node ($type), latency ${latency
.inMicroseconds}, current block $currentBlock');
'Ping tested in node $node ($type), latency ${latency.inMicroseconds}, current block $currentBlock');
return NodeCheck(latency: latency, currentBlock: currentBlock);
} catch (e) {
// Handle exception when node is unavailable etc
......@@ -481,20 +473,22 @@ Future<http.Response> requestCPlusWithRetry(String path,
Future<http.Response> requestGvaWithRetry(String path,
{bool retryWith404 = true,
bool isGet = true,
Map<String, String>? headers,
Object? body,
Encoding? encoding}) async {
HttpType httpType = HttpType.get,
Map<String, String>? headers,
Object? body,
Encoding? encoding}) async {
return _requestWithRetry(NodeType.gva, path, true, retryWith404,
isGet: isGet, headers: headers, body: body, encoding: encoding);
httpType: httpType, headers: headers, body: body, encoding: encoding);
}
Future<http.Response> _requestWithRetry(NodeType type, String path,
bool dontRecord, bool retryWith404,
{bool isGet = true,
Map<String, String>? headers,
Object? body,
Encoding? encoding}) async {
enum HttpType { get, post, delete }
Future<http.Response> _requestWithRetry(
NodeType type, String path, bool dontRecord, bool retryWith404,
{HttpType httpType = HttpType.get,
Map<String, String>? headers,
Object? body,
Encoding? encoding}) async {
final List<Node> nodes = NodeManager()
.nodeList(type)
.where((Node node) => node.errors <= NodeManager.maxNodeErrors)
......@@ -503,8 +497,8 @@ Future<http.Response> _requestWithRetry(NodeType type, String path,
nodes.addAll(type == NodeType.duniter
? defaultDuniterNodes
: type == NodeType.cesiumPlus
? defaultCesiumPlusNodes
: defaultGvaNodes);
? defaultCesiumPlusNodes
: defaultGvaNodes);
}
for (final int timeout in <int>[10]) {
// only one timeout for now
......@@ -513,17 +507,16 @@ Future<http.Response> _requestWithRetry(NodeType type, String path,
try {
final Uri url = Uri.parse('${node.url}$path');
logger('Fetching $url (${type.name})');
final int startTime = DateTime
.now()
.millisecondsSinceEpoch;
final Response response = isGet
final int startTime = DateTime.now().millisecondsSinceEpoch;
final Response response = httpType == HttpType.get
? await http.get(url).timeout(Duration(seconds: timeout))
: await http
.post(url, body: body, headers: headers, encoding: encoding)
.timeout(Duration(seconds: timeout));
final int endTime = DateTime
.now()
.millisecondsSinceEpoch;
: httpType == HttpType.post
? await http
.post(url, body: body, headers: headers, encoding: encoding)
.timeout(Duration(seconds: timeout))
: await http.delete(url,
body: body, headers: headers, encoding: encoding);
final int endTime = DateTime.now().millisecondsSinceEpoch;
final int newLatency = endTime - startTime;
if (!kReleaseMode) {
logger('response.statusCode: ${response.statusCode}');
......@@ -568,10 +561,11 @@ Future<http.Response> _requestWithRetry(NodeType type, String path,
'Cannot make the request to any of the ${nodes.length} nodes');
}
Future<PayResult> pay({required String to,
required double amount,
String? comment,
bool? useMempool}) async {
Future<PayResult> pay(
{required String to,
required double amount,
String? comment,
bool? useMempool}) async {
try {
final SelectedGvaNode selected = getGvaNode();
......@@ -580,8 +574,7 @@ Future<PayResult> pay({required String to,
final Gva gva = Gva(node: nodeUrl);
final CesiumWallet wallet = await SharedPreferencesHelper().getWallet();
logger(
'Trying $nodeUrl to send $amount to $to with comment ${comment ??
''}');
'Trying $nodeUrl to send $amount to $to with comment ${comment ?? ''}');
final String response = await gva.pay(
recipient: extractPublicKey(to),
......@@ -641,9 +634,7 @@ class PayResult {
String proxyfyNode(String nodeUrl) {
final String url = inProduction && kIsWeb
? '${window.location.protocol}//${window.location
.hostname}/proxy/${nodeUrl.replaceFirst('https://', '').replaceFirst(
'http://', '')}/'
? '${window.location.protocol}//${window.location.hostname}/proxy/${nodeUrl.replaceFirst('https://', '').replaceFirst('http://', '')}/'
: nodeUrl;
return url;
}
......@@ -651,7 +642,7 @@ String proxyfyNode(String nodeUrl) {
Future<Tuple2<Map<String, dynamic>?, Node>> gvaHistoryAndBalance(
String pubKeyRaw,
[int? pageSize,
String? cursor]) async {
String? cursor]) async {
logger('Get tx history (page size: $pageSize: cursor $cursor)');
final String pubKey = extractPublicKey(pubKeyRaw);
return gvaFunctionWrapper<Map<String, dynamic>>(
......@@ -668,8 +659,8 @@ Future<Tuple2<String?, Node>> gvaNick(String pubKey) async {
pubKey, (Gva gva) => gva.getUsername(extractPublicKey(pubKey)));
}
Future<Tuple2<T?, Node>> gvaFunctionWrapper<T>(String pubKey,
Future<T?> Function(Gva) specificFunction) async {
Future<Tuple2<T?, Node>> gvaFunctionWrapper<T>(
String pubKey, Future<T?> Function(Gva) specificFunction) async {
final List<Node> nodes = _getBestGvaNodes();
for (int i = 0; i < nodes.length; i++) {
final Node node = nodes[i];
......@@ -700,8 +691,8 @@ List<Node> _getBestGvaNodes() {
.toList();
final int maxCurrentBlock = fnodes.fold(
0,
(int max, Node node) =>
node.currentBlock > max ? node.currentBlock : max);
(int max, Node node) =>
node.currentBlock > max ? node.currentBlock : max);
final List<Node> nodes = fnodes
.where((Node node) => node.currentBlock == maxCurrentBlock)
.toList();
......@@ -729,24 +720,25 @@ void increaseNodeErrors(NodeType type, Node node) {
// http://doc.e-is.pro/cesium-plus-pod/REST_API.html#userprofile
// https://git.p2p.legal/axiom-team/jaklis/src/branch/master/lib/cesiumCommon.py
// Get an profile, by public key: user/profile/<pubkey>
// Add a new profile: user/profile (POST)
// Update an existing profile: user/profile/_update (POST)
// Delete an existing profile: user/profile/_delete (DELETE?)
Future<void> createOrUpdateCesiumPlusUser(String name) async {
final CesiumWallet wallet = await SharedPreferencesHelper().getWallet();
// Define the endpoints
final String pubKey = wallet.pubkey;
// Make the HTTP GET request to check if the user exists
final http.Response getResponse = await _requestWithRetry(
NodeType.cesiumPlus, '/api/user/profile/$pubKey', false, false);
// Check if the user exists
final String? userName = await getCesiumPlusUser(pubKey);
// Prepare the user profile data
final Map<String, dynamic> userProfile = <String, dynamic>{
'version': 2,
'issuer': pubKey,
'title': name + userNameSuffix,
'time': DateTime
.now()
.millisecondsSinceEpoch ~/
'time': DateTime.now().millisecondsSinceEpoch ~/
1000, // current time in seconds
'tags': <String>[],
};
final String userProfileJson = jsonEncode(userProfile);
......@@ -762,25 +754,25 @@ Future<void> createOrUpdateCesiumPlusUser(String name) async {
'Content-Type': 'application/json',
};
if (getResponse.statusCode == 200) {
if (userName != null) {
// User exists, update the user profile
final http.Response updateResponse = await _requestWithRetry(
NodeType.cesiumPlus, '/api/user/profile/_update', false, false,
isGet: false,
NodeType.cesiumPlus, '/user/profile/_update', false, true,
httpType: HttpType.post,
headers: headers,
body: userProfileJsonWithHashAndSignature);
if (updateResponse.statusCode == 200) {
logger('User profile updated successfully.');
} else {
logger(
'Failed to update user profile. Status code: ${updateResponse
.statusCode}');
'Failed to update user profile. Status code: ${updateResponse.statusCode}');
logger('Response body: ${updateResponse.body}');
}
} else if (getResponse.statusCode == 404) {
} else if (userName == null) {
// User does not exist, create a new user profile
final http.Response createResponse = await _requestWithRetry(
NodeType.cesiumPlus, '/api/user/profile', false, false,
isGet: false,
NodeType.cesiumPlus, '/user/profile', false, false,
httpType: HttpType.post,
headers: headers,
body: userProfileJsonWithHashAndSignature);
......@@ -788,16 +780,39 @@ Future<void> createOrUpdateCesiumPlusUser(String name) async {
logger('User profile created successfully.');
} else {
logger(
'Failed to create user profile. Status code: ${createResponse
.statusCode}');
'Failed to create user profile. Status code: ${createResponse.statusCode}');
}
} else {
logger(
'Failed to check if user exists. Status code: ${getResponse
.statusCode}');
}
}
Future<String?> getCesiumPlusUser(String pubKey) async {
final Contact c = await getProfile(pubKey, true);
return c.name;
}
Future<bool> deleteCesiumPlusUser() async {
final CesiumWallet wallet = await SharedPreferencesHelper().getWallet();
final String pubKey = wallet.pubkey;
final Map<String, dynamic> userProfile = <String, dynamic>{
'version': 2,
'id': pubKey,
'issuer': pubKey,
'index': 'user',
'type': 'profile',
'time': DateTime.now().millisecondsSinceEpoch ~/
1000, // current time in seconds
};
final String userProfileJson = jsonEncode(userProfile);
final String signature = wallet.sign(userProfileJson);
userProfile['hash'] = calculateHash(userProfileJson);
userProfile['signature'] = signature;
final http.Response delResponse = await _requestWithRetry(
NodeType.cesiumPlus, '/user/profile/_delete', false, false,
httpType: HttpType.post);
return delResponse.statusCode == 200;
}
String calculateHash(String input) {
final List<int> bytes = utf8.encode(input); // data being hashed
......
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import '../../../g1/api.dart';
import '../../../shared_prefs.dart';
import '../../logger.dart';
import '../../ui_helpers.dart';
......@@ -15,22 +16,48 @@ class CardNameEditable extends StatefulWidget {
class _CardNameEditableState extends State<CardNameEditable> {
bool _isEditingText = false;
late TextEditingController _editingController;
final TextEditingController _editingController = TextEditingController();
late String currentText;
late String defValue;
late String name;
// late FocusNode myFocusNode;
final String pubKey = SharedPreferencesHelper().getPubKey();
String _previousValue = '';
@override
void initState() {
super.initState();
defValue = tr('your_name_here');
name = SharedPreferencesHelper().getName();
currentText = _currentTextOrDef();
_editingController = TextEditingController(text: currentText);
currentText = defValue;
// myFocusNode = FocusNode();
_initValue();
}
String _currentTextOrDef() =>
(name == tr('g1_wallet') || name.isEmpty) ? defValue : name;
Future<void> _initValue() async {
final String localUsername = SharedPreferencesHelper().getName();
try {
final String? name = await getCesiumPlusUser(pubKey);
logger(
'currentText: $currentText, localUsername: $localUsername, _previousValue: $_previousValue, retrieved_name: $name');
if (name != null) {
_editingController.text = name;
currentText = name;
SharedPreferencesHelper().setName(name: name);
} else {
_editingController.text = '';
currentText = defValue;
SharedPreferencesHelper().setName(name: '');
}
} catch (e) {
logger(e);
_editingController.text = localUsername;
currentText = localUsername;
}
_previousValue = _editingController.text;
logger(
'currentText: $currentText, localUsername: $localUsername, _previousValue: $_previousValue');
}
@override
void dispose() {
......@@ -40,13 +67,14 @@ class _CardNameEditableState extends State<CardNameEditable> {
@override
Widget build(BuildContext context) {
final bool isSetted = currentText.isNotEmpty && currentText != defValue;
final bool isSet = currentText.isNotEmpty && currentText != defValue;
return _isEditingText
? SizedBox(
width: 150.0,
child: SizedBox(
height: 40.0,
child: TextField(
// focusNode: myFocusNode,
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(
vertical: 5.0, horizontal: 7.0),
......@@ -58,17 +86,42 @@ class _CardNameEditableState extends State<CardNameEditable> {
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(width: 2.0),
),
suffixIcon: IconButton(
icon: const Icon(Icons.check),
onPressed: () {
updateName(_editingController.text);
},
suffixIcon: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
_isEditingText = false;
});
},
child: const Padding(
padding: EdgeInsets.only(right: 8.0),
child: Icon(Icons.cancel_outlined),
),
),
GestureDetector(
onTap: () {
_updateValue(_editingController.text);
},
child: const Padding(
padding: EdgeInsets.only(right: 8.0),
child: Icon(Icons.check),
),
),
],
),
),
cursorColor: Colors.black,
onSubmitted: (String newValue) {
updateName(newValue);
onSubmitted: _updateValue,
onChanged: (String value) {
if (value.isEmpty) {
_deleteValue();
}
},
/*onSubmitted: (String newValue) {
updateName(newValue);
}, */
// maxLength: 15,
autofocus: true,
controller: _editingController,
......@@ -88,17 +141,17 @@ class _CardNameEditableState extends State<CardNameEditable> {
text: currentText.toUpperCase(),
style: const TextStyle(
fontFamily: 'SourceCodePro', color: Colors.grey)),
if (isSetted)
if (isSet)
TextSpan(
text: currentText, style: cardTextStyle(context, 15)),
/* TextSpan(
text: ' Lorem ipsum dolor sit amet, consectetur adipiscing elit',
style: cardTextStyle(context, 15),
), */
if (isSetted)
if (isSet)
TextSpan(
text: userNameSuffix,
style: cardTextStyle(context, 10),
style: cardTextStyle(context, 12),
),
],
),
......@@ -106,8 +159,8 @@ class _CardNameEditableState extends State<CardNameEditable> {
onTap: () {
setState(() {
try {
_editingController.selection = TextSelection(
baseOffset: 0, extentOffset: currentText.length);
/* _editingController.selection = TextSelection(
baseOffset: 0, extentOffset: currentText.length);*/
} catch (e) {
logger(e);
}
......@@ -117,6 +170,7 @@ class _CardNameEditableState extends State<CardNameEditable> {
));
}
/*
void updateName(String newValue) {
setState(() {
if (newValue.isEmpty) {
......@@ -137,5 +191,60 @@ class _CardNameEditableState extends State<CardNameEditable> {
}
_isEditingText = false;
});
}*/
Future<void> _updateValue(String newValue) async {
logger('updating with newValue: $newValue');
try {
if (_validate(newValue)) {
await createOrUpdateCesiumPlusUser(newValue);
setState(() {
_previousValue = newValue;
});
} else {
setState(() {
_editingController.text = _previousValue;
currentText = _previousValue.isEmpty ? defValue : _previousValue;
});
}
} catch (e) {
setState(() {
_editingController.text = _previousValue;
currentText = _previousValue.isEmpty ? defValue : _previousValue;
});
}
setState(() {
_isEditingText = false;
});
logger(
'currentText: $currentText, newValue: $newValue, _previousValue: $_previousValue');
}
Future<void> _deleteValue() async {
try {
await deleteCesiumPlusUser();
SharedPreferencesHelper().setName(name: '');
setState(() {
_editingController.text = '';
currentText = defValue;
});
} catch (e) {
setState(() {
_editingController.text = _previousValue;
currentText = _previousValue.isEmpty ? defValue : _previousValue;
});
}
setState(() {
_isEditingText = false;
});
logger(
'delete with currentText: $currentText, _previousValue: $_previousValue');
}
bool _validate(String newValue) {
if (newValue.isEmpty) {
return false;
}
return true;
}
}
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