Merge branch 'staging' into togglebalance

This commit is contained in:
sneurlax 2024-07-03 17:46:55 -05:00 committed by GitHub
commit 9e6d9f8bad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 258 additions and 44 deletions

View file

@ -116,6 +116,22 @@ cd ..
or manually by creating the files referenced in that script with the specified content.
### Build plugins
#### Build script: `build_app.sh`
The `build_app.sh` script is use to build applications Stack Wallet. View the script's help message with `./build_app.sh -h` for more information on its usage.
Options:
- `a <app>`: Specify the application ID (required). Valid options are `stack_wallet` or `stack_duo`.
- `b <build_number>`: Specify the build number in 123 (required).
- `p <platform>`: Specify the platform to build for (required). Valid options are `android`, `ios`, `macos`, `linux`, or `windows`.
- `v <version>`: Specify the version of the application in 1.2.3 format (required).
- `i`: Optional flag to skip building crypto plugins. Useful for updating `pubspec.yaml` and white-labelling different apps with the same plugins.
For example,
```
./build_app.sh -a stack_wallet -p linux -v 2.1.0 -b 210
```
#### Building plugins for Android
> Warning: This will take a long time, please be patient
```

View file

@ -8,11 +8,15 @@
*
*/
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import '../../models/buy/response_objects/order.dart';
import '../../notifications/show_flush_bar.dart';
import '../../themes/stack_colors.dart';
import '../../themes/theme_providers.dart';
import '../../utilities/assets.dart';
@ -44,6 +48,16 @@ class _BuyOrderDetailsViewState extends ConsumerState<BuyOrderDetailsView> {
@override
Widget build(BuildContext context) {
final orderDetails = '''
Purchase ID: ${widget.order.paymentId}
User ID: ${widget.order.userId}
Quote ID: ${widget.order.quote.id}
Quoted cost: ${widget.order.quote.youPayFiatPrice.toStringAsFixed(2)} ${widget.order.quote.fiat.ticker.toUpperCase()}
Quoted amount: ${widget.order.quote.youReceiveCryptoAmount} ${widget.order.quote.crypto.ticker.toUpperCase()}
Receiving ${widget.order.quote.crypto.ticker.toUpperCase()} address: ${widget.order.quote.receivingAddress}
Provider: Simplex
''';
return ConditionalParent(
condition: !isDesktop,
builder: (child) {
@ -272,6 +286,43 @@ class _BuyOrderDetailsViewState extends ConsumerState<BuyOrderDetailsView> {
),
],
),
const SizedBox(height: 8),
TextButton(
onPressed: () async {
await Clipboard.setData(ClipboardData(text: orderDetails));
if (context.mounted) {
unawaited(
showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
context: context,
),
);
}
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
Assets.svg.copy,
width: 20,
height: 20,
color: Theme.of(context)
.extension<StackColors>()!
.buttonTextSecondary,
),
const SizedBox(
width: 10,
),
Text(
"Copy to clipboard",
style: STextStyles.desktopButtonSecondaryEnabled(
context,
),
),
],
),
),
const Spacer(),
PrimaryButton(
label: "Dismiss",

View file

@ -188,12 +188,14 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
_timeout = Duration.zero;
_checkUseBiometrics();
_pinTextController.addListener(_onPinChanged);
super.initState();
}
@override
dispose() {
// _shakeController.dispose();
_pinTextController.removeListener(_onPinChanged);
super.dispose();
}
@ -208,13 +210,27 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
);
}
final _pinTextController = TextEditingController();
final FocusNode _pinFocusNode = FocusNode();
late SecureStorageInterface _secureStore;
late Biometrics biometrics;
int pinCount = 1;
final _pinTextController = TextEditingController();
void _onPinChanged() async {
String enteredPin = _pinTextController.text;
final storedPin = await _secureStore.read(key: 'stack_pin');
final autoPin = ref.read(prefsChangeNotifierProvider).autoPin;
if (enteredPin.length >= 4 && autoPin && enteredPin == storedPin) {
await Future<void>.delayed(
const Duration(milliseconds: 200),
);
unawaited(_onUnlock());
}
}
Widget get _body => Background(
child: SafeArea(
child: Scaffold(

View file

@ -61,9 +61,12 @@ class _ChangePinViewState extends ConsumerState<ChangePinView> {
int pinCount = 1;
final TextEditingController _pinTextController = TextEditingController();
@override
void initState() {
_secureStore = ref.read(secureStoreProvider);
_pinTextController.addListener(_onPinChanged);
super.initState();
}
@ -74,9 +77,23 @@ class _ChangePinViewState extends ConsumerState<ChangePinView> {
_pinPutController2.dispose();
_pinPutFocusNode1.dispose();
_pinPutFocusNode2.dispose();
_pinTextController.removeListener(_onPinChanged);
super.dispose();
}
void _onPinChanged() async {
String enteredPin = _pinTextController.text;
final storedPin = await _secureStore.read(key: 'stack_pin');
final autoPin = ref.read(prefsChangeNotifierProvider).autoPin;
if (enteredPin.length >= 4 && autoPin && enteredPin == storedPin) {
await _pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.linear,
);
}
}
@override
Widget build(BuildContext context) {
return Background(

View file

@ -10,8 +10,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../pinpad_views/lock_screen_view.dart';
import 'change_pin_view/change_pin_view.dart';
import '../../../../providers/global/prefs_provider.dart';
import '../../../../route_generator.dart';
import '../../../../themes/stack_colors.dart';
@ -21,6 +20,8 @@ import '../../../../widgets/background.dart';
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../../../widgets/custom_buttons/draggable_switch_button.dart';
import '../../../../widgets/rounded_white_container.dart';
import '../../../pinpad_views/lock_screen_view.dart';
import 'change_pin_view/change_pin_view.dart';
class SecurityView extends StatelessWidget {
const SecurityView({
@ -203,6 +204,54 @@ class SecurityView extends StatelessWidget {
},
),
),
// The "autoPin" preference (whether to automatically accept a correct PIN).
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
child: Consumer(
builder: (_, ref, __) {
return RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: null,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Auto-accept correct PIN",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
SizedBox(
height: 20,
width: 40,
child: DraggableSwitchButton(
isOn: ref.watch(
prefsChangeNotifierProvider
.select((value) => value.autoPin),
),
onValueChanged: (newValue) {
ref
.read(prefsChangeNotifierProvider)
.autoPin = newValue;
},
),
),
],
),
),
);
},
),
),
],
),
),

View file

@ -11,18 +11,19 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:uuid/uuid.dart';
import '../app_config.dart';
import '../db/hive/db.dart';
import '../services/event_bus/events/global/tor_status_changed_event.dart';
import '../services/event_bus/global_event_bus.dart';
import '../app_config.dart';
import '../wallets/crypto_currency/crypto_currency.dart';
import '../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart';
import 'amount/amount_unit.dart';
import 'constants.dart';
import 'enums/backup_frequency_type.dart';
import 'enums/languages_enum.dart';
import 'enums/sync_type_enum.dart';
import '../wallets/crypto_currency/crypto_currency.dart';
import '../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart';
import 'package:uuid/uuid.dart';
class Prefs extends ChangeNotifier {
Prefs._();
@ -1103,4 +1104,30 @@ class Prefs extends ChangeNotifier {
return actualMap;
}
// Automatic PIN entry.
bool _autoPin = false;
bool get autoPin => _autoPin;
set autoPin(bool autoPin) {
if (_autoPin != autoPin) {
DB.instance.put<dynamic>(
boxName: DB.boxNamePrefs,
key: "autoPin",
value: autoPin,
);
_autoPin = autoPin;
notifyListeners();
}
}
Future<bool> _getAutoPin() async {
return await DB.instance.get<dynamic>(
boxName: DB.boxNamePrefs,
key: "autoPin",
) as bool? ??
false;
}
}

View file

@ -4,7 +4,6 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:solana/solana.dart';
import '../networking/http.dart';
import '../pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart';
@ -15,6 +14,7 @@ import '../wallets/crypto_currency/crypto_currency.dart';
import '../wallets/crypto_currency/interfaces/electrumx_currency_interface.dart';
import '../wallets/crypto_currency/intermediate/cryptonote_currency.dart';
import '../wallets/crypto_currency/intermediate/nano_currency.dart';
import '../wallets/wallet/impl/solana_wallet.dart';
import 'connection_check/electrum_connection_check.dart';
import 'logger.dart';
import 'test_epic_box_connection.dart';
@ -210,14 +210,20 @@ Future<bool> testNodeConnection({
case Solana():
try {
RpcClient rpcClient;
if (formData.host!.startsWith("http") ||
formData.host!.startsWith("https")) {
rpcClient = RpcClient("${formData.host}:${formData.port}");
} else {
rpcClient = RpcClient("http://${formData.host}:${formData.port}");
}
await rpcClient.getEpochInfo().then((value) => testPassed = true);
final rpcClient = SolanaWallet.createRpcClient(
formData.host!,
formData.port!,
formData.useSSL ?? false,
ref.read(prefsChangeNotifierProvider),
ref.read(pTorService),
);
final health = await rpcClient.getHealth();
Logging.instance.log(
"Solana testNodeConnection \"health=$health\"",
level: LogLevel.Info,
);
return true;
} catch (_) {
testPassed = false;
}

View file

@ -55,8 +55,6 @@ class Bitcoincash extends Bip39HDCurrency with ElectrumXCurrencyInterface {
@override
int get maxUnusedAddressGap => 50;
@override
int get maxNumberOfIndexesToCheck => 10000000;
@override
// change this to change the number of confirms a tx needs in order to show as confirmed

View file

@ -50,8 +50,6 @@ class Ecash extends Bip39HDCurrency with ElectrumXCurrencyInterface {
@override
int get maxUnusedAddressGap => 50;
@override
int get maxNumberOfIndexesToCheck => 10000000;
@override
// change this to change the number of confirms a tx needs in order to show as confirmed

View file

@ -46,8 +46,7 @@ class Solana extends Bip39Currency {
switch (network) {
case CryptoCurrencyNetwork.main:
return NodeModel(
host:
"https://api.mainnet-beta.solana.com/", // TODO: Change this to stack wallet one
host: "https://solana.stackwallet.com",
port: 443,
name: DefaultNodes.defaultName,
id: DefaultNodes.buildId(this),
@ -70,9 +69,13 @@ class Solana extends Bip39Currency {
@override
bool validateAddress(String address) {
return isPointOnEd25519Curve(
Ed25519HDPublicKey.fromBase58(address).toByteArray(),
);
try {
return isPointOnEd25519Curve(
Ed25519HDPublicKey.fromBase58(address).toByteArray(),
);
} catch (_) {
return false;
}
}
@override

View file

@ -1,6 +1,7 @@
import 'package:coinlib_flutter/coinlib_flutter.dart' as coinlib;
import 'package:crypto/crypto.dart';
import 'package:flutter/foundation.dart';
import '../../../models/isar/models/blockchain_data/address.dart';
import '../../../utilities/amount/amount.dart';
import '../../../utilities/enums/derive_path_type_enum.dart';
@ -16,7 +17,6 @@ abstract class Bip39HDCurrency extends Bip39Currency {
List<DerivePathType> get supportedDerivationPathTypes;
int get maxUnusedAddressGap => 50;
int get maxNumberOfIndexesToCheck => 10000;
String constructDerivePath({
required DerivePathType derivePathType,

View file

@ -18,6 +18,7 @@ import '../../../services/node_service.dart';
import '../../../services/tor_service.dart';
import '../../../utilities/amount/amount.dart';
import '../../../utilities/logger.dart';
import '../../../utilities/prefs.dart';
import '../../crypto_currency/crypto_currency.dart';
import '../../models/tx_data.dart';
import '../intermediate/bip39_wallet.dart';
@ -245,14 +246,15 @@ class SolanaWallet extends Bip39Wallet<Solana> {
}
@override
Future<bool> pingCheck() {
Future<bool> pingCheck() async {
String? health;
try {
_checkClient();
_rpcClient?.getHealth();
return Future.value(true);
health = await _rpcClient?.getHealth();
return health != null;
} catch (e, s) {
Logging.instance.log(
"$runtimeType Solana pingCheck failed: $e\n$s",
"$runtimeType Solana pingCheck failed \"health=$health\": $e\n$s",
level: LogLevel.Error,
);
return Future.value(false);
@ -453,32 +455,67 @@ class SolanaWallet extends Bip39Wallet<Solana> {
}
@override
Future<bool> updateUTXOs() {
Future<bool> updateUTXOs() async {
// No UTXOs in Solana
return Future.value(false);
return false;
}
/// Make sure the Solana RpcClient uses Tor if it's enabled.
///
void _checkClient() async {
void _checkClient() {
final node = getCurrentNode();
_rpcClient = createRpcClient(
node.host,
node.port,
node.useSSL,
prefs,
TorService.sharedInstance,
);
}
// static helper function for building a sol rpc client
static RpcClient createRpcClient(
final String host,
final int port,
final bool useSSL,
final Prefs prefs,
final TorService torService,
) {
HttpClient? httpClient;
if (prefs.useTor) {
// Make proxied HttpClient.
final ({InternetAddress host, int port}) proxyInfo =
TorService.sharedInstance.getProxyInfo();
final proxyInfo = torService.getProxyInfo();
final proxySettings = ProxySettings(proxyInfo.host, proxyInfo.port);
httpClient = HttpClient();
SocksTCPClient.assignToHttpClient(httpClient, [proxySettings]);
}
_rpcClient = RpcClient(
"${getCurrentNode().host}:${getCurrentNode().port}",
final regex = RegExp("^(http|https)://");
String editedHost;
if (host.startsWith(regex)) {
editedHost = host.replaceFirst(regex, "");
} else {
editedHost = host;
}
while (editedHost.endsWith("/")) {
editedHost = editedHost.substring(0, editedHost.length - 1);
}
final uri = Uri(
scheme: useSSL ? "https" : "http",
host: editedHost,
port: port,
);
return RpcClient(
uri.toString(),
timeout: const Duration(seconds: 30),
customHeaders: {},
httpClient: httpClient,
);
return;
}
}

View file

@ -937,8 +937,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
int highestIndexWithHistory = 0;
for (int index = 0;
index < cryptoCurrency.maxNumberOfIndexesToCheck &&
gapCounter < cryptoCurrency.maxUnusedAddressGap;
gapCounter < cryptoCurrency.maxUnusedAddressGap;
index += txCountBatchSize) {
Logging.instance.log(
"index: $index, \t GapCounter $chain ${type.name}: $gapCounter",
@ -1017,10 +1016,7 @@ mixin ElectrumXInterface<T extends ElectrumXCurrencyInterface>
final List<Address> addressArray = [];
int gapCounter = 0;
int index = 0;
for (;
index < cryptoCurrency.maxNumberOfIndexesToCheck &&
gapCounter < cryptoCurrency.maxUnusedAddressGap;
index++) {
for (; gapCounter < cryptoCurrency.maxUnusedAddressGap; index++) {
Logging.instance.log(
"index: $index, \t GapCounter chain=$chain ${type.name}: $gapCounter",
level: LogLevel.Info,