mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-19 10:41:02 +00:00
Merge remote-tracking branch 'origin/staging' into tor
This commit is contained in:
commit
e9dd911fbb
84 changed files with 11860 additions and 3290 deletions
Binary file not shown.
Binary file not shown.
83
assets/svg/anonymize.svg
Normal file
83
assets/svg/anonymize.svg
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
version="1.1"
|
||||||
|
id="svg18"
|
||||||
|
sodipodi:docname="tx-icon-anonymize.svg"
|
||||||
|
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||||
|
<metadata
|
||||||
|
id="metadata22">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="2262"
|
||||||
|
inkscape:window-height="1263"
|
||||||
|
id="namedview20"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="9.8333333"
|
||||||
|
inkscape:cx="-15.813559"
|
||||||
|
inkscape:cy="12"
|
||||||
|
inkscape:window-x="1019"
|
||||||
|
inkscape:window-y="569"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="g8" />
|
||||||
|
<g
|
||||||
|
clip-path="url(#clip0_5035_57299)"
|
||||||
|
id="g8">
|
||||||
|
<g
|
||||||
|
clip-path="url(#clip1_5035_57299)"
|
||||||
|
id="g6"
|
||||||
|
transform="matrix(1.6610229,0,0,1.7055051,-7.9831634,-8.2054918)">
|
||||||
|
<path
|
||||||
|
d="m 9.92728,11.6469 c 0.13122,0.5688 -0.49218,1.0145 -0.9871,0.7028 L 8.27572,11.934 6.88393,14.1606 c -0.3645,0.583 0.05479,1.3393 0.74238,1.3393 h 0.87554 c 0.48235,0 0.87446,0.3916 0.87446,0.8739 0,0.4824 -0.39211,0.8764 -0.87446,0.8764 H 7.62904 c -2.06062,0 -3.31679,-2.2651 -2.22769,-4.0141 L 6.79123,11.0098 6.12514,10.5915 C 5.62994,10.2797 5.75627,9.52505 6.32557,9.3938 L 8.8256,8.81548 C 9.06049,8.7649 9.29838,8.90709 9.35307,9.14498 Z M 12.741,7.15873 13.8689,8.96724 13.2058,9.37959 c -0.4966,0.30925 -0.3711,1.06481 0.199,1.19681 l 2.4992,0.5783 c 0.2358,0.0546 0.4712,-0.0926 0.5253,-0.3284 L 17.0046,8.32631 C 17.1356,7.75728 16.5138,7.31322 16.0183,7.62193 L 15.3522,8.03755 14.2257,6.23396 C 13.1981,4.58951 10.8023,4.58841 9.77416,6.23227 L 9.57182,6.55552 C 9.31752,6.96158 9.4433,7.50381 9.84799,7.75865 10.256,8.01456 10.7987,7.89225 11.0541,7.48412 L 11.2576,7.159 c 0.3486,-0.55754 1.1498,-0.53703 1.4834,-2.7e-4 z m 5.857,6.07957 -0.4646,-0.7454 c -0.2553,-0.4096 -0.7946,-0.5348 -1.2042,-0.2791 -0.4085,0.2548 -0.5338,0.797 -0.2784,1.2053 l 0.4646,0.7424 c 0.365,0.5829 -0.0542,1.3398 -0.7421,1.3398 h -2.6247 l 6e-4,-0.7859 c 0,-0.5846 -0.7068,-0.8774 -1.1203,-0.464 l -1.8159,1.8165 c -0.1701,0.1701 -0.1701,0.4487 2e-4,0.6188 l 1.8161,1.8139 c 0.4135,0.4129 1.1198,0.12 1.1198,-0.4643 l -7e-4,-0.7842 h 2.6212 c 2.0616,3e-4 3.3194,-2.2665 2.2284,-4.0138 z"
|
||||||
|
id="path4"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#494949" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs
|
||||||
|
id="defs16">
|
||||||
|
<clipPath
|
||||||
|
id="clip0_5035_57299">
|
||||||
|
<rect
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
fill="white"
|
||||||
|
id="rect10" />
|
||||||
|
</clipPath>
|
||||||
|
<clipPath
|
||||||
|
id="clip1_5035_57299">
|
||||||
|
<rect
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
fill="white"
|
||||||
|
transform="translate(5 5)"
|
||||||
|
id="rect13" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.5 KiB |
|
@ -1 +1 @@
|
||||||
Subproject commit 81659ce57952c5ab54ffe6bacfbf43da159fff3e
|
Subproject commit 73d257ed2fe5b204cf3589822e226301b187b86d
|
|
@ -4,43 +4,23 @@ import 'package:stackwallet/db/hive/db.dart';
|
||||||
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/utilities/prefs.dart';
|
|
||||||
import 'package:string_validator/string_validator.dart';
|
import 'package:string_validator/string_validator.dart';
|
||||||
|
|
||||||
class CachedElectrumX {
|
class CachedElectrumX {
|
||||||
final ElectrumX? electrumXClient;
|
final ElectrumX electrumXClient;
|
||||||
|
|
||||||
final String server;
|
|
||||||
final int port;
|
|
||||||
final bool useSSL;
|
|
||||||
|
|
||||||
final Prefs prefs;
|
|
||||||
final List<ElectrumXNode> failovers;
|
|
||||||
|
|
||||||
static const minCacheConfirms = 30;
|
static const minCacheConfirms = 30;
|
||||||
|
|
||||||
const CachedElectrumX({
|
const CachedElectrumX({
|
||||||
required this.server,
|
required this.electrumXClient,
|
||||||
required this.port,
|
|
||||||
required this.useSSL,
|
|
||||||
required this.prefs,
|
|
||||||
required this.failovers,
|
|
||||||
this.electrumXClient,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
factory CachedElectrumX.from({
|
factory CachedElectrumX.from({
|
||||||
required ElectrumXNode node,
|
required ElectrumX electrumXClient,
|
||||||
required Prefs prefs,
|
|
||||||
required List<ElectrumXNode> failovers,
|
|
||||||
ElectrumX? electrumXClient,
|
|
||||||
}) =>
|
}) =>
|
||||||
CachedElectrumX(
|
CachedElectrumX(
|
||||||
server: node.address,
|
electrumXClient: electrumXClient,
|
||||||
port: node.port,
|
);
|
||||||
useSSL: node.useSSL,
|
|
||||||
prefs: prefs,
|
|
||||||
failovers: failovers,
|
|
||||||
electrumXClient: electrumXClient);
|
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getAnonymitySet({
|
Future<Map<String, dynamic>> getAnonymitySet({
|
||||||
required String groupId,
|
required String groupId,
|
||||||
|
@ -66,16 +46,7 @@ class CachedElectrumX {
|
||||||
set = Map<String, dynamic>.from(cachedSet);
|
set = Map<String, dynamic>.from(cachedSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
final client = electrumXClient ??
|
final newSet = await electrumXClient.getAnonymitySet(
|
||||||
ElectrumX(
|
|
||||||
host: server,
|
|
||||||
port: port,
|
|
||||||
useSSL: useSSL,
|
|
||||||
prefs: prefs,
|
|
||||||
failovers: failovers,
|
|
||||||
);
|
|
||||||
|
|
||||||
final newSet = await client.getAnonymitySet(
|
|
||||||
groupId: groupId,
|
groupId: groupId,
|
||||||
blockhash: set["blockHash"] as String,
|
blockhash: set["blockHash"] as String,
|
||||||
);
|
);
|
||||||
|
@ -115,8 +86,9 @@ class CachedElectrumX {
|
||||||
key: groupId,
|
key: groupId,
|
||||||
value: set);
|
value: set);
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
"Updated currently anonymity set for ${coin.name} with group ID $groupId",
|
"Updated current anonymity set for ${coin.name} with group ID $groupId",
|
||||||
level: LogLevel.Info);
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return set;
|
return set;
|
||||||
|
@ -151,16 +123,8 @@ class CachedElectrumX {
|
||||||
final cachedTx = DB.instance.get<dynamic>(
|
final cachedTx = DB.instance.get<dynamic>(
|
||||||
boxName: DB.instance.boxNameTxCache(coin: coin), key: txHash) as Map?;
|
boxName: DB.instance.boxNameTxCache(coin: coin), key: txHash) as Map?;
|
||||||
if (cachedTx == null) {
|
if (cachedTx == null) {
|
||||||
final client = electrumXClient ??
|
final Map<String, dynamic> result = await electrumXClient
|
||||||
ElectrumX(
|
.getTransaction(txHash: txHash, verbose: verbose);
|
||||||
host: server,
|
|
||||||
port: port,
|
|
||||||
useSSL: useSSL,
|
|
||||||
prefs: prefs,
|
|
||||||
failovers: failovers,
|
|
||||||
);
|
|
||||||
final Map<String, dynamic> result =
|
|
||||||
await client.getTransaction(txHash: txHash, verbose: verbose);
|
|
||||||
|
|
||||||
result.remove("hex");
|
result.remove("hex");
|
||||||
result.remove("lelantusData");
|
result.remove("lelantusData");
|
||||||
|
@ -187,31 +151,25 @@ class CachedElectrumX {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<dynamic>> getUsedCoinSerials({
|
Future<List<String>> getUsedCoinSerials({
|
||||||
required Coin coin,
|
required Coin coin,
|
||||||
int startNumber = 0,
|
int startNumber = 0,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
List<dynamic>? cachedSerials = DB.instance.get<dynamic>(
|
final _list = DB.instance.get<dynamic>(
|
||||||
boxName: DB.instance.boxNameUsedSerialsCache(coin: coin),
|
boxName: DB.instance.boxNameUsedSerialsCache(coin: coin),
|
||||||
key: "serials") as List?;
|
key: "serials") as List?;
|
||||||
|
|
||||||
cachedSerials ??= [];
|
List<String> cachedSerials =
|
||||||
|
_list == null ? [] : List<String>.from(_list);
|
||||||
|
|
||||||
final startNumber = cachedSerials.length;
|
final startNumber = cachedSerials.length;
|
||||||
|
|
||||||
final client = electrumXClient ??
|
final serials =
|
||||||
ElectrumX(
|
await electrumXClient.getUsedCoinSerials(startNumber: startNumber);
|
||||||
host: server,
|
List<String> newSerials = [];
|
||||||
port: port,
|
|
||||||
useSSL: useSSL,
|
|
||||||
prefs: prefs,
|
|
||||||
failovers: failovers,
|
|
||||||
);
|
|
||||||
|
|
||||||
final serials = await client.getUsedCoinSerials(startNumber: startNumber);
|
for (final element in (serials["serials"] as List)) {
|
||||||
List newSerials = [];
|
|
||||||
for (var element in (serials["serials"] as List)) {
|
|
||||||
if (!isHexadecimal(element as String)) {
|
if (!isHexadecimal(element as String)) {
|
||||||
newSerials.add(base64ToHex(element));
|
newSerials.add(base64ToHex(element));
|
||||||
} else {
|
} else {
|
||||||
|
@ -221,9 +179,10 @@ class CachedElectrumX {
|
||||||
cachedSerials.addAll(newSerials);
|
cachedSerials.addAll(newSerials);
|
||||||
|
|
||||||
await DB.instance.put<dynamic>(
|
await DB.instance.put<dynamic>(
|
||||||
boxName: DB.instance.boxNameUsedSerialsCache(coin: coin),
|
boxName: DB.instance.boxNameUsedSerialsCache(coin: coin),
|
||||||
key: "serials",
|
key: "serials",
|
||||||
value: cachedSerials);
|
value: cachedSerials,
|
||||||
|
);
|
||||||
|
|
||||||
return cachedSerials;
|
return cachedSerials;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
|
|
@ -132,6 +132,11 @@ class ElectrumX {
|
||||||
|
|
||||||
final response = await _rpcClient!.request(jsonRequestString);
|
final response = await _rpcClient!.request(jsonRequestString);
|
||||||
|
|
||||||
|
print("=================================================");
|
||||||
|
print("TYPE: ${response.runtimeType}");
|
||||||
|
print("RESPONSE: $response");
|
||||||
|
print("=================================================");
|
||||||
|
|
||||||
if (response["error"] != null) {
|
if (response["error"] != null) {
|
||||||
if (response["error"]
|
if (response["error"]
|
||||||
.toString()
|
.toString()
|
||||||
|
@ -310,6 +315,13 @@ class ElectrumX {
|
||||||
requestID: requestID,
|
requestID: requestID,
|
||||||
command: 'blockchain.headers.subscribe',
|
command: 'blockchain.headers.subscribe',
|
||||||
);
|
);
|
||||||
|
if (response["result"] == null) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"getBlockHeadTip returned null response",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
throw 'getBlockHeadTip returned null response';
|
||||||
|
}
|
||||||
return Map<String, dynamic>.from(response["result"] as Map);
|
return Map<String, dynamic>.from(response["result"] as Map);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:mutex/mutex.dart';
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
|
|
||||||
// hacky fix to receive large jsonrpc responses
|
// hacky fix to receive large jsonrpc responses
|
||||||
|
@ -12,65 +14,219 @@ class JsonRPC {
|
||||||
this.useSSL = false,
|
this.useSSL = false,
|
||||||
this.connectionTimeout = const Duration(seconds: 60),
|
this.connectionTimeout = const Duration(seconds: 60),
|
||||||
});
|
});
|
||||||
bool useSSL;
|
final bool useSSL;
|
||||||
String host;
|
final String host;
|
||||||
int port;
|
final int port;
|
||||||
Duration connectionTimeout;
|
final Duration connectionTimeout;
|
||||||
|
|
||||||
Future<dynamic> request(String jsonRpcRequest) async {
|
final _requestMutex = Mutex();
|
||||||
Socket? socket;
|
final _JsonRPCRequestQueue _requestQueue = _JsonRPCRequestQueue();
|
||||||
final completer = Completer<dynamic>();
|
Socket? _socket;
|
||||||
final List<int> responseData = [];
|
StreamSubscription<Uint8List>? _subscription;
|
||||||
|
|
||||||
void dataHandler(List<int> data) {
|
void _dataHandler(List<int> data) {
|
||||||
responseData.addAll(data);
|
if (_requestQueue.isEmpty) {
|
||||||
|
// probably just return although this case should never actually hit
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 0x0A is newline
|
final req = _requestQueue.next;
|
||||||
// https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-basics.html
|
req.appendDataAndCheckIfComplete(data);
|
||||||
if (data.last == 0x0A) {
|
|
||||||
try {
|
if (req.isComplete) {
|
||||||
final response = json.decode(String.fromCharCodes(responseData));
|
_onReqCompleted(req);
|
||||||
completer.complete(response);
|
}
|
||||||
} catch (e, s) {
|
}
|
||||||
Logging.instance
|
|
||||||
.log("JsonRPC json.decode: $e\n$s", level: LogLevel.Error);
|
void _errorHandler(Object error, StackTrace trace) {
|
||||||
completer.completeError(e, s);
|
Logging.instance.log(
|
||||||
} finally {
|
"JsonRPC errorHandler: $error\n$trace",
|
||||||
socket?.destroy();
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
|
||||||
|
final req = _requestQueue.next;
|
||||||
|
req.completer.completeError(error, trace);
|
||||||
|
_onReqCompleted(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _doneHandler() {
|
||||||
|
Logging.instance.log(
|
||||||
|
"JsonRPC doneHandler: "
|
||||||
|
"connection closed to $host:$port, destroying socket",
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (_requestQueue.isNotEmpty) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"JsonRPC doneHandler: queue not empty but connection closed, "
|
||||||
|
"completing pending requests with errors",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (final req in _requestQueue.queue) {
|
||||||
|
if (!req.isComplete) {
|
||||||
|
try {
|
||||||
|
throw Exception(
|
||||||
|
"JsonRPC doneHandler: socket closed "
|
||||||
|
"before request could complete",
|
||||||
|
);
|
||||||
|
} catch (e, s) {
|
||||||
|
req.completer.completeError(e, s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_requestQueue.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void errorHandler(Object error, StackTrace trace) {
|
disconnect();
|
||||||
Logging.instance
|
}
|
||||||
.log("JsonRPC errorHandler: $error\n$trace", level: LogLevel.Error);
|
|
||||||
completer.completeError(error, trace);
|
void _onReqCompleted(_JsonRPCRequest req) {
|
||||||
socket?.destroy();
|
_requestQueue.remove(req);
|
||||||
|
if (_requestQueue.isNotEmpty) {
|
||||||
|
_sendNextAvailableRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _sendNextAvailableRequest() {
|
||||||
|
if (_requestQueue.isEmpty) {
|
||||||
|
// TODO handle properly
|
||||||
|
throw Exception("JSON RPC queue empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
void doneHandler() {
|
final req = _requestQueue.next;
|
||||||
socket?.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useSSL) {
|
_socket!.write('${req.jsonRequest}\r\n');
|
||||||
await SecureSocket.connect(host, port,
|
|
||||||
timeout: connectionTimeout,
|
req.initiateTimeout(const Duration(seconds: 10));
|
||||||
onBadCertificate: (_) => true).then((Socket sock) {
|
// Logging.instance.log(
|
||||||
socket = sock;
|
// "JsonRPC request: wrote request ${req.jsonRequest} "
|
||||||
socket?.listen(dataHandler,
|
// "to socket $host:$port",
|
||||||
onError: errorHandler, onDone: doneHandler, cancelOnError: true);
|
// level: LogLevel.Info,
|
||||||
});
|
// );
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> request(String jsonRpcRequest) async {
|
||||||
|
// todo: handle this better?
|
||||||
|
// Do we need to check the subscription, too?
|
||||||
|
await _requestMutex.protect(() async {
|
||||||
|
if (_socket == null) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"JsonRPC request: opening socket $host:$port",
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
await connect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final req = _JsonRPCRequest(
|
||||||
|
jsonRequest: jsonRpcRequest,
|
||||||
|
completer: Completer<dynamic>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
_requestQueue.add(req);
|
||||||
|
|
||||||
|
// if this is the only/first request then send it right away
|
||||||
|
if (_requestQueue.length == 1) {
|
||||||
|
_sendNextAvailableRequest();
|
||||||
} else {
|
} else {
|
||||||
await Socket.connect(host, port, timeout: connectionTimeout)
|
// Logging.instance.log(
|
||||||
.then((Socket sock) {
|
// "JsonRPC request: queued request $jsonRpcRequest "
|
||||||
socket = sock;
|
// "to socket $host:$port",
|
||||||
socket?.listen(dataHandler,
|
// level: LogLevel.Info,
|
||||||
onError: errorHandler, onDone: doneHandler, cancelOnError: true);
|
// );
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
socket?.write('$jsonRpcRequest\r\n');
|
return req.completer.future.onError(
|
||||||
|
(error, stackTrace) =>
|
||||||
|
Exception("return req.completer.future.onError: $error"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return completer.future;
|
void disconnect() {
|
||||||
|
// TODO: maybe clear req queue here and wrap in mutex?
|
||||||
|
_subscription?.cancel().then((_) => _subscription = null);
|
||||||
|
_socket?.destroy();
|
||||||
|
_socket = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> connect() async {
|
||||||
|
if (useSSL) {
|
||||||
|
_socket ??= await SecureSocket.connect(
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
timeout: connectionTimeout,
|
||||||
|
onBadCertificate: (_) => true,
|
||||||
|
); // TODO do not automatically trust bad certificates
|
||||||
|
} else {
|
||||||
|
_socket ??= await Socket.connect(
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
timeout: connectionTimeout,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await _subscription?.cancel();
|
||||||
|
_subscription = _socket!.listen(
|
||||||
|
_dataHandler,
|
||||||
|
onError: _errorHandler,
|
||||||
|
onDone: _doneHandler,
|
||||||
|
cancelOnError: true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _JsonRPCRequestQueue {
|
||||||
|
final List<_JsonRPCRequest> _rq = [];
|
||||||
|
|
||||||
|
void add(_JsonRPCRequest req) => _rq.add(req);
|
||||||
|
|
||||||
|
bool remove(_JsonRPCRequest req) => _rq.remove(req);
|
||||||
|
|
||||||
|
void clear() => _rq.clear();
|
||||||
|
|
||||||
|
bool get isEmpty => _rq.isEmpty;
|
||||||
|
bool get isNotEmpty => _rq.isNotEmpty;
|
||||||
|
int get length => _rq.length;
|
||||||
|
_JsonRPCRequest get next => _rq.first;
|
||||||
|
List<_JsonRPCRequest> get queue => _rq.toList(growable: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _JsonRPCRequest {
|
||||||
|
final String jsonRequest;
|
||||||
|
final Completer<dynamic> completer;
|
||||||
|
final List<int> _responseData = [];
|
||||||
|
|
||||||
|
_JsonRPCRequest({required this.jsonRequest, required this.completer});
|
||||||
|
|
||||||
|
void appendDataAndCheckIfComplete(List<int> data) {
|
||||||
|
_responseData.addAll(data);
|
||||||
|
// 0x0A is newline
|
||||||
|
// https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-basics.html
|
||||||
|
if (data.last == 0x0A) {
|
||||||
|
try {
|
||||||
|
final response = json.decode(String.fromCharCodes(_responseData));
|
||||||
|
completer.complete(response);
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"JsonRPC json.decode: $e\n$s",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
completer.completeError(e, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void initiateTimeout(Duration timeout) {
|
||||||
|
Future<void>.delayed(timeout).then((_) {
|
||||||
|
if (!isComplete) {
|
||||||
|
try {
|
||||||
|
throw Exception("_JsonRPCRequest timed out: $jsonRequest");
|
||||||
|
} catch (e, s) {
|
||||||
|
completer.completeError(e, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isComplete => completer.isCompleted;
|
||||||
|
}
|
||||||
|
|
261
lib/electrumx_rpc/rpc2.dart
Normal file
261
lib/electrumx_rpc/rpc2.dart
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:mutex/mutex.dart';
|
||||||
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
|
|
||||||
|
// hacky fix to receive large jsonrpc responses
|
||||||
|
class JsonRPC {
|
||||||
|
JsonRPC({
|
||||||
|
required this.host,
|
||||||
|
required this.port,
|
||||||
|
this.useSSL = false,
|
||||||
|
this.connectionTimeout = const Duration(seconds: 60),
|
||||||
|
});
|
||||||
|
final bool useSSL;
|
||||||
|
final String host;
|
||||||
|
final int port;
|
||||||
|
final Duration connectionTimeout;
|
||||||
|
|
||||||
|
final _requestMutex = Mutex();
|
||||||
|
final _JsonRPCRequestQueue _requestQueue = _JsonRPCRequestQueue();
|
||||||
|
Socket? _socket;
|
||||||
|
StreamSubscription<Uint8List>? _subscription;
|
||||||
|
|
||||||
|
void _dataHandler(List<int> data) {
|
||||||
|
_requestQueue.nextIncompleteReq.then((req) {
|
||||||
|
if (req != null) {
|
||||||
|
req.appendDataAndCheckIfComplete(data);
|
||||||
|
|
||||||
|
if (req.isComplete) {
|
||||||
|
_onReqCompleted(req);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logging.instance.log(
|
||||||
|
"_dataHandler found a null req!",
|
||||||
|
level: LogLevel.Warning,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _errorHandler(Object error, StackTrace trace) {
|
||||||
|
_requestQueue.nextIncompleteReq.then((req) {
|
||||||
|
if (req != null) {
|
||||||
|
req.completer.completeError(error, trace);
|
||||||
|
_onReqCompleted(req);
|
||||||
|
} else {
|
||||||
|
Logging.instance.log(
|
||||||
|
"_errorHandler found a null req!",
|
||||||
|
level: LogLevel.Warning,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _doneHandler() {
|
||||||
|
Logging.instance.log(
|
||||||
|
"JsonRPC doneHandler: "
|
||||||
|
"connection closed to $host:$port, destroying socket",
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
|
||||||
|
disconnect(reason: "JsonRPC _doneHandler() called");
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onReqCompleted(_JsonRPCRequest req) {
|
||||||
|
_requestQueue.remove(req).then((value) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("Request removed from queue: $value");
|
||||||
|
}
|
||||||
|
// attempt to send next request
|
||||||
|
_sendNextAvailableRequest();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _sendNextAvailableRequest() {
|
||||||
|
_requestQueue.nextIncompleteReq.then((req) {
|
||||||
|
if (req != null) {
|
||||||
|
// \r\n required by electrumx server
|
||||||
|
_socket!.write('${req.jsonRequest}\r\n');
|
||||||
|
|
||||||
|
// TODO different timeout length?
|
||||||
|
req.initiateTimeout(const Duration(seconds: 10));
|
||||||
|
} else {
|
||||||
|
Logging.instance.log(
|
||||||
|
"_sendNextAvailableRequest found a null req!",
|
||||||
|
level: LogLevel.Warning,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: non dynamic type
|
||||||
|
Future<dynamic> request(String jsonRpcRequest) async {
|
||||||
|
await _requestMutex.protect(() async {
|
||||||
|
if (_socket == null) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"JsonRPC request: opening socket $host:$port",
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
await connect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final req = _JsonRPCRequest(
|
||||||
|
jsonRequest: jsonRpcRequest,
|
||||||
|
completer: Completer<dynamic>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// if this is the only/first request then send it right away
|
||||||
|
await _requestQueue.add(
|
||||||
|
req,
|
||||||
|
onInitialRequestAdded: _sendNextAvailableRequest,
|
||||||
|
);
|
||||||
|
|
||||||
|
return req.completer.future.onError(
|
||||||
|
(error, stackTrace) => Exception(
|
||||||
|
"return req.completer.future.onError: $error",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> disconnect({String reason = "none"}) async {
|
||||||
|
await _requestMutex.protect(() async {
|
||||||
|
await _subscription?.cancel();
|
||||||
|
_subscription = null;
|
||||||
|
_socket?.destroy();
|
||||||
|
_socket = null;
|
||||||
|
|
||||||
|
// clean up remaining queue
|
||||||
|
await _requestQueue.completeRemainingWithError(
|
||||||
|
"JsonRPC disconnect() called with reason: \"$reason\"",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> connect() async {
|
||||||
|
if (_socket != null) {
|
||||||
|
throw Exception(
|
||||||
|
"JsonRPC attempted to connect to an already existing socket!",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useSSL) {
|
||||||
|
_socket = await SecureSocket.connect(
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
timeout: connectionTimeout,
|
||||||
|
onBadCertificate: (_) => true,
|
||||||
|
); // TODO do not automatically trust bad certificates
|
||||||
|
} else {
|
||||||
|
_socket = await Socket.connect(
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
timeout: connectionTimeout,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_subscription = _socket!.listen(
|
||||||
|
_dataHandler,
|
||||||
|
onError: _errorHandler,
|
||||||
|
onDone: _doneHandler,
|
||||||
|
cancelOnError: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _JsonRPCRequestQueue {
|
||||||
|
final m = Mutex();
|
||||||
|
|
||||||
|
final List<_JsonRPCRequest> _rq = [];
|
||||||
|
|
||||||
|
Future<void> add(
|
||||||
|
_JsonRPCRequest req, {
|
||||||
|
VoidCallback? onInitialRequestAdded,
|
||||||
|
}) async {
|
||||||
|
return await m.protect(() async {
|
||||||
|
_rq.add(req);
|
||||||
|
if (_rq.length == 1) {
|
||||||
|
onInitialRequestAdded?.call();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> remove(_JsonRPCRequest req) async {
|
||||||
|
return await m.protect(() async => _rq.remove(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<_JsonRPCRequest?> get nextIncompleteReq async {
|
||||||
|
return await m.protect(() async {
|
||||||
|
try {
|
||||||
|
return _rq.firstWhere((e) => !e.isComplete);
|
||||||
|
} catch (_) {
|
||||||
|
// no incomplete requests found
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> completeRemainingWithError(
|
||||||
|
String error, {
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
}) async {
|
||||||
|
await m.protect(() async {
|
||||||
|
for (final req in _rq) {
|
||||||
|
if (!req.isComplete) {
|
||||||
|
req.completer.completeError(Exception(error), stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_rq.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> get isEmpty async {
|
||||||
|
return await m.protect(() async {
|
||||||
|
return _rq.isEmpty;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _JsonRPCRequest {
|
||||||
|
final String jsonRequest;
|
||||||
|
final Completer<dynamic> completer;
|
||||||
|
final List<int> _responseData = [];
|
||||||
|
|
||||||
|
_JsonRPCRequest({required this.jsonRequest, required this.completer});
|
||||||
|
|
||||||
|
void appendDataAndCheckIfComplete(List<int> data) {
|
||||||
|
_responseData.addAll(data);
|
||||||
|
// 0x0A is newline
|
||||||
|
// https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-basics.html
|
||||||
|
if (data.last == 0x0A) {
|
||||||
|
try {
|
||||||
|
final response = json.decode(String.fromCharCodes(_responseData));
|
||||||
|
completer.complete(response);
|
||||||
|
} catch (e, s) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"JsonRPC json.decode: $e\n$s",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
|
completer.completeError(e, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void initiateTimeout(Duration timeout) {
|
||||||
|
Future<void>.delayed(timeout).then((_) {
|
||||||
|
if (!isComplete) {
|
||||||
|
try {
|
||||||
|
throw Exception("_JsonRPCRequest timed out: $jsonRequest");
|
||||||
|
} catch (e, s) {
|
||||||
|
completer.completeError(e, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isComplete => completer.isCompleted;
|
||||||
|
}
|
|
@ -71,7 +71,7 @@ final openedFromSWBFileStringStateProvider =
|
||||||
// runs the MyApp widget and checks for new users, caching the value in the
|
// runs the MyApp widget and checks for new users, caching the value in the
|
||||||
// miscellaneous box for later use
|
// miscellaneous box for later use
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
GoogleFonts.config.allowRuntimeFetching = false;
|
GoogleFonts.config.allowRuntimeFetching = false;
|
||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
Util.libraryPath = await getLibraryDirectory();
|
Util.libraryPath = await getLibraryDirectory();
|
||||||
|
@ -180,7 +180,9 @@ void main() async {
|
||||||
}
|
}
|
||||||
|
|
||||||
monero.onStartup();
|
monero.onStartup();
|
||||||
wownero.onStartup();
|
if (!Platform.isLinux && !Platform.isWindows) {
|
||||||
|
wownero.onStartup();
|
||||||
|
}
|
||||||
|
|
||||||
dynamic tor = Tor();
|
dynamic tor = Tor();
|
||||||
tor.start();
|
tor.start();
|
||||||
|
@ -192,33 +194,8 @@ void main() async {
|
||||||
await MainDB.instance.initMainDB();
|
await MainDB.instance.initMainDB();
|
||||||
ThemeService.instance.init(MainDB.instance);
|
ThemeService.instance.init(MainDB.instance);
|
||||||
|
|
||||||
// install default themes
|
// check and update or install default themes
|
||||||
if (!(await ThemeService.instance.verifyInstalled(themeId: "light"))) {
|
await ThemeService.instance.checkDefaultThemesOnStartup();
|
||||||
Logging.instance.log(
|
|
||||||
"Installing default light theme...",
|
|
||||||
level: LogLevel.Info,
|
|
||||||
);
|
|
||||||
final lightZip = await rootBundle.load("assets/default_themes/light.zip");
|
|
||||||
await ThemeService.instance
|
|
||||||
.install(themeArchiveData: lightZip.buffer.asUint8List());
|
|
||||||
Logging.instance.log(
|
|
||||||
"Installing default light theme... finished",
|
|
||||||
level: LogLevel.Info,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!(await ThemeService.instance.verifyInstalled(themeId: "dark"))) {
|
|
||||||
Logging.instance.log(
|
|
||||||
"Installing default dark theme... ",
|
|
||||||
level: LogLevel.Info,
|
|
||||||
);
|
|
||||||
final darkZip = await rootBundle.load("assets/default_themes/dark.zip");
|
|
||||||
await ThemeService.instance
|
|
||||||
.install(themeArchiveData: darkZip.buffer.asUint8List());
|
|
||||||
Logging.instance.log(
|
|
||||||
"Installing default dark theme... finished",
|
|
||||||
level: LogLevel.Info,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
runApp(const ProviderScope(child: MyApp()));
|
runApp(const ProviderScope(child: MyApp()));
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -32,7 +32,7 @@ class NotificationCard extends ConsumerWidget {
|
||||||
static const double mobileIconSize = 24;
|
static const double mobileIconSize = 24;
|
||||||
static const double desktopIconSize = 30;
|
static const double desktopIconSize = 30;
|
||||||
|
|
||||||
String coinIconPath(ThemeAssets assets, WidgetRef ref) {
|
String coinIconPath(IThemeAssets assets, WidgetRef ref) {
|
||||||
try {
|
try {
|
||||||
final coin = coinFromPrettyName(notification.coinName);
|
final coin = coinFromPrettyName(notification.coinName);
|
||||||
return ref.read(coinIconProvider(coin));
|
return ref.read(coinIconProvider(coin));
|
||||||
|
@ -61,7 +61,7 @@ class NotificationCard extends ConsumerWidget {
|
||||||
File(
|
File(
|
||||||
coinIconPath(
|
coinIconPath(
|
||||||
ref.watch(
|
ref.watch(
|
||||||
themeProvider.select((value) => value.assets),
|
themeAssetsProvider,
|
||||||
),
|
),
|
||||||
ref),
|
ref),
|
||||||
),
|
),
|
||||||
|
@ -79,7 +79,7 @@ class NotificationCard extends ConsumerWidget {
|
||||||
File(
|
File(
|
||||||
coinIconPath(
|
coinIconPath(
|
||||||
ref.watch(
|
ref.watch(
|
||||||
themeProvider.select((value) => value.assets),
|
themeAssetsProvider,
|
||||||
),
|
),
|
||||||
ref),
|
ref),
|
||||||
),
|
),
|
||||||
|
|
|
@ -120,6 +120,8 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
|
||||||
if (Platform.isWindows) {
|
if (Platform.isWindows) {
|
||||||
_coins.remove(Coin.monero);
|
_coins.remove(Coin.monero);
|
||||||
_coins.remove(Coin.wownero);
|
_coins.remove(Coin.wownero);
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
_coins.remove(Coin.wownero);
|
||||||
}
|
}
|
||||||
|
|
||||||
coinEntities.addAll(_coins.map((e) => CoinEntity(e)));
|
coinEntities.addAll(_coins.map((e) => CoinEntity(e)));
|
||||||
|
|
|
@ -270,7 +270,7 @@ class _ContactDetailsViewState extends ConsumerState<ContactDetailsView> {
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pushNamed(
|
Navigator.of(context).pushNamed(
|
||||||
EditContactNameEmojiView.routeName,
|
EditContactNameEmojiView.routeName,
|
||||||
arguments: _contact.id,
|
arguments: _contact.customId,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
|
@ -318,7 +318,7 @@ class _ContactDetailsViewState extends ConsumerState<ContactDetailsView> {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pushNamed(
|
Navigator.of(context).pushNamed(
|
||||||
AddNewContactAddressView.routeName,
|
AddNewContactAddressView.routeName,
|
||||||
arguments: _contact.id,
|
arguments: _contact.customId,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -385,7 +385,7 @@ class _ContactDetailsViewState extends ConsumerState<ContactDetailsView> {
|
||||||
|
|
||||||
Navigator.of(context).pushNamed(
|
Navigator.of(context).pushNamed(
|
||||||
EditContactAddressView.routeName,
|
EditContactAddressView.routeName,
|
||||||
arguments: Tuple2(_contact.id, e),
|
arguments: Tuple2(_contact.customId, e),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: RoundedContainer(
|
child: RoundedContainer(
|
||||||
|
|
|
@ -110,7 +110,7 @@ class ContactPopUp extends ConsumerWidget {
|
||||||
.textFieldDefaultBG,
|
.textFieldDefaultBG,
|
||||||
borderRadius: BorderRadius.circular(32),
|
borderRadius: BorderRadius.circular(32),
|
||||||
),
|
),
|
||||||
child: contact.id == "default"
|
child: contact.customId == "default"
|
||||||
? Center(
|
? Center(
|
||||||
child: SvgPicture.file(
|
child: SvgPicture.file(
|
||||||
File(
|
File(
|
||||||
|
@ -146,13 +146,13 @@ class ContactPopUp extends ConsumerWidget {
|
||||||
STextStyles.itemSubtitle12(context),
|
STextStyles.itemSubtitle12(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (contact.id != "default")
|
if (contact.customId != "default")
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
Navigator.of(context).pushNamed(
|
Navigator.of(context).pushNamed(
|
||||||
ContactDetailsView.routeName,
|
ContactDetailsView.routeName,
|
||||||
arguments: contact.id,
|
arguments: contact.customId,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
|
@ -176,7 +176,7 @@ class ContactPopUp extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: contact.id == "default" ? 16 : 8,
|
height: contact.customId == "default" ? 16 : 8,
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
height: 1,
|
height: 1,
|
||||||
|
@ -233,14 +233,14 @@ class ContactPopUp extends ConsumerWidget {
|
||||||
crossAxisAlignment:
|
crossAxisAlignment:
|
||||||
CrossAxisAlignment.start,
|
CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (contact.id == "default")
|
if (contact.customId == "default")
|
||||||
Text(
|
Text(
|
||||||
e.other!,
|
e.other!,
|
||||||
style:
|
style:
|
||||||
STextStyles.itemSubtitle12(
|
STextStyles.itemSubtitle12(
|
||||||
context),
|
context),
|
||||||
),
|
),
|
||||||
if (contact.id != "default")
|
if (contact.customId != "default")
|
||||||
Text(
|
Text(
|
||||||
"${e.label} (${e.coin.ticker})",
|
"${e.label} (${e.coin.ticker})",
|
||||||
style:
|
style:
|
||||||
|
@ -336,13 +336,13 @@ class ContactPopUp extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (contact.id != "default" &&
|
if (contact.customId != "default" &&
|
||||||
hasActiveWallet &&
|
hasActiveWallet &&
|
||||||
!isExchangeFlow)
|
!isExchangeFlow)
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 4,
|
width: 4,
|
||||||
),
|
),
|
||||||
if (contact.id != "default" &&
|
if (contact.customId != "default" &&
|
||||||
hasActiveWallet &&
|
hasActiveWallet &&
|
||||||
!isExchangeFlow)
|
!isExchangeFlow)
|
||||||
Column(
|
Column(
|
||||||
|
|
|
@ -113,7 +113,7 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
String _fetchIconAssetForStatus(String statusString, ThemeAssets assets) {
|
String _fetchIconAssetForStatus(String statusString, IThemeAssets assets) {
|
||||||
ChangeNowTransactionStatus? status;
|
ChangeNowTransactionStatus? status;
|
||||||
try {
|
try {
|
||||||
if (statusString.toLowerCase().startsWith("waiting")) {
|
if (statusString.toLowerCase().startsWith("waiting")) {
|
||||||
|
@ -322,11 +322,7 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
||||||
File(
|
File(
|
||||||
_fetchIconAssetForStatus(
|
_fetchIconAssetForStatus(
|
||||||
trade.status,
|
trade.status,
|
||||||
ref.watch(
|
ref.watch(themeAssetsProvider),
|
||||||
themeProvider.select(
|
|
||||||
(value) => value.assets,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
width: 32,
|
width: 32,
|
||||||
|
@ -393,11 +389,7 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
||||||
File(
|
File(
|
||||||
_fetchIconAssetForStatus(
|
_fetchIconAssetForStatus(
|
||||||
trade.status,
|
trade.status,
|
||||||
ref.watch(
|
ref.watch(themeAssetsProvider),
|
||||||
themeProvider.select(
|
|
||||||
(value) => value.assets,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
width: 32,
|
width: 32,
|
||||||
|
@ -1232,7 +1224,7 @@ class _TradeDetailsViewState extends ConsumerState<TradeDetailsView> {
|
||||||
if (trade.exchangeName
|
if (trade.exchangeName
|
||||||
.startsWith(TrocadorExchange.exchangeName)) {
|
.startsWith(TrocadorExchange.exchangeName)) {
|
||||||
url =
|
url =
|
||||||
"https://trocador.app/en/checkout${trade.tradeId}";
|
"https://trocador.app/en/checkout/${trade.tradeId}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ConditionalParent(
|
return ConditionalParent(
|
||||||
|
|
|
@ -138,10 +138,8 @@ class _PaynymClaimViewState extends ConsumerState<PaynymClaimView> {
|
||||||
const Spacer(
|
const Spacer(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
),
|
),
|
||||||
Image(
|
SvgPicture.asset(
|
||||||
image: AssetImage(
|
Assets.svg.unclaimedPaynym,
|
||||||
Assets.svg.unclaimedPaynym,
|
|
||||||
),
|
|
||||||
width: MediaQuery.of(context).size.width / 2,
|
width: MediaQuery.of(context).size.width / 2,
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -9,7 +10,6 @@ import 'package:stackwallet/providers/global/secure_store_provider.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
import 'package:stackwallet/utilities/assets.dart';
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
import 'package:stackwallet/utilities/biometrics.dart';
|
import 'package:stackwallet/utilities/biometrics.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
|
||||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
import 'package:stackwallet/widgets/background.dart';
|
||||||
|
@ -35,10 +35,11 @@ class CreatePinView extends ConsumerStatefulWidget {
|
||||||
class _CreatePinViewState extends ConsumerState<CreatePinView> {
|
class _CreatePinViewState extends ConsumerState<CreatePinView> {
|
||||||
BoxDecoration get _pinPutDecoration {
|
BoxDecoration get _pinPutDecoration {
|
||||||
return BoxDecoration(
|
return BoxDecoration(
|
||||||
color: Theme.of(context).extension<StackColors>()!.textSubtitle3,
|
color: Theme.of(context).extension<StackColors>()!.infoItemIcons,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
width: 1,
|
width: 1,
|
||||||
color: Theme.of(context).extension<StackColors>()!.textSubtitle3),
|
color: Theme.of(context).extension<StackColors>()!.infoItemIcons,
|
||||||
|
),
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -57,10 +58,13 @@ class _CreatePinViewState extends ConsumerState<CreatePinView> {
|
||||||
late SecureStorageInterface _secureStore;
|
late SecureStorageInterface _secureStore;
|
||||||
late Biometrics biometrics;
|
late Biometrics biometrics;
|
||||||
|
|
||||||
|
int pinCount = 1;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
initState() {
|
initState() {
|
||||||
_secureStore = ref.read(secureStoreProvider);
|
_secureStore = ref.read(secureStoreProvider);
|
||||||
biometrics = widget.biometrics;
|
biometrics = widget.biometrics;
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,11 +75,13 @@ class _CreatePinViewState extends ConsumerState<CreatePinView> {
|
||||||
_pinPutController2.dispose();
|
_pinPutController2.dispose();
|
||||||
_pinPutFocusNode1.dispose();
|
_pinPutFocusNode1.dispose();
|
||||||
_pinPutFocusNode2.dispose();
|
_pinPutFocusNode2.dispose();
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// int pinCount = 1;
|
||||||
return Background(
|
return Background(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||||
|
@ -116,7 +122,7 @@ class _CreatePinViewState extends ConsumerState<CreatePinView> {
|
||||||
height: 36,
|
height: 36,
|
||||||
),
|
),
|
||||||
CustomPinPut(
|
CustomPinPut(
|
||||||
fieldsCount: Constants.pinLength,
|
fieldsCount: pinCount,
|
||||||
eachFieldHeight: 12,
|
eachFieldHeight: 12,
|
||||||
eachFieldWidth: 12,
|
eachFieldWidth: 12,
|
||||||
textStyle: STextStyles.label(context).copyWith(
|
textStyle: STextStyles.label(context).copyWith(
|
||||||
|
@ -140,21 +146,23 @@ class _CreatePinViewState extends ConsumerState<CreatePinView> {
|
||||||
),
|
),
|
||||||
isRandom:
|
isRandom:
|
||||||
ref.read(prefsChangeNotifierProvider).randomizePIN,
|
ref.read(prefsChangeNotifierProvider).randomizePIN,
|
||||||
submittedFieldDecoration: _pinPutDecoration.copyWith(
|
submittedFieldDecoration: _pinPutDecoration,
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.infoItemIcons,
|
|
||||||
border: Border.all(
|
|
||||||
width: 1,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.infoItemIcons,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
selectedFieldDecoration: _pinPutDecoration,
|
selectedFieldDecoration: _pinPutDecoration,
|
||||||
followingFieldDecoration: _pinPutDecoration,
|
followingFieldDecoration: _pinPutDecoration,
|
||||||
|
onPinLengthChanged: (newLength) {
|
||||||
|
setState(() {
|
||||||
|
pinCount = newLength;
|
||||||
|
});
|
||||||
|
},
|
||||||
onSubmit: (String pin) {
|
onSubmit: (String pin) {
|
||||||
if (pin.length == Constants.pinLength) {
|
if (pin.length < 4) {
|
||||||
|
showFloatingFlushBar(
|
||||||
|
type: FlushBarType.warning,
|
||||||
|
message: "PIN not long enough!",
|
||||||
|
iconAsset: Assets.svg.alertCircle,
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
_pageController.nextPage(
|
_pageController.nextPage(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
curve: Curves.linear,
|
curve: Curves.linear,
|
||||||
|
@ -184,7 +192,7 @@ class _CreatePinViewState extends ConsumerState<CreatePinView> {
|
||||||
height: 36,
|
height: 36,
|
||||||
),
|
),
|
||||||
CustomPinPut(
|
CustomPinPut(
|
||||||
fieldsCount: Constants.pinLength,
|
fieldsCount: pinCount,
|
||||||
eachFieldHeight: 12,
|
eachFieldHeight: 12,
|
||||||
eachFieldWidth: 12,
|
eachFieldWidth: 12,
|
||||||
textStyle: STextStyles.infoSmall(context).copyWith(
|
textStyle: STextStyles.infoSmall(context).copyWith(
|
||||||
|
|
|
@ -13,7 +13,6 @@ import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
// import 'package:stackwallet/providers/global/should_show_lockscreen_on_resume_state_provider.dart';
|
// import 'package:stackwallet/providers/global/should_show_lockscreen_on_resume_state_provider.dart';
|
||||||
import 'package:stackwallet/utilities/assets.dart';
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
import 'package:stackwallet/utilities/biometrics.dart';
|
import 'package:stackwallet/utilities/biometrics.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||||
import 'package:stackwallet/utilities/show_loading.dart';
|
import 'package:stackwallet/utilities/show_loading.dart';
|
||||||
|
@ -189,10 +188,11 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
|
||||||
|
|
||||||
BoxDecoration get _pinPutDecoration {
|
BoxDecoration get _pinPutDecoration {
|
||||||
return BoxDecoration(
|
return BoxDecoration(
|
||||||
color: Theme.of(context).extension<StackColors>()!.textSubtitle2,
|
color: Theme.of(context).extension<StackColors>()!.infoItemIcons,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
width: 1,
|
width: 1,
|
||||||
color: Theme.of(context).extension<StackColors>()!.textSubtitle2),
|
color: Theme.of(context).extension<StackColors>()!.infoItemIcons,
|
||||||
|
),
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -202,6 +202,7 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
|
||||||
|
|
||||||
late SecureStorageInterface _secureStore;
|
late SecureStorageInterface _secureStore;
|
||||||
late Biometrics biometrics;
|
late Biometrics biometrics;
|
||||||
|
int pinCount = 1;
|
||||||
|
|
||||||
Widget get _body => Background(
|
Widget get _body => Background(
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
|
@ -274,13 +275,7 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
|
||||||
height: 52,
|
height: 52,
|
||||||
),
|
),
|
||||||
CustomPinPut(
|
CustomPinPut(
|
||||||
// customKey: CustomKey(
|
fieldsCount: pinCount,
|
||||||
// onPressed: _checkUseBiometrics,
|
|
||||||
// iconAssetName: Platform.isIOS
|
|
||||||
// ? Assets.svg.faceId
|
|
||||||
// : Assets.svg.fingerprint,
|
|
||||||
// ),
|
|
||||||
fieldsCount: Constants.pinLength,
|
|
||||||
eachFieldHeight: 12,
|
eachFieldHeight: 12,
|
||||||
eachFieldWidth: 12,
|
eachFieldWidth: 12,
|
||||||
textStyle: STextStyles.label(context).copyWith(
|
textStyle: STextStyles.label(context).copyWith(
|
||||||
|
@ -302,19 +297,7 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
|
||||||
.background,
|
.background,
|
||||||
counterText: "",
|
counterText: "",
|
||||||
),
|
),
|
||||||
submittedFieldDecoration: _pinPutDecoration.copyWith(
|
submittedFieldDecoration: _pinPutDecoration,
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.infoItemIcons,
|
|
||||||
border: Border.all(
|
|
||||||
width: 1,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.infoItemIcons,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
selectedFieldDecoration: _pinPutDecoration,
|
|
||||||
followingFieldDecoration: _pinPutDecoration,
|
|
||||||
isRandom: ref
|
isRandom: ref
|
||||||
.read(prefsChangeNotifierProvider)
|
.read(prefsChangeNotifierProvider)
|
||||||
.randomizePIN,
|
.randomizePIN,
|
||||||
|
|
|
@ -150,6 +150,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
||||||
|
|
||||||
String receivingAddress = widget.receivingAddress;
|
String receivingAddress = widget.receivingAddress;
|
||||||
if ((widget.coin == Coin.bitcoincash ||
|
if ((widget.coin == Coin.bitcoincash ||
|
||||||
|
widget.coin == Coin.eCash ||
|
||||||
widget.coin == Coin.bitcoincashTestnet) &&
|
widget.coin == Coin.bitcoincashTestnet) &&
|
||||||
receivingAddress.contains(":")) {
|
receivingAddress.contains(":")) {
|
||||||
// remove cash addr prefix
|
// remove cash addr prefix
|
||||||
|
@ -246,6 +247,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
||||||
|
|
||||||
String receivingAddress = widget.receivingAddress;
|
String receivingAddress = widget.receivingAddress;
|
||||||
if ((widget.coin == Coin.bitcoincash ||
|
if ((widget.coin == Coin.bitcoincash ||
|
||||||
|
widget.coin == Coin.eCash ||
|
||||||
widget.coin == Coin.bitcoincashTestnet) &&
|
widget.coin == Coin.bitcoincashTestnet) &&
|
||||||
receivingAddress.contains(":")) {
|
receivingAddress.contains(":")) {
|
||||||
// remove cash addr prefix
|
// remove cash addr prefix
|
||||||
|
|
|
@ -225,14 +225,14 @@ class _IncognitoInstalledThemesState
|
||||||
extends ConsumerState<IncognitoInstalledThemes> {
|
extends ConsumerState<IncognitoInstalledThemes> {
|
||||||
late final StreamSubscription<void> _subscription;
|
late final StreamSubscription<void> _subscription;
|
||||||
|
|
||||||
List<Tuple2<String, String>> installedThemeIdNames = [];
|
List<Tuple3<String, String, int?>> installedThemeIdNames = [];
|
||||||
|
|
||||||
void _updateInstalledList() {
|
void _updateInstalledList() {
|
||||||
installedThemeIdNames = ref
|
installedThemeIdNames = ref
|
||||||
.read(pThemeService)
|
.read(pThemeService)
|
||||||
.installedThemes
|
.installedThemes
|
||||||
.where((e) => e.themeId != "light" && e.themeId != "dark")
|
.where((e) => e.themeId != "light" && e.themeId != "dark")
|
||||||
.map((e) => Tuple2(e.themeId, e.name))
|
.map((e) => Tuple3(e.themeId, e.name, e.version))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,6 +274,7 @@ class _IncognitoInstalledThemesState
|
||||||
data: StackThemeMetaData(
|
data: StackThemeMetaData(
|
||||||
name: e.item2,
|
name: e.item2,
|
||||||
id: e.item1,
|
id: e.item1,
|
||||||
|
version: e.item3 ?? 1,
|
||||||
sha256: "",
|
sha256: "",
|
||||||
size: "",
|
size: "",
|
||||||
previewImageUrl: "",
|
previewImageUrl: "",
|
||||||
|
|
|
@ -39,6 +39,7 @@ class _StackThemeCardState extends ConsumerState<StackThemeCard> {
|
||||||
late final StreamSubscription<void> _subscription;
|
late final StreamSubscription<void> _subscription;
|
||||||
|
|
||||||
late bool _hasTheme;
|
late bool _hasTheme;
|
||||||
|
bool _needsUpdate = false;
|
||||||
String? _cachedSize;
|
String? _cachedSize;
|
||||||
|
|
||||||
Future<bool> _downloadAndInstall() async {
|
Future<bool> _downloadAndInstall() async {
|
||||||
|
@ -84,6 +85,7 @@ class _StackThemeCardState extends ConsumerState<StackThemeCard> {
|
||||||
title: message,
|
title: message,
|
||||||
onOkPressed: (_) {
|
onOkPressed: (_) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
_needsUpdate = !result;
|
||||||
_hasTheme = result;
|
_hasTheme = result;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -141,16 +143,21 @@ class _StackThemeCardState extends ConsumerState<StackThemeCard> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StackTheme? getInstalled() => ref
|
||||||
|
.read(mainDBProvider)
|
||||||
|
.isar
|
||||||
|
.stackThemes
|
||||||
|
.where()
|
||||||
|
.themeIdEqualTo(widget.data.id)
|
||||||
|
.findFirstSync();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_hasTheme = ref
|
final installedTheme = getInstalled();
|
||||||
.read(mainDBProvider)
|
_hasTheme = installedTheme != null;
|
||||||
.isar
|
if (_hasTheme) {
|
||||||
.stackThemes
|
_needsUpdate = widget.data.version > (installedTheme?.version ?? 0);
|
||||||
.where()
|
}
|
||||||
.themeIdEqualTo(widget.data.id)
|
|
||||||
.countSync() >
|
|
||||||
0;
|
|
||||||
|
|
||||||
_subscription = ref
|
_subscription = ref
|
||||||
.read(mainDBProvider)
|
.read(mainDBProvider)
|
||||||
|
@ -158,18 +165,16 @@ class _StackThemeCardState extends ConsumerState<StackThemeCard> {
|
||||||
.stackThemes
|
.stackThemes
|
||||||
.watchLazy()
|
.watchLazy()
|
||||||
.listen((event) async {
|
.listen((event) async {
|
||||||
final hasTheme = (await ref
|
final installedTheme = getInstalled();
|
||||||
.read(mainDBProvider)
|
final hasTheme = installedTheme != null;
|
||||||
.isar
|
|
||||||
.stackThemes
|
|
||||||
.where()
|
|
||||||
.themeIdEqualTo(widget.data.id)
|
|
||||||
.count()) >
|
|
||||||
0;
|
|
||||||
if (_hasTheme != hasTheme && mounted) {
|
if (_hasTheme != hasTheme && mounted) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_hasTheme = hasTheme;
|
_hasTheme = hasTheme;
|
||||||
|
if (hasTheme) {
|
||||||
|
_needsUpdate =
|
||||||
|
widget.data.version > (installedTheme.version ?? 0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -272,6 +277,16 @@ class _StackThemeCardState extends ConsumerState<StackThemeCard> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
if (_hasTheme && _needsUpdate)
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
if (_hasTheme && _needsUpdate)
|
||||||
|
PrimaryButton(
|
||||||
|
label: "Update",
|
||||||
|
buttonHeight: isDesktop ? ButtonHeight.s : ButtonHeight.l,
|
||||||
|
onPressed: _downloadPressed,
|
||||||
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
|
|
|
@ -150,6 +150,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
||||||
case Coin.bitcoincash:
|
case Coin.bitcoincash:
|
||||||
case Coin.litecoin:
|
case Coin.litecoin:
|
||||||
case Coin.dogecoin:
|
case Coin.dogecoin:
|
||||||
|
case Coin.eCash:
|
||||||
case Coin.firo:
|
case Coin.firo:
|
||||||
case Coin.namecoin:
|
case Coin.namecoin:
|
||||||
case Coin.particl:
|
case Coin.particl:
|
||||||
|
@ -717,6 +718,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
|
||||||
case Coin.firoTestNet:
|
case Coin.firoTestNet:
|
||||||
case Coin.dogecoinTestNet:
|
case Coin.dogecoinTestNet:
|
||||||
case Coin.epicCash:
|
case Coin.epicCash:
|
||||||
|
case Coin.eCash:
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case Coin.ethereum:
|
case Coin.ethereum:
|
||||||
|
|
|
@ -6,7 +6,6 @@ import 'package:stackwallet/providers/global/prefs_provider.dart';
|
||||||
import 'package:stackwallet/providers/global/secure_store_provider.dart';
|
import 'package:stackwallet/providers/global/secure_store_provider.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
import 'package:stackwallet/utilities/assets.dart';
|
import 'package:stackwallet/utilities/assets.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
|
||||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/widgets/background.dart';
|
import 'package:stackwallet/widgets/background.dart';
|
||||||
|
@ -27,10 +26,11 @@ class ChangePinView extends ConsumerStatefulWidget {
|
||||||
class _ChangePinViewState extends ConsumerState<ChangePinView> {
|
class _ChangePinViewState extends ConsumerState<ChangePinView> {
|
||||||
BoxDecoration get _pinPutDecoration {
|
BoxDecoration get _pinPutDecoration {
|
||||||
return BoxDecoration(
|
return BoxDecoration(
|
||||||
color: Theme.of(context).extension<StackColors>()!.textSubtitle2,
|
color: Theme.of(context).extension<StackColors>()!.infoItemIcons,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
width: 1,
|
width: 1,
|
||||||
color: Theme.of(context).extension<StackColors>()!.textSubtitle2),
|
color: Theme.of(context).extension<StackColors>()!.infoItemIcons,
|
||||||
|
),
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,8 @@ class _ChangePinViewState extends ConsumerState<ChangePinView> {
|
||||||
|
|
||||||
late final SecureStorageInterface _secureStore;
|
late final SecureStorageInterface _secureStore;
|
||||||
|
|
||||||
|
int pinCount = 1;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_secureStore = ref.read(secureStoreProvider);
|
_secureStore = ref.read(secureStoreProvider);
|
||||||
|
@ -101,7 +103,7 @@ class _ChangePinViewState extends ConsumerState<ChangePinView> {
|
||||||
height: 52,
|
height: 52,
|
||||||
),
|
),
|
||||||
CustomPinPut(
|
CustomPinPut(
|
||||||
fieldsCount: Constants.pinLength,
|
fieldsCount: pinCount,
|
||||||
eachFieldHeight: 12,
|
eachFieldHeight: 12,
|
||||||
eachFieldWidth: 12,
|
eachFieldWidth: 12,
|
||||||
textStyle: STextStyles.label(context).copyWith(
|
textStyle: STextStyles.label(context).copyWith(
|
||||||
|
@ -125,21 +127,18 @@ class _ChangePinViewState extends ConsumerState<ChangePinView> {
|
||||||
),
|
),
|
||||||
isRandom:
|
isRandom:
|
||||||
ref.read(prefsChangeNotifierProvider).randomizePIN,
|
ref.read(prefsChangeNotifierProvider).randomizePIN,
|
||||||
submittedFieldDecoration: _pinPutDecoration.copyWith(
|
submittedFieldDecoration: _pinPutDecoration,
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.infoItemIcons,
|
|
||||||
border: Border.all(
|
|
||||||
width: 1,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.infoItemIcons,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
selectedFieldDecoration: _pinPutDecoration,
|
selectedFieldDecoration: _pinPutDecoration,
|
||||||
followingFieldDecoration: _pinPutDecoration,
|
followingFieldDecoration: _pinPutDecoration,
|
||||||
onSubmit: (String pin) {
|
onSubmit: (String pin) {
|
||||||
if (pin.length == Constants.pinLength) {
|
if (pin.length < 4) {
|
||||||
|
showFloatingFlushBar(
|
||||||
|
type: FlushBarType.warning,
|
||||||
|
message: "PIN not long enough!",
|
||||||
|
iconAsset: Assets.svg.alertCircle,
|
||||||
|
context: context,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
_pageController.nextPage(
|
_pageController.nextPage(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
curve: Curves.linear,
|
curve: Curves.linear,
|
||||||
|
@ -165,7 +164,7 @@ class _ChangePinViewState extends ConsumerState<ChangePinView> {
|
||||||
height: 52,
|
height: 52,
|
||||||
),
|
),
|
||||||
CustomPinPut(
|
CustomPinPut(
|
||||||
fieldsCount: Constants.pinLength,
|
fieldsCount: pinCount,
|
||||||
eachFieldHeight: 12,
|
eachFieldHeight: 12,
|
||||||
eachFieldWidth: 12,
|
eachFieldWidth: 12,
|
||||||
textStyle: STextStyles.infoSmall(context).copyWith(
|
textStyle: STextStyles.infoSmall(context).copyWith(
|
||||||
|
@ -192,17 +191,7 @@ class _ChangePinViewState extends ConsumerState<ChangePinView> {
|
||||||
),
|
),
|
||||||
isRandom:
|
isRandom:
|
||||||
ref.read(prefsChangeNotifierProvider).randomizePIN,
|
ref.read(prefsChangeNotifierProvider).randomizePIN,
|
||||||
submittedFieldDecoration: _pinPutDecoration.copyWith(
|
submittedFieldDecoration: _pinPutDecoration,
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.infoItemIcons,
|
|
||||||
border: Border.all(
|
|
||||||
width: 1,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.extension<StackColors>()!
|
|
||||||
.infoItemIcons,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
selectedFieldDecoration: _pinPutDecoration,
|
selectedFieldDecoration: _pinPutDecoration,
|
||||||
followingFieldDecoration: _pinPutDecoration,
|
followingFieldDecoration: _pinPutDecoration,
|
||||||
onSubmit: (String pin) async {
|
onSubmit: (String pin) async {
|
||||||
|
|
|
@ -190,7 +190,7 @@ class AboutItem extends StatelessWidget {
|
||||||
height: iconSize,
|
height: iconSize,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
.bottomNavIconIcon,
|
.topNavIconPrimary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
|
|
@ -24,7 +24,7 @@ class TxIcon extends ConsumerWidget {
|
||||||
static const Size size = Size(32, 32);
|
static const Size size = Size(32, 32);
|
||||||
|
|
||||||
String _getAssetName(
|
String _getAssetName(
|
||||||
bool isCancelled, bool isReceived, bool isPending, ThemeAssets assets) {
|
bool isCancelled, bool isReceived, bool isPending, IThemeAssets assets) {
|
||||||
if (!isReceived && transaction.subType == TransactionSubType.mint) {
|
if (!isReceived && transaction.subType == TransactionSubType.mint) {
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
return Assets.svg.anonymizeFailed;
|
return Assets.svg.anonymizeFailed;
|
||||||
|
@ -65,7 +65,7 @@ class TxIcon extends ConsumerWidget {
|
||||||
currentHeight,
|
currentHeight,
|
||||||
coin.requiredConfirmations,
|
coin.requiredConfirmations,
|
||||||
),
|
),
|
||||||
ref.watch(themeProvider).assets,
|
ref.watch(themeAssetsProvider),
|
||||||
);
|
);
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
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:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||||
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
import 'package:stackwallet/notifications/show_flush_bar.dart';
|
||||||
|
import 'package:stackwallet/pages/receive_view/addresses/address_details_view.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
|
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart';
|
import 'package:stackwallet/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart';
|
import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart';
|
||||||
|
@ -605,17 +607,66 @@ class _TransactionDetailsViewState
|
||||||
crossAxisAlignment:
|
crossAxisAlignment:
|
||||||
CrossAxisAlignment.start,
|
CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
ConditionalParent(
|
||||||
_transaction.type ==
|
condition: kDebugMode,
|
||||||
TransactionType.outgoing
|
builder: (child) {
|
||||||
? "Sent to"
|
return Row(
|
||||||
: "Receiving address",
|
mainAxisAlignment:
|
||||||
style: isDesktop
|
MainAxisAlignment
|
||||||
? STextStyles
|
.spaceBetween,
|
||||||
.desktopTextExtraExtraSmall(
|
children: [
|
||||||
context)
|
child,
|
||||||
: STextStyles.itemSubtitle(
|
CustomTextButton(
|
||||||
context),
|
text: "Info",
|
||||||
|
onTap: () {
|
||||||
|
if (isDesktop) {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) =>
|
||||||
|
DesktopDialog(
|
||||||
|
maxHeight:
|
||||||
|
double.infinity,
|
||||||
|
child:
|
||||||
|
AddressDetailsView(
|
||||||
|
addressId:
|
||||||
|
_transaction
|
||||||
|
.address
|
||||||
|
.value!
|
||||||
|
.id,
|
||||||
|
walletId: widget
|
||||||
|
.walletId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Navigator.of(context)
|
||||||
|
.pushNamed(
|
||||||
|
AddressDetailsView
|
||||||
|
.routeName,
|
||||||
|
arguments: Tuple2(
|
||||||
|
_transaction.address
|
||||||
|
.value!.id,
|
||||||
|
widget.walletId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
_transaction.type ==
|
||||||
|
TransactionType.outgoing
|
||||||
|
? "Sent to"
|
||||||
|
: "Receiving address",
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles
|
||||||
|
.desktopTextExtraExtraSmall(
|
||||||
|
context)
|
||||||
|
: STextStyles.itemSubtitle(
|
||||||
|
context),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
|
@ -982,6 +1033,7 @@ class _TransactionDetailsViewState
|
||||||
final String height;
|
final String height;
|
||||||
|
|
||||||
if (widget.coin == Coin.bitcoincash ||
|
if (widget.coin == Coin.bitcoincash ||
|
||||||
|
widget.coin == Coin.eCash ||
|
||||||
widget.coin == Coin.bitcoincashTestnet) {
|
widget.coin == Coin.bitcoincashTestnet) {
|
||||||
height =
|
height =
|
||||||
"${_transaction.height != null && _transaction.height! > 0 ? _transaction.height! : "Pending"}";
|
"${_transaction.height != null && _transaction.height! > 0 ? _transaction.height! : "Pending"}";
|
||||||
|
@ -1099,6 +1151,46 @@ class _TransactionDetailsViewState
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (kDebugMode)
|
||||||
|
isDesktop
|
||||||
|
? const _Divider()
|
||||||
|
: const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
if (kDebugMode)
|
||||||
|
RoundedWhiteContainer(
|
||||||
|
padding: isDesktop
|
||||||
|
? const EdgeInsets.all(16)
|
||||||
|
: const EdgeInsets.all(12),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Tx sub type",
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles
|
||||||
|
.desktopTextExtraExtraSmall(
|
||||||
|
context)
|
||||||
|
: STextStyles.itemSubtitle(context),
|
||||||
|
),
|
||||||
|
SelectableText(
|
||||||
|
_transaction.subType.toString(),
|
||||||
|
style: isDesktop
|
||||||
|
? STextStyles
|
||||||
|
.desktopTextExtraExtraSmall(
|
||||||
|
context)
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<StackColors>()!
|
||||||
|
.textDark,
|
||||||
|
)
|
||||||
|
: STextStyles.itemSubtitle12(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
isDesktop
|
isDesktop
|
||||||
? const _Divider()
|
? const _Divider()
|
||||||
: const SizedBox(
|
: const SizedBox(
|
||||||
|
|
|
@ -6,7 +6,7 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
||||||
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
|
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
|
||||||
import 'package:stackwallet/providers/providers.dart';
|
import 'package:stackwallet/providers/providers.dart';
|
||||||
import 'package:stackwallet/services/coins/manager.dart';
|
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||||
import 'package:stackwallet/themes/coin_icon_provider.dart';
|
import 'package:stackwallet/themes/coin_icon_provider.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
|
@ -24,13 +24,11 @@ class FavoriteCard extends ConsumerStatefulWidget {
|
||||||
required this.walletId,
|
required this.walletId,
|
||||||
required this.width,
|
required this.width,
|
||||||
required this.height,
|
required this.height,
|
||||||
required this.managerProvider,
|
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final String walletId;
|
final String walletId;
|
||||||
final double width;
|
final double width;
|
||||||
final double height;
|
final double height;
|
||||||
final ChangeNotifierProvider<Manager> managerProvider;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<FavoriteCard> createState() => _FavoriteCardState();
|
ConsumerState<FavoriteCard> createState() => _FavoriteCardState();
|
||||||
|
@ -38,15 +36,10 @@ class FavoriteCard extends ConsumerStatefulWidget {
|
||||||
|
|
||||||
class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
||||||
late final String walletId;
|
late final String walletId;
|
||||||
late final ChangeNotifierProvider<Manager> managerProvider;
|
|
||||||
|
|
||||||
Amount _cachedBalance = Amount.zero;
|
|
||||||
Amount _cachedFiatValue = Amount.zero;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
walletId = widget.walletId;
|
walletId = widget.walletId;
|
||||||
managerProvider = widget.managerProvider;
|
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
@ -55,9 +48,13 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final coin = ref.watch(managerProvider.select((value) => value.coin));
|
final coin = ref.watch(
|
||||||
|
walletsChangeNotifierProvider
|
||||||
|
.select((value) => value.getManager(walletId).coin),
|
||||||
|
);
|
||||||
final externalCalls = ref.watch(
|
final externalCalls = ref.watch(
|
||||||
prefsChangeNotifierProvider.select((value) => value.externalCalls));
|
prefsChangeNotifierProvider.select((value) => value.externalCalls),
|
||||||
|
);
|
||||||
|
|
||||||
return ConditionalParent(
|
return ConditionalParent(
|
||||||
condition: Util.isDesktop,
|
condition: Util.isDesktop,
|
||||||
|
@ -109,7 +106,10 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (coin == Coin.monero || coin == Coin.wownero) {
|
if (coin == Coin.monero || coin == Coin.wownero) {
|
||||||
await ref.read(managerProvider).initializeExisting();
|
await ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManager(walletId)
|
||||||
|
.initializeExisting();
|
||||||
}
|
}
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
if (Util.isDesktop) {
|
if (Util.isDesktop) {
|
||||||
|
@ -122,7 +122,9 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
||||||
WalletView.routeName,
|
WalletView.routeName,
|
||||||
arguments: Tuple2(
|
arguments: Tuple2(
|
||||||
walletId,
|
walletId,
|
||||||
managerProvider,
|
ref
|
||||||
|
.read(walletsChangeNotifierProvider)
|
||||||
|
.getManagerProvider(walletId),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -205,8 +207,12 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
ref.watch(managerProvider
|
ref.watch(
|
||||||
.select((value) => value.walletName)),
|
walletsChangeNotifierProvider.select(
|
||||||
|
(value) =>
|
||||||
|
value.getManager(walletId).walletName,
|
||||||
|
),
|
||||||
|
),
|
||||||
style: STextStyles.itemSubtitle12(context).copyWith(
|
style: STextStyles.itemSubtitle12(context).copyWith(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
|
@ -225,41 +231,54 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
FutureBuilder(
|
Builder(
|
||||||
future: Future(() => ref.watch(managerProvider
|
builder: (context) {
|
||||||
.select((value) => value.balance.total))),
|
final balance = ref.watch(
|
||||||
builder: (builderContext, AsyncSnapshot<Amount> snapshot) {
|
walletsChangeNotifierProvider.select(
|
||||||
if (snapshot.connectionState == ConnectionState.done &&
|
(value) => value.getManager(walletId).balance,
|
||||||
snapshot.hasData) {
|
),
|
||||||
if (snapshot.data != null) {
|
);
|
||||||
_cachedBalance = snapshot.data!;
|
|
||||||
if (externalCalls && _cachedBalance > Amount.zero) {
|
Amount total = balance.total;
|
||||||
_cachedFiatValue = (_cachedBalance.decimal *
|
if (coin == Coin.firo || coin == Coin.firoTestNet) {
|
||||||
ref
|
final balancePrivate = ref.watch(
|
||||||
.watch(
|
walletsChangeNotifierProvider.select(
|
||||||
priceAnd24hChangeNotifierProvider
|
(value) => (value.getManager(walletId).wallet
|
||||||
.select(
|
as FiroWallet)
|
||||||
(value) => value.getPrice(coin),
|
.balancePrivate,
|
||||||
),
|
),
|
||||||
)
|
);
|
||||||
.item1)
|
|
||||||
.toAmount(fractionDigits: 2);
|
total += balancePrivate.total;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Amount fiatTotal = Amount.zero;
|
||||||
|
|
||||||
|
if (externalCalls && total > Amount.zero) {
|
||||||
|
fiatTotal = (total.decimal *
|
||||||
|
ref
|
||||||
|
.watch(
|
||||||
|
priceAnd24hChangeNotifierProvider.select(
|
||||||
|
(value) => value.getPrice(coin),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.item1)
|
||||||
|
.toAmount(fractionDigits: 2);
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
FittedBox(
|
FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: Text(
|
child: Text(
|
||||||
"${_cachedBalance.localizedStringAsFixed(
|
"${total.localizedStringAsFixed(
|
||||||
locale: ref.watch(
|
locale: ref.watch(
|
||||||
localeServiceChangeNotifierProvider
|
localeServiceChangeNotifierProvider.select(
|
||||||
.select((value) => value.locale),
|
(value) => value.locale,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
decimalPlaces: ref.watch(managerProvider
|
decimalPlaces: coin.decimals,
|
||||||
.select((value) => value.coin.decimals)),
|
|
||||||
)} ${coin.ticker}",
|
)} ${coin.ticker}",
|
||||||
style: STextStyles.titleBold12(context).copyWith(
|
style: STextStyles.titleBold12(context).copyWith(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
@ -275,15 +294,17 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
||||||
),
|
),
|
||||||
if (externalCalls)
|
if (externalCalls)
|
||||||
Text(
|
Text(
|
||||||
"${_cachedFiatValue.localizedStringAsFixed(
|
"${fiatTotal.localizedStringAsFixed(
|
||||||
locale: ref.watch(
|
locale: ref.watch(
|
||||||
localeServiceChangeNotifierProvider
|
localeServiceChangeNotifierProvider.select(
|
||||||
.select((value) => value.locale),
|
(value) => value.locale,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
decimalPlaces: 2,
|
decimalPlaces: 2,
|
||||||
)} ${ref.watch(
|
)} ${ref.watch(
|
||||||
prefsChangeNotifierProvider
|
prefsChangeNotifierProvider.select(
|
||||||
.select((value) => value.currency),
|
(value) => value.currency,
|
||||||
|
),
|
||||||
)}",
|
)}",
|
||||||
style:
|
style:
|
||||||
STextStyles.itemSubtitle12(context).copyWith(
|
STextStyles.itemSubtitle12(context).copyWith(
|
||||||
|
|
|
@ -211,7 +211,6 @@ class _FavoriteWalletsState extends ConsumerState<FavoriteWallets> {
|
||||||
child: FavoriteCard(
|
child: FavoriteCard(
|
||||||
key: Key("favCard_$walletId"),
|
key: Key("favCard_$walletId"),
|
||||||
walletId: walletId!,
|
walletId: walletId!,
|
||||||
managerProvider: managerProvider!,
|
|
||||||
width: cardWidth,
|
width: cardWidth,
|
||||||
height: cardHeight,
|
height: cardHeight,
|
||||||
),
|
),
|
||||||
|
@ -219,7 +218,6 @@ class _FavoriteWalletsState extends ConsumerState<FavoriteWallets> {
|
||||||
: FavoriteCard(
|
: FavoriteCard(
|
||||||
key: Key("favCard_$walletId"),
|
key: Key("favCard_$walletId"),
|
||||||
walletId: walletId!,
|
walletId: walletId!,
|
||||||
managerProvider: managerProvider!,
|
|
||||||
width: cardWidth,
|
width: cardWidth,
|
||||||
height: cardHeight,
|
height: cardHeight,
|
||||||
)
|
)
|
||||||
|
|
|
@ -111,7 +111,7 @@ class _DesktopContactDetailsState extends ConsumerState<DesktopContactDetails> {
|
||||||
width: 32,
|
width: 32,
|
||||||
height: 32,
|
height: 32,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: contact.id == "default"
|
color: contact.customId == "default"
|
||||||
? Colors.transparent
|
? Colors.transparent
|
||||||
: Theme.of(context)
|
: Theme.of(context)
|
||||||
.extension<StackColors>()!
|
.extension<StackColors>()!
|
||||||
|
|
|
@ -288,7 +288,7 @@ class DesktopTradeRowCard extends ConsumerStatefulWidget {
|
||||||
class _DesktopTradeRowCardState extends ConsumerState<DesktopTradeRowCard> {
|
class _DesktopTradeRowCardState extends ConsumerState<DesktopTradeRowCard> {
|
||||||
late final String tradeId;
|
late final String tradeId;
|
||||||
|
|
||||||
String _fetchIconAssetForStatus(String statusString, ThemeAssets assets) {
|
String _fetchIconAssetForStatus(String statusString, IThemeAssets assets) {
|
||||||
ChangeNowTransactionStatus? status;
|
ChangeNowTransactionStatus? status;
|
||||||
try {
|
try {
|
||||||
if (statusString.toLowerCase().startsWith("waiting")) {
|
if (statusString.toLowerCase().startsWith("waiting")) {
|
||||||
|
@ -528,7 +528,7 @@ class _DesktopTradeRowCardState extends ConsumerState<DesktopTradeRowCard> {
|
||||||
_fetchIconAssetForStatus(
|
_fetchIconAssetForStatus(
|
||||||
trade.status,
|
trade.status,
|
||||||
ref.watch(
|
ref.watch(
|
||||||
themeProvider.select((value) => value.assets),
|
themeAssetsProvider,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -63,8 +63,8 @@ class DesktopBuyIcon extends ConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return SvgPicture.asset(
|
return SvgPicture.file(
|
||||||
ref.watch(themeProvider.select((value) => value.assets.buy)),
|
File(ref.watch(themeAssetsProvider).buy),
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
color: DesktopMenuItemId.buy ==
|
color: DesktopMenuItemId.buy ==
|
||||||
|
|
|
@ -74,7 +74,6 @@ class DesktopFavoriteWallets extends ConsumerWidget {
|
||||||
key: Key(walletName),
|
key: Key(walletName),
|
||||||
width: cardWidth,
|
width: cardWidth,
|
||||||
height: cardHeight,
|
height: cardHeight,
|
||||||
managerProvider: managerProvider,
|
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
|
|
|
@ -69,7 +69,7 @@ class _MoreFeaturesDialogState extends ConsumerState<MoreFeaturesDialog> {
|
||||||
_MoreFeaturesItem(
|
_MoreFeaturesItem(
|
||||||
label: "Anonymize funds",
|
label: "Anonymize funds",
|
||||||
detail: "Anonymize funds",
|
detail: "Anonymize funds",
|
||||||
iconAsset: Assets.svg.anonymize,
|
iconAsset: Assets.svg.recycle,
|
||||||
onPressed: () => widget.onAnonymizeAllPressed?.call(),
|
onPressed: () => widget.onAnonymizeAllPressed?.call(),
|
||||||
),
|
),
|
||||||
if (manager.hasWhirlpoolSupport)
|
if (manager.hasWhirlpoolSupport)
|
||||||
|
|
|
@ -252,7 +252,7 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
|
||||||
),
|
),
|
||||||
suffixIcon: UnconstrainedBox(
|
suffixIcon: UnconstrainedBox(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 70,
|
height: 40,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
|
|
@ -118,44 +118,6 @@ class _AppearanceOptionSettings
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// const Padding(
|
|
||||||
// padding: EdgeInsets.all(10.0),
|
|
||||||
// child: Divider(
|
|
||||||
// thickness: 0.5,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// Padding(
|
|
||||||
// padding: const EdgeInsets.all(10.0),
|
|
||||||
// child: Row(
|
|
||||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
// children: [
|
|
||||||
// Text(
|
|
||||||
// "System brightness",
|
|
||||||
// style: STextStyles.desktopTextExtraSmall(context)
|
|
||||||
// .copyWith(
|
|
||||||
// color: Theme.of(context)
|
|
||||||
// .extension<StackColors>()!
|
|
||||||
// .textDark),
|
|
||||||
// textAlign: TextAlign.left,
|
|
||||||
// ),
|
|
||||||
// SizedBox(
|
|
||||||
// height: 20,
|
|
||||||
// width: 40,
|
|
||||||
// child: DraggableSwitchButton(
|
|
||||||
// isOn: ref.watch(
|
|
||||||
// prefsChangeNotifierProvider.select(
|
|
||||||
// (value) => value.enableSystemBrightness),
|
|
||||||
// ),
|
|
||||||
// onValueChanged: (newValue) {
|
|
||||||
// ref
|
|
||||||
// .read(prefsChangeNotifierProvider)
|
|
||||||
// .enableSystemBrightness = newValue;
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.all(10.0),
|
padding: EdgeInsets.all(10.0),
|
||||||
child: Divider(
|
child: Divider(
|
||||||
|
|
|
@ -58,6 +58,8 @@ class _NodesSettings extends ConsumerState<NodesSettings> {
|
||||||
if (Platform.isWindows) {
|
if (Platform.isWindows) {
|
||||||
_coins.remove(Coin.monero);
|
_coins.remove(Coin.monero);
|
||||||
_coins.remove(Coin.wownero);
|
_coins.remove(Coin.wownero);
|
||||||
|
} else if (Platform.isLinux) {
|
||||||
|
_coins.remove(Coin.wownero);
|
||||||
}
|
}
|
||||||
|
|
||||||
searchNodeController = TextEditingController();
|
searchNodeController = TextEditingController();
|
||||||
|
|
|
@ -1342,16 +1342,14 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
final newNode = await getCurrentNode();
|
final newNode = await getCurrentNode();
|
||||||
_cachedElectrumXClient = CachedElectrumX.from(
|
|
||||||
node: newNode,
|
|
||||||
prefs: _prefs,
|
|
||||||
failovers: failovers,
|
|
||||||
);
|
|
||||||
_electrumXClient = ElectrumX.from(
|
_electrumXClient = ElectrumX.from(
|
||||||
node: newNode,
|
node: newNode,
|
||||||
prefs: _prefs,
|
prefs: _prefs,
|
||||||
failovers: failovers,
|
failovers: failovers,
|
||||||
);
|
);
|
||||||
|
_cachedElectrumXClient = CachedElectrumX.from(
|
||||||
|
electrumXClient: _electrumXClient,
|
||||||
|
);
|
||||||
|
|
||||||
if (shouldRefresh) {
|
if (shouldRefresh) {
|
||||||
unawaited(refresh());
|
unawaited(refresh());
|
||||||
|
@ -1468,7 +1466,7 @@ class BitcoinWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
await _secureStore.write(
|
await _secureStore.write(
|
||||||
key: '${_walletId}_mnemonic',
|
key: '${_walletId}_mnemonic',
|
||||||
value: bip39.generateMnemonic(strength: 256));
|
value: bip39.generateMnemonic(strength: 128));
|
||||||
await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: "");
|
await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: "");
|
||||||
|
|
||||||
// Generate and add addresses to relevant arrays
|
// Generate and add addresses to relevant arrays
|
||||||
|
|
|
@ -1234,16 +1234,14 @@ class BitcoinCashWallet extends CoinServiceAPI
|
||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
final newNode = await getCurrentNode();
|
final newNode = await getCurrentNode();
|
||||||
_cachedElectrumXClient = CachedElectrumX.from(
|
|
||||||
node: newNode,
|
|
||||||
prefs: _prefs,
|
|
||||||
failovers: failovers,
|
|
||||||
);
|
|
||||||
_electrumXClient = ElectrumX.from(
|
_electrumXClient = ElectrumX.from(
|
||||||
node: newNode,
|
node: newNode,
|
||||||
prefs: _prefs,
|
prefs: _prefs,
|
||||||
failovers: failovers,
|
failovers: failovers,
|
||||||
);
|
);
|
||||||
|
_cachedElectrumXClient = CachedElectrumX.from(
|
||||||
|
electrumXClient: _electrumXClient,
|
||||||
|
);
|
||||||
|
|
||||||
if (shouldRefresh) {
|
if (shouldRefresh) {
|
||||||
unawaited(refresh());
|
unawaited(refresh());
|
||||||
|
@ -1371,7 +1369,7 @@ class BitcoinCashWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
await _secureStore.write(
|
await _secureStore.write(
|
||||||
key: '${_walletId}_mnemonic',
|
key: '${_walletId}_mnemonic',
|
||||||
value: bip39.generateMnemonic(strength: 256));
|
value: bip39.generateMnemonic(strength: 128));
|
||||||
await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: "");
|
await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: "");
|
||||||
|
|
||||||
// Generate and add addresses to relevant arrays
|
// Generate and add addresses to relevant arrays
|
||||||
|
|
|
@ -7,6 +7,7 @@ import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||||
import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart';
|
import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart';
|
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||||
|
import 'package:stackwallet/services/coins/ecash/ecash_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
|
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart';
|
import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||||
|
@ -55,17 +56,7 @@ abstract class CoinServiceAPI {
|
||||||
prefs: prefs,
|
prefs: prefs,
|
||||||
);
|
);
|
||||||
final cachedClient = CachedElectrumX.from(
|
final cachedClient = CachedElectrumX.from(
|
||||||
node: electrumxNode,
|
electrumXClient: client,
|
||||||
failovers: failovers
|
|
||||||
.map((e) => ElectrumXNode(
|
|
||||||
address: e.host,
|
|
||||||
port: e.port,
|
|
||||||
name: e.name,
|
|
||||||
id: e.id,
|
|
||||||
useSSL: e.useSSL,
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
prefs: prefs,
|
|
||||||
);
|
);
|
||||||
switch (coin) {
|
switch (coin) {
|
||||||
case Coin.firo:
|
case Coin.firo:
|
||||||
|
@ -233,6 +224,17 @@ abstract class CoinServiceAPI {
|
||||||
cachedClient: cachedClient,
|
cachedClient: cachedClient,
|
||||||
tracker: tracker,
|
tracker: tracker,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
case Coin.eCash:
|
||||||
|
return ECashWallet(
|
||||||
|
walletId: walletId,
|
||||||
|
walletName: walletName,
|
||||||
|
coin: coin,
|
||||||
|
secureStore: secureStorageInterface,
|
||||||
|
client: client,
|
||||||
|
cachedClient: cachedClient,
|
||||||
|
tracker: tracker,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1194,16 +1194,14 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
final newNode = await getCurrentNode();
|
final newNode = await getCurrentNode();
|
||||||
_cachedElectrumXClient = CachedElectrumX.from(
|
|
||||||
node: newNode,
|
|
||||||
prefs: _prefs,
|
|
||||||
failovers: failovers,
|
|
||||||
);
|
|
||||||
_electrumXClient = ElectrumX.from(
|
_electrumXClient = ElectrumX.from(
|
||||||
node: newNode,
|
node: newNode,
|
||||||
prefs: _prefs,
|
prefs: _prefs,
|
||||||
failovers: failovers,
|
failovers: failovers,
|
||||||
);
|
);
|
||||||
|
_cachedElectrumXClient = CachedElectrumX.from(
|
||||||
|
electrumXClient: _electrumXClient,
|
||||||
|
);
|
||||||
|
|
||||||
if (shouldRefresh) {
|
if (shouldRefresh) {
|
||||||
unawaited(refresh());
|
unawaited(refresh());
|
||||||
|
@ -1318,7 +1316,7 @@ class DogecoinWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
await _secureStore.write(
|
await _secureStore.write(
|
||||||
key: '${_walletId}_mnemonic',
|
key: '${_walletId}_mnemonic',
|
||||||
value: bip39.generateMnemonic(strength: 256));
|
value: bip39.generateMnemonic(strength: 128));
|
||||||
await _secureStore.write(
|
await _secureStore.write(
|
||||||
key: '${_walletId}_mnemonicPassphrase',
|
key: '${_walletId}_mnemonicPassphrase',
|
||||||
value: "",
|
value: "",
|
||||||
|
|
3137
lib/services/coins/ecash/ecash_wallet.dart
Normal file
3137
lib/services/coins/ecash/ecash_wallet.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -350,7 +350,7 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
||||||
"Attempted to overwrite mnemonic on generate new wallet!");
|
"Attempted to overwrite mnemonic on generate new wallet!");
|
||||||
}
|
}
|
||||||
|
|
||||||
final String mnemonic = bip39.generateMnemonic(strength: 256);
|
final String mnemonic = bip39.generateMnemonic(strength: 128);
|
||||||
await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic);
|
await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic);
|
||||||
await _secureStore.write(
|
await _secureStore.write(
|
||||||
key: '${_walletId}_mnemonicPassphrase',
|
key: '${_walletId}_mnemonicPassphrase',
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1325,16 +1325,14 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
final newNode = await getCurrentNode();
|
final newNode = await getCurrentNode();
|
||||||
_cachedElectrumXClient = CachedElectrumX.from(
|
|
||||||
node: newNode,
|
|
||||||
prefs: _prefs,
|
|
||||||
failovers: failovers,
|
|
||||||
);
|
|
||||||
_electrumXClient = ElectrumX.from(
|
_electrumXClient = ElectrumX.from(
|
||||||
node: newNode,
|
node: newNode,
|
||||||
prefs: _prefs,
|
prefs: _prefs,
|
||||||
failovers: failovers,
|
failovers: failovers,
|
||||||
);
|
);
|
||||||
|
_cachedElectrumXClient = CachedElectrumX.from(
|
||||||
|
electrumXClient: _electrumXClient,
|
||||||
|
);
|
||||||
|
|
||||||
if (shouldRefresh) {
|
if (shouldRefresh) {
|
||||||
unawaited(refresh());
|
unawaited(refresh());
|
||||||
|
@ -1498,7 +1496,7 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
await _secureStore.write(
|
await _secureStore.write(
|
||||||
key: '${_walletId}_mnemonic',
|
key: '${_walletId}_mnemonic',
|
||||||
value: bip39.generateMnemonic(strength: 256));
|
value: bip39.generateMnemonic(strength: 128));
|
||||||
await _secureStore.write(
|
await _secureStore.write(
|
||||||
key: '${_walletId}_mnemonicPassphrase',
|
key: '${_walletId}_mnemonicPassphrase',
|
||||||
value: "",
|
value: "",
|
||||||
|
@ -1793,6 +1791,18 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
coin: coin,
|
coin: coin,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bool shouldBlock = false;
|
||||||
|
String? blockReason;
|
||||||
|
String? label;
|
||||||
|
|
||||||
|
final utxoAmount = jsonUTXO["value"] as int;
|
||||||
|
|
||||||
|
if (utxoAmount <= 10000) {
|
||||||
|
shouldBlock = true;
|
||||||
|
blockReason = "May contain ordinal";
|
||||||
|
label = "Possible ordinal";
|
||||||
|
}
|
||||||
|
|
||||||
final vout = jsonUTXO["tx_pos"] as int;
|
final vout = jsonUTXO["tx_pos"] as int;
|
||||||
|
|
||||||
final outputs = txn["vout"] as List;
|
final outputs = txn["vout"] as List;
|
||||||
|
@ -1811,10 +1821,10 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
txid: txn["txid"] as String,
|
txid: txn["txid"] as String,
|
||||||
vout: vout,
|
vout: vout,
|
||||||
value: jsonUTXO["value"] as int,
|
value: utxoAmount,
|
||||||
name: "",
|
name: label ?? "",
|
||||||
isBlocked: false,
|
isBlocked: shouldBlock,
|
||||||
blockedReason: null,
|
blockedReason: blockReason,
|
||||||
isCoinbase: txn["is_coinbase"] as bool? ?? false,
|
isCoinbase: txn["is_coinbase"] as bool? ?? false,
|
||||||
blockHash: txn["blockhash"] as String?,
|
blockHash: txn["blockhash"] as String?,
|
||||||
blockHeight: jsonUTXO["height"] as int?,
|
blockHeight: jsonUTXO["height"] as int?,
|
||||||
|
@ -1826,16 +1836,20 @@ class LitecoinWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logging.instance
|
Logging.instance.log(
|
||||||
.log('Outputs fetched: $outputArray', level: LogLevel.Info);
|
'Outputs fetched: $outputArray',
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
|
||||||
await db.updateUTXOs(walletId, outputArray);
|
await db.updateUTXOs(walletId, outputArray);
|
||||||
|
|
||||||
// finally update balance
|
// finally update balance
|
||||||
await _updateBalance();
|
await _updateBalance();
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logging.instance
|
Logging.instance.log(
|
||||||
.log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error);
|
"Output fetch unsuccessful: $e\n$s",
|
||||||
|
level: LogLevel.Error,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1314,16 +1314,14 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
final newNode = await getCurrentNode();
|
final newNode = await getCurrentNode();
|
||||||
_cachedElectrumXClient = CachedElectrumX.from(
|
|
||||||
node: newNode,
|
|
||||||
prefs: _prefs,
|
|
||||||
failovers: failovers,
|
|
||||||
);
|
|
||||||
_electrumXClient = ElectrumX.from(
|
_electrumXClient = ElectrumX.from(
|
||||||
node: newNode,
|
node: newNode,
|
||||||
prefs: _prefs,
|
prefs: _prefs,
|
||||||
failovers: failovers,
|
failovers: failovers,
|
||||||
);
|
);
|
||||||
|
_cachedElectrumXClient = CachedElectrumX.from(
|
||||||
|
electrumXClient: _electrumXClient,
|
||||||
|
);
|
||||||
|
|
||||||
if (shouldRefresh) {
|
if (shouldRefresh) {
|
||||||
unawaited(refresh());
|
unawaited(refresh());
|
||||||
|
@ -1479,7 +1477,7 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
await _secureStore.write(
|
await _secureStore.write(
|
||||||
key: '${_walletId}_mnemonic',
|
key: '${_walletId}_mnemonic',
|
||||||
value: bip39.generateMnemonic(strength: 256));
|
value: bip39.generateMnemonic(strength: 128));
|
||||||
await _secureStore.write(
|
await _secureStore.write(
|
||||||
key: '${_walletId}_mnemonicPassphrase',
|
key: '${_walletId}_mnemonicPassphrase',
|
||||||
value: "",
|
value: "",
|
||||||
|
@ -2803,19 +2801,18 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
|
|
||||||
// Add transaction output
|
// Add transaction output
|
||||||
for (var i = 0; i < recipients.length; i++) {
|
for (var i = 0; i < recipients.length; i++) {
|
||||||
txb.addOutput(recipients[i], satoshiAmounts[i], namecoin.bech32!);
|
txb.addOutput(recipients[i], satoshiAmounts[i], _network.bech32!);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Sign the transaction accordingly
|
// Sign the transaction accordingly
|
||||||
for (var i = 0; i < utxoSigningData.length; i++) {
|
for (var i = 0; i < utxoSigningData.length; i++) {
|
||||||
final txid = utxoSigningData[i].utxo.txid;
|
txb.sign(
|
||||||
txb.addInput(
|
vin: i,
|
||||||
txid,
|
keyPair: utxoSigningData[i].keyPair!,
|
||||||
utxoSigningData[i].utxo.vout,
|
witnessValue: utxoSigningData[i].utxo.value,
|
||||||
null,
|
redeemScript: utxoSigningData[i].redeemScript,
|
||||||
utxoSigningData[i].output!,
|
overridePrefix: _network.bech32!,
|
||||||
_network.bech32!,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
@ -2824,7 +2821,7 @@ class NamecoinWallet extends CoinServiceAPI
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
|
||||||
final builtTx = txb.build(namecoin.bech32!);
|
final builtTx = txb.build(_network.bech32!);
|
||||||
final vSize = builtTx.virtualSize();
|
final vSize = builtTx.virtualSize();
|
||||||
|
|
||||||
return {"hex": builtTx.toHex(), "vSize": vSize};
|
return {"hex": builtTx.toHex(), "vSize": vSize};
|
||||||
|
|
|
@ -1242,16 +1242,14 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
final newNode = await getCurrentNode();
|
final newNode = await getCurrentNode();
|
||||||
_cachedElectrumXClient = CachedElectrumX.from(
|
|
||||||
node: newNode,
|
|
||||||
prefs: _prefs,
|
|
||||||
failovers: failovers,
|
|
||||||
);
|
|
||||||
_electrumXClient = ElectrumX.from(
|
_electrumXClient = ElectrumX.from(
|
||||||
node: newNode,
|
node: newNode,
|
||||||
prefs: _prefs,
|
prefs: _prefs,
|
||||||
failovers: failovers,
|
failovers: failovers,
|
||||||
);
|
);
|
||||||
|
_cachedElectrumXClient = CachedElectrumX.from(
|
||||||
|
electrumXClient: _electrumXClient,
|
||||||
|
);
|
||||||
|
|
||||||
if (shouldRefresh) {
|
if (shouldRefresh) {
|
||||||
unawaited(refresh());
|
unawaited(refresh());
|
||||||
|
@ -1394,7 +1392,7 @@ class ParticlWallet extends CoinServiceAPI
|
||||||
}
|
}
|
||||||
await _secureStore.write(
|
await _secureStore.write(
|
||||||
key: '${_walletId}_mnemonic',
|
key: '${_walletId}_mnemonic',
|
||||||
value: bip39.generateMnemonic(strength: 256));
|
value: bip39.generateMnemonic(strength: 128));
|
||||||
await _secureStore.write(
|
await _secureStore.write(
|
||||||
key: '${_walletId}_mnemonicPassphrase',
|
key: '${_walletId}_mnemonicPassphrase',
|
||||||
value: "",
|
value: "",
|
||||||
|
|
|
@ -148,19 +148,31 @@ mixin ElectrumXParsing {
|
||||||
type = TransactionType.outgoing;
|
type = TransactionType.outgoing;
|
||||||
amount = amountSentFromWallet - changeAmount - fee;
|
amount = amountSentFromWallet - changeAmount - fee;
|
||||||
|
|
||||||
final possible =
|
// non wallet addresses found in tx outputs
|
||||||
outputAddresses.difference(myChangeReceivedOnAddresses).first;
|
final nonWalletOutAddresses = outputAddresses.difference(
|
||||||
|
myChangeReceivedOnAddresses,
|
||||||
|
);
|
||||||
|
|
||||||
if (transactionAddress.value != possible) {
|
if (nonWalletOutAddresses.isNotEmpty) {
|
||||||
transactionAddress = Address(
|
final possible = nonWalletOutAddresses.first;
|
||||||
walletId: walletId,
|
|
||||||
value: possible,
|
if (transactionAddress.value != possible) {
|
||||||
derivationIndex: -1,
|
transactionAddress = Address(
|
||||||
derivationPath: null,
|
walletId: walletId,
|
||||||
subType: AddressSubType.nonWallet,
|
value: possible,
|
||||||
type: AddressType.nonWallet,
|
derivationIndex: -1,
|
||||||
publicKey: [],
|
derivationPath: null,
|
||||||
);
|
subType: AddressSubType.nonWallet,
|
||||||
|
type: AddressType.nonWallet,
|
||||||
|
publicKey: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// some other type of tx where the receiving address is
|
||||||
|
// one of my change addresses
|
||||||
|
|
||||||
|
type = TransactionType.sentToSelf;
|
||||||
|
amount = changeAmount;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// incoming tx
|
// incoming tx
|
||||||
|
|
|
@ -35,9 +35,10 @@ mixin FiroHive {
|
||||||
}
|
}
|
||||||
|
|
||||||
// mintIndex
|
// mintIndex
|
||||||
int? firoGetMintIndex() {
|
int firoGetMintIndex() {
|
||||||
return DB.instance.get<dynamic>(boxName: _walletId, key: "mintIndex")
|
return DB.instance.get<dynamic>(boxName: _walletId, key: "mintIndex")
|
||||||
as int?;
|
as int? ??
|
||||||
|
0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> firoUpdateMintIndex(int mintIndex) async {
|
Future<void> firoUpdateMintIndex(int mintIndex) async {
|
||||||
|
|
|
@ -89,7 +89,7 @@ class PriceAPI {
|
||||||
final uri =
|
final uri =
|
||||||
Uri.parse("https://api.coingecko.com/api/v3/coins/markets?vs_currency"
|
Uri.parse("https://api.coingecko.com/api/v3/coins/markets?vs_currency"
|
||||||
"=${baseCurrency.toLowerCase()}"
|
"=${baseCurrency.toLowerCase()}"
|
||||||
"&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,"
|
"&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,"
|
||||||
"bitcoin-cash,namecoin,wownero,ethereum,particl"
|
"bitcoin-cash,namecoin,wownero,ethereum,particl"
|
||||||
"&order=market_cap_desc&per_page=50&page=1&sparkline=false");
|
"&order=market_cap_desc&per_page=50&page=1&sparkline=false");
|
||||||
|
|
||||||
|
|
|
@ -1,36 +1,44 @@
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:stackwallet/models/isar/stack_theme.dart';
|
||||||
import 'package:stackwallet/themes/theme_providers.dart';
|
import 'package:stackwallet/themes/theme_providers.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
|
||||||
final coinIconProvider = Provider.family<String, Coin>((ref, coin) {
|
final coinIconProvider = Provider.family<String, Coin>((ref, coin) {
|
||||||
final assets = ref.watch(themeProvider).assets;
|
final assets = ref.watch(themeAssetsProvider);
|
||||||
switch (coin) {
|
|
||||||
case Coin.bitcoin:
|
if (assets is ThemeAssets) {
|
||||||
case Coin.bitcoinTestNet:
|
switch (coin) {
|
||||||
return assets.bitcoin;
|
case Coin.bitcoin:
|
||||||
case Coin.litecoin:
|
case Coin.bitcoinTestNet:
|
||||||
case Coin.litecoinTestNet:
|
return assets.bitcoin;
|
||||||
return assets.litecoin;
|
case Coin.litecoin:
|
||||||
case Coin.bitcoincash:
|
case Coin.litecoinTestNet:
|
||||||
case Coin.bitcoincashTestnet:
|
return assets.litecoin;
|
||||||
return assets.bitcoincash;
|
case Coin.bitcoincash:
|
||||||
case Coin.dogecoin:
|
case Coin.bitcoincashTestnet:
|
||||||
case Coin.dogecoinTestNet:
|
return assets.bitcoincash;
|
||||||
return assets.dogecoin;
|
case Coin.dogecoin:
|
||||||
case Coin.epicCash:
|
case Coin.dogecoinTestNet:
|
||||||
return assets.epicCash;
|
return assets.dogecoin;
|
||||||
case Coin.firo:
|
case Coin.eCash:
|
||||||
case Coin.firoTestNet:
|
return assets.bitcoin;
|
||||||
return assets.firo;
|
case Coin.epicCash:
|
||||||
case Coin.monero:
|
return assets.epicCash;
|
||||||
return assets.monero;
|
case Coin.firo:
|
||||||
case Coin.wownero:
|
case Coin.firoTestNet:
|
||||||
return assets.wownero;
|
return assets.firo;
|
||||||
case Coin.namecoin:
|
case Coin.monero:
|
||||||
return assets.namecoin;
|
return assets.monero;
|
||||||
case Coin.particl:
|
case Coin.wownero:
|
||||||
return assets.particl;
|
return assets.wownero;
|
||||||
case Coin.ethereum:
|
case Coin.namecoin:
|
||||||
return assets.ethereum;
|
return assets.namecoin;
|
||||||
|
case Coin.particl:
|
||||||
|
return assets.particl;
|
||||||
|
case Coin.ethereum:
|
||||||
|
return assets.ethereum;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (assets as ThemeAssetsV2).coinIcons[coin.mainNetVersion]!;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,77 +1,92 @@
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:stackwallet/models/isar/stack_theme.dart';
|
||||||
import 'package:stackwallet/themes/theme_providers.dart';
|
import 'package:stackwallet/themes/theme_providers.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
|
|
||||||
final coinImageProvider = Provider.family<String, Coin>((ref, coin) {
|
final coinImageProvider = Provider.family<String, Coin>((ref, coin) {
|
||||||
final assets = ref.watch(themeProvider).assets;
|
final assets = ref.watch(themeAssetsProvider);
|
||||||
switch (coin) {
|
|
||||||
case Coin.bitcoin:
|
if (assets is ThemeAssets) {
|
||||||
return assets.bitcoinImage;
|
switch (coin) {
|
||||||
case Coin.litecoin:
|
case Coin.bitcoin:
|
||||||
case Coin.litecoinTestNet:
|
return assets.bitcoinImage;
|
||||||
return assets.litecoinImage;
|
case Coin.litecoin:
|
||||||
case Coin.bitcoincash:
|
case Coin.litecoinTestNet:
|
||||||
return assets.bitcoincashImage;
|
return assets.litecoinImage;
|
||||||
case Coin.dogecoin:
|
case Coin.bitcoincash:
|
||||||
return assets.dogecoinImage;
|
return assets.bitcoincashImage;
|
||||||
case Coin.epicCash:
|
case Coin.dogecoin:
|
||||||
return assets.epicCashImage;
|
return assets.dogecoinImage;
|
||||||
case Coin.firo:
|
case Coin.eCash:
|
||||||
return assets.firoImage;
|
return assets.bitcoinImage;
|
||||||
case Coin.monero:
|
case Coin.epicCash:
|
||||||
return assets.moneroImage;
|
return assets.epicCashImage;
|
||||||
case Coin.wownero:
|
case Coin.firo:
|
||||||
return assets.wowneroImage;
|
return assets.firoImage;
|
||||||
case Coin.namecoin:
|
case Coin.monero:
|
||||||
return assets.namecoinImage;
|
return assets.moneroImage;
|
||||||
case Coin.particl:
|
case Coin.wownero:
|
||||||
return assets.particlImage;
|
return assets.wowneroImage;
|
||||||
case Coin.bitcoinTestNet:
|
case Coin.namecoin:
|
||||||
return assets.bitcoinImage;
|
return assets.namecoinImage;
|
||||||
case Coin.bitcoincashTestnet:
|
case Coin.particl:
|
||||||
return assets.bitcoincashImage;
|
return assets.particlImage;
|
||||||
case Coin.firoTestNet:
|
case Coin.bitcoinTestNet:
|
||||||
return assets.firoImage;
|
return assets.bitcoinImage;
|
||||||
case Coin.dogecoinTestNet:
|
case Coin.bitcoincashTestnet:
|
||||||
return assets.dogecoinImage;
|
return assets.bitcoincashImage;
|
||||||
case Coin.ethereum:
|
case Coin.firoTestNet:
|
||||||
return assets.ethereumImage;
|
return assets.firoImage;
|
||||||
|
case Coin.dogecoinTestNet:
|
||||||
|
return assets.dogecoinImage;
|
||||||
|
case Coin.ethereum:
|
||||||
|
return assets.ethereumImage;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (assets as ThemeAssetsV2).coinImages[coin.mainNetVersion]!;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final coinImageSecondaryProvider = Provider.family<String, Coin>((ref, coin) {
|
final coinImageSecondaryProvider = Provider.family<String, Coin>((ref, coin) {
|
||||||
final assets = ref.watch(themeProvider).assets;
|
final assets = ref.watch(themeAssetsProvider);
|
||||||
switch (coin) {
|
|
||||||
case Coin.bitcoin:
|
if (assets is ThemeAssets) {
|
||||||
return assets.bitcoinImageSecondary;
|
switch (coin) {
|
||||||
case Coin.litecoin:
|
case Coin.bitcoin:
|
||||||
case Coin.litecoinTestNet:
|
return assets.bitcoinImageSecondary;
|
||||||
return assets.litecoinImageSecondary;
|
case Coin.litecoin:
|
||||||
case Coin.bitcoincash:
|
case Coin.litecoinTestNet:
|
||||||
return assets.bitcoincashImageSecondary;
|
return assets.litecoinImageSecondary;
|
||||||
case Coin.dogecoin:
|
case Coin.bitcoincash:
|
||||||
return assets.dogecoinImageSecondary;
|
return assets.bitcoincashImageSecondary;
|
||||||
case Coin.epicCash:
|
case Coin.dogecoin:
|
||||||
return assets.epicCashImageSecondary;
|
return assets.dogecoinImageSecondary;
|
||||||
case Coin.firo:
|
case Coin.eCash:
|
||||||
return assets.firoImageSecondary;
|
return assets.bitcoinImageSecondary;
|
||||||
case Coin.monero:
|
case Coin.epicCash:
|
||||||
return assets.moneroImageSecondary;
|
return assets.epicCashImageSecondary;
|
||||||
case Coin.wownero:
|
case Coin.firo:
|
||||||
return assets.wowneroImageSecondary;
|
return assets.firoImageSecondary;
|
||||||
case Coin.namecoin:
|
case Coin.monero:
|
||||||
return assets.namecoinImageSecondary;
|
return assets.moneroImageSecondary;
|
||||||
case Coin.particl:
|
case Coin.wownero:
|
||||||
return assets.particlImageSecondary;
|
return assets.wowneroImageSecondary;
|
||||||
case Coin.bitcoinTestNet:
|
case Coin.namecoin:
|
||||||
return assets.bitcoinImageSecondary;
|
return assets.namecoinImageSecondary;
|
||||||
case Coin.bitcoincashTestnet:
|
case Coin.particl:
|
||||||
return assets.bitcoincashImageSecondary;
|
return assets.particlImageSecondary;
|
||||||
case Coin.firoTestNet:
|
case Coin.bitcoinTestNet:
|
||||||
return assets.firoImageSecondary;
|
return assets.bitcoinImageSecondary;
|
||||||
case Coin.dogecoinTestNet:
|
case Coin.bitcoincashTestnet:
|
||||||
return assets.dogecoinImageSecondary;
|
return assets.bitcoincashImageSecondary;
|
||||||
case Coin.ethereum:
|
case Coin.firoTestNet:
|
||||||
return assets.ethereumImageSecondary;
|
return assets.firoImageSecondary;
|
||||||
|
case Coin.dogecoinTestNet:
|
||||||
|
return assets.dogecoinImageSecondary;
|
||||||
|
case Coin.ethereum:
|
||||||
|
return assets.ethereumImageSecondary;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (assets as ThemeAssetsV2).coinSecondaryImages[coin.mainNetVersion]!;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,6 +12,7 @@ class CoinThemeColorDefault {
|
||||||
Color get firo => const Color(0xFFFF897A);
|
Color get firo => const Color(0xFFFF897A);
|
||||||
Color get dogecoin => const Color(0xFFFFE079);
|
Color get dogecoin => const Color(0xFFFFE079);
|
||||||
Color get epicCash => const Color(0xFFC5C7CB);
|
Color get epicCash => const Color(0xFFC5C7CB);
|
||||||
|
Color get eCash => const Color(0xFFC5C7CB);
|
||||||
Color get ethereum => const Color(0xFFA7ADE9);
|
Color get ethereum => const Color(0xFFA7ADE9);
|
||||||
Color get monero => const Color(0xFFFF9E6B);
|
Color get monero => const Color(0xFFFF9E6B);
|
||||||
Color get namecoin => const Color(0xFF91B1E1);
|
Color get namecoin => const Color(0xFF91B1E1);
|
||||||
|
@ -32,6 +33,8 @@ class CoinThemeColorDefault {
|
||||||
case Coin.dogecoin:
|
case Coin.dogecoin:
|
||||||
case Coin.dogecoinTestNet:
|
case Coin.dogecoinTestNet:
|
||||||
return dogecoin;
|
return dogecoin;
|
||||||
|
case Coin.eCash:
|
||||||
|
return eCash;
|
||||||
case Coin.epicCash:
|
case Coin.epicCash:
|
||||||
return epicCash;
|
return epicCash;
|
||||||
case Coin.ethereum:
|
case Coin.ethereum:
|
||||||
|
|
|
@ -1682,6 +1682,8 @@ class StackColors extends ThemeExtension<StackColors> {
|
||||||
return _coin.dogecoin;
|
return _coin.dogecoin;
|
||||||
case Coin.epicCash:
|
case Coin.epicCash:
|
||||||
return _coin.epicCash;
|
return _coin.epicCash;
|
||||||
|
case Coin.eCash:
|
||||||
|
return _coin.eCash;
|
||||||
case Coin.ethereum:
|
case Coin.ethereum:
|
||||||
return _coin.ethereum;
|
return _coin.ethereum;
|
||||||
case Coin.firo:
|
case Coin.firo:
|
||||||
|
|
|
@ -20,3 +20,11 @@ final themeProvider = StateProvider<StackTheme>(
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final themeAssetsProvider = StateProvider<IThemeAssets>(
|
||||||
|
(ref) => ref.watch(
|
||||||
|
themeProvider.select(
|
||||||
|
(value) => value.assets,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:archive/archive_io.dart';
|
import 'package:archive/archive_io.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
@ -17,6 +17,7 @@ final pThemeService = Provider<ThemeService>((ref) {
|
||||||
});
|
});
|
||||||
|
|
||||||
class ThemeService {
|
class ThemeService {
|
||||||
|
static const _currentDefaultThemeVersion = 2;
|
||||||
ThemeService._();
|
ThemeService._();
|
||||||
static ThemeService? _instance;
|
static ThemeService? _instance;
|
||||||
static ThemeService get instance => _instance ??= ThemeService._();
|
static ThemeService get instance => _instance ??= ThemeService._();
|
||||||
|
@ -92,6 +93,70 @@ class ThemeService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> checkDefaultThemesOnStartup() async {
|
||||||
|
// install default themes
|
||||||
|
if (!(await ThemeService.instance.verifyInstalled(themeId: "light"))) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"Installing default light theme...",
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
final lightZip = await rootBundle.load("assets/default_themes/light.zip");
|
||||||
|
await ThemeService.instance
|
||||||
|
.install(themeArchiveData: lightZip.buffer.asUint8List());
|
||||||
|
Logging.instance.log(
|
||||||
|
"Installing default light theme... finished",
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// check installed version
|
||||||
|
final theme = ThemeService.instance.getTheme(themeId: "light");
|
||||||
|
if ((theme?.version ?? 1) < _currentDefaultThemeVersion) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"Updating default light theme...",
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
final lightZip =
|
||||||
|
await rootBundle.load("assets/default_themes/light.zip");
|
||||||
|
await ThemeService.instance
|
||||||
|
.install(themeArchiveData: lightZip.buffer.asUint8List());
|
||||||
|
Logging.instance.log(
|
||||||
|
"Updating default light theme... finished",
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await ThemeService.instance.verifyInstalled(themeId: "dark"))) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"Installing default dark theme... ",
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
final darkZip = await rootBundle.load("assets/default_themes/dark.zip");
|
||||||
|
await ThemeService.instance
|
||||||
|
.install(themeArchiveData: darkZip.buffer.asUint8List());
|
||||||
|
Logging.instance.log(
|
||||||
|
"Installing default dark theme... finished",
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// check installed version
|
||||||
|
final theme = ThemeService.instance.getTheme(themeId: "dark");
|
||||||
|
if ((theme?.version ?? 1) < _currentDefaultThemeVersion) {
|
||||||
|
Logging.instance.log(
|
||||||
|
"Updating default dark theme...",
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
final darkZip = await rootBundle.load("assets/default_themes/dark.zip");
|
||||||
|
await ThemeService.instance
|
||||||
|
.install(themeArchiveData: darkZip.buffer.asUint8List());
|
||||||
|
Logging.instance.log(
|
||||||
|
"Updating default dark theme... finished",
|
||||||
|
level: LogLevel.Info,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO more thorough check/verification of theme
|
// TODO more thorough check/verification of theme
|
||||||
Future<bool> verifyInstalled({required String themeId}) async {
|
Future<bool> verifyInstalled({required String themeId}) async {
|
||||||
final dbHasTheme =
|
final dbHasTheme =
|
||||||
|
@ -175,6 +240,7 @@ class ThemeService {
|
||||||
class StackThemeMetaData {
|
class StackThemeMetaData {
|
||||||
final String name;
|
final String name;
|
||||||
final String id;
|
final String id;
|
||||||
|
final int version;
|
||||||
final String sha256;
|
final String sha256;
|
||||||
final String size;
|
final String size;
|
||||||
final String previewImageUrl;
|
final String previewImageUrl;
|
||||||
|
@ -182,6 +248,7 @@ class StackThemeMetaData {
|
||||||
StackThemeMetaData({
|
StackThemeMetaData({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.id,
|
required this.id,
|
||||||
|
required this.version,
|
||||||
required this.sha256,
|
required this.sha256,
|
||||||
required this.size,
|
required this.size,
|
||||||
required this.previewImageUrl,
|
required this.previewImageUrl,
|
||||||
|
@ -192,6 +259,7 @@ class StackThemeMetaData {
|
||||||
return StackThemeMetaData(
|
return StackThemeMetaData(
|
||||||
name: map["name"] as String,
|
name: map["name"] as String,
|
||||||
id: map["id"] as String,
|
id: map["id"] as String,
|
||||||
|
version: map["version"] as int? ?? 1,
|
||||||
sha256: map["sha256"] as String,
|
sha256: map["sha256"] as String,
|
||||||
size: map["size"] as String,
|
size: map["size"] as String,
|
||||||
previewImageUrl: map["previewImageUrl"] as String,
|
previewImageUrl: map["previewImageUrl"] as String,
|
||||||
|
@ -210,6 +278,7 @@ class StackThemeMetaData {
|
||||||
return "$runtimeType("
|
return "$runtimeType("
|
||||||
"name: $name, "
|
"name: $name, "
|
||||||
"id: $id, "
|
"id: $id, "
|
||||||
|
"version: $version, "
|
||||||
"sha256: $sha256, "
|
"sha256: $sha256, "
|
||||||
"size: $size, "
|
"size: $size, "
|
||||||
"previewImageUrl: $previewImageUrl"
|
"previewImageUrl: $previewImageUrl"
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:crypto/crypto.dart';
|
||||||
import 'package:flutter_libepiccash/epic_cash.dart';
|
import 'package:flutter_libepiccash/epic_cash.dart';
|
||||||
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart';
|
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
|
||||||
|
import 'package:stackwallet/services/coins/ecash/ecash_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/litecoin/litecoin_wallet.dart';
|
import 'package:stackwallet/services/coins/litecoin/litecoin_wallet.dart';
|
||||||
import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart';
|
import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart';
|
||||||
|
@ -61,6 +62,8 @@ class AddressUtils {
|
||||||
return true; //TODO - validate ETH address
|
return true; //TODO - validate ETH address
|
||||||
case Coin.firo:
|
case Coin.firo:
|
||||||
return Address.validateAddress(address, firoNetwork);
|
return Address.validateAddress(address, firoNetwork);
|
||||||
|
case Coin.eCash:
|
||||||
|
return Address.validateAddress(address, eCashNetwork);
|
||||||
case Coin.monero:
|
case Coin.monero:
|
||||||
return RegExp("[a-zA-Z0-9]{95}").hasMatch(address) ||
|
return RegExp("[a-zA-Z0-9]{95}").hasMatch(address) ||
|
||||||
RegExp("[a-zA-Z0-9]{106}").hasMatch(address);
|
RegExp("[a-zA-Z0-9]{106}").hasMatch(address);
|
||||||
|
|
|
@ -165,6 +165,7 @@ class _SVG {
|
||||||
String get questionMessage => "assets/svg/message-question.svg";
|
String get questionMessage => "assets/svg/message-question.svg";
|
||||||
String get envelope => "assets/svg/envelope.svg";
|
String get envelope => "assets/svg/envelope.svg";
|
||||||
String get share => "assets/svg/share-2.svg";
|
String get share => "assets/svg/share-2.svg";
|
||||||
|
String get recycle => "assets/svg/anonymize.svg";
|
||||||
String get anonymize => "assets/svg/tx-icon-anonymize.svg";
|
String get anonymize => "assets/svg/tx-icon-anonymize.svg";
|
||||||
String get anonymizePending => "assets/svg/tx-icon-anonymize-pending.svg";
|
String get anonymizePending => "assets/svg/tx-icon-anonymize-pending.svg";
|
||||||
String get anonymizeFailed => "assets/svg/tx-icon-anonymize-failed.svg";
|
String get anonymizeFailed => "assets/svg/tx-icon-anonymize-failed.svg";
|
||||||
|
@ -193,7 +194,7 @@ class _SVG {
|
||||||
String get exchange3 => "assets/svg/exchange-3.svg";
|
String get exchange3 => "assets/svg/exchange-3.svg";
|
||||||
String get messageQuestion => "assets/svg/message-question-1.svg";
|
String get messageQuestion => "assets/svg/message-question-1.svg";
|
||||||
String get list => "assets/svg/list-ul.svg";
|
String get list => "assets/svg/list-ul.svg";
|
||||||
String get unclaimedPaynym => "assets/svg/unclaimed.png";
|
String get unclaimedPaynym => "assets/svg/unclaimed.svg";
|
||||||
|
|
||||||
String get trocadorRatingA => "assets/svg/trocador_rating_a.svg";
|
String get trocadorRatingA => "assets/svg/trocador_rating_a.svg";
|
||||||
String get trocadorRatingB => "assets/svg/trocador_rating_b.svg";
|
String get trocadorRatingB => "assets/svg/trocador_rating_b.svg";
|
||||||
|
|
|
@ -17,6 +17,8 @@ Uri getDefaultBlockExplorerUrlFor({
|
||||||
return Uri.parse("https://mempool.space/testnet/tx/$txid");
|
return Uri.parse("https://mempool.space/testnet/tx/$txid");
|
||||||
case Coin.dogecoin:
|
case Coin.dogecoin:
|
||||||
return Uri.parse("https://chain.so/tx/DOGE/$txid");
|
return Uri.parse("https://chain.so/tx/DOGE/$txid");
|
||||||
|
case Coin.eCash:
|
||||||
|
return Uri.parse("https://explorer.bitcoinabc.org/tx/$txid");
|
||||||
case Coin.dogecoinTestNet:
|
case Coin.dogecoinTestNet:
|
||||||
return Uri.parse("https://chain.so/tx/DOGETEST/$txid");
|
return Uri.parse("https://chain.so/tx/DOGETEST/$txid");
|
||||||
case Coin.epicCash:
|
case Coin.epicCash:
|
||||||
|
|
|
@ -26,6 +26,7 @@ abstract class Constants {
|
||||||
// // true; // true for development,
|
// // true; // true for development,
|
||||||
|
|
||||||
static const int _satsPerCoinEthereum = 1000000000000000000;
|
static const int _satsPerCoinEthereum = 1000000000000000000;
|
||||||
|
static const int _satsPerCoinECash = 100;
|
||||||
static const int _satsPerCoinMonero = 1000000000000;
|
static const int _satsPerCoinMonero = 1000000000000;
|
||||||
static const int _satsPerCoinWownero = 100000000000;
|
static const int _satsPerCoinWownero = 100000000000;
|
||||||
static const int _satsPerCoin = 100000000;
|
static const int _satsPerCoin = 100000000;
|
||||||
|
@ -33,12 +34,11 @@ abstract class Constants {
|
||||||
static const int _decimalPlacesWownero = 11;
|
static const int _decimalPlacesWownero = 11;
|
||||||
static const int _decimalPlacesMonero = 12;
|
static const int _decimalPlacesMonero = 12;
|
||||||
static const int _decimalPlacesEthereum = 18;
|
static const int _decimalPlacesEthereum = 18;
|
||||||
|
static const int _decimalPlacesECash = 2;
|
||||||
|
|
||||||
static const int notificationsMax = 0xFFFFFFFF;
|
static const int notificationsMax = 0xFFFFFFFF;
|
||||||
static const Duration networkAliveTimerDuration = Duration(seconds: 10);
|
static const Duration networkAliveTimerDuration = Duration(seconds: 10);
|
||||||
|
|
||||||
static const int pinLength = 4;
|
|
||||||
|
|
||||||
// Enable Logger.print statements
|
// Enable Logger.print statements
|
||||||
static const bool disableLogger = false;
|
static const bool disableLogger = false;
|
||||||
|
|
||||||
|
@ -71,6 +71,9 @@ abstract class Constants {
|
||||||
|
|
||||||
case Coin.ethereum:
|
case Coin.ethereum:
|
||||||
return _satsPerCoinEthereum;
|
return _satsPerCoinEthereum;
|
||||||
|
|
||||||
|
case Coin.eCash:
|
||||||
|
return _satsPerCoinECash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +102,9 @@ abstract class Constants {
|
||||||
|
|
||||||
case Coin.ethereum:
|
case Coin.ethereum:
|
||||||
return _decimalPlacesEthereum;
|
return _decimalPlacesEthereum;
|
||||||
|
|
||||||
|
case Coin.eCash:
|
||||||
|
return _decimalPlacesECash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,6 +121,7 @@ abstract class Constants {
|
||||||
case Coin.bitcoinTestNet:
|
case Coin.bitcoinTestNet:
|
||||||
case Coin.dogecoinTestNet:
|
case Coin.dogecoinTestNet:
|
||||||
case Coin.firoTestNet:
|
case Coin.firoTestNet:
|
||||||
|
case Coin.eCash:
|
||||||
case Coin.epicCash:
|
case Coin.epicCash:
|
||||||
case Coin.ethereum:
|
case Coin.ethereum:
|
||||||
case Coin.namecoin:
|
case Coin.namecoin:
|
||||||
|
@ -137,10 +144,9 @@ abstract class Constants {
|
||||||
switch (coin) {
|
switch (coin) {
|
||||||
case Coin.bitcoin:
|
case Coin.bitcoin:
|
||||||
case Coin.bitcoinTestNet:
|
case Coin.bitcoinTestNet:
|
||||||
return 600;
|
|
||||||
|
|
||||||
case Coin.bitcoincash:
|
case Coin.bitcoincash:
|
||||||
case Coin.bitcoincashTestnet:
|
case Coin.bitcoincashTestnet:
|
||||||
|
case Coin.eCash:
|
||||||
return 600;
|
return 600;
|
||||||
|
|
||||||
case Coin.dogecoin:
|
case Coin.dogecoin:
|
||||||
|
@ -175,7 +181,7 @@ abstract class Constants {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const int seedPhraseWordCountBip39 = 24;
|
static const int seedPhraseWordCountBip39 = 12;
|
||||||
static const int seedPhraseWordCountMonero = 25;
|
static const int seedPhraseWordCountMonero = 25;
|
||||||
|
|
||||||
static const Map<int, String> monthMapShort = {
|
static const Map<int, String> monthMapShort = {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import 'package:stackwallet/models/node_model.dart';
|
import 'package:stackwallet/models/node_model.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
// import 'package:web3dart/browser.dart';
|
|
||||||
|
|
||||||
abstract class DefaultNodes {
|
abstract class DefaultNodes {
|
||||||
static const String defaultNodeIdPrefix = "default_";
|
static const String defaultNodeIdPrefix = "default_";
|
||||||
|
@ -13,6 +12,7 @@ abstract class DefaultNodes {
|
||||||
dogecoin,
|
dogecoin,
|
||||||
firo,
|
firo,
|
||||||
monero,
|
monero,
|
||||||
|
eCash,
|
||||||
epicCash,
|
epicCash,
|
||||||
ethereum,
|
ethereum,
|
||||||
bitcoincash,
|
bitcoincash,
|
||||||
|
@ -219,6 +219,18 @@ abstract class DefaultNodes {
|
||||||
isDown: false,
|
isDown: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
static NodeModel get eCash => NodeModel(
|
||||||
|
host: "electrum.bitcoinabc.org",
|
||||||
|
port: 50002,
|
||||||
|
name: defaultName,
|
||||||
|
id: _nodeId(Coin.eCash),
|
||||||
|
useSSL: true,
|
||||||
|
enabled: true,
|
||||||
|
coinName: Coin.eCash.name,
|
||||||
|
isFailover: true,
|
||||||
|
isDown: false,
|
||||||
|
);
|
||||||
|
|
||||||
static NodeModel getNodeFor(Coin coin) {
|
static NodeModel getNodeFor(Coin coin) {
|
||||||
switch (coin) {
|
switch (coin) {
|
||||||
case Coin.bitcoin:
|
case Coin.bitcoin:
|
||||||
|
@ -233,6 +245,9 @@ abstract class DefaultNodes {
|
||||||
case Coin.dogecoin:
|
case Coin.dogecoin:
|
||||||
return dogecoin;
|
return dogecoin;
|
||||||
|
|
||||||
|
case Coin.eCash:
|
||||||
|
return eCash;
|
||||||
|
|
||||||
case Coin.epicCash:
|
case Coin.epicCash:
|
||||||
return epicCash;
|
return epicCash;
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart'
|
||||||
as bch;
|
as bch;
|
||||||
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart'
|
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart'
|
||||||
as doge;
|
as doge;
|
||||||
|
import 'package:stackwallet/services/coins/ecash/ecash_wallet.dart' as ecash;
|
||||||
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'
|
import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'
|
||||||
as epic;
|
as epic;
|
||||||
import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'
|
import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'
|
||||||
|
@ -22,6 +23,7 @@ enum Coin {
|
||||||
bitcoin,
|
bitcoin,
|
||||||
bitcoincash,
|
bitcoincash,
|
||||||
dogecoin,
|
dogecoin,
|
||||||
|
eCash,
|
||||||
epicCash,
|
epicCash,
|
||||||
ethereum,
|
ethereum,
|
||||||
firo,
|
firo,
|
||||||
|
@ -59,6 +61,8 @@ extension CoinExt on Coin {
|
||||||
return "Dogecoin";
|
return "Dogecoin";
|
||||||
case Coin.epicCash:
|
case Coin.epicCash:
|
||||||
return "Epic Cash";
|
return "Epic Cash";
|
||||||
|
case Coin.eCash:
|
||||||
|
return "eCash";
|
||||||
case Coin.ethereum:
|
case Coin.ethereum:
|
||||||
return "Ethereum";
|
return "Ethereum";
|
||||||
case Coin.firo:
|
case Coin.firo:
|
||||||
|
@ -98,6 +102,8 @@ extension CoinExt on Coin {
|
||||||
return "EPIC";
|
return "EPIC";
|
||||||
case Coin.ethereum:
|
case Coin.ethereum:
|
||||||
return "ETH";
|
return "ETH";
|
||||||
|
case Coin.eCash:
|
||||||
|
return "XEC";
|
||||||
case Coin.firo:
|
case Coin.firo:
|
||||||
return "FIRO";
|
return "FIRO";
|
||||||
case Coin.monero:
|
case Coin.monero:
|
||||||
|
@ -136,6 +142,8 @@ extension CoinExt on Coin {
|
||||||
return "epic";
|
return "epic";
|
||||||
case Coin.ethereum:
|
case Coin.ethereum:
|
||||||
return "ethereum";
|
return "ethereum";
|
||||||
|
case Coin.eCash:
|
||||||
|
return "ecash";
|
||||||
case Coin.firo:
|
case Coin.firo:
|
||||||
return "firo";
|
return "firo";
|
||||||
case Coin.monero:
|
case Coin.monero:
|
||||||
|
@ -173,6 +181,7 @@ extension CoinExt on Coin {
|
||||||
case Coin.bitcoincashTestnet:
|
case Coin.bitcoincashTestnet:
|
||||||
case Coin.firoTestNet:
|
case Coin.firoTestNet:
|
||||||
case Coin.dogecoinTestNet:
|
case Coin.dogecoinTestNet:
|
||||||
|
case Coin.eCash:
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case Coin.epicCash:
|
case Coin.epicCash:
|
||||||
|
@ -195,6 +204,7 @@ extension CoinExt on Coin {
|
||||||
case Coin.firo:
|
case Coin.firo:
|
||||||
case Coin.namecoin:
|
case Coin.namecoin:
|
||||||
case Coin.particl:
|
case Coin.particl:
|
||||||
|
case Coin.eCash:
|
||||||
case Coin.epicCash:
|
case Coin.epicCash:
|
||||||
case Coin.monero:
|
case Coin.monero:
|
||||||
case Coin.wownero:
|
case Coin.wownero:
|
||||||
|
@ -220,6 +230,7 @@ extension CoinExt on Coin {
|
||||||
case Coin.ethereum:
|
case Coin.ethereum:
|
||||||
case Coin.monero:
|
case Coin.monero:
|
||||||
case Coin.wownero:
|
case Coin.wownero:
|
||||||
|
case Coin.eCash:
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case Coin.dogecoinTestNet:
|
case Coin.dogecoinTestNet:
|
||||||
|
@ -244,6 +255,7 @@ extension CoinExt on Coin {
|
||||||
case Coin.ethereum:
|
case Coin.ethereum:
|
||||||
case Coin.monero:
|
case Coin.monero:
|
||||||
case Coin.wownero:
|
case Coin.wownero:
|
||||||
|
case Coin.eCash:
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
case Coin.dogecoinTestNet:
|
case Coin.dogecoinTestNet:
|
||||||
|
@ -288,6 +300,9 @@ extension CoinExt on Coin {
|
||||||
case Coin.epicCash:
|
case Coin.epicCash:
|
||||||
return epic.MINIMUM_CONFIRMATIONS;
|
return epic.MINIMUM_CONFIRMATIONS;
|
||||||
|
|
||||||
|
case Coin.eCash:
|
||||||
|
return ecash.MINIMUM_CONFIRMATIONS;
|
||||||
|
|
||||||
case Coin.ethereum:
|
case Coin.ethereum:
|
||||||
return eth.MINIMUM_CONFIRMATIONS;
|
return eth.MINIMUM_CONFIRMATIONS;
|
||||||
|
|
||||||
|
@ -339,6 +354,11 @@ Coin coinFromPrettyName(String name) {
|
||||||
case "firo":
|
case "firo":
|
||||||
return Coin.firo;
|
return Coin.firo;
|
||||||
|
|
||||||
|
case "E-Cash":
|
||||||
|
case "ecash":
|
||||||
|
case "eCash":
|
||||||
|
return Coin.eCash;
|
||||||
|
|
||||||
case "Monero":
|
case "Monero":
|
||||||
case "monero":
|
case "monero":
|
||||||
return Coin.monero;
|
return Coin.monero;
|
||||||
|
@ -404,6 +424,8 @@ Coin coinFromTickerCaseInsensitive(String ticker) {
|
||||||
return Coin.dogecoin;
|
return Coin.dogecoin;
|
||||||
case "epic":
|
case "epic":
|
||||||
return Coin.epicCash;
|
return Coin.epicCash;
|
||||||
|
case "xec":
|
||||||
|
return Coin.eCash;
|
||||||
case "eth":
|
case "eth":
|
||||||
return Coin.ethereum;
|
return Coin.ethereum;
|
||||||
case "firo":
|
case "firo":
|
||||||
|
|
|
@ -6,6 +6,7 @@ enum DerivePathType {
|
||||||
bip49,
|
bip49,
|
||||||
bip84,
|
bip84,
|
||||||
eth,
|
eth,
|
||||||
|
eCash44,
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DerivePathTypeExt on DerivePathType {
|
extension DerivePathTypeExt on DerivePathType {
|
||||||
|
@ -27,6 +28,9 @@ extension DerivePathTypeExt on DerivePathType {
|
||||||
case Coin.particl:
|
case Coin.particl:
|
||||||
return DerivePathType.bip84;
|
return DerivePathType.bip84;
|
||||||
|
|
||||||
|
case Coin.eCash:
|
||||||
|
return DerivePathType.eCash44;
|
||||||
|
|
||||||
case Coin.ethereum: // TODO: do we need something here?
|
case Coin.ethereum: // TODO: do we need something here?
|
||||||
return DerivePathType.eth;
|
return DerivePathType.eth;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
@ -50,4 +52,15 @@ abstract class Util {
|
||||||
}
|
}
|
||||||
return MaterialColor(color.value, swatch);
|
return MaterialColor(color.value, swatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void printJson(dynamic json) {
|
||||||
|
if (json is Map || json is List) {
|
||||||
|
final spaces = ' ' * 4;
|
||||||
|
final encoder = JsonEncoder.withIndent(spaces);
|
||||||
|
final pretty = encoder.convert(json);
|
||||||
|
log(pretty);
|
||||||
|
} else {
|
||||||
|
log(dynamic.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,8 +53,10 @@ class CustomPinPut extends StatefulWidget {
|
||||||
this.mainAxisSize = MainAxisSize.max,
|
this.mainAxisSize = MainAxisSize.max,
|
||||||
this.autofillHints,
|
this.autofillHints,
|
||||||
this.customKey,
|
this.customKey,
|
||||||
}) : assert(fieldsCount > 0),
|
this.onPinLengthChanged,
|
||||||
super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final void Function(int)? onPinLengthChanged;
|
||||||
|
|
||||||
final double? width;
|
final double? width;
|
||||||
final double? height;
|
final double? height;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put.dart';
|
import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put.dart';
|
||||||
import 'package:stackwallet/widgets/custom_pin_put/pin_keyboard.dart';
|
import 'package:stackwallet/widgets/custom_pin_put/pin_keyboard.dart';
|
||||||
|
@ -10,6 +12,13 @@ class CustomPinPutState extends State<CustomPinPut>
|
||||||
|
|
||||||
int get selectedIndex => _controller.value.text.length;
|
int get selectedIndex => _controller.value.text.length;
|
||||||
|
|
||||||
|
int _pinCount = 0;
|
||||||
|
int get pinCount => _pinCount;
|
||||||
|
set pinCount(int newCount) {
|
||||||
|
_pinCount = newCount;
|
||||||
|
widget.onPinLengthChanged?.call(newCount);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_controller = widget.controller ?? TextEditingController();
|
_controller = widget.controller ?? TextEditingController();
|
||||||
|
@ -50,22 +59,19 @@ class CustomPinPutState extends State<CustomPinPut>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// final bool randomize = ref
|
|
||||||
// .read(prefsChangeNotifierProvider)
|
|
||||||
// .randomizePIN;
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: widget.width,
|
width: widget.width,
|
||||||
height: widget.height,
|
height: widget.height,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: (30 * widget.fieldsCount) - 18,
|
width: max((30 * pinCount) - 18, 1),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
_hiddenTextField,
|
_hiddenTextField,
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: _fields,
|
child: _fields(pinCount),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -75,15 +81,22 @@ class CustomPinPutState extends State<CustomPinPut>
|
||||||
isRandom: widget.isRandom,
|
isRandom: widget.isRandom,
|
||||||
customKey: widget.customKey,
|
customKey: widget.customKey,
|
||||||
onNumberKeyPressed: (number) {
|
onNumberKeyPressed: (number) {
|
||||||
if (_controller.text.length < widget.fieldsCount) {
|
_controller.text += number;
|
||||||
_controller.text += number;
|
|
||||||
}
|
// add a set state and have the counter increment
|
||||||
|
setState(() {
|
||||||
|
pinCount = _controller.text.length;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onBackPressed: () {
|
onBackPressed: () {
|
||||||
final text = _controller.text;
|
final text = _controller.text;
|
||||||
if (text.isNotEmpty) {
|
if (text.isNotEmpty) {
|
||||||
_controller.text = text.substring(0, text.length - 1);
|
_controller.text = text.substring(0, text.length - 1);
|
||||||
|
setState(() {
|
||||||
|
pinCount = _controller.text.length;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
// decrement counter here
|
||||||
},
|
},
|
||||||
onSubmitPressed: () {
|
onSubmitPressed: () {
|
||||||
final pin = _controller.value.text;
|
final pin = _controller.value.text;
|
||||||
|
@ -117,7 +130,7 @@ class CustomPinPutState extends State<CustomPinPut>
|
||||||
textCapitalization: widget.textCapitalization,
|
textCapitalization: widget.textCapitalization,
|
||||||
inputFormatters: widget.inputFormatters,
|
inputFormatters: widget.inputFormatters,
|
||||||
enableInteractiveSelection: false,
|
enableInteractiveSelection: false,
|
||||||
maxLength: widget.fieldsCount,
|
maxLength: 10,
|
||||||
showCursor: false,
|
showCursor: false,
|
||||||
scrollPadding: EdgeInsets.zero,
|
scrollPadding: EdgeInsets.zero,
|
||||||
decoration: widget.inputDecoration,
|
decoration: widget.inputDecoration,
|
||||||
|
@ -127,21 +140,22 @@ class CustomPinPutState extends State<CustomPinPut>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget get _fields {
|
// have it include an int as a param
|
||||||
|
Widget _fields(int count) {
|
||||||
return ValueListenableBuilder<String>(
|
return ValueListenableBuilder<String>(
|
||||||
valueListenable: _textControllerValue,
|
valueListenable: _textControllerValue,
|
||||||
builder: (BuildContext context, value, Widget? child) {
|
builder: (BuildContext context, value, Widget? child) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: widget.mainAxisSize,
|
mainAxisSize: widget.mainAxisSize,
|
||||||
mainAxisAlignment: widget.fieldsAlignment,
|
mainAxisAlignment: widget.fieldsAlignment,
|
||||||
children: _buildFieldsWithSeparator(),
|
children: _buildFieldsWithSeparator(count),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildFieldsWithSeparator() {
|
List<Widget> _buildFieldsWithSeparator(int count) {
|
||||||
final fields = Iterable<int>.generate(widget.fieldsCount).map((index) {
|
final fields = Iterable<int>.generate(count).map((index) {
|
||||||
return _getField(index);
|
return _getField(index);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,10 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:stackwallet/providers/providers.dart';
|
import 'package:stackwallet/providers/providers.dart';
|
||||||
|
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||||
import 'package:stackwallet/themes/coin_icon_provider.dart';
|
import 'package:stackwallet/themes/coin_icon_provider.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
import 'package:stackwallet/utilities/constants.dart';
|
import 'package:stackwallet/utilities/constants.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/text_styles.dart';
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
|
@ -34,6 +36,28 @@ class _ManagedFavoriteCardState extends ConsumerState<ManagedFavorite> {
|
||||||
|
|
||||||
final isDesktop = Util.isDesktop;
|
final isDesktop = Util.isDesktop;
|
||||||
|
|
||||||
|
final balance = ref.watch(
|
||||||
|
walletsChangeNotifierProvider.select(
|
||||||
|
(value) => value.getManager(widget.walletId).balance,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Amount total = balance.total;
|
||||||
|
if (manager.coin == Coin.firo || manager.coin == Coin.firoTestNet) {
|
||||||
|
final balancePrivate = ref.watch(
|
||||||
|
walletsChangeNotifierProvider.select(
|
||||||
|
(value) => (value
|
||||||
|
.getManager(
|
||||||
|
widget.walletId,
|
||||||
|
)
|
||||||
|
.wallet as FiroWallet)
|
||||||
|
.balancePrivate,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
total += balancePrivate.total;
|
||||||
|
}
|
||||||
|
|
||||||
return RoundedWhiteContainer(
|
return RoundedWhiteContainer(
|
||||||
padding: EdgeInsets.all(isDesktop ? 0 : 4.0),
|
padding: EdgeInsets.all(isDesktop ? 0 : 4.0),
|
||||||
child: RawMaterialButton(
|
child: RawMaterialButton(
|
||||||
|
@ -107,7 +131,7 @@ class _ManagedFavoriteCardState extends ConsumerState<ManagedFavorite> {
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
"${manager.balance.total.localizedStringAsFixed(
|
"${total.localizedStringAsFixed(
|
||||||
locale: ref.watch(
|
locale: ref.watch(
|
||||||
localeServiceChangeNotifierProvider.select(
|
localeServiceChangeNotifierProvider.select(
|
||||||
(value) => value.locale,
|
(value) => value.locale,
|
||||||
|
@ -150,7 +174,7 @@ class _ManagedFavoriteCardState extends ConsumerState<ManagedFavorite> {
|
||||||
height: 2,
|
height: 2,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
"${manager.balance.total.localizedStringAsFixed(
|
"${total.localizedStringAsFixed(
|
||||||
locale: ref.watch(
|
locale: ref.watch(
|
||||||
localeServiceChangeNotifierProvider.select(
|
localeServiceChangeNotifierProvider.select(
|
||||||
(value) => value.locale,
|
(value) => value.locale,
|
||||||
|
|
|
@ -24,7 +24,7 @@ class TradeCard extends ConsumerWidget {
|
||||||
final Trade trade;
|
final Trade trade;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
|
|
||||||
String _fetchIconAssetForStatus(String statusString, ThemeAssets assets) {
|
String _fetchIconAssetForStatus(String statusString, IThemeAssets assets) {
|
||||||
ChangeNowTransactionStatus? status;
|
ChangeNowTransactionStatus? status;
|
||||||
try {
|
try {
|
||||||
if (statusString.toLowerCase().startsWith("waiting")) {
|
if (statusString.toLowerCase().startsWith("waiting")) {
|
||||||
|
@ -89,11 +89,7 @@ class TradeCard extends ConsumerWidget {
|
||||||
File(
|
File(
|
||||||
_fetchIconAssetForStatus(
|
_fetchIconAssetForStatus(
|
||||||
trade.status,
|
trade.status,
|
||||||
ref.watch(
|
ref.watch(themeAssetsProvider),
|
||||||
themeProvider.select(
|
|
||||||
(value) => value.assets,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
width: 32,
|
width: 32,
|
||||||
|
|
|
@ -120,9 +120,9 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11
|
ref: "50bf29957514a5712466ba37590a851212a244bf"
|
||||||
resolved-ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11
|
resolved-ref: "50bf29957514a5712466ba37590a851212a244bf"
|
||||||
url: "https://github.com/Quppy/bitbox-flutter.git"
|
url: "https://github.com/PiRK/bitbox-flutter.git"
|
||||||
source: git
|
source: git
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
bitcoindart:
|
bitcoindart:
|
||||||
|
|
|
@ -11,7 +11,7 @@ description: Stack Wallet
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.7.8+171
|
version: 1.7.11+175
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.17.0 <3.0.0"
|
sdk: ">=2.17.0 <3.0.0"
|
||||||
|
@ -88,8 +88,8 @@ dependencies:
|
||||||
ref: 3bef5acc21340f3cc78df0ad1dce5868a3ed68a5
|
ref: 3bef5acc21340f3cc78df0ad1dce5868a3ed68a5
|
||||||
bitbox:
|
bitbox:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Quppy/bitbox-flutter.git
|
url: https://github.com/PiRK/bitbox-flutter.git
|
||||||
ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11
|
ref: 50bf29957514a5712466ba37590a851212a244bf
|
||||||
bip32: ^2.0.0
|
bip32: ^2.0.0
|
||||||
bech32:
|
bech32:
|
||||||
git:
|
git:
|
||||||
|
@ -291,6 +291,7 @@ flutter:
|
||||||
- assets/svg/message-question.svg
|
- assets/svg/message-question.svg
|
||||||
- assets/svg/envelope.svg
|
- assets/svg/envelope.svg
|
||||||
- assets/svg/share-2.svg
|
- assets/svg/share-2.svg
|
||||||
|
- assets/svg/anonymize.svg
|
||||||
- assets/svg/tx-icon-anonymize.svg
|
- assets/svg/tx-icon-anonymize.svg
|
||||||
- assets/svg/tx-icon-anonymize-pending.svg
|
- assets/svg/tx-icon-anonymize-pending.svg
|
||||||
- assets/svg/tx-icon-anonymize-failed.svg
|
- assets/svg/tx-icon-anonymize-failed.svg
|
||||||
|
|
|
@ -126,12 +126,8 @@ void main() {
|
||||||
).thenThrow(Exception());
|
).thenThrow(Exception());
|
||||||
|
|
||||||
final cachedClient = CachedElectrumX(
|
final cachedClient = CachedElectrumX(
|
||||||
electrumXClient: client,
|
electrumXClient: client,
|
||||||
port: 0,
|
);
|
||||||
failovers: [],
|
|
||||||
server: '',
|
|
||||||
useSSL: true,
|
|
||||||
prefs: Prefs.instance);
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
() async => await cachedClient.getTransaction(
|
() async => await cachedClient.getTransaction(
|
||||||
|
@ -143,12 +139,8 @@ void main() {
|
||||||
|
|
||||||
test("clearSharedTransactionCache", () async {
|
test("clearSharedTransactionCache", () async {
|
||||||
final cachedClient = CachedElectrumX(
|
final cachedClient = CachedElectrumX(
|
||||||
server: '',
|
electrumXClient: MockElectrumX(),
|
||||||
electrumXClient: MockElectrumX(),
|
);
|
||||||
port: 0,
|
|
||||||
useSSL: true,
|
|
||||||
prefs: MockPrefs(),
|
|
||||||
failovers: []);
|
|
||||||
|
|
||||||
bool didThrow = false;
|
bool didThrow = false;
|
||||||
try {
|
try {
|
||||||
|
@ -174,11 +166,7 @@ void main() {
|
||||||
useSSL: true,
|
useSSL: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
final client = CachedElectrumX.from(
|
final client = CachedElectrumX.from(electrumXClient: MockElectrumX());
|
||||||
node: node,
|
|
||||||
prefs: MockPrefs(),
|
|
||||||
failovers: [],
|
|
||||||
electrumXClient: MockElectrumX());
|
|
||||||
|
|
||||||
expect(client, isA<CachedElectrumX>());
|
expect(client, isA<CachedElectrumX>());
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,13 +27,69 @@ void main() {
|
||||||
when(client.get(
|
when(client.get(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids"
|
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids"
|
||||||
"=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash"
|
"=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,bitcoin-cash"
|
||||||
",namecoin,wownero,ethereum,particl&order=market_cap_desc&per_page=50"
|
",namecoin,wownero,ethereum,particl&order=market_cap_desc&per_page=50"
|
||||||
"&page=1&sparkline=false"),
|
"&page=1&sparkline=false"),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
})).thenAnswer((_) async => Response(
|
})).thenAnswer((_) async => Response(
|
||||||
'[{"id":"bitcoin","symbol":"btc","name":"Bitcoin","image":"https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579","current_price":1.0,"market_cap":19128800,"market_cap_rank":1,"fully_diluted_valuation":21000000,"total_volume":1272132,"high_24h":1.0,"low_24h":1.0,"price_change_24h":0.0,"price_change_percentage_24h":0.0,"market_cap_change_24h":950.0,"market_cap_change_percentage_24h":0.00497,"circulating_supply":19128800.0,"total_supply":21000000.0,"max_supply":21000000.0,"ath":1.003301,"ath_change_percentage":-0.32896,"ath_date":"2019-10-15T16:00:56.136Z","atl":0.99895134,"atl_change_percentage":0.10498,"atl_date":"2019-10-21T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin","symbol":"doge","name":"Dogecoin","image":"https://assets.coingecko.com/coins/images/5/large/dogecoin.png?1547792256","current_price":3.15e-06,"market_cap":417916,"market_cap_rank":10,"fully_diluted_valuation":null,"total_volume":27498,"high_24h":3.26e-06,"low_24h":3.13e-06,"price_change_24h":-8.6889947714e-08,"price_change_percentage_24h":-2.68533,"market_cap_change_24h":-11370.894861206936,"market_cap_change_percentage_24h":-2.64879,"circulating_supply":132670764299.894,"total_supply":null,"max_supply":null,"ath":1.264e-05,"ath_change_percentage":-75.05046,"ath_date":"2021-05-07T23:04:53.026Z","atl":1.50936e-07,"atl_change_percentage":1989.69346,"atl_date":"2020-12-17T09:18:05.654Z","roi":null,"last_updated":"2022-08-22T16:38:15.113Z"},{"id":"monero","symbol":"xmr","name":"Monero","image":"https://assets.coingecko.com/coins/images/69/large/monero_logo.png?1547033729","current_price":0.00717236,"market_cap":130002,"market_cap_rank":29,"fully_diluted_valuation":null,"total_volume":4901,"high_24h":0.00731999,"low_24h":0.00707511,"price_change_24h":-5.6133543212467e-05,"price_change_percentage_24h":-0.77656,"market_cap_change_24h":-1007.8447677436197,"market_cap_change_percentage_24h":-0.76929,"circulating_supply":18147820.3764146,"total_supply":null,"max_supply":null,"ath":0.03475393,"ath_change_percentage":-79.32037,"ath_date":"2018-01-09T00:00:00.000Z","atl":0.00101492,"atl_change_percentage":608.13327,"atl_date":"2014-12-18T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:38:26.347Z"},{"id":"zcoin","symbol":"firo","name":"Firo","image":"https://assets.coingecko.com/coins/images/479/large/firocoingecko.png?1636537544","current_price":0.0001096,"market_cap":1252,"market_cap_rank":604,"fully_diluted_valuation":2349,"total_volume":90.573,"high_24h":0.00011148,"low_24h":0.00010834,"price_change_24h":-9.87561775002e-07,"price_change_percentage_24h":-0.89304,"market_cap_change_24h":-10.046635178462793,"market_cap_change_percentage_24h":-0.79578,"circulating_supply":11411043.8354697,"total_supply":21400000.0,"max_supply":21400000.0,"ath":0.01616272,"ath_change_percentage":-99.3208,"ath_date":"2018-04-04T16:04:48.408Z","atl":4.268e-05,"atl_change_percentage":157.22799,"atl_date":"2022-05-12T07:28:47.088Z","roi":null,"last_updated":"2022-08-22T16:38:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"Epic Cash","image":"https://assets.coingecko.com/coins/images/9520/large/Epic_Coin_NO_drop_shadow.png?1620122642","current_price":2.803e-05,"market_cap":415.109,"market_cap_rank":953,"fully_diluted_valuation":null,"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h":2.581e-05,"price_change_24h":1.9e-06,"price_change_percentage_24h":7.27524,"market_cap_change_24h":28.26753,"market_cap_change_percentage_24h":7.30726,"circulating_supply":14808052.0,"total_supply":21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_percentage":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-07,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]',
|
'[{"id":"bitcoin","symbol":"btc","name":"Bitcoin","image":"https://asse'
|
||||||
|
'ts.coingecko.com/coins/images/1/large/bitcoin.png?1547033579","curr'
|
||||||
|
'ent_price":1.0,"market_cap":19128800,"market_cap_rank":1,"fully_dil'
|
||||||
|
'uted_valuation":21000000,"total_volume":1272132,"high_24h":1.0,"low'
|
||||||
|
'_24h":1.0,"price_change_24h":0.0,"price_change_percentage_24h":0.0,'
|
||||||
|
'"market_cap_change_24h":950.0,"market_cap_change_percentage_24h":0.0'
|
||||||
|
'0497,"circulating_supply":19128800.0,"total_supply":21000000.0,"max'
|
||||||
|
'_supply":21000000.0,"ath":1.003301,"ath_change_percentage":-0.32896'
|
||||||
|
',"ath_date":"2019-10-15T16:00:56.136Z","atl":0.99895134,"atl_change_'
|
||||||
|
'percentage":0.10498,"atl_date":"2019-10-21T00:00:00.000Z","roi":nul'
|
||||||
|
'l,"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin","symb'
|
||||||
|
'ol":"doge","name":"Dogecoin","image":"https://assets.coingecko.com/'
|
||||||
|
'coins/images/5/large/dogecoin.png?1547792256","current_price":3.15e'
|
||||||
|
'-06,"market_cap":417916,"market_cap_rank":10,"fully_diluted_valuati'
|
||||||
|
'on":null,"total_volume":27498,"high_24h":3.26e-06,"low_24h":3.13e-0'
|
||||||
|
'6,"price_change_24h":-8.6889947714e-08,"price_change_percentage_24h'
|
||||||
|
'":-2.68533,"market_cap_change_24h":-11370.894861206936,"market_cap_c'
|
||||||
|
'hange_percentage_24h":-2.64879,"circulating_supply":132670764299.89'
|
||||||
|
'4,"total_supply":null,"max_supply":null,"ath":1.264e-05,"ath_change'
|
||||||
|
'_percentage":-75.05046,"ath_date":"2021-05-07T23:04:53.026Z","atl":'
|
||||||
|
'1.50936e-07,"atl_change_percentage":1989.69346,"atl_date":"2020-12-'
|
||||||
|
'17T09:18:05.654Z","roi":null,"last_updated":"2022-08-22T16:38:15.11'
|
||||||
|
'3Z"},{"id":"monero","symbol":"xmr","name":"Monero","image":"https:/'
|
||||||
|
'/assets.coingecko.com/coins/images/69/large/monero_logo.png?1547033'
|
||||||
|
'729","current_price":0.00717236,"market_cap":130002,"market_cap_ran'
|
||||||
|
'k":29,"fully_diluted_valuation":null,"total_volume":4901,"high_24h":'
|
||||||
|
'0.00731999,"low_24h":0.00707511,"price_change_24h":-5.6133543212467'
|
||||||
|
'e-05,"price_change_percentage_24h":-0.77656,"market_cap_change_24h"'
|
||||||
|
':-1007.8447677436197,"market_cap_change_percentage_24h":-0.76929,"c'
|
||||||
|
'irculating_supply":18147820.3764146,"total_supply":null,"max_supply'
|
||||||
|
'":null,"ath":0.03475393,"ath_change_percentage":-79.32037,"ath_date'
|
||||||
|
'":"2018-01-09T00:00:00.000Z","atl":0.00101492,"atl_change_percentag'
|
||||||
|
'e":608.13327,"atl_date":"2014-12-18T00:00:00.000Z","roi":null,"las'
|
||||||
|
't_updated":"2022-08-22T16:38:26.347Z"},{"id":"zcoin","symbol":"firo'
|
||||||
|
'","name":"Firo","image":"https://assets.coingecko.com/coins/images/'
|
||||||
|
'479/large/firocoingecko.png?1636537544","current_price":0.0001096,"'
|
||||||
|
'market_cap":1252,"market_cap_rank":604,"fully_diluted_valuation":234'
|
||||||
|
'9,"total_volume":90.573,"high_24h":0.00011148,"low_24h":0.00010834,'
|
||||||
|
'"price_change_24h":-9.87561775002e-07,"price_change_percentage_24h'
|
||||||
|
'":-0.89304,"market_cap_change_24h":-10.046635178462793,"market_cap_'
|
||||||
|
'change_percentage_24h":-0.79578,"circulating_supply":11411043.83546'
|
||||||
|
'97,"total_supply":21400000.0,"max_supply":21400000.0,"ath":0.016162'
|
||||||
|
'72,"ath_change_percentage":-99.3208,"ath_date":"2018-04-04T16:04:48.'
|
||||||
|
'408Z","atl":4.268e-05,"atl_change_percentage":157.22799,"atl_date":"'
|
||||||
|
'2022-05-12T07:28:47.088Z","roi":null,"last_updated":"2022-08-22T16'
|
||||||
|
':38:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"Epic Cash",'
|
||||||
|
'"image":"https://assets.coingecko.com/coins/images/9520/large/Epic_C'
|
||||||
|
'oin_NO_drop_shadow.png?1620122642","current_price":2.803e-05,"marke'
|
||||||
|
't_cap":415.109,"market_cap_rank":953,"fully_diluted_valuation":null'
|
||||||
|
',"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h":2.581e-05'
|
||||||
|
',"price_change_24h":1.9e-06,"price_change_percentage_24h":7.27524,"'
|
||||||
|
'market_cap_change_24h":28.26753,"market_cap_change_percentage_24h":'
|
||||||
|
'7.30726,"circulating_supply":14808052.0,"total_supply":21000000.0,"'
|
||||||
|
'max_supply":null,"ath":0.00013848,"ath_change_percentage":-79.75864'
|
||||||
|
',"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-07,"atl_chang'
|
||||||
|
'e_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01.177Z","roi'
|
||||||
|
'":null,"last_updated":"2022-08-22T16:38:32.826Z"}]',
|
||||||
200));
|
200));
|
||||||
|
|
||||||
final priceAPI = PriceAPI(client);
|
final priceAPI = PriceAPI(client);
|
||||||
|
@ -45,7 +101,7 @@ void main() {
|
||||||
price.toString(),
|
price.toString(),
|
||||||
'{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0], '
|
'{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0], '
|
||||||
'Coin.dogecoin: [0.00000315, -2.68533], '
|
'Coin.dogecoin: [0.00000315, -2.68533], '
|
||||||
'Coin.epicCash: [0.00002803, 7.27524], '
|
'Coin.eCash: [0, 0.0], Coin.epicCash: [0.00002803, 7.27524], '
|
||||||
'Coin.ethereum: [0, 0.0], '
|
'Coin.ethereum: [0, 0.0], '
|
||||||
'Coin.firo: [0.0001096, -0.89304], Coin.litecoin: [0, 0.0], '
|
'Coin.firo: [0.0001096, -0.89304], Coin.litecoin: [0, 0.0], '
|
||||||
'Coin.monero: [0.00717236, -0.77656], Coin.namecoin: [0, 0.0], '
|
'Coin.monero: [0.00717236, -0.77656], Coin.namecoin: [0, 0.0], '
|
||||||
|
@ -57,7 +113,7 @@ void main() {
|
||||||
verify(client.get(
|
verify(client.get(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc"
|
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc"
|
||||||
"&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,"
|
"&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,"
|
||||||
"bitcoin-cash,namecoin,wownero,ethereum,particl"
|
"bitcoin-cash,namecoin,wownero,ethereum,particl"
|
||||||
"&order=market_cap_desc&per_page=50&page=1&sparkline=false",
|
"&order=market_cap_desc&per_page=50&page=1&sparkline=false",
|
||||||
),
|
),
|
||||||
|
@ -72,13 +128,69 @@ void main() {
|
||||||
when(client.get(
|
when(client.get(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&"
|
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&"
|
||||||
"ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,"
|
"ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,"
|
||||||
"bitcoin-cash,namecoin,wownero,ethereum,particl"
|
"bitcoin-cash,namecoin,wownero,ethereum,particl"
|
||||||
"&order=market_cap_desc&per_page=50&page=1&sparkline=false"),
|
"&order=market_cap_desc&per_page=50&page=1&sparkline=false"),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
})).thenAnswer((_) async => Response(
|
})).thenAnswer((_) async => Response(
|
||||||
'[{"id":"bitcoin","symbol":"btc","name":"Bitcoin","image":"https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579","current_price":1.0,"market_cap":19128800,"market_cap_rank":1,"fully_diluted_valuation":21000000,"total_volume":1272132,"high_24h":1.0,"low_24h":1.0,"price_change_24h":0.0,"price_change_percentage_24h":0.0,"market_cap_change_24h":950.0,"market_cap_change_percentage_24h":0.00497,"circulating_supply":19128800.0,"total_supply":21000000.0,"max_supply":21000000.0,"ath":1.003301,"ath_change_percentage":-0.32896,"ath_date":"2019-10-15T16:00:56.136Z","atl":0.99895134,"atl_change_percentage":0.10498,"atl_date":"2019-10-21T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin","symbol":"doge","name":"Dogecoin","image":"https://assets.coingecko.com/coins/images/5/large/dogecoin.png?1547792256","current_price":3.15e-06,"market_cap":417916,"market_cap_rank":10,"fully_diluted_valuation":null,"total_volume":27498,"high_24h":3.26e-06,"low_24h":3.13e-06,"price_change_24h":-8.6889947714e-08,"price_change_percentage_24h":-2.68533,"market_cap_change_24h":-11370.894861206936,"market_cap_change_percentage_24h":-2.64879,"circulating_supply":132670764299.894,"total_supply":null,"max_supply":null,"ath":1.264e-05,"ath_change_percentage":-75.05046,"ath_date":"2021-05-07T23:04:53.026Z","atl":1.50936e-07,"atl_change_percentage":1989.69346,"atl_date":"2020-12-17T09:18:05.654Z","roi":null,"last_updated":"2022-08-22T16:38:15.113Z"},{"id":"monero","symbol":"xmr","name":"Monero","image":"https://assets.coingecko.com/coins/images/69/large/monero_logo.png?1547033729","current_price":0.00717236,"market_cap":130002,"market_cap_rank":29,"fully_diluted_valuation":null,"total_volume":4901,"high_24h":0.00731999,"low_24h":0.00707511,"price_change_24h":-5.6133543212467e-05,"price_change_percentage_24h":-0.77656,"market_cap_change_24h":-1007.8447677436197,"market_cap_change_percentage_24h":-0.76929,"circulating_supply":18147820.3764146,"total_supply":null,"max_supply":null,"ath":0.03475393,"ath_change_percentage":-79.32037,"ath_date":"2018-01-09T00:00:00.000Z","atl":0.00101492,"atl_change_percentage":608.13327,"atl_date":"2014-12-18T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:38:26.347Z"},{"id":"zcoin","symbol":"firo","name":"Firo","image":"https://assets.coingecko.com/coins/images/479/large/firocoingecko.png?1636537544","current_price":0.0001096,"market_cap":1252,"market_cap_rank":604,"fully_diluted_valuation":2349,"total_volume":90.573,"high_24h":0.00011148,"low_24h":0.00010834,"price_change_24h":-9.87561775002e-07,"price_change_percentage_24h":-0.89304,"market_cap_change_24h":-10.046635178462793,"market_cap_change_percentage_24h":-0.79578,"circulating_supply":11411043.8354697,"total_supply":21400000.0,"max_supply":21400000.0,"ath":0.01616272,"ath_change_percentage":-99.3208,"ath_date":"2018-04-04T16:04:48.408Z","atl":4.268e-05,"atl_change_percentage":157.22799,"atl_date":"2022-05-12T07:28:47.088Z","roi":null,"last_updated":"2022-08-22T16:38:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"Epic Cash","image":"https://assets.coingecko.com/coins/images/9520/large/Epic_Coin_NO_drop_shadow.png?1620122642","current_price":2.803e-05,"market_cap":415.109,"market_cap_rank":953,"fully_diluted_valuation":null,"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h":2.581e-05,"price_change_24h":1.9e-06,"price_change_percentage_24h":7.27524,"market_cap_change_24h":28.26753,"market_cap_change_percentage_24h":7.30726,"circulating_supply":14808052.0,"total_supply":21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_percentage":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-07,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]',
|
'[{"id":"bitcoin","symbol":"btc","name":"Bitcoin","image":"https://a'
|
||||||
|
'ssets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579","c'
|
||||||
|
'urrent_price":1.0,"market_cap":19128800,"market_cap_rank":1,"fully_'
|
||||||
|
'diluted_valuation":21000000,"total_volume":1272132,"high_24h":1.0,"'
|
||||||
|
'low_24h":1.0,"price_change_24h":0.0,"price_change_percentage_24h":0'
|
||||||
|
'.0,"market_cap_change_24h":950.0,"market_cap_change_percentage_24h"'
|
||||||
|
':0.00497,"circulating_supply":19128800.0,"total_supply":21000000.0,"'
|
||||||
|
'max_supply":21000000.0,"ath":1.003301,"ath_change_percentage":-0.32'
|
||||||
|
'896,"ath_date":"2019-10-15T16:00:56.136Z","atl":0.99895134,"atl_cha'
|
||||||
|
'nge_percentage":0.10498,"atl_date":"2019-10-21T00:00:00.000Z","roi"'
|
||||||
|
':null,"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin","'
|
||||||
|
'symbol":"doge","name":"Dogecoin","image":"https://assets.coingecko.'
|
||||||
|
'com/coins/images/5/large/dogecoin.png?1547792256","current_price":3'
|
||||||
|
'.15e-06,"market_cap":417916,"market_cap_rank":10,"fully_diluted_val'
|
||||||
|
'uation":null,"total_volume":27498,"high_24h":3.26e-06,"low_24h":3.1'
|
||||||
|
'3e-06,"price_change_24h":-8.6889947714e-08,"price_change_percentage'
|
||||||
|
'_24h":-2.68533,"market_cap_change_24h":-11370.894861206936,"market_'
|
||||||
|
'cap_change_percentage_24h":-2.64879,"circulating_supply":1326707642'
|
||||||
|
'99.894,"total_supply":null,"max_supply":null,"ath":1.264e-05,"ath_c'
|
||||||
|
'hange_percentage":-75.05046,"ath_date":"2021-05-07T23:04:53.026Z","'
|
||||||
|
'atl":1.50936e-07,"atl_change_percentage":1989.69346,"atl_date":"202'
|
||||||
|
'0-12-17T09:18:05.654Z","roi":null,"last_updated":"2022-08-22T16:38:'
|
||||||
|
'15.113Z"},{"id":"monero","symbol":"xmr","name":"Monero","image":"ht'
|
||||||
|
'tps://assets.coingecko.com/coins/images/69/large/monero_logo.png?15'
|
||||||
|
'47033729","current_price":0.00717236,"market_cap":130002,"market_cap'
|
||||||
|
'_rank":29,"fully_diluted_valuation":null,"total_volume":4901,"high'
|
||||||
|
'_24h":0.00731999,"low_24h":0.00707511,"price_change_24h":-5.613354'
|
||||||
|
'3212467e-05,"price_change_percentage_24h":-0.77656,"market_cap_chan'
|
||||||
|
'ge_24h":-1007.8447677436197,"market_cap_change_percentage_24h":-0.7'
|
||||||
|
'6929,"circulating_supply":18147820.3764146,"total_supply":null,"ma'
|
||||||
|
'x_supply":null,"ath":0.03475393,"ath_change_percentage":-79.32037,"'
|
||||||
|
'ath_date":"2018-01-09T00:00:00.000Z","atl":0.00101492,"atl_change_'
|
||||||
|
'percentage":608.13327,"atl_date":"2014-12-18T00:00:00.000Z","roi":n'
|
||||||
|
'ull,"last_updated":"2022-08-22T16:38:26.347Z"},{"id":"zcoin","symbo'
|
||||||
|
'l":"firo","name":"Firo","image":"https://assets.coingecko.com/coins'
|
||||||
|
'/images/479/large/firocoingecko.png?1636537544","current_price":0.0'
|
||||||
|
'001096,"market_cap":1252,"market_cap_rank":604,"fully_diluted_valu'
|
||||||
|
'ation":2349,"total_volume":90.573,"high_24h":0.00011148,"low_24h":0'
|
||||||
|
'.00010834,"price_change_24h":-9.87561775002e-07,"price_change_perce'
|
||||||
|
'ntage_24h":-0.89304,"market_cap_change_24h":-10.046635178462793,"ma'
|
||||||
|
'rket_cap_change_percentage_24h":-0.79578,"circulating_supply":11411'
|
||||||
|
'043.8354697,"total_supply":21400000.0,"max_supply":21400000.0,"ath"'
|
||||||
|
':0.01616272,"ath_change_percentage":-99.3208,"ath_date":"2018-04-04'
|
||||||
|
'T16:04:48.408Z","atl":4.268e-05,"atl_change_percentage":157.22799,'
|
||||||
|
'"atl_date":"2022-05-12T07:28:47.088Z","roi":null,"last_updated":"2'
|
||||||
|
'022-08-22T16:38:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"'
|
||||||
|
'Epic Cash","image":"https://assets.coingecko.com/coins/images/9520/'
|
||||||
|
'large/Epic_Coin_NO_drop_shadow.png?1620122642","current_price":2.80'
|
||||||
|
'3e-05,"market_cap":415.109,"market_cap_rank":953,"fully_diluted_val'
|
||||||
|
'uation":null,"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h'
|
||||||
|
'":2.581e-05,"price_change_24h":1.9e-06,"price_change_percentage_24'
|
||||||
|
'h":7.27524,"market_cap_change_24h":28.26753,"market_cap_change_perc'
|
||||||
|
'entage_24h":7.30726,"circulating_supply":14808052.0,"total_supply":'
|
||||||
|
'21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_percentag'
|
||||||
|
'e":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-0'
|
||||||
|
'7,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01'
|
||||||
|
'.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]',
|
||||||
200));
|
200));
|
||||||
|
|
||||||
final priceAPI = PriceAPI(client);
|
final priceAPI = PriceAPI(client);
|
||||||
|
@ -94,7 +206,7 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
cachedPrice.toString(),
|
cachedPrice.toString(),
|
||||||
'{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0],'
|
'{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0],'
|
||||||
' Coin.dogecoin: [0.00000315, -2.68533], Coin.epicCash: [0.00002803, 7.27524],'
|
' Coin.dogecoin: [0.00000315, -2.68533], Coin.eCash: [0, 0.0], Coin.epicCash: [0.00002803, 7.27524],'
|
||||||
' Coin.ethereum: [0, 0.0], Coin.firo: [0.0001096, -0.89304], '
|
' Coin.ethereum: [0, 0.0], Coin.firo: [0.0001096, -0.89304], '
|
||||||
'Coin.litecoin: [0, 0.0], Coin.monero: [0.00717236, -0.77656], '
|
'Coin.litecoin: [0, 0.0], Coin.monero: [0.00717236, -0.77656], '
|
||||||
'Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0], '
|
'Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0], '
|
||||||
|
@ -105,7 +217,10 @@ void main() {
|
||||||
// verify only called once during filling of cache
|
// verify only called once during filling of cache
|
||||||
verify(client.get(
|
verify(client.get(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero,ethereum,particl&order=market_cap_desc&per_page=50&page=1&sparkline=false"),
|
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids"
|
||||||
|
"=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,"
|
||||||
|
"bitcoin-cash,namecoin,wownero,ethereum,particl"
|
||||||
|
"&order=market_cap_desc&per_page=50&page=1&sparkline=false"),
|
||||||
headers: {'Content-Type': 'application/json'})).called(1);
|
headers: {'Content-Type': 'application/json'})).called(1);
|
||||||
|
|
||||||
verifyNoMoreInteractions(client);
|
verifyNoMoreInteractions(client);
|
||||||
|
@ -117,13 +232,70 @@ void main() {
|
||||||
when(client.get(
|
when(client.get(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc"
|
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc"
|
||||||
"&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,"
|
"&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,"
|
||||||
"bitcoin-cash,namecoin,wownero,ethereum,particl"
|
"bitcoin-cash,namecoin,wownero,ethereum,particl"
|
||||||
"&order=market_cap_desc&per_page=50&page=1&sparkline=false"),
|
"&order=market_cap_desc&per_page=50&page=1&sparkline=false"),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
})).thenAnswer((_) async => Response(
|
})).thenAnswer((_) async => Response(
|
||||||
'[{"id":"bitcoin","symbol":"btc","name":com/coins/images/1/large/bitcoin.png?1547033579","current_price":1.0,"market_cap":19128800,"market_cap_rank":1,"fully_diluted_valuation":21000000,"total_volume":1272132,"high_24h":1.0,"low_24h":1.0,"price_change_24h":0.0,"price_change_percentage_24h":0.0,"market_cap_change_24h":950.0,"market_cap_change_percentage_24h":0.00497,"circulating_supply":19128800.0,"total_supply":21000000.0,"max_supply":21000000.0,"ath":1.003301,"ath_change_percentage":-0.32896,"ath_date":"2019-10-15T16:00:56.136Z","atl":0.99895134,"atl_change_percentage":0.10498,"atl_date":"2019-10-21T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin","symbol":"doge","name":"Dogecoin","image":"https://assets.coingecko.com/coins/images/5/large/dogecoin.png?1547792256","current_price":3.15e-06,"market_cap":417916,"market_cap_rank":10,"fully_diluted_valuation":null,"total_volume":27498,"high_24h":3.26e-06,"low_24h":3.13e-06,"price_change_24h":-8.6889947714e-08,"price_change_percentage_24h":-2.68533,"market_cap_change_24h":-11370.894861206936,"market_cap_change_percentage_24h":-2.64879,"circulating_supply":132670764299.894,"total_supply":null,"max_supply":null,"ath":1.264e-05,"ath_change_percentage":-75.05046,"ath_date":"2021-05-07T23:04:53.026Z","atl":1.50936e-07,"atl_change_percentage":1989.69346,"atl_date":"2020-12-17T09:18:05.654Z","roi":null,"last_updated":"2022-08-22T16:38:15.113Z"},{"id":"monero","symbol":"xmr","name":"Monero","image":"https://assets.coingecko.com/coins/images/69/large/monero_logo.png?1547033729","current_price":0.00717236,"market_cap":130002,"market_cap_rank":29,"fully_diluted_valuation":null,"total_volume":4901,"high_24h":0.00731999,"low_24h":0.00707511,"price_change_24h":-5.6133543212467e-05,"price_change_percentage_24h":-0.77656,"market_cap_change_24h":-1007.8447677436197,"market_cap_change_percentage_24h":-0.76929,"circulating_supply":18147820.3764146,"total_supply":null,"max_supply":null,"ath":0.03475393,"ath_change_percentage":-79.32037,"ath_date":"2018-01-09T00:00:00.000Z","atl":0.00101492,"atl_change_percentage":608.13327,"atl_date":"2014-12-18T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:38:26.347Z"},{"id":"zcoin","symbol":"firo","name":"Firo","image":"https://assets.coingecko.com/coins/images/479/large/firocoingecko.png?1636537544","current_price":0.0001096,"market_cap":1252,"market_cap_rank":604,"fully_diluted_valuation":2349,"total_volume":90.573,"high_24h":0.00011148,"low_24h":0.00010834,"price_change_24h":-9.87561775002e-07,"price_change_percentage_24h":-0.89304,"market_cap_change_24h":-10.046635178462793,"market_cap_change_percentage_24h":-0.79578,"circulating_supply":11411043.8354697,"total_supply":21400000.0,"max_supply":21400000.0,"ath":0.01616272,"ath_change_percentage":-99.3208,"ath_date":"2018-04-04T16:04:48.408Z","atl":4.268e-05,"atl_change_percentage":157.22799,"atl_date":"2022-05-12T07:28:47.088Z","roi":null,"last_updated":"2022-08-22T16:38:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"Epic Cash","image":"https://assets.coingecko.com/coins/images/9520/large/Epic_Coin_NO_drop_shadow.png?1620122642","current_price":2.803e-05,"market_cap":415.109,"market_cap_rank":953,"fully_diluted_valuation":null,"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h":2.581e-05,"price_change_24h":1.9e-06,"price_change_percentage_24h":7.27524,"market_cap_change_24h":28.26753,"market_cap_change_percentage_24h":7.30726,"circulating_supply":14808052.0,"total_supply":21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_percentage":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-07,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]',
|
'[{"id":"bitcoin","symbol":"btc","name":com/coins/images/1/large/'
|
||||||
|
'bitcoin.png?1547033579","current_price":1.0,"market_cap":19128800'
|
||||||
|
',"market_cap_rank":1,"fully_diluted_valuation":21000000,"total_volum'
|
||||||
|
'e":1272132,"high_24h":1.0,"low_24h":1.0,"price_change_24h":0.0,"pri'
|
||||||
|
'ce_change_percentage_24h":0.0,"market_cap_change_24h":950.0,"market_'
|
||||||
|
'cap_change_percentage_24h":0.00497,"circulating_supply":19128800.0,"t'
|
||||||
|
'otal_supply":21000000.0,"max_supply":21000000.0,"ath":1.003301,"ath'
|
||||||
|
'_change_percentage":-0.32896,"ath_date":"2019-10-15T16:00:56.136Z",'
|
||||||
|
'"atl":0.99895134,"atl_change_percentage":0.10498,"atl_date":'
|
||||||
|
'"2019-10-21T00:00:00.000Z","roi":null,'
|
||||||
|
'"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin"'
|
||||||
|
',"symbol":"doge","name":"Dogecoin","image":'
|
||||||
|
'"https://assets.coingecko.com/coins/images/5/large/dogecoin.png?1547792256",'
|
||||||
|
'"current_price":3.15e-06,"market_cap":417916,"market_cap_rank":10'
|
||||||
|
',"fully_diluted_valuation":null,"total_volume":27498,"high_24h":3'
|
||||||
|
'.26e-06,"low_24h":3.13e-06,"price_change_24h":-8.6889947714e-08,"'
|
||||||
|
'price_change_percentage_24h":-2.68533,"market_cap_change_24h":-11'
|
||||||
|
'370.894861206936,"market_cap_change_percentage_24h":-2.64879,"cir'
|
||||||
|
'culating_supply":132670764299.894,"total_supply":null,"max_supply'
|
||||||
|
'":null,"ath":1.264e-05,"ath_change_percentage":-75.05046,"ath_date'
|
||||||
|
'":"2021-05-07T23:04:53.026Z","atl":1.50936e-07,"atl_change_percen'
|
||||||
|
'tage":1989.69346,"atl_date":"2020-12-17T09:18:05.654Z","roi":null,'
|
||||||
|
'"last_updated":"2022-08-22T16:38:15.113Z"},{"id":"monero","symbol"'
|
||||||
|
':"xmr","name":"Monero","image":"https://assets.coingecko.com/coins'
|
||||||
|
'/images/69/large/monero_logo.png?1547033729","current_price":0.007'
|
||||||
|
'17236,"market_cap":130002,"market_cap_rank":29,"fully_diluted_valu'
|
||||||
|
'ation":null,"total_volume":4901,"high_24h":0.00731999,"low_24h":0.'
|
||||||
|
'00707511,"price_change_24h":-5.6133543212467e-05,"price_change_per'
|
||||||
|
'centage_24h":-0.77656,"market_cap_change_24h":-1007.8447677436197'
|
||||||
|
',"market_cap_change_percentage_24h":-0.76929,"circulating_supply":'
|
||||||
|
'18147820.3764146,"total_supply":null,"max_supply":null,"ath":0.034'
|
||||||
|
'75393,"ath_change_percentage":-79.32037,"ath_date":"2018-01-09T00:'
|
||||||
|
'00:00.000Z","atl":0.00101492,"atl_change_percentage":608.13327,"at'
|
||||||
|
'l_date":"2014-12-18T00:00:00.000Z","roi":null,"last_updated":"2022'
|
||||||
|
'-08-22T16:38:26.347Z"},{"id":"zcoin","symbol":"firo","name":"Firo"'
|
||||||
|
',"image":"https://assets.coingecko.com/coins/images/479/large/firo'
|
||||||
|
'coingecko.png?1636537544","current_price":0.0001096,"market_cap":1'
|
||||||
|
'252,"market_cap_rank":604,"fully_diluted_valuation":2349,"total_vo'
|
||||||
|
'lume":90.573,"high_24h":0.00011148,"low_24h":0.00010834,"price_chang'
|
||||||
|
'e_24h":-9.87561775002e-07,"price_change_percentage_24h":-0.89304,'
|
||||||
|
'"market_cap_change_24h":-10.046635178462793,"market_cap_change_per'
|
||||||
|
'centage_24h":-0.79578,"circulating_supply":11411043.8354697,"tota'
|
||||||
|
'l_supply":21400000.0,"max_supply":21400000.0,"ath":0.01616272,"ath'
|
||||||
|
'_change_percentage":-99.3208,"ath_date":"2018-04-04T16:04:48.408Z"'
|
||||||
|
',"atl":4.268e-05,"atl_change_percentage":157.22799,"atl_date":"202'
|
||||||
|
'2-05-12T07:28:47.088Z","roi":null,"last_updated":"2022-08-22T16:3'
|
||||||
|
'8:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"Epic Cash",'
|
||||||
|
'"image":"https://assets.coingecko.com/coins/images/9520/large/'
|
||||||
|
'Epic_Coin_NO_drop_shadow.png?1620122642","current_price":2.803e-0'
|
||||||
|
'5,"market_cap":415.109,"market_cap_rank":953,"fully_diluted_valuat'
|
||||||
|
'ion":null,"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h":'
|
||||||
|
'2.581e-05,"price_change_24h":1.9e-06,"price_change_percentage_24h"'
|
||||||
|
':7.27524,"market_cap_change_24h":28.26753,"market_cap_change_per'
|
||||||
|
'centage_24h":7.30726,"circulating_supply":14808052.0,"total_suppl'
|
||||||
|
'y":21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_perce'
|
||||||
|
'ntage":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74'
|
||||||
|
'028e-07,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T'
|
||||||
|
'16:55:01.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]',
|
||||||
200));
|
200));
|
||||||
|
|
||||||
final priceAPI = PriceAPI(client);
|
final priceAPI = PriceAPI(client);
|
||||||
|
@ -131,8 +303,15 @@ void main() {
|
||||||
|
|
||||||
final price = await priceAPI.getPricesAnd24hChange(baseCurrency: "btc");
|
final price = await priceAPI.getPricesAnd24hChange(baseCurrency: "btc");
|
||||||
|
|
||||||
expect(price.toString(),
|
expect(
|
||||||
'{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.ethereum: [0, 0.0], Coin.firo: [0, 0.0], Coin.litecoin: [0, 0.0], Coin.monero: [0, 0.0], Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}');
|
price.toString(),
|
||||||
|
'{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: '
|
||||||
|
'[0, 0.0], Coin.eCash: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.ethereum: [0, 0.0],'
|
||||||
|
' Coin.firo: [0, 0.0], Coin.litecoin: [0, 0.0], Coin.monero: [0, 0.0],'
|
||||||
|
' Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0],'
|
||||||
|
' Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], '
|
||||||
|
'Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0],'
|
||||||
|
' Coin.firoTestNet: [0, 0.0]}');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("no internet available", () async {
|
test("no internet available", () async {
|
||||||
|
@ -141,7 +320,7 @@ void main() {
|
||||||
when(client.get(
|
when(client.get(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc"
|
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc"
|
||||||
"&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,"
|
"&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,"
|
||||||
"bitcoin-cash,namecoin,wownero,ethereum,particl"
|
"bitcoin-cash,namecoin,wownero,ethereum,particl"
|
||||||
"&order=market_cap_desc&per_page=50&page=1&sparkline=false"),
|
"&order=market_cap_desc&per_page=50&page=1&sparkline=false"),
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -157,7 +336,7 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
price.toString(),
|
price.toString(),
|
||||||
'{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], '
|
'{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], '
|
||||||
'Coin.dogecoin: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.ethereum: [0, 0.0],'
|
'Coin.dogecoin: [0, 0.0], Coin.eCash: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.ethereum: [0, 0.0],'
|
||||||
' Coin.firo: [0, 0.0], Coin.litecoin: [0, 0.0], Coin.monero: [0, 0.0],'
|
' Coin.firo: [0, 0.0], Coin.litecoin: [0, 0.0], Coin.monero: [0, 0.0],'
|
||||||
' Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0],'
|
' Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0],'
|
||||||
' Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], '
|
' Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], '
|
||||||
|
|
252
test/sample_data/theme_json_v2.dart
Normal file
252
test/sample_data/theme_json_v2.dart
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
const Map<String, dynamic> lightThemeJsonMap = {
|
||||||
|
"name": "Light",
|
||||||
|
"version": 2,
|
||||||
|
"id": "light",
|
||||||
|
"brightness": "light",
|
||||||
|
"colors": {
|
||||||
|
// keys for coin colors must match the Coin enum name value exactly
|
||||||
|
"coin": {
|
||||||
|
"bitcoin": "0xFFFCC17B",
|
||||||
|
"litecoin": "0xFF7FA6E1",
|
||||||
|
"bitcoincash": "0xFF7BCFB8",
|
||||||
|
"firo": "0xFFFF897A",
|
||||||
|
"dogecoin": "0xFFFFE079",
|
||||||
|
"eCash": "0xFFC5C7CB",
|
||||||
|
"epicCash": "0xFFC5C7CB",
|
||||||
|
"ethereum": "0xFFA7ADE9",
|
||||||
|
"monero": "0xFFFF9E6B",
|
||||||
|
"namecoin": "0xFF91B1E1",
|
||||||
|
"wownero": "0xFFED80C1",
|
||||||
|
"particl": "0xFF8175BD"
|
||||||
|
},
|
||||||
|
"background": "0xFFF7F7F7",
|
||||||
|
"background_app_bar": "0xFFF7F7F7",
|
||||||
|
"overlay": "0xFF111215",
|
||||||
|
"accent_color_blue": "0xFF0052DF",
|
||||||
|
"accent_color_green": "0xFF4CC0A0",
|
||||||
|
"accent_color_yellow": "0xFFF7D65D",
|
||||||
|
"accent_color_red": "0xFFD34E50",
|
||||||
|
"accent_color_orange": "0xFFFEA68D",
|
||||||
|
"accent_color_dark": "0xFF232323",
|
||||||
|
"shadow": "0x0F2D3132",
|
||||||
|
"text_dark_one": "0xFF232323",
|
||||||
|
"text_dark_two": "0xFF414141",
|
||||||
|
"text_dark_three": "0xFF747778",
|
||||||
|
"text_white": "0xFFFFFFFF",
|
||||||
|
"text_favorite": "0xFF232323",
|
||||||
|
"text_error": "0xFF930006",
|
||||||
|
"text_restore": "0xFF111215",
|
||||||
|
"text_subtitle_one": "0xFF8E9192",
|
||||||
|
"text_subtitle_two": "0xFFA9ACAC",
|
||||||
|
"text_subtitle_three": "0xFFC4C7C7",
|
||||||
|
"text_subtitle_four": "0xFFE0E3E3",
|
||||||
|
"text_subtitle_five": "0xFFEEEFF1",
|
||||||
|
"text_subtitle_six": "0xFFF5F5F5",
|
||||||
|
"button_back_primary": "0xFF232323",
|
||||||
|
"button_back_secondary": "0xFFE0E3E3",
|
||||||
|
"button_back_primary_disabled": "0xFFD7D7D7",
|
||||||
|
"button_back_secondary_disabled": "0xFFF0F1F1",
|
||||||
|
"button_back_border": "0xFF232323",
|
||||||
|
"button_back_border_disabled": "0xFFB6B6B6",
|
||||||
|
"button_back_border_secondary": "0xFFE0E3E3",
|
||||||
|
"button_back_border_secondary_disabled": "0xFFF0F1F1",
|
||||||
|
"number_back_default": "0xFFFFFFFF",
|
||||||
|
"numpad_back_default": "0xFF232323",
|
||||||
|
"bottom_nav_back": "0xFFFFFFFF",
|
||||||
|
"button_text_primary": "0xFFFFFFFF",
|
||||||
|
"button_text_secondary": "0xFF232323",
|
||||||
|
"button_text_primary_disabled": "0xFFF8F8F8",
|
||||||
|
"button_text_secondary_disabled": "0xFFB7B7B7",
|
||||||
|
"button_text_border": "0xFF232323",
|
||||||
|
"button_text_disabled": "0xFFB6B6B6",
|
||||||
|
"button_text_borderless": "0xFF0052DF",
|
||||||
|
"button_text_borderless_disabled": "0xFFB6B6B6",
|
||||||
|
"number_text_default": "0xFF232323",
|
||||||
|
"numpad_text_default": "0xFFFFFFFF",
|
||||||
|
"bottom_nav_text": "0xFF232323",
|
||||||
|
"custom_text_button_enabled_text": "0xFF0052DF",
|
||||||
|
"custom_text_button_disabled_text": "0xFF8E9192",
|
||||||
|
"switch_bg_on": "0xFF0052DF",
|
||||||
|
"switch_bg_off": "0xFFD8E4FB",
|
||||||
|
"switch_bg_disabled": "0xFFC5C6C9",
|
||||||
|
"switch_circle_on": "0xFFDAE2FF",
|
||||||
|
"switch_circle_off": "0xFFFBFCFF",
|
||||||
|
"switch_circle_disabled": "0xFFFBFCFF",
|
||||||
|
"step_indicator_bg_check": "0xFFD9E2FF",
|
||||||
|
"step_indicator_bg_number": "0xFFD9E2FF",
|
||||||
|
"step_indicator_bg_inactive": "0xFFCDCDCD",
|
||||||
|
"step_indicator_bg_lines": "0xFF0056D2",
|
||||||
|
"step_indicator_bg_lines_inactive": "0xFFCDCDCD",
|
||||||
|
"step_indicator_icon_text": "0xFF0056D2",
|
||||||
|
"step_indicator_icon_number": "0xFF0056D2",
|
||||||
|
"step_indicator_icon_inactive": "0xFFF7F7F7",
|
||||||
|
"checkbox_bg_checked": "0xFF0056D2",
|
||||||
|
"checkbox_border_empty": "0xFF8E9192",
|
||||||
|
"checkbox_bg_disabled": "0xFFADC7EC",
|
||||||
|
"checkbox_icon_checked": "0xFFFFFFFF",
|
||||||
|
"checkbox_icon_disabled": "0xFFFFFFFF",
|
||||||
|
"checkbox_text_label": "0xFF232323",
|
||||||
|
"snack_bar_back_success": "0xFFB9E9D4",
|
||||||
|
"snack_bar_back_error": "0xFFFFDAD4",
|
||||||
|
"snack_bar_back_info": "0xFFDAE2FF",
|
||||||
|
"snack_bar_text_success": "0xFF006C4D",
|
||||||
|
"snack_bar_text_error": "0xFF930006",
|
||||||
|
"snack_bar_text_info": "0xFF002A78",
|
||||||
|
"bottom_nav_icon_back": "0xFFA2A2A2",
|
||||||
|
"bottom_nav_icon_icon": "0xFF232323",
|
||||||
|
"bottom_nav_icon_icon_highlighted": "0xFF232323",
|
||||||
|
"top_nav_icon_primary": "0xFF232323",
|
||||||
|
"top_nav_icon_green": "0xFF00A578",
|
||||||
|
"top_nav_icon_yellow": "0xFFF4C517",
|
||||||
|
"top_nav_icon_red": "0xFFC00205",
|
||||||
|
"settings_icon_back": "0xFFE0E3E3",
|
||||||
|
"settings_icon_icon": "0xFF232323",
|
||||||
|
"settings_icon_back_two": "0xFF94D6C4",
|
||||||
|
"settings_icon_element": "0xFF00A578",
|
||||||
|
"text_field_active_bg": "0xFFEEEFF1",
|
||||||
|
"text_field_default_bg": "0xFFEEEFF1",
|
||||||
|
"text_field_error_bg": "0xFFFFDAD4",
|
||||||
|
"text_field_success_bg": "0xFFB9E9D4",
|
||||||
|
"text_field_error_border": "0xFFFFDAD4",
|
||||||
|
"text_field_success_border": "0xFFB9E9D4",
|
||||||
|
"text_field_active_search_icon_left": "0xFFA9ACAC",
|
||||||
|
"text_field_default_search_icon_left": "0xFFA9ACAC",
|
||||||
|
"text_field_error_search_icon_left": "0xFF930006",
|
||||||
|
"text_field_success_search_icon_left": "0xFF006C4D",
|
||||||
|
"text_field_active_text": "0xFF232323",
|
||||||
|
"text_field_default_text": "0xFFA9ACAC",
|
||||||
|
"text_field_error_text": "0xFF000000",
|
||||||
|
"text_field_success_text": "0xFF000000",
|
||||||
|
"text_field_active_label": "0xFFA9ACAC",
|
||||||
|
"text_field_error_label": "0xFF930006",
|
||||||
|
"text_field_success_label": "0xFF006C4D",
|
||||||
|
"text_field_active_search_icon_right": "0xFF747778",
|
||||||
|
"text_field_default_search_icon_right": "0xFF747778",
|
||||||
|
"text_field_error_search_icon_right": "0xFF930006",
|
||||||
|
"text_field_success_search_icon_right": "0xFF006C4D",
|
||||||
|
"settings_item_level_two_active_bg": "0xFFFFFFFF",
|
||||||
|
"settings_item_level_two_active_text": "0xFF232323",
|
||||||
|
"settings_item_level_two_active_sub": "0xFF8E9192",
|
||||||
|
"radio_button_icon_border": "0xFF0056D2",
|
||||||
|
"radio_button_icon_border_disabled": "0xFF8F909A",
|
||||||
|
"radio_button_border_enabled": "0xFF0056D2",
|
||||||
|
"radio_button_border_disabled": "0xFF8F909A",
|
||||||
|
"radio_button_icon_circle": "0xFF0056D2",
|
||||||
|
"radio_button_icon_enabled": "0xFF0056D2",
|
||||||
|
"radio_button_text_enabled": "0xFF44464E",
|
||||||
|
"radio_button_text_disabled": "0xFF44464E",
|
||||||
|
"radio_button_label_enabled": "0xFF8E9192",
|
||||||
|
"radio_button_label_disabled": "0xFF8E9192",
|
||||||
|
"info_item_bg": "0xFFFFFFFF",
|
||||||
|
"info_item_label": "0xFF8E9192",
|
||||||
|
"info_item_text": "0xFF232323",
|
||||||
|
"info_item_icons": "0xFF0056D2",
|
||||||
|
"popup_bg": "0xFFFFFFFF",
|
||||||
|
"currency_list_item_bg": "0xFFF9F9FC",
|
||||||
|
"sw_bg": "0xFFFFFFFF",
|
||||||
|
"sw_mid": "0xFFFFFFFF",
|
||||||
|
"sw_bottom": "0xFF232323",
|
||||||
|
"bottom_nav_shadow": "0xFF282E33",
|
||||||
|
"favorite_star_active": "0xFF0056D2",
|
||||||
|
"favorite_star_inactive": "0xFFC4C7C7",
|
||||||
|
"splash": "0x358E9192",
|
||||||
|
"highlight": "0x44A9ACAC",
|
||||||
|
"warning_foreground": "0xFF232323",
|
||||||
|
"warning_background": "0xFFFFDAD3",
|
||||||
|
"loading_overlay_text_color": "0xFFF7F7F7",
|
||||||
|
"my_stack_contact_icon_bg": "0xFFEEEFF1",
|
||||||
|
"text_confirm_total_amount": "0xFF232323",
|
||||||
|
"text_selected_word_table_iterm": "0xFF232323",
|
||||||
|
"rate_type_toggle_color_on": "0xFFEEEFF1",
|
||||||
|
"rate_type_toggle_color_off": "0xFFFFFFFF",
|
||||||
|
"rate_type_toggle_desktop_color_on": "0xFFEEEFF1",
|
||||||
|
"rate_type_toggle_desktop_color_off": "0xFFE0E3E3",
|
||||||
|
"eth_tag_text": "0xFFFFFFFF",
|
||||||
|
"eth_tag_bg": "0xFF4D5798",
|
||||||
|
"eth_wallet_tag_text": "0xFF4D5798",
|
||||||
|
"eth_wallet_tag_bg": "0xFFF0F3FD",
|
||||||
|
"token_summary_text_primary": "0xFF232323",
|
||||||
|
"token_summary_text_secondary": "0xFF8488AB",
|
||||||
|
"token_summary_bg": "0xFFE9EAFF",
|
||||||
|
"token_summary_button_bg": "0xFFFFFFFF",
|
||||||
|
"token_summary_icon": "0xFF424A97",
|
||||||
|
"box_shadows": {
|
||||||
|
"standard": {
|
||||||
|
"color": "0x0F2D3132",
|
||||||
|
"spread_radius": 3.0,
|
||||||
|
"blur_radius": 4.0
|
||||||
|
},
|
||||||
|
"home_view_button_bar": {
|
||||||
|
"color": "0x0F2D3132",
|
||||||
|
"spread_radius": 3.0,
|
||||||
|
"blur_radius": 4.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"assets": {
|
||||||
|
"coin_placeholder": "dummy.svg",
|
||||||
|
// keys for coin assets must match the Coin enum name value exactly
|
||||||
|
"coins": {
|
||||||
|
"icons": {
|
||||||
|
"bitcoin": "dummy.svg",
|
||||||
|
"litecoin": "dummy.svg",
|
||||||
|
"bitcoincash": "dummy.svg",
|
||||||
|
"dogecoin": "dummy.svg",
|
||||||
|
"eCash": "dummy.svg",
|
||||||
|
"epicCash": "dummy.svg",
|
||||||
|
"ethereum": "dummy.svg",
|
||||||
|
"firo": "dummy.svg",
|
||||||
|
"monero": "dummy.svg",
|
||||||
|
"wownero": "dummy.svg",
|
||||||
|
"namecoin": "dummy.svg",
|
||||||
|
"particl": "dummy.svg"
|
||||||
|
},
|
||||||
|
"images": {
|
||||||
|
"bitcoin": "dummy.svg",
|
||||||
|
"litecoin": "dummy.svg",
|
||||||
|
"bitcoincash": "dummy.svg",
|
||||||
|
"dogecoin": "dummy.svg",
|
||||||
|
"eCash": "dummy.svg",
|
||||||
|
"epicCash": "dummy.svg",
|
||||||
|
"ethereum": "dummy.svg",
|
||||||
|
"firo": "dummy.svg",
|
||||||
|
"monero": "dummy.svg",
|
||||||
|
"wownero": "dummy.svg",
|
||||||
|
"namecoin": "dummy.svg",
|
||||||
|
"particl": "dummy.svg"
|
||||||
|
},
|
||||||
|
"secondaries": {
|
||||||
|
"bitcoin": "dummy.svg",
|
||||||
|
"litecoin": "dummy.svg",
|
||||||
|
"bitcoincash": "dummy.svg",
|
||||||
|
"dogecoin": "dummy.svg",
|
||||||
|
"eCash": "dummy.svg",
|
||||||
|
"epicCash": "dummy.svg",
|
||||||
|
"ethereum": "dummy.svg",
|
||||||
|
"firo": "dummy.svg",
|
||||||
|
"monero": "dummy.svg",
|
||||||
|
"wownero": "dummy.svg",
|
||||||
|
"namecoin": "dummy.svg",
|
||||||
|
"particl": "dummy.svg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bell_new": "dummy.svg",
|
||||||
|
"persona_incognito": "dummy.svg.svg",
|
||||||
|
"persona_easy": "dummy.svg",
|
||||||
|
"stack": "dummy.svg",
|
||||||
|
"stack_icon": "dummy.svg",
|
||||||
|
"receive": "dummy.svg",
|
||||||
|
"receive_pending": "dummy.svg",
|
||||||
|
"receive_cancelled": "dummy.svg",
|
||||||
|
"send": "dummy.svg",
|
||||||
|
"tx_exchange": "dummy.svg",
|
||||||
|
"tx_exchange_pending": "dummy.svg",
|
||||||
|
"tx_exchange_failed": "dummy.svg",
|
||||||
|
"buy": "dummy.svg",
|
||||||
|
"exchange": "dummy.svg",
|
||||||
|
"send_pending": "dummy.svg",
|
||||||
|
"send_cancelled": "dummy.svg",
|
||||||
|
"theme_selector": "dummy.svg",
|
||||||
|
"theme_preview": "dummy.png"
|
||||||
|
}
|
||||||
|
};
|
|
@ -175,7 +175,7 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX {
|
||||||
_i9.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
_i9.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||||
) as _i9.Future<Map<String, dynamic>>);
|
) as _i9.Future<Map<String, dynamic>>);
|
||||||
@override
|
@override
|
||||||
_i9.Future<List<dynamic>> getUsedCoinSerials({
|
_i9.Future<List<String>> getUsedCoinSerials({
|
||||||
required _i10.Coin? coin,
|
required _i10.Coin? coin,
|
||||||
int? startNumber = 0,
|
int? startNumber = 0,
|
||||||
}) =>
|
}) =>
|
||||||
|
@ -188,8 +188,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX {
|
||||||
#startNumber: startNumber,
|
#startNumber: startNumber,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
returnValue: _i9.Future<List<dynamic>>.value(<dynamic>[]),
|
returnValue: _i9.Future<List<String>>.value(<String>[]),
|
||||||
) as _i9.Future<List<dynamic>>);
|
) as _i9.Future<List<String>>);
|
||||||
@override
|
@override
|
||||||
_i9.Future<void> clearSharedTransactionCache({required _i10.Coin? coin}) =>
|
_i9.Future<void> clearSharedTransactionCache({required _i10.Coin? coin}) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
|
|
|
@ -487,7 +487,7 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX {
|
||||||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||||
) as _i5.Future<Map<String, dynamic>>);
|
) as _i5.Future<Map<String, dynamic>>);
|
||||||
@override
|
@override
|
||||||
_i5.Future<List<dynamic>> getUsedCoinSerials({
|
_i5.Future<List<String>> getUsedCoinSerials({
|
||||||
required _i7.Coin? coin,
|
required _i7.Coin? coin,
|
||||||
int? startNumber = 0,
|
int? startNumber = 0,
|
||||||
}) =>
|
}) =>
|
||||||
|
@ -500,8 +500,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX {
|
||||||
#startNumber: startNumber,
|
#startNumber: startNumber,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
returnValue: _i5.Future<List<dynamic>>.value(<dynamic>[]),
|
returnValue: _i5.Future<List<String>>.value(<String>[]),
|
||||||
) as _i5.Future<List<dynamic>>);
|
) as _i5.Future<List<String>>);
|
||||||
@override
|
@override
|
||||||
_i5.Future<void> clearSharedTransactionCache({required _i7.Coin? coin}) =>
|
_i5.Future<void> clearSharedTransactionCache({required _i7.Coin? coin}) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
|
|
|
@ -487,7 +487,7 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX {
|
||||||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||||
) as _i5.Future<Map<String, dynamic>>);
|
) as _i5.Future<Map<String, dynamic>>);
|
||||||
@override
|
@override
|
||||||
_i5.Future<List<dynamic>> getUsedCoinSerials({
|
_i5.Future<List<String>> getUsedCoinSerials({
|
||||||
required _i7.Coin? coin,
|
required _i7.Coin? coin,
|
||||||
int? startNumber = 0,
|
int? startNumber = 0,
|
||||||
}) =>
|
}) =>
|
||||||
|
@ -500,8 +500,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX {
|
||||||
#startNumber: startNumber,
|
#startNumber: startNumber,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
returnValue: _i5.Future<List<dynamic>>.value(<dynamic>[]),
|
returnValue: _i5.Future<List<String>>.value(<String>[]),
|
||||||
) as _i5.Future<List<dynamic>>);
|
) as _i5.Future<List<String>>);
|
||||||
@override
|
@override
|
||||||
_i5.Future<void> clearSharedTransactionCache({required _i7.Coin? coin}) =>
|
_i5.Future<void> clearSharedTransactionCache({required _i7.Coin? coin}) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
|
|
|
@ -487,7 +487,7 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX {
|
||||||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||||
) as _i5.Future<Map<String, dynamic>>);
|
) as _i5.Future<Map<String, dynamic>>);
|
||||||
@override
|
@override
|
||||||
_i5.Future<List<dynamic>> getUsedCoinSerials({
|
_i5.Future<List<String>> getUsedCoinSerials({
|
||||||
required _i7.Coin? coin,
|
required _i7.Coin? coin,
|
||||||
int? startNumber = 0,
|
int? startNumber = 0,
|
||||||
}) =>
|
}) =>
|
||||||
|
@ -500,8 +500,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX {
|
||||||
#startNumber: startNumber,
|
#startNumber: startNumber,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
returnValue: _i5.Future<List<dynamic>>.value(<dynamic>[]),
|
returnValue: _i5.Future<List<String>>.value(<String>[]),
|
||||||
) as _i5.Future<List<dynamic>>);
|
) as _i5.Future<List<String>>);
|
||||||
@override
|
@override
|
||||||
_i5.Future<void> clearSharedTransactionCache({required _i7.Coin? coin}) =>
|
_i5.Future<void> clearSharedTransactionCache({required _i7.Coin? coin}) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
|
|
|
@ -8,6 +8,7 @@ import 'package:hive/hive.dart';
|
||||||
import 'package:hive_test/hive_test.dart';
|
import 'package:hive_test/hive_test.dart';
|
||||||
import 'package:mockito/annotations.dart';
|
import 'package:mockito/annotations.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:stackwallet/db/isar/main_db.dart';
|
||||||
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
|
import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart';
|
||||||
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
import 'package:stackwallet/electrumx_rpc/electrumx.dart';
|
||||||
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
|
||||||
|
@ -17,7 +18,6 @@ import 'package:stackwallet/models/lelantus_fee_data.dart';
|
||||||
import 'package:stackwallet/models/paymint/transactions_model.dart' as old;
|
import 'package:stackwallet/models/paymint/transactions_model.dart' as old;
|
||||||
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
|
||||||
import 'package:stackwallet/services/transaction_notification_tracker.dart';
|
import 'package:stackwallet/services/transaction_notification_tracker.dart';
|
||||||
import 'package:stackwallet/utilities/address_utils.dart';
|
|
||||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
|
||||||
|
@ -34,16 +34,10 @@ import 'sample_data/transaction_data_samples.dart';
|
||||||
ElectrumX,
|
ElectrumX,
|
||||||
CachedElectrumX,
|
CachedElectrumX,
|
||||||
TransactionNotificationTracker,
|
TransactionNotificationTracker,
|
||||||
|
MainDB,
|
||||||
])
|
])
|
||||||
void main() {
|
void main() {
|
||||||
group("isolate functions", () {
|
group("isolate functions", () {
|
||||||
test("isolateDerive", () async {
|
|
||||||
final result = await isolateDerive(
|
|
||||||
IsolateDeriveParams.mnemonic, "", 0, 2, firoNetwork);
|
|
||||||
expect(result, isA<Map<String, dynamic>>());
|
|
||||||
expect(result.toString(), IsolateDeriveParams.expected);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("isolateRestore success", () async {
|
test("isolateRestore success", () async {
|
||||||
final cachedClient = MockCachedElectrumX();
|
final cachedClient = MockCachedElectrumX();
|
||||||
final txDataOLD = old.TransactionData.fromJson(dateTimeChunksJson);
|
final txDataOLD = old.TransactionData.fromJson(dateTimeChunksJson);
|
||||||
|
@ -76,7 +70,7 @@ void main() {
|
||||||
Coin.firo,
|
Coin.firo,
|
||||||
1,
|
1,
|
||||||
setData,
|
setData,
|
||||||
usedSerials,
|
List<String>.from(usedSerials),
|
||||||
firoNetwork,
|
firoNetwork,
|
||||||
);
|
);
|
||||||
const currentHeight = 100000000000;
|
const currentHeight = 100000000000;
|
||||||
|
@ -136,7 +130,7 @@ void main() {
|
||||||
Coin.firo,
|
Coin.firo,
|
||||||
1,
|
1,
|
||||||
setData,
|
setData,
|
||||||
usedSerials,
|
List<String>.from(usedSerials),
|
||||||
firoNetwork,
|
firoNetwork,
|
||||||
),
|
),
|
||||||
throwsA(isA<Error>()));
|
throwsA(isA<Error>()));
|
||||||
|
@ -1169,42 +1163,6 @@ void main() {
|
||||||
"b36161c6e619395b3d40a851c45c1fef7a5c541eed911b5524a66c5703a689c9");
|
"b36161c6e619395b3d40a851c45c1fef7a5c541eed911b5524a66c5703a689c9");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("fillAddresses", () async {
|
|
||||||
final client = MockElectrumX();
|
|
||||||
final cachedClient = MockCachedElectrumX();
|
|
||||||
final secureStore = FakeSecureStorage();
|
|
||||||
|
|
||||||
final firo = FiroWallet(
|
|
||||||
walletName: testWalletName,
|
|
||||||
walletId: "${testWalletId}fillAddresses",
|
|
||||||
coin: Coin.firo,
|
|
||||||
client: client,
|
|
||||||
cachedClient: cachedClient,
|
|
||||||
secureStore: secureStore,
|
|
||||||
tracker: MockTransactionNotificationTracker(),
|
|
||||||
);
|
|
||||||
|
|
||||||
await firo.fillAddresses(
|
|
||||||
FillAddressesParams.mnemonic,
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
|
|
||||||
final rcv = await secureStore.read(
|
|
||||||
key: "${testWalletId}fillAddresses_receiveDerivations");
|
|
||||||
final chg = await secureStore.read(
|
|
||||||
key: "${testWalletId}fillAddresses_changeDerivations");
|
|
||||||
final receiveDerivations =
|
|
||||||
Map<String, dynamic>.from(jsonDecode(rcv as String) as Map);
|
|
||||||
final changeDerivations =
|
|
||||||
Map<String, dynamic>.from(jsonDecode(chg as String) as Map);
|
|
||||||
|
|
||||||
expect(receiveDerivations.toString(),
|
|
||||||
FillAddressesParams.expectedReceiveDerivationsString);
|
|
||||||
|
|
||||||
expect(changeDerivations.toString(),
|
|
||||||
FillAddressesParams.expectedChangeDerivationsString);
|
|
||||||
});
|
|
||||||
|
|
||||||
// the above test needs to pass in order for this test to pass
|
// the above test needs to pass in order for this test to pass
|
||||||
test("buildMintTransaction", () async {
|
test("buildMintTransaction", () async {
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
@ -1230,6 +1188,7 @@ void main() {
|
||||||
final client = MockElectrumX();
|
final client = MockElectrumX();
|
||||||
final cachedClient = MockCachedElectrumX();
|
final cachedClient = MockCachedElectrumX();
|
||||||
final secureStore = FakeSecureStorage();
|
final secureStore = FakeSecureStorage();
|
||||||
|
final mainDB = MockMainDB();
|
||||||
|
|
||||||
await secureStore.write(
|
await secureStore.write(
|
||||||
key: "${testWalletId}buildMintTransaction_mnemonic",
|
key: "${testWalletId}buildMintTransaction_mnemonic",
|
||||||
|
@ -1246,6 +1205,9 @@ void main() {
|
||||||
when(client.getBlockHeadTip()).thenAnswer(
|
when(client.getBlockHeadTip()).thenAnswer(
|
||||||
(_) async => {"height": 455873, "hex": "this value not used here"});
|
(_) async => {"height": 455873, "hex": "this value not used here"});
|
||||||
|
|
||||||
|
when(mainDB.getAddress("${testWalletId}buildMintTransaction", any))
|
||||||
|
.thenAnswer((realInvocation) async => null);
|
||||||
|
|
||||||
final firo = FiroWallet(
|
final firo = FiroWallet(
|
||||||
walletName: testWalletName,
|
walletName: testWalletName,
|
||||||
walletId: "${testWalletId}buildMintTransaction",
|
walletId: "${testWalletId}buildMintTransaction",
|
||||||
|
@ -1254,6 +1216,7 @@ void main() {
|
||||||
cachedClient: cachedClient,
|
cachedClient: cachedClient,
|
||||||
secureStore: secureStore,
|
secureStore: secureStore,
|
||||||
tracker: MockTransactionNotificationTracker(),
|
tracker: MockTransactionNotificationTracker(),
|
||||||
|
mockableOverride: mainDB,
|
||||||
);
|
);
|
||||||
|
|
||||||
final wallet =
|
final wallet =
|
||||||
|
@ -2314,8 +2277,8 @@ void main() {
|
||||||
groupId: "1", blockhash: "", coin: Coin.firo))
|
groupId: "1", blockhash: "", coin: Coin.firo))
|
||||||
.thenAnswer((_) async => GetAnonymitySetSampleData.data);
|
.thenAnswer((_) async => GetAnonymitySetSampleData.data);
|
||||||
when(cachedClient.getUsedCoinSerials(startNumber: 0, coin: Coin.firo))
|
when(cachedClient.getUsedCoinSerials(startNumber: 0, coin: Coin.firo))
|
||||||
.thenAnswer(
|
.thenAnswer((_) async => List<String>.from(
|
||||||
(_) async => GetUsedSerialsSampleData.serials['serials'] as List);
|
GetUsedSerialsSampleData.serials['serials'] as List));
|
||||||
|
|
||||||
final firo = FiroWallet(
|
final firo = FiroWallet(
|
||||||
walletId: "${testWalletId}getUsedCoinSerials",
|
walletId: "${testWalletId}getUsedCoinSerials",
|
||||||
|
@ -2811,169 +2774,6 @@ void main() {
|
||||||
// throwsA(isA<Exception>()));
|
// throwsA(isA<Exception>()));
|
||||||
// }, timeout: const Timeout(Duration(minutes: 3)));
|
// }, timeout: const Timeout(Duration(minutes: 3)));
|
||||||
|
|
||||||
test("send fails due to bad transaction created", () async {
|
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
const MethodChannel('uk.spiralarm.flutter/devicelocale')
|
|
||||||
.setMockMethodCallHandler((methodCall) async => 'en_US');
|
|
||||||
|
|
||||||
final client = MockElectrumX();
|
|
||||||
final cachedClient = MockCachedElectrumX();
|
|
||||||
final secureStore = FakeSecureStorage();
|
|
||||||
|
|
||||||
when(client.getLatestCoinId()).thenAnswer((_) async => 1);
|
|
||||||
when(client.getBlockHeadTip()).thenAnswer(
|
|
||||||
(_) async => {"height": 459185, "hex": "... some block hex ..."});
|
|
||||||
|
|
||||||
when(client.broadcastTransaction(rawTx: anyNamed("rawTx")))
|
|
||||||
.thenAnswer((_) async {
|
|
||||||
return "some bad txid";
|
|
||||||
});
|
|
||||||
|
|
||||||
when(client.getBatchHistory(args: batchHistoryRequest0))
|
|
||||||
.thenAnswer((realInvocation) async => batchHistoryResponse0);
|
|
||||||
|
|
||||||
when(cachedClient.getAnonymitySet(
|
|
||||||
groupId: "1",
|
|
||||||
coin: Coin.firo,
|
|
||||||
)).thenAnswer((_) async => GetAnonymitySetSampleData.data);
|
|
||||||
|
|
||||||
// mock transaction calls
|
|
||||||
when(cachedClient.getTransaction(
|
|
||||||
txHash: SampleGetTransactionData.txHash0,
|
|
||||||
coin: Coin.firo,
|
|
||||||
)).thenAnswer((_) async => SampleGetTransactionData.txData0);
|
|
||||||
when(cachedClient.getTransaction(
|
|
||||||
txHash: SampleGetTransactionData.txHash1,
|
|
||||||
coin: Coin.firo,
|
|
||||||
)).thenAnswer((_) async => SampleGetTransactionData.txData1);
|
|
||||||
when(cachedClient.getTransaction(
|
|
||||||
txHash: SampleGetTransactionData.txHash2,
|
|
||||||
coin: Coin.firo,
|
|
||||||
)).thenAnswer((_) async => SampleGetTransactionData.txData2);
|
|
||||||
when(cachedClient.getTransaction(
|
|
||||||
txHash: SampleGetTransactionData.txHash3,
|
|
||||||
coin: Coin.firo,
|
|
||||||
)).thenAnswer((_) async => SampleGetTransactionData.txData3);
|
|
||||||
when(cachedClient.getTransaction(
|
|
||||||
txHash: SampleGetTransactionData.txHash4,
|
|
||||||
coin: Coin.firo,
|
|
||||||
)).thenAnswer((_) async => SampleGetTransactionData.txData4);
|
|
||||||
when(cachedClient.getTransaction(
|
|
||||||
txHash: SampleGetTransactionData.txHash5,
|
|
||||||
coin: Coin.firo,
|
|
||||||
)).thenAnswer((_) async => SampleGetTransactionData.txData5);
|
|
||||||
when(cachedClient.getTransaction(
|
|
||||||
txHash: SampleGetTransactionData.txHash6,
|
|
||||||
coin: Coin.firo,
|
|
||||||
)).thenAnswer((_) async => SampleGetTransactionData.txData6);
|
|
||||||
when(cachedClient.getTransaction(
|
|
||||||
txHash: SampleGetTransactionData.txHash7,
|
|
||||||
coin: Coin.firo,
|
|
||||||
)).thenAnswer((_) async => SampleGetTransactionData.txData7);
|
|
||||||
when(cachedClient.getTransaction(
|
|
||||||
txHash: SampleGetTransactionData.txHash8,
|
|
||||||
coin: Coin.firo,
|
|
||||||
)).thenAnswer((_) async => SampleGetTransactionData.txData8);
|
|
||||||
when(cachedClient.getTransaction(
|
|
||||||
txHash: SampleGetTransactionData.txHash9,
|
|
||||||
coin: Coin.firo,
|
|
||||||
)).thenAnswer((_) async => SampleGetTransactionData.txData9);
|
|
||||||
when(cachedClient.getTransaction(
|
|
||||||
txHash: SampleGetTransactionData.txHash10,
|
|
||||||
coin: Coin.firo,
|
|
||||||
)).thenAnswer((_) async => SampleGetTransactionData.txData10);
|
|
||||||
|
|
||||||
final firo = FiroWallet(
|
|
||||||
walletId: "${testWalletId}send",
|
|
||||||
coin: Coin.firo,
|
|
||||||
walletName: testWalletName,
|
|
||||||
client: client,
|
|
||||||
cachedClient: cachedClient,
|
|
||||||
secureStore: secureStore,
|
|
||||||
tracker: MockTransactionNotificationTracker(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// set mnemonic
|
|
||||||
await secureStore.write(
|
|
||||||
key: "${testWalletId}send_mnemonic", value: TEST_MNEMONIC);
|
|
||||||
|
|
||||||
// set timer to non null so a periodic timer isn't created
|
|
||||||
firo.timer = Timer(const Duration(), () {});
|
|
||||||
|
|
||||||
// build sending wallet
|
|
||||||
await firo.fillAddresses(
|
|
||||||
TEST_MNEMONIC,
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
final wallet = await Hive.openBox<dynamic>("${testWalletId}send");
|
|
||||||
|
|
||||||
final rcv =
|
|
||||||
await secureStore.read(key: "${testWalletId}send_receiveDerivations");
|
|
||||||
final chg =
|
|
||||||
await secureStore.read(key: "${testWalletId}send_changeDerivations");
|
|
||||||
final receiveDerivations =
|
|
||||||
Map<String, dynamic>.from(jsonDecode(rcv as String) as Map);
|
|
||||||
final changeDerivations =
|
|
||||||
Map<String, dynamic>.from(jsonDecode(chg as String) as Map);
|
|
||||||
|
|
||||||
for (int i = 0; i < receiveDerivations.length; i++) {
|
|
||||||
final receiveHash = AddressUtils.convertToScriptHash(
|
|
||||||
receiveDerivations["$i"]!["address"] as String, firoNetwork);
|
|
||||||
final changeHash = AddressUtils.convertToScriptHash(
|
|
||||||
changeDerivations["$i"]!["address"] as String, firoNetwork);
|
|
||||||
List<Map<String, dynamic>> data;
|
|
||||||
switch (receiveHash) {
|
|
||||||
case SampleGetHistoryData.scripthash0:
|
|
||||||
data = SampleGetHistoryData.data0;
|
|
||||||
break;
|
|
||||||
case SampleGetHistoryData.scripthash1:
|
|
||||||
data = SampleGetHistoryData.data1;
|
|
||||||
break;
|
|
||||||
case SampleGetHistoryData.scripthash2:
|
|
||||||
data = SampleGetHistoryData.data2;
|
|
||||||
break;
|
|
||||||
case SampleGetHistoryData.scripthash3:
|
|
||||||
data = SampleGetHistoryData.data3;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
data = [];
|
|
||||||
}
|
|
||||||
when(client.getHistory(scripthash: receiveHash))
|
|
||||||
.thenAnswer((_) async => data);
|
|
||||||
|
|
||||||
switch (changeHash) {
|
|
||||||
case SampleGetHistoryData.scripthash0:
|
|
||||||
data = SampleGetHistoryData.data0;
|
|
||||||
break;
|
|
||||||
case SampleGetHistoryData.scripthash1:
|
|
||||||
data = SampleGetHistoryData.data1;
|
|
||||||
break;
|
|
||||||
case SampleGetHistoryData.scripthash2:
|
|
||||||
data = SampleGetHistoryData.data2;
|
|
||||||
break;
|
|
||||||
case SampleGetHistoryData.scripthash3:
|
|
||||||
data = SampleGetHistoryData.data3;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
data = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
when(client.getHistory(scripthash: changeHash))
|
|
||||||
.thenAnswer((_) async => data);
|
|
||||||
}
|
|
||||||
|
|
||||||
await wallet.put('_lelantus_coins', SampleLelantus.lelantusCoins);
|
|
||||||
await wallet.put('jindex', [2, 4, 6]);
|
|
||||||
await wallet.put('mintIndex', 8);
|
|
||||||
await wallet.put('receivingAddresses', [
|
|
||||||
"a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg",
|
|
||||||
"aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh",
|
|
||||||
"aKmXfS7nEZdqWBGRdAXcyMoEoKhZQDPBoq"
|
|
||||||
]);
|
|
||||||
await wallet
|
|
||||||
.put('changeAddresses', ["a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w"]);
|
|
||||||
}, timeout: const Timeout(Duration(minutes: 3)));
|
|
||||||
|
|
||||||
// test("wallet balances", () async {
|
// test("wallet balances", () async {
|
||||||
// TestWidgetsFlutterBinding.ensureInitialized();
|
// TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
// const MethodChannel('uk.spiralarm.flutter/devicelocale')
|
// const MethodChannel('uk.spiralarm.flutter/devicelocale')
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -658,28 +658,6 @@ class MockFiroWallet extends _i1.Mock implements _i10.FiroWallet {
|
||||||
returnValueForMissingStub: _i11.Future<void>.value(),
|
returnValueForMissingStub: _i11.Future<void>.value(),
|
||||||
) as _i11.Future<void>);
|
) as _i11.Future<void>);
|
||||||
@override
|
@override
|
||||||
_i11.Future<void> fillAddresses(
|
|
||||||
String? suppliedMnemonic,
|
|
||||||
String? mnemonicPassphrase, {
|
|
||||||
int? perBatch = 50,
|
|
||||||
int? numberOfThreads = 4,
|
|
||||||
}) =>
|
|
||||||
(super.noSuchMethod(
|
|
||||||
Invocation.method(
|
|
||||||
#fillAddresses,
|
|
||||||
[
|
|
||||||
suppliedMnemonic,
|
|
||||||
mnemonicPassphrase,
|
|
||||||
],
|
|
||||||
{
|
|
||||||
#perBatch: perBatch,
|
|
||||||
#numberOfThreads: numberOfThreads,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
returnValue: _i11.Future<void>.value(),
|
|
||||||
returnValueForMissingStub: _i11.Future<void>.value(),
|
|
||||||
) as _i11.Future<void>);
|
|
||||||
@override
|
|
||||||
_i11.Future<void> fullRescan(
|
_i11.Future<void> fullRescan(
|
||||||
int? maxUnusedAddressGap,
|
int? maxUnusedAddressGap,
|
||||||
int? maxNumberOfIndexesToCheck,
|
int? maxNumberOfIndexesToCheck,
|
||||||
|
@ -728,6 +706,16 @@ class MockFiroWallet extends _i1.Mock implements _i10.FiroWallet {
|
||||||
returnValue: _i11.Future<Map<int, dynamic>>.value(<int, dynamic>{}),
|
returnValue: _i11.Future<Map<int, dynamic>>.value(<int, dynamic>{}),
|
||||||
) as _i11.Future<Map<int, dynamic>>);
|
) as _i11.Future<Map<int, dynamic>>);
|
||||||
@override
|
@override
|
||||||
|
_i11.Future<void> getTransactionCacheEarly(List<String>? allAddresses) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(
|
||||||
|
#getTransactionCacheEarly,
|
||||||
|
[allAddresses],
|
||||||
|
),
|
||||||
|
returnValue: _i11.Future<void>.value(),
|
||||||
|
returnValueForMissingStub: _i11.Future<void>.value(),
|
||||||
|
) as _i11.Future<void>);
|
||||||
|
@override
|
||||||
_i11.Future<List<Map<String, dynamic>>> fetchAnonymitySets() =>
|
_i11.Future<List<Map<String, dynamic>>> fetchAnonymitySets() =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
|
@ -746,13 +734,13 @@ class MockFiroWallet extends _i1.Mock implements _i10.FiroWallet {
|
||||||
returnValue: _i11.Future<int>.value(0),
|
returnValue: _i11.Future<int>.value(0),
|
||||||
) as _i11.Future<int>);
|
) as _i11.Future<int>);
|
||||||
@override
|
@override
|
||||||
_i11.Future<List<dynamic>> getUsedCoinSerials() => (super.noSuchMethod(
|
_i11.Future<List<String>> getUsedCoinSerials() => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getUsedCoinSerials,
|
#getUsedCoinSerials,
|
||||||
[],
|
[],
|
||||||
),
|
),
|
||||||
returnValue: _i11.Future<List<dynamic>>.value(<dynamic>[]),
|
returnValue: _i11.Future<List<String>>.value(<String>[]),
|
||||||
) as _i11.Future<List<dynamic>>);
|
) as _i11.Future<List<String>>);
|
||||||
@override
|
@override
|
||||||
_i11.Future<void> exit() => (super.noSuchMethod(
|
_i11.Future<void> exit() => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
|
|
|
@ -487,7 +487,7 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX {
|
||||||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||||
) as _i5.Future<Map<String, dynamic>>);
|
) as _i5.Future<Map<String, dynamic>>);
|
||||||
@override
|
@override
|
||||||
_i5.Future<List<dynamic>> getUsedCoinSerials({
|
_i5.Future<List<String>> getUsedCoinSerials({
|
||||||
required _i7.Coin? coin,
|
required _i7.Coin? coin,
|
||||||
int? startNumber = 0,
|
int? startNumber = 0,
|
||||||
}) =>
|
}) =>
|
||||||
|
@ -500,8 +500,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX {
|
||||||
#startNumber: startNumber,
|
#startNumber: startNumber,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
returnValue: _i5.Future<List<dynamic>>.value(<dynamic>[]),
|
returnValue: _i5.Future<List<String>>.value(<String>[]),
|
||||||
) as _i5.Future<List<dynamic>>);
|
) as _i5.Future<List<String>>);
|
||||||
@override
|
@override
|
||||||
_i5.Future<void> clearSharedTransactionCache({required _i7.Coin? coin}) =>
|
_i5.Future<void> clearSharedTransactionCache({required _i7.Coin? coin}) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
|
|
|
@ -487,7 +487,7 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX {
|
||||||
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||||
) as _i5.Future<Map<String, dynamic>>);
|
) as _i5.Future<Map<String, dynamic>>);
|
||||||
@override
|
@override
|
||||||
_i5.Future<List<dynamic>> getUsedCoinSerials({
|
_i5.Future<List<String>> getUsedCoinSerials({
|
||||||
required _i7.Coin? coin,
|
required _i7.Coin? coin,
|
||||||
int? startNumber = 0,
|
int? startNumber = 0,
|
||||||
}) =>
|
}) =>
|
||||||
|
@ -500,8 +500,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX {
|
||||||
#startNumber: startNumber,
|
#startNumber: startNumber,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
returnValue: _i5.Future<List<dynamic>>.value(<dynamic>[]),
|
returnValue: _i5.Future<List<String>>.value(<String>[]),
|
||||||
) as _i5.Future<List<dynamic>>);
|
) as _i5.Future<List<String>>);
|
||||||
@override
|
@override
|
||||||
_i5.Future<void> clearSharedTransactionCache({required _i7.Coin? coin}) =>
|
_i5.Future<void> clearSharedTransactionCache({required _i7.Coin? coin}) =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
|
|
|
@ -3,20 +3,63 @@ import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:stackwallet/models/isar/stack_theme.dart';
|
import 'package:stackwallet/models/isar/stack_theme.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/text_styles.dart';
|
||||||
import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put.dart';
|
import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put.dart';
|
||||||
import 'package:stackwallet/widgets/custom_pin_put/pin_keyboard.dart';
|
import 'package:stackwallet/widgets/custom_pin_put/pin_keyboard.dart';
|
||||||
|
|
||||||
import '../sample_data/theme_json.dart';
|
import '../sample_data/theme_json.dart';
|
||||||
|
|
||||||
|
class PinWidget extends StatefulWidget {
|
||||||
|
const PinWidget({
|
||||||
|
super.key,
|
||||||
|
this.onSubmit,
|
||||||
|
this.controller,
|
||||||
|
required this.pinAnimation,
|
||||||
|
required this.isRandom,
|
||||||
|
});
|
||||||
|
|
||||||
|
final void Function(String)? onSubmit;
|
||||||
|
final TextEditingController? controller;
|
||||||
|
final bool isRandom;
|
||||||
|
final PinAnimationType pinAnimation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
PinWidgetState createState() => PinWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class PinWidgetState extends State<PinWidget> {
|
||||||
|
int pinCount = 1;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
bool submittedPinMatches = false;
|
||||||
|
|
||||||
|
return CustomPinPut(
|
||||||
|
fieldsCount: pinCount,
|
||||||
|
isRandom: widget.isRandom,
|
||||||
|
useNativeKeyboard: false,
|
||||||
|
eachFieldHeight: 12,
|
||||||
|
eachFieldWidth: 12,
|
||||||
|
textStyle: STextStyles.label(context).copyWith(
|
||||||
|
fontSize: 1,
|
||||||
|
),
|
||||||
|
obscureText: "",
|
||||||
|
onPinLengthChanged: (newLength) {
|
||||||
|
setState(() {
|
||||||
|
pinCount = newLength;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSubmit: widget.onSubmit,
|
||||||
|
controller: widget.controller,
|
||||||
|
pinAnimationType: widget.pinAnimation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group("CustomPinPut tests, non-random PIN", () {
|
group("CustomPinPut tests, non-random PIN", () {
|
||||||
testWidgets("CustomPinPut with 4 fields builds correctly, non-random PIN",
|
testWidgets("CustomPinPut with 4 fields builds correctly, non-random PIN",
|
||||||
(tester) async {
|
(tester) async {
|
||||||
const pinPut = CustomPinPut(
|
|
||||||
fieldsCount: 4,
|
|
||||||
isRandom: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
|
@ -30,13 +73,16 @@ void main() {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
home: const Material(
|
home: const Material(
|
||||||
child: pinPut,
|
child: PinWidget(
|
||||||
|
pinAnimation: PinAnimationType.none,
|
||||||
|
isRandom: false,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// expects 5 here. Four + the actual text field text
|
// expects 5 here. Four + the actual text field text
|
||||||
expect(find.text(""), findsNWidgets(5));
|
expect(find.text(""), findsNWidgets(1));
|
||||||
expect(find.byType(PinKeyboard), findsOneWidget);
|
expect(find.byType(PinKeyboard), findsOneWidget);
|
||||||
expect(find.byType(BackspaceKey), findsOneWidget);
|
expect(find.byType(BackspaceKey), findsOneWidget);
|
||||||
expect(find.byType(NumberKey), findsNWidgets(10));
|
expect(find.byType(NumberKey), findsNWidgets(10));
|
||||||
|
@ -45,15 +91,6 @@ void main() {
|
||||||
testWidgets("CustomPinPut entering a pin successfully, non-random PIN",
|
testWidgets("CustomPinPut entering a pin successfully, non-random PIN",
|
||||||
(tester) async {
|
(tester) async {
|
||||||
bool submittedPinMatches = false;
|
bool submittedPinMatches = false;
|
||||||
final pinPut = CustomPinPut(
|
|
||||||
fieldsCount: 4,
|
|
||||||
onSubmit: (pin) {
|
|
||||||
submittedPinMatches = pin == "1234";
|
|
||||||
print("pin entered: $pin");
|
|
||||||
},
|
|
||||||
useNativeKeyboard: false,
|
|
||||||
isRandom: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
@ -68,7 +105,14 @@ void main() {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
home: Material(
|
home: Material(
|
||||||
child: pinPut,
|
child: PinWidget(
|
||||||
|
pinAnimation: PinAnimationType.none,
|
||||||
|
isRandom: false,
|
||||||
|
onSubmit: (pin) {
|
||||||
|
submittedPinMatches = pin == "1234";
|
||||||
|
print("pin entered: $pin");
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -99,12 +143,6 @@ void main() {
|
||||||
testWidgets("CustomPinPut pin enter fade animation, non-random PIN",
|
testWidgets("CustomPinPut pin enter fade animation, non-random PIN",
|
||||||
(tester) async {
|
(tester) async {
|
||||||
final controller = TextEditingController();
|
final controller = TextEditingController();
|
||||||
final pinPut = CustomPinPut(
|
|
||||||
fieldsCount: 4,
|
|
||||||
pinAnimationType: PinAnimationType.fade,
|
|
||||||
controller: controller,
|
|
||||||
isRandom: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
@ -119,7 +157,11 @@ void main() {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
home: Material(
|
home: Material(
|
||||||
child: pinPut,
|
child: PinWidget(
|
||||||
|
pinAnimation: PinAnimationType.none,
|
||||||
|
isRandom: false,
|
||||||
|
controller: controller,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -137,12 +179,6 @@ void main() {
|
||||||
testWidgets("CustomPinPut pin enter scale animation, non-random PIN",
|
testWidgets("CustomPinPut pin enter scale animation, non-random PIN",
|
||||||
(tester) async {
|
(tester) async {
|
||||||
final controller = TextEditingController();
|
final controller = TextEditingController();
|
||||||
final pinPut = CustomPinPut(
|
|
||||||
fieldsCount: 4,
|
|
||||||
pinAnimationType: PinAnimationType.scale,
|
|
||||||
controller: controller,
|
|
||||||
isRandom: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
@ -157,7 +193,11 @@ void main() {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
home: Material(
|
home: Material(
|
||||||
child: pinPut,
|
child: PinWidget(
|
||||||
|
pinAnimation: PinAnimationType.scale,
|
||||||
|
isRandom: false,
|
||||||
|
controller: controller,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -175,12 +215,6 @@ void main() {
|
||||||
testWidgets("CustomPinPut pin enter rotate animation, non-random PIN",
|
testWidgets("CustomPinPut pin enter rotate animation, non-random PIN",
|
||||||
(tester) async {
|
(tester) async {
|
||||||
final controller = TextEditingController();
|
final controller = TextEditingController();
|
||||||
final pinPut = CustomPinPut(
|
|
||||||
fieldsCount: 4,
|
|
||||||
pinAnimationType: PinAnimationType.rotation,
|
|
||||||
controller: controller,
|
|
||||||
isRandom: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
@ -195,7 +229,11 @@ void main() {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
home: Material(
|
home: Material(
|
||||||
child: pinPut,
|
child: PinWidget(
|
||||||
|
pinAnimation: PinAnimationType.rotation,
|
||||||
|
isRandom: false,
|
||||||
|
controller: controller,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -256,11 +294,6 @@ void main() {
|
||||||
group("CustomPinPut tests, with random PIN", () {
|
group("CustomPinPut tests, with random PIN", () {
|
||||||
testWidgets("CustomPinPut with 4 fields builds correctly, with random PIN",
|
testWidgets("CustomPinPut with 4 fields builds correctly, with random PIN",
|
||||||
(tester) async {
|
(tester) async {
|
||||||
const pinPut = CustomPinPut(
|
|
||||||
fieldsCount: 4,
|
|
||||||
isRandom: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
|
@ -274,81 +307,76 @@ void main() {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
home: const Material(
|
home: const Material(
|
||||||
child: pinPut,
|
child: PinWidget(
|
||||||
|
pinAnimation: PinAnimationType.none,
|
||||||
|
isRandom: true,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// expects 5 here. Four + the actual text field text
|
// expects 5 here. Four + the actual text field text
|
||||||
expect(find.text(""), findsNWidgets(5));
|
expect(find.text(""), findsNWidgets(1));
|
||||||
expect(find.byType(PinKeyboard), findsOneWidget);
|
expect(find.byType(PinKeyboard), findsOneWidget);
|
||||||
expect(find.byType(BackspaceKey), findsOneWidget);
|
expect(find.byType(BackspaceKey), findsOneWidget);
|
||||||
expect(find.byType(NumberKey), findsNWidgets(10));
|
expect(find.byType(NumberKey), findsNWidgets(10));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets("CustomPinPut entering a pin successfully, with random PIN",
|
// testWidgets("CustomPinPut entering a pin successfully, with random PIN",
|
||||||
(tester) async {
|
// (tester) async {
|
||||||
bool submittedPinMatches = false;
|
// bool submittedPinMatches = false;
|
||||||
final pinPut = CustomPinPut(
|
//
|
||||||
fieldsCount: 4,
|
// await tester.pumpWidget(
|
||||||
onSubmit: (pin) {
|
// MaterialApp(
|
||||||
submittedPinMatches = pin == "1234";
|
// theme: ThemeData(
|
||||||
print("pin entered: $pin");
|
// extensions: [
|
||||||
},
|
// StackColors.fromStackColorTheme(
|
||||||
useNativeKeyboard: false,
|
// StackTheme.fromJson(
|
||||||
isRandom: true,
|
// json: lightThemeJsonMap,
|
||||||
);
|
// applicationThemesDirectoryPath: "test",
|
||||||
|
// ),
|
||||||
await tester.pumpWidget(
|
// ),
|
||||||
MaterialApp(
|
// ],
|
||||||
theme: ThemeData(
|
// ),
|
||||||
extensions: [
|
// home: Material(
|
||||||
StackColors.fromStackColorTheme(
|
// child: PinWidget(
|
||||||
StackTheme.fromJson(
|
// pinAnimation: PinAnimationType.none,
|
||||||
json: lightThemeJsonMap,
|
// isRandom: true,
|
||||||
applicationThemesDirectoryPath: "test",
|
// onSubmit: (pin) {
|
||||||
),
|
// submittedPinMatches = pin == "1234";
|
||||||
),
|
// print("pin entered: $pin");
|
||||||
],
|
// },
|
||||||
),
|
// ),
|
||||||
home: Material(
|
// ),
|
||||||
child: pinPut,
|
// ),
|
||||||
),
|
// );
|
||||||
),
|
//
|
||||||
);
|
// await tester.tap(find.byWidgetPredicate(
|
||||||
|
// (widget) => widget is NumberKey && widget.number == "1"));
|
||||||
await tester.tap(find.byWidgetPredicate(
|
// await tester.pumpAndSettle();
|
||||||
(widget) => widget is NumberKey && widget.number == "1"));
|
// await tester.tap(find.byWidgetPredicate(
|
||||||
await tester.pumpAndSettle();
|
// (widget) => widget is NumberKey && widget.number == "2"));
|
||||||
await tester.tap(find.byWidgetPredicate(
|
// await tester.pumpAndSettle();
|
||||||
(widget) => widget is NumberKey && widget.number == "2"));
|
// await tester.tap(find.byWidgetPredicate(
|
||||||
await tester.pumpAndSettle();
|
// (widget) => widget is NumberKey && widget.number == "6"));
|
||||||
await tester.tap(find.byWidgetPredicate(
|
// await tester.pumpAndSettle();
|
||||||
(widget) => widget is NumberKey && widget.number == "6"));
|
// await tester.tap(find.byType(BackspaceKey));
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
await tester.tap(find.byType(BackspaceKey));
|
// await tester.tap(find.byWidgetPredicate(
|
||||||
await tester.pumpAndSettle();
|
// (widget) => widget is NumberKey && widget.number == "3"));
|
||||||
await tester.tap(find.byWidgetPredicate(
|
// await tester.pumpAndSettle();
|
||||||
(widget) => widget is NumberKey && widget.number == "3"));
|
// await tester.tap(find.byWidgetPredicate(
|
||||||
await tester.pumpAndSettle();
|
// (widget) => widget is NumberKey && widget.number == "4"));
|
||||||
await tester.tap(find.byWidgetPredicate(
|
// await tester.pumpAndSettle();
|
||||||
(widget) => widget is NumberKey && widget.number == "4"));
|
// await tester.tap(find.byType(SubmitKey));
|
||||||
await tester.pumpAndSettle();
|
// await tester.pumpAndSettle();
|
||||||
await tester.tap(find.byType(SubmitKey));
|
//
|
||||||
await tester.pumpAndSettle();
|
// expect(submittedPinMatches, true);
|
||||||
|
// });
|
||||||
expect(submittedPinMatches, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets("CustomPinPut pin enter fade animation, with random PIN",
|
testWidgets("CustomPinPut pin enter fade animation, with random PIN",
|
||||||
(tester) async {
|
(tester) async {
|
||||||
final controller = TextEditingController();
|
final controller = TextEditingController();
|
||||||
final pinPut = CustomPinPut(
|
|
||||||
fieldsCount: 4,
|
|
||||||
pinAnimationType: PinAnimationType.fade,
|
|
||||||
controller: controller,
|
|
||||||
isRandom: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
@ -363,7 +391,11 @@ void main() {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
home: Material(
|
home: Material(
|
||||||
child: pinPut,
|
child: PinWidget(
|
||||||
|
pinAnimation: PinAnimationType.fade,
|
||||||
|
isRandom: true,
|
||||||
|
controller: controller,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -401,7 +433,11 @@ void main() {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
home: Material(
|
home: Material(
|
||||||
child: pinPut,
|
child: PinWidget(
|
||||||
|
isRandom: true,
|
||||||
|
controller: controller,
|
||||||
|
pinAnimation: PinAnimationType.scale,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -439,7 +475,11 @@ void main() {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
home: Material(
|
home: Material(
|
||||||
child: pinPut,
|
child: PinWidget(
|
||||||
|
isRandom: true,
|
||||||
|
controller: controller,
|
||||||
|
pinAnimation: PinAnimationType.rotation,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1647,28 +1647,6 @@ class MockFiroWallet extends _i1.Mock implements _i22.FiroWallet {
|
||||||
returnValueForMissingStub: _i18.Future<void>.value(),
|
returnValueForMissingStub: _i18.Future<void>.value(),
|
||||||
) as _i18.Future<void>);
|
) as _i18.Future<void>);
|
||||||
@override
|
@override
|
||||||
_i18.Future<void> fillAddresses(
|
|
||||||
String? suppliedMnemonic,
|
|
||||||
String? mnemonicPassphrase, {
|
|
||||||
int? perBatch = 50,
|
|
||||||
int? numberOfThreads = 4,
|
|
||||||
}) =>
|
|
||||||
(super.noSuchMethod(
|
|
||||||
Invocation.method(
|
|
||||||
#fillAddresses,
|
|
||||||
[
|
|
||||||
suppliedMnemonic,
|
|
||||||
mnemonicPassphrase,
|
|
||||||
],
|
|
||||||
{
|
|
||||||
#perBatch: perBatch,
|
|
||||||
#numberOfThreads: numberOfThreads,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
returnValue: _i18.Future<void>.value(),
|
|
||||||
returnValueForMissingStub: _i18.Future<void>.value(),
|
|
||||||
) as _i18.Future<void>);
|
|
||||||
@override
|
|
||||||
_i18.Future<void> fullRescan(
|
_i18.Future<void> fullRescan(
|
||||||
int? maxUnusedAddressGap,
|
int? maxUnusedAddressGap,
|
||||||
int? maxNumberOfIndexesToCheck,
|
int? maxNumberOfIndexesToCheck,
|
||||||
|
@ -1717,6 +1695,16 @@ class MockFiroWallet extends _i1.Mock implements _i22.FiroWallet {
|
||||||
returnValue: _i18.Future<Map<int, dynamic>>.value(<int, dynamic>{}),
|
returnValue: _i18.Future<Map<int, dynamic>>.value(<int, dynamic>{}),
|
||||||
) as _i18.Future<Map<int, dynamic>>);
|
) as _i18.Future<Map<int, dynamic>>);
|
||||||
@override
|
@override
|
||||||
|
_i18.Future<void> getTransactionCacheEarly(List<String>? allAddresses) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(
|
||||||
|
#getTransactionCacheEarly,
|
||||||
|
[allAddresses],
|
||||||
|
),
|
||||||
|
returnValue: _i18.Future<void>.value(),
|
||||||
|
returnValueForMissingStub: _i18.Future<void>.value(),
|
||||||
|
) as _i18.Future<void>);
|
||||||
|
@override
|
||||||
_i18.Future<List<Map<String, dynamic>>> fetchAnonymitySets() =>
|
_i18.Future<List<Map<String, dynamic>>> fetchAnonymitySets() =>
|
||||||
(super.noSuchMethod(
|
(super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
|
@ -1735,13 +1723,13 @@ class MockFiroWallet extends _i1.Mock implements _i22.FiroWallet {
|
||||||
returnValue: _i18.Future<int>.value(0),
|
returnValue: _i18.Future<int>.value(0),
|
||||||
) as _i18.Future<int>);
|
) as _i18.Future<int>);
|
||||||
@override
|
@override
|
||||||
_i18.Future<List<dynamic>> getUsedCoinSerials() => (super.noSuchMethod(
|
_i18.Future<List<String>> getUsedCoinSerials() => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
#getUsedCoinSerials,
|
#getUsedCoinSerials,
|
||||||
[],
|
[],
|
||||||
),
|
),
|
||||||
returnValue: _i18.Future<List<dynamic>>.value(<dynamic>[]),
|
returnValue: _i18.Future<List<String>>.value(<String>[]),
|
||||||
) as _i18.Future<List<dynamic>>);
|
) as _i18.Future<List<String>>);
|
||||||
@override
|
@override
|
||||||
_i18.Future<void> exit() => (super.noSuchMethod(
|
_i18.Future<void> exit() => (super.noSuchMethod(
|
||||||
Invocation.method(
|
Invocation.method(
|
||||||
|
|
Loading…
Reference in a new issue