Merge branch 'staging' into tor

This commit is contained in:
Josh Babb 2023-05-03 18:22:38 -05:00
commit a48ec94927
31 changed files with 1263 additions and 357 deletions

View file

@ -333,53 +333,57 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius, Constants.size.circularBorderRadius,
), ),
child: TextField( child: Semantics(
autofocus: isDesktop, label: "Search Text Field. Inputs Text To Search In Wallets.",
autocorrect: !isDesktop, excludeSemantics: true,
enableSuggestions: !isDesktop, child: TextField(
controller: _searchFieldController, autofocus: isDesktop,
focusNode: _searchFocusNode, autocorrect: !isDesktop,
onChanged: (value) => setState(() => _searchTerm = value), enableSuggestions: !isDesktop,
style: STextStyles.field(context), controller: _searchFieldController,
decoration: standardInputDecoration( focusNode: _searchFocusNode,
"Search", onChanged: (value) => setState(() => _searchTerm = value),
_searchFocusNode, style: STextStyles.field(context),
context, decoration: standardInputDecoration(
desktopMed: isDesktop, "Search",
).copyWith( _searchFocusNode,
prefixIcon: Padding( context,
padding: const EdgeInsets.symmetric( desktopMed: isDesktop,
horizontal: 10, ).copyWith(
vertical: 16, prefixIcon: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 16,
),
child: SvgPicture.asset(
Assets.svg.search,
width: 16,
height: 16,
),
), ),
child: SvgPicture.asset( suffixIcon: _searchFieldController.text.isNotEmpty
Assets.svg.search, ? Padding(
width: 16, padding: const EdgeInsets.only(right: 0),
height: 16, child: UnconstrainedBox(
), child: Row(
), children: [
suffixIcon: _searchFieldController.text.isNotEmpty TextFieldIconButton(
? Padding( child: const XIcon(),
padding: const EdgeInsets.only(right: 0), onTap: () async {
child: UnconstrainedBox( setState(() {
child: Row( _searchFieldController.text = "";
children: [ _searchTerm = "";
TextFieldIconButton( });
child: const XIcon(), },
onTap: () async {
setState(() {
_searchFieldController.text = "";
_searchTerm = "";
});
},
),
],
), ),
), ],
) ),
: null, ),
)
: null,
),
), ),
), )
), ),
const SizedBox( const SizedBox(
height: 10, height: 10,

View file

@ -238,14 +238,22 @@ class _NameYourWalletViewState extends ConsumerState<NameYourWalletView> {
TextFieldIconButton( TextFieldIconButton(
key: const Key("genRandomWalletNameButtonKey"), key: const Key("genRandomWalletNameButtonKey"),
child: _showDiceIcon child: _showDiceIcon
? DiceIcon( ? Semantics(
width: isDesktop ? 20 : 17, label: "Generate Random Wallet Name Button. Generates A Random Name For Wallet.",
height: isDesktop ? 20 : 17, excludeSemantics: true,
) child: DiceIcon(
: XIcon( width: isDesktop ? 20 : 17,
height: isDesktop ? 20 : 17,
),
)
: Semantics(
label: "Generate Random Wallet Name Button. Generates A Random Name For Wallet.",
excludeSemantics: true,
child: XIcon(
width: isDesktop ? 21 : 18, width: isDesktop ? 21 : 18,
height: isDesktop ? 21 : 18, height: isDesktop ? 21 : 18,
), ),
),
onTap: () async { onTap: () async {
if (_showDiceIcon) { if (_showDiceIcon) {
textEditingController.text = textEditingController.text =

View file

@ -140,6 +140,7 @@ class _NewWalletRecoveryPhraseViewState
child: AspectRatio( child: AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: AppBarIconButton( child: AppBarIconButton(
semanticsLabel: "Copy Button. Copies The Recovery Phrase To Clipboard.",
color: Theme.of(context) color: Theme.of(context)
.extension<StackColors>()! .extension<StackColors>()!
.background, .background,

View file

@ -87,6 +87,7 @@ class _NewWalletRecoveryPhraseWarningViewState
right: 10, right: 10,
), ),
child: AppBarIconButton( child: AppBarIconButton(
semanticsLabel: "Question Button. Opens A Dialog For Recovery Phrase Explanation.",
icon: SvgPicture.asset( icon: SvgPicture.asset(
Assets.svg.circleQuestion, Assets.svg.circleQuestion,
width: 20, width: 20,

View file

@ -636,6 +636,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
child: AspectRatio( child: AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: AppBarIconButton( child: AppBarIconButton(
semanticsLabel: "View QR Code Button. Opens Camera To Scan QR Code For Restoring Wallet.",
key: const Key("restoreWalletViewQrCodeButton"), key: const Key("restoreWalletViewQrCodeButton"),
size: 36, size: 36,
shadows: const [], shadows: const [],
@ -662,6 +663,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
child: AspectRatio( child: AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: AppBarIconButton( child: AppBarIconButton(
semanticsLabel: "Paste Button. Pastes From Clipboard For Restoring Wallet.",
key: const Key("restoreWalletPasteButton"), key: const Key("restoreWalletPasteButton"),
size: 36, size: 36,
shadows: const [], shadows: const [],

View file

@ -781,31 +781,35 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
child: child, child: child,
), ),
child: RoundedContainer( child: Semantics(
padding: isDesktop label: "Swap Button. Reverse The Exchange Currencies.",
? const EdgeInsets.all(6) excludeSemantics: true,
: const EdgeInsets.all(2), child: RoundedContainer(
color: Theme.of(context) padding: isDesktop
.extension<StackColors>()! ? const EdgeInsets.all(6)
.buttonBackSecondary, : const EdgeInsets.all(2),
radiusMultiplier: 0.75, color: Theme.of(context)
child: GestureDetector( .extension<StackColors>()!
onTap: () async { .buttonBackSecondary,
await _swap(); radiusMultiplier: 0.75,
}, child: GestureDetector(
child: Padding( onTap: () async {
padding: const EdgeInsets.all(4), await _swap();
child: SvgPicture.asset( },
Assets.svg.swap, child: Padding(
width: 20, padding: const EdgeInsets.all(4),
height: 20, child: SvgPicture.asset(
color: Theme.of(context) Assets.svg.swap,
.extension<StackColors>()! width: 20,
.accentColorDark, height: 20,
color: Theme.of(context)
.extension<StackColors>()!
.accentColorDark,
),
), ),
), ),
), ),
), )
), ),
], ],
), ),

View file

@ -191,6 +191,7 @@ class _HomeViewState extends ConsumerState<HomeView> {
child: AspectRatio( child: AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: AppBarIconButton( child: AppBarIconButton(
semanticsLabel: "Notifications Button. Takes To Notifications Page.",
key: const Key("walletsViewAlertsButton"), key: const Key("walletsViewAlertsButton"),
size: 36, size: 36,
shadows: const [], shadows: const [],
@ -254,6 +255,7 @@ class _HomeViewState extends ConsumerState<HomeView> {
child: AspectRatio( child: AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: AppBarIconButton( child: AppBarIconButton(
semanticsLabel: "Settings Button. Takes To Settings Page.",
key: const Key("walletsViewSettingsButton"), key: const Key("walletsViewSettingsButton"),
size: 36, size: 36,
shadows: const [], shadows: const [],

View file

@ -9,7 +9,6 @@ import 'package:stackwallet/providers/global/secure_store_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/constants.dart';
import 'package:stackwallet/utilities/enums/flush_bar_type.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/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
@ -139,6 +138,8 @@ class _CreatePinViewState extends ConsumerState<CreatePinView> {
.background, .background,
counterText: "", counterText: "",
), ),
isRandom:
ref.read(prefsChangeNotifierProvider).randomizePIN,
submittedFieldDecoration: _pinPutDecoration.copyWith( submittedFieldDecoration: _pinPutDecoration.copyWith(
color: Theme.of(context) color: Theme.of(context)
.extension<StackColors>()! .extension<StackColors>()!
@ -221,6 +222,8 @@ class _CreatePinViewState extends ConsumerState<CreatePinView> {
), ),
selectedFieldDecoration: _pinPutDecoration, selectedFieldDecoration: _pinPutDecoration,
followingFieldDecoration: _pinPutDecoration, followingFieldDecoration: _pinPutDecoration,
isRandom:
ref.read(prefsChangeNotifierProvider).randomizePIN,
onSubmit: (String pin) async { onSubmit: (String pin) async {
// _onSubmitCount++; // _onSubmitCount++;
// if (_onSubmitCount - _onSubmitFailCount > 1) return; // if (_onSubmitCount - _onSubmitFailCount > 1) return;

View file

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -21,8 +20,8 @@ import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.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/shake/shake.dart'; import 'package:stackwallet/widgets/shake/shake.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
@ -39,6 +38,7 @@ class LockscreenView extends ConsumerStatefulWidget {
this.routeOnSuccessArguments, this.routeOnSuccessArguments,
this.biometrics = const Biometrics(), this.biometrics = const Biometrics(),
this.onSuccess, this.onSuccess,
this.customKeyLabel = "Button",
}) : super(key: key); }) : super(key: key);
static const String routeName = "/lockscreen"; static const String routeName = "/lockscreen";
@ -53,6 +53,8 @@ class LockscreenView extends ConsumerStatefulWidget {
final String biometricsCancelButtonString; final String biometricsCancelButtonString;
final Biometrics biometrics; final Biometrics biometrics;
final VoidCallback? onSuccess; final VoidCallback? onSuccess;
final String customKeyLabel;
@override @override
ConsumerState<LockscreenView> createState() => _LockscreenViewState(); ConsumerState<LockscreenView> createState() => _LockscreenViewState();
@ -90,7 +92,7 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
final manager = final manager =
ref.read(walletsChangeNotifierProvider).getManager(walletId); ref.read(walletsChangeNotifierProvider).getManager(walletId);
if (manager.coin == Coin.monero || manager.coin == Coin.wownero) { if (manager.coin == Coin.monero) {
await showLoading( await showLoading(
opaqueBG: true, opaqueBG: true,
whileFuture: manager.initializeExisting(), whileFuture: manager.initializeExisting(),
@ -226,6 +228,26 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
// check prefs and hide if user has biometrics toggle off?
Padding(
padding: const EdgeInsets.only(right: 40.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (ref.read(prefsChangeNotifierProvider).useBiometrics ==
true)
CustomTextButton(
text: "Use biometrics",
onTap: () async {
await _checkUseBiometrics();
},
),
],
),
),
const SizedBox(
height: 55,
),
Shake( Shake(
animationDuration: const Duration(milliseconds: 700), animationDuration: const Duration(milliseconds: 700),
animationRange: 12, animationRange: 12,
@ -244,12 +266,12 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
height: 52, height: 52,
), ),
CustomPinPut( CustomPinPut(
customKey: CustomKey( // customKey: CustomKey(
onPressed: _checkUseBiometrics, // onPressed: _checkUseBiometrics,
iconAssetName: Platform.isIOS // iconAssetName: Platform.isIOS
? Assets.svg.faceId // ? Assets.svg.faceId
: Assets.svg.fingerprint, // : Assets.svg.fingerprint,
), // ),
fieldsCount: Constants.pinLength, fieldsCount: Constants.pinLength,
eachFieldHeight: 12, eachFieldHeight: 12,
eachFieldWidth: 12, eachFieldWidth: 12,
@ -285,6 +307,9 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
), ),
selectedFieldDecoration: _pinPutDecoration, selectedFieldDecoration: _pinPutDecoration,
followingFieldDecoration: _pinPutDecoration, followingFieldDecoration: _pinPutDecoration,
isRandom: ref
.read(prefsChangeNotifierProvider)
.randomizePIN,
onSubmit: (String pin) async { onSubmit: (String pin) async {
_attempts++; _attempts++;

View file

@ -143,6 +143,7 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
child: AspectRatio( child: AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: AppBarIconButton( child: AppBarIconButton(
semanticsLabel: "Address List Pop-up Button. Opens A Pop-up For Adress List Button.",
key: const Key("walletNetworkSettingsAddNewNodeViewButton"), key: const Key("walletNetworkSettingsAddNewNodeViewButton"),
size: 36, size: 36,
shadows: const [], shadows: const [],

View file

@ -981,6 +981,7 @@ class _SendViewState extends ConsumerState<SendView> {
children: [ children: [
_addressToggleFlag _addressToggleFlag
? TextFieldIconButton( ? TextFieldIconButton(
semanticsLabel: "Clear Button. Clears The Address Field Input.",
key: const Key( key: const Key(
"sendViewClearAddressFieldButtonKey"), "sendViewClearAddressFieldButtonKey"),
onTap: () { onTap: () {
@ -997,6 +998,7 @@ class _SendViewState extends ConsumerState<SendView> {
child: const XIcon(), child: const XIcon(),
) )
: TextFieldIconButton( : TextFieldIconButton(
semanticsLabel: "Paste Button. Pastes From Clipboard To Address Field Input.",
key: const Key( key: const Key(
"sendViewPasteAddressFieldButtonKey"), "sendViewPasteAddressFieldButtonKey"),
onTap: () async { onTap: () async {
@ -1046,6 +1048,7 @@ class _SendViewState extends ConsumerState<SendView> {
), ),
if (sendToController.text.isEmpty) if (sendToController.text.isEmpty)
TextFieldIconButton( TextFieldIconButton(
semanticsLabel: "Address Book Button. Opens Address Book For Address Field.",
key: const Key( key: const Key(
"sendViewAddressBookButtonKey"), "sendViewAddressBookButtonKey"),
onTap: () { onTap: () {
@ -1058,6 +1061,7 @@ class _SendViewState extends ConsumerState<SendView> {
), ),
if (sendToController.text.isEmpty) if (sendToController.text.isEmpty)
TextFieldIconButton( TextFieldIconButton(
semanticsLabel: "Scan QR Button. Opens Camera For Scanning QR Code.",
key: const Key( key: const Key(
"sendViewScanQrButtonKey"), "sendViewScanQrButtonKey"),
onTap: () async { onTap: () async {

View file

@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/security_views/security_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/security_views/security_view.dart';
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/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/flush_bar_type.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/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
@ -123,6 +123,8 @@ class _ChangePinViewState extends ConsumerState<ChangePinView> {
.background, .background,
counterText: "", counterText: "",
), ),
isRandom:
ref.read(prefsChangeNotifierProvider).randomizePIN,
submittedFieldDecoration: _pinPutDecoration.copyWith( submittedFieldDecoration: _pinPutDecoration.copyWith(
color: Theme.of(context) color: Theme.of(context)
.extension<StackColors>()! .extension<StackColors>()!
@ -188,6 +190,8 @@ class _ChangePinViewState extends ConsumerState<ChangePinView> {
.background, .background,
counterText: "", counterText: "",
), ),
isRandom:
ref.read(prefsChangeNotifierProvider).randomizePIN,
submittedFieldDecoration: _pinPutDecoration.copyWith( submittedFieldDecoration: _pinPutDecoration.copyWith(
color: Theme.of(context) color: Theme.of(context)
.extension<StackColors>()! .extension<StackColors>()!

View file

@ -146,6 +146,53 @@ class SecurityView extends StatelessWidget {
}, },
), ),
), ),
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(
"Randomize PIN Pad",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
SizedBox(
height: 20,
width: 40,
child: DraggableSwitchButton(
isOn: ref.watch(
prefsChangeNotifierProvider
.select((value) => value.randomizePIN),
),
onValueChanged: (newValue) {
ref
.read(prefsChangeNotifierProvider)
.randomizePIN = newValue;
},
),
),
],
),
),
);
},
),
),
], ],
), ),
), ),

View file

@ -14,6 +14,8 @@ import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/dialogs/basic_dialog.dart';
import 'package:stackwallet/widgets/icon_widgets/eth_token_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/eth_token_icon.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart';
@ -36,6 +38,36 @@ class _MyTokenSelectItemState extends ConsumerState<MyTokenSelectItem> {
late final CachedEthTokenBalance cachedBalance; late final CachedEthTokenBalance cachedBalance;
Future<bool> _loadTokenWallet(
BuildContext context,
WidgetRef ref,
) async {
try {
await ref.read(tokenServiceProvider)!.initialize();
return true;
} catch (_) {
await showDialog<void>(
barrierDismissible: false,
context: context,
builder: (context) => BasicDialog(
title: "Failed to load token data",
desktopHeight: double.infinity,
desktopWidth: 450,
rightButton: PrimaryButton(
label: "OK",
onPressed: () {
Navigator.of(context).pop();
if (!isDesktop) {
Navigator.of(context).pop();
}
},
),
),
);
return false;
}
}
void _onPressed() async { void _onPressed() async {
ref.read(tokenServiceStateProvider.state).state = EthTokenWallet( ref.read(tokenServiceStateProvider.state).state = EthTokenWallet(
token: widget.token, token: widget.token,
@ -49,13 +81,17 @@ class _MyTokenSelectItemState extends ConsumerState<MyTokenSelectItem> {
), ),
); );
await showLoading<void>( final success = await showLoading<bool>(
whileFuture: ref.read(tokenServiceProvider)!.initialize(), whileFuture: _loadTokenWallet(context, ref),
context: context, context: context,
isDesktop: isDesktop, isDesktop: isDesktop,
message: "Loading ${widget.token.name}", message: "Loading ${widget.token.name}",
); );
if (!success) {
return;
}
if (mounted) { if (mounted) {
await Navigator.of(context).pushNamed( await Navigator.of(context).pushNamed(
isDesktop ? DesktopTokenView.routeName : TokenView.routeName, isDesktop ? DesktopTokenView.routeName : TokenView.routeName,

View file

@ -99,56 +99,60 @@ class _RefreshButtonState extends ConsumerState<WalletRefreshButton> {
return SizedBox( return SizedBox(
height: isDesktop ? 22 : 36, height: isDesktop ? 22 : 36,
width: isDesktop ? 22 : 36, width: isDesktop ? 22 : 36,
child: MaterialButton( child: Semantics(
color: isDesktop label: "Refresh Button. Refreshes The Values In Summary.",
? Theme.of(context).extension<StackColors>()!.buttonBackSecondary excludeSemantics: true,
: null, child: MaterialButton(
splashColor: Theme.of(context).extension<StackColors>()!.highlight, color: isDesktop
onPressed: () { ? Theme.of(context).extension<StackColors>()!.buttonBackSecondary
if (widget.tokenContractAddress == null) { : null,
final managerProvider = ref splashColor: Theme.of(context).extension<StackColors>()!.highlight,
.read(walletsChangeNotifierProvider) onPressed: () {
.getManagerProvider(widget.walletId); if (widget.tokenContractAddress == null) {
final isRefreshing = ref.read(managerProvider).isRefreshing; final managerProvider = ref
if (!isRefreshing) { .read(walletsChangeNotifierProvider)
_spinController.repeat?.call(); .getManagerProvider(widget.walletId);
ref final isRefreshing = ref.read(managerProvider).isRefreshing;
.read(managerProvider) if (!isRefreshing) {
.refresh() _spinController.repeat?.call();
.then((_) => _spinController.stop?.call()); ref
.read(managerProvider)
.refresh()
.then((_) => _spinController.stop?.call());
}
} else {
if (!ref.read(tokenServiceProvider)!.isRefreshing) {
ref.read(tokenServiceProvider)!.refresh();
}
} }
} else { },
if (!ref.read(tokenServiceProvider)!.isRefreshing) { elevation: 0,
ref.read(tokenServiceProvider)!.refresh(); highlightElevation: 0,
} hoverElevation: 0,
} padding: EdgeInsets.zero,
}, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
elevation: 0, shape: RoundedRectangleBorder(
highlightElevation: 0, borderRadius: BorderRadius.circular(
hoverElevation: 0, Constants.size.circularBorderRadius,
padding: EdgeInsets.zero, ),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ),
shape: RoundedRectangleBorder( child: RotatingArrows(
borderRadius: BorderRadius.circular( spinByDefault: widget.initialSyncStatus == WalletSyncStatus.syncing,
Constants.size.circularBorderRadius, width: isDesktop ? 12 : 24,
height: isDesktop ? 12 : 24,
controller: _spinController,
color: widget.overrideIconColor != null
? widget.overrideIconColor!
: isDesktop
? Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultSearchIconRight
: Theme.of(context)
.extension<StackColors>()!
.textFavoriteCard,
), ),
), ),
child: RotatingArrows( )
spinByDefault: widget.initialSyncStatus == WalletSyncStatus.syncing,
width: isDesktop ? 12 : 24,
height: isDesktop ? 12 : 24,
controller: _spinController,
color: widget.overrideIconColor != null
? widget.overrideIconColor!
: isDesktop
? Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultSearchIconRight
: Theme.of(context)
.extension<StackColors>()!
.textFavoriteCard,
),
),
); );
} }
} }

View file

@ -53,6 +53,7 @@ import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_dialog.dart';
import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/buy_nav_icon.dart'; import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/buy_nav_icon.dart';
@ -420,6 +421,33 @@ class _WalletViewState extends ConsumerState<WalletView> {
eventBus: null, eventBus: null,
textColor: textColor:
Theme.of(context).extension<StackColors>()!.textDark, Theme.of(context).extension<StackColors>()!.textDark,
actionButton: SecondaryButton(
label: "Cancel",
onPressed: () async {
await showDialog<void>(
context: context,
builder: (context) => StackDialog(
title: "Warning!",
message: "Skipping this process can completely"
" break your wallet. It is only meant to be done in"
" emergency situations where the migration fails"
" and will not let you continue. Still skip?",
leftButton: SecondaryButton(
label: "Cancel",
onPressed:
Navigator.of(context, rootNavigator: true).pop,
),
rightButton: SecondaryButton(
label: "Ok",
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
setState(() => _rescanningOnOpen = false);
},
),
),
);
},
),
), ),
) )
], ],
@ -473,6 +501,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
child: AspectRatio( child: AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: AppBarIconButton( child: AppBarIconButton(
semanticsLabel: "Network Button. Takes To Network Status Page.",
key: const Key("walletViewRadioButton"), key: const Key("walletViewRadioButton"),
size: 36, size: 36,
shadows: const [], shadows: const [],
@ -502,6 +531,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
child: AspectRatio( child: AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: AppBarIconButton( child: AppBarIconButton(
semanticsLabel: "Notifications Button. Takes To Notifications Page.",
key: const Key("walletViewAlertsButton"), key: const Key("walletViewAlertsButton"),
size: 36, size: 36,
shadows: const [], shadows: const [],
@ -569,6 +599,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
child: AspectRatio( child: AspectRatio(
aspectRatio: 1, aspectRatio: 1,
child: AppBarIconButton( child: AppBarIconButton(
semanticsLabel: "Settings Button. Takes To Wallet Settings Page.",
key: const Key("walletViewSettingsButton"), key: const Key("walletViewSettingsButton"),
size: 36, size: 36,
shadows: const [], shadows: const [],
@ -790,7 +821,6 @@ class _WalletViewState extends ConsumerState<WalletView> {
label: "Receive", label: "Receive",
icon: const ReceiveNavIcon(), icon: const ReceiveNavIcon(),
onTap: () { onTap: () {
final coin = ref.read(managerProvider).coin;
if (mounted) { if (mounted) {
unawaited( unawaited(
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(

View file

@ -31,7 +31,11 @@ import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart';
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/hover_text_field.dart'; import 'package:stackwallet/widgets/hover_text_field.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart';
@ -150,6 +154,83 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
subMessage: "This only needs to run once per wallet", subMessage: "This only needs to run once per wallet",
eventBus: null, eventBus: null,
textColor: Theme.of(context).extension<StackColors>()!.textDark, textColor: Theme.of(context).extension<StackColors>()!.textDark,
actionButton: SecondaryButton(
label: "Skip",
buttonHeight: ButtonHeight.l,
onPressed: () async {
await showDialog<void>(
context: context,
builder: (context) => DesktopDialog(
maxWidth: 500,
maxHeight: double.infinity,
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 32),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"Warning!",
style: STextStyles.desktopH3(context),
),
const DesktopDialogCloseButton(),
],
),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 32),
child: Text(
"Skipping this process can completely"
" break your wallet. It is only meant to be done in"
" emergency situations where the migration fails"
" and will not let you continue. Still skip?",
style: STextStyles.desktopTextSmall(context),
),
),
const SizedBox(
height: 32,
),
Padding(
padding: const EdgeInsets.all(32),
child: Row(
children: [
Expanded(
child: SecondaryButton(
label: "Cancel",
buttonHeight: ButtonHeight.l,
onPressed: Navigator.of(context,
rootNavigator: true)
.pop,
),
),
const SizedBox(
width: 16,
),
Expanded(
child: PrimaryButton(
label: "Ok",
buttonHeight: ButtonHeight.l,
onPressed: () {
Navigator.of(context,
rootNavigator: true)
.pop();
setState(
() => _rescanningOnOpen = false);
},
),
),
],
),
)
],
),
),
);
},
),
), ),
) )
], ],

View file

@ -1949,8 +1949,6 @@ class BitcoinCashWallet extends CoinServiceAPI
List<Map<String, dynamic>> allTransactions = []; List<Map<String, dynamic>> allTransactions = [];
final currentHeight = await chainHeight;
for (final txHash in allTxHashes) { for (final txHash in allTxHashes) {
final storedTx = await db final storedTx = await db
.getTransactions(walletId) .getTransactions(walletId)
@ -1958,7 +1956,9 @@ class BitcoinCashWallet extends CoinServiceAPI
.txidEqualTo(txHash["tx_hash"] as String) .txidEqualTo(txHash["tx_hash"] as String)
.findFirst(); .findFirst();
if (storedTx == null || storedTx.address.value == null if (storedTx == null ||
storedTx.address.value == null ||
storedTx.height == null
// zero conf messes this up // zero conf messes this up
// !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS) // !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)
) { ) {

View file

@ -253,13 +253,17 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache {
); );
_credentials = web3dart.EthPrivateKey.fromHex(privateKey); _credentials = web3dart.EthPrivateKey.fromHex(privateKey);
_deployedContract = web3dart.DeployedContract( try {
ContractAbiExtensions.fromJsonList( _deployedContract = web3dart.DeployedContract(
jsonList: tokenContract.abi!, ContractAbiExtensions.fromJsonList(
name: tokenContract.name, jsonList: tokenContract.abi!,
), name: tokenContract.name,
contractAddress, ),
); contractAddress,
);
} catch (_) {
rethrow;
}
try { try {
_sendFunction = _deployedContract.function('transfer'); _sendFunction = _deployedContract.function('transfer');
@ -328,13 +332,17 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache {
//==================================================================== //====================================================================
} }
_deployedContract = web3dart.DeployedContract( try {
ContractAbiExtensions.fromJsonList( _deployedContract = web3dart.DeployedContract(
jsonList: tokenContract.abi!, ContractAbiExtensions.fromJsonList(
name: tokenContract.name, jsonList: tokenContract.abi!,
), name: tokenContract.name,
contractAddress, ),
); contractAddress,
);
} catch (_) {
rethrow;
}
_sendFunction = _deployedContract.function('transfer'); _sendFunction = _deployedContract.function('transfer');

View file

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:stackwallet/utilities/logger.dart';
import 'package:web3dart/web3dart.dart'; import 'package:web3dart/web3dart.dart';
extension ContractAbiExtensions on ContractAbi { extension ContractAbiExtensions on ContractAbi {
@ -7,50 +8,61 @@ extension ContractAbiExtensions on ContractAbi {
required String name, required String name,
required String jsonList, required String jsonList,
}) { }) {
final List<ContractFunction> functions = []; try {
final List<ContractEvent> events = []; final List<ContractFunction> functions = [];
final List<ContractEvent> events = [];
final list = List<Map<String, dynamic>>.from(jsonDecode(jsonList) as List); final list =
List<Map<String, dynamic>>.from(jsonDecode(jsonList) as List);
for (final json in list) { for (final json in list) {
final type = json["type"] as String; final type = json["type"] as String;
final name = json["name"] as String? ?? ""; final name = json["name"] as String? ?? "";
if (type == "event") { if (type == "event") {
final anonymous = json["anonymous"] as bool? ?? false; final anonymous = json["anonymous"] as bool? ?? false;
final List<EventComponent<dynamic>> components = []; final List<EventComponent<dynamic>> components = [];
for (final input in json["inputs"] as List) { if (json["inputs"] is List) {
components.add( for (final input in json["inputs"] as List) {
EventComponent( components.add(
_parseParam(input as Map), EventComponent(
input['indexed'] as bool? ?? false, _parseParam(input as Map),
), input['indexed'] as bool? ?? false,
); ),
} );
}
}
events.add(ContractEvent(anonymous, name, components)); events.add(ContractEvent(anonymous, name, components));
} else { } else {
final mutability = _mutabilityNames[json['stateMutability']]; final mutability = _mutabilityNames[json['stateMutability']];
final parsedType = _functionTypeNames[json['type']]; final parsedType = _functionTypeNames[json['type']];
if (parsedType != null) { if (parsedType != null) {
final inputs = _parseParams(json['inputs'] as List?); final inputs = _parseParams(json['inputs'] as List?);
final outputs = _parseParams(json['outputs'] as List?); final outputs = _parseParams(json['outputs'] as List?);
functions.add( functions.add(
ContractFunction( ContractFunction(
name, name,
inputs, inputs,
outputs: outputs, outputs: outputs,
type: parsedType, type: parsedType,
mutability: mutability ?? StateMutability.nonPayable, mutability: mutability ?? StateMutability.nonPayable,
), ),
); );
}
} }
} }
}
return ContractAbi(name, functions, events); return ContractAbi(name, functions, events);
} catch (e, s) {
Logging.instance.log(
"Failed to parse ABI for $name: $e\n$s",
level: LogLevel.Error,
);
rethrow;
}
} }
static const Map<String, ContractFunctionType> _functionTypeNames = { static const Map<String, ContractFunctionType> _functionTypeNames = {

View file

@ -19,6 +19,7 @@ class Prefs extends ChangeNotifier {
if (!_initialized) { if (!_initialized) {
_currency = await _getPreferredCurrency(); _currency = await _getPreferredCurrency();
// _exchangeRateType = await _getExchangeRateType(); // _exchangeRateType = await _getExchangeRateType();
_randomizePIN = await _getRandomizePIN();
_useBiometrics = await _getUseBiometrics(); _useBiometrics = await _getUseBiometrics();
_hasPin = await _getHasPin(); _hasPin = await _getHasPin();
_language = await _getPreferredLanguage(); _language = await _getPreferredLanguage();
@ -295,6 +296,27 @@ class Prefs extends ChangeNotifier {
// } // }
// } // }
// randomize PIN
bool _randomizePIN = false;
bool get randomizePIN => _randomizePIN;
set randomizePIN(bool randomizePIN) {
if (_randomizePIN != randomizePIN) {
DB.instance.put<dynamic>(
boxName: DB.boxNamePrefs, key: "randomizePIN", value: randomizePIN);
_randomizePIN = randomizePIN;
notifyListeners();
}
}
Future<bool> _getRandomizePIN() async {
return await DB.instance.get<dynamic>(
boxName: DB.boxNamePrefs, key: "randomizePIN") as bool? ??
false;
}
// use biometrics // use biometrics
bool _useBiometrics = false; bool _useBiometrics = false;

View file

@ -13,6 +13,7 @@ class AppBarIconButton extends StatelessWidget {
// this.circularBorderRadius = 10.0, // this.circularBorderRadius = 10.0,
this.size = 36.0, this.size = 36.0,
this.shadows = const [], this.shadows = const [],
this.semanticsLabel = "Button",
}) : super(key: key); }) : super(key: key);
final Widget icon; final Widget icon;
@ -21,6 +22,7 @@ class AppBarIconButton extends StatelessWidget {
// final double circularBorderRadius; // final double circularBorderRadius;
final double size; final double size;
final List<BoxShadow> shadows; final List<BoxShadow> shadows;
final String semanticsLabel;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -32,16 +34,20 @@ class AppBarIconButton extends StatelessWidget {
color: color ?? Theme.of(context).extension<StackColors>()!.background, color: color ?? Theme.of(context).extension<StackColors>()!.background,
boxShadow: shadows, boxShadow: shadows,
), ),
child: MaterialButton( child: Semantics(
splashColor: Theme.of(context).extension<StackColors>()!.highlight, excludeSemantics: true,
padding: EdgeInsets.zero, label: semanticsLabel,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, child: MaterialButton(
shape: RoundedRectangleBorder( splashColor: Theme.of(context).extension<StackColors>()!.highlight,
borderRadius: BorderRadius.circular(1000), padding: EdgeInsets.zero,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(1000),
),
onPressed: onPressed,
child: icon,
), ),
onPressed: onPressed, )
child: icon,
),
); );
} }
} }
@ -53,12 +59,14 @@ class AppBarBackButton extends StatelessWidget {
this.isCompact = false, this.isCompact = false,
this.size, this.size,
this.iconSize, this.iconSize,
this.semanticsLabel = "Back Button. Takes Back To Previous Page.",
}) : super(key: key); }) : super(key: key);
final VoidCallback? onPressed; final VoidCallback? onPressed;
final bool isCompact; final bool isCompact;
final double? size; final double? size;
final double? iconSize; final double? iconSize;
final String semanticsLabel;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -71,24 +79,25 @@ class AppBarBackButton extends StatelessWidget {
) )
: const EdgeInsets.all(10), : const EdgeInsets.all(10),
child: AppBarIconButton( child: AppBarIconButton(
size: size ?? semanticsLabel: semanticsLabel,
(isDesktop size: size ??
? isCompact (isDesktop
? 42 ? isCompact
: 56 ? 42
: 32), : 56
color: isDesktop : 32),
? Theme.of(context).extension<StackColors>()!.textFieldDefaultBG color: isDesktop
: Theme.of(context).extension<StackColors>()!.background, ? Theme.of(context).extension<StackColors>()!.textFieldDefaultBG
shadows: const [], : Theme.of(context).extension<StackColors>()!.background,
icon: SvgPicture.asset( shadows: const [],
Assets.svg.arrowLeft, icon: SvgPicture.asset(
width: iconSize ?? (isCompact ? 18 : 24), Assets.svg.arrowLeft,
height: iconSize ?? (isCompact ? 18 : 24), width: iconSize ?? (isCompact ? 18 : 24),
color: Theme.of(context).extension<StackColors>()!.topNavIconPrimary, height: iconSize ?? (isCompact ? 18 : 24),
), color: Theme.of(context).extension<StackColors>()!.topNavIconPrimary,
onPressed: onPressed ?? Navigator.of(context).pop, ),
), onPressed: onPressed ?? Navigator.of(context).pop,
)
); );
} }
} }

View file

@ -5,6 +5,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/loading_indicator.dart';
class CustomLoadingOverlay extends ConsumerStatefulWidget { class CustomLoadingOverlay extends ConsumerStatefulWidget {
@ -14,12 +16,14 @@ class CustomLoadingOverlay extends ConsumerStatefulWidget {
this.subMessage, this.subMessage,
required this.eventBus, required this.eventBus,
this.textColor, this.textColor,
this.actionButton,
}) : super(key: key); }) : super(key: key);
final String message; final String message;
final String? subMessage; final String? subMessage;
final EventBus? eventBus; final EventBus? eventBus;
final Color? textColor; final Color? textColor;
final Widget? actionButton;
@override @override
ConsumerState<CustomLoadingOverlay> createState() => ConsumerState<CustomLoadingOverlay> createState() =>
@ -30,6 +34,7 @@ class _CustomLoadingOverlayState extends ConsumerState<CustomLoadingOverlay> {
double _percent = 0; double _percent = 0;
late final StreamSubscription<double>? subscription; late final StreamSubscription<double>? subscription;
final bool isDesktop = Util.isDesktop;
@override @override
void initState() { void initState() {
@ -49,68 +54,97 @@ class _CustomLoadingOverlayState extends ConsumerState<CustomLoadingOverlay> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Material(
crossAxisAlignment: CrossAxisAlignment.stretch, color: Colors.transparent,
mainAxisAlignment: MainAxisAlignment.center, child: ConditionalParent(
children: [ condition: widget.actionButton != null,
Material( builder: (child) => Stack(
color: Colors.transparent, children: [
child: Center( child,
child: Column( if (isDesktop)
children: [ Row(
Text( mainAxisAlignment: MainAxisAlignment.end,
widget.message, children: [
textAlign: TextAlign.center, Padding(
style: STextStyles.pageTitleH2(context).copyWith( padding: const EdgeInsets.all(16),
color: widget.textColor ?? child: SizedBox(
Theme.of(context) width: 100,
.extension<StackColors>()! child: widget.actionButton!,
.loadingOverlayTextColor, ),
),
],
),
if (!isDesktop)
Positioned(
bottom: 1,
left: 0,
right: 1,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(child: widget.actionButton!),
],
), ),
), ),
if (widget.eventBus != null) ),
const SizedBox( ],
height: 10, ),
), child: Column(
if (widget.eventBus != null) crossAxisAlignment: CrossAxisAlignment.stretch,
Text( mainAxisAlignment: MainAxisAlignment.center,
"${(_percent * 100).toStringAsFixed(2)}%", children: [
style: STextStyles.pageTitleH2(context).copyWith( Text(
color: widget.textColor ?? widget.message,
Theme.of(context) textAlign: TextAlign.center,
.extension<StackColors>()! style: STextStyles.pageTitleH2(context).copyWith(
.loadingOverlayTextColor, color: widget.textColor ??
), Theme.of(context)
), .extension<StackColors>()!
if (widget.subMessage != null) .loadingOverlayTextColor,
const SizedBox( ),
height: 10,
),
if (widget.subMessage != null)
Text(
widget.subMessage!,
textAlign: TextAlign.center,
style: STextStyles.pageTitleH2(context).copyWith(
fontSize: 14,
color: widget.textColor ??
Theme.of(context)
.extension<StackColors>()!
.loadingOverlayTextColor,
),
)
],
), ),
), if (widget.eventBus != null)
const SizedBox(
height: 10,
),
if (widget.eventBus != null)
Text(
"${(_percent * 100).toStringAsFixed(2)}%",
style: STextStyles.pageTitleH2(context).copyWith(
color: widget.textColor ??
Theme.of(context)
.extension<StackColors>()!
.loadingOverlayTextColor,
),
),
if (widget.subMessage != null)
const SizedBox(
height: 10,
),
if (widget.subMessage != null)
Text(
widget.subMessage!,
textAlign: TextAlign.center,
style: STextStyles.pageTitleH2(context).copyWith(
fontSize: 14,
color: widget.textColor ??
Theme.of(context)
.extension<StackColors>()!
.loadingOverlayTextColor,
),
),
const SizedBox(
height: 64,
),
const Center(
child: LoadingIndicator(
width: 100,
),
),
],
), ),
const SizedBox( ),
height: 64,
),
const Center(
child: LoadingIndicator(
width: 100,
),
),
],
); );
} }
} }

View file

@ -7,6 +7,7 @@ class CustomPinPut extends StatefulWidget {
const CustomPinPut({ const CustomPinPut({
Key? key, Key? key,
required this.fieldsCount, required this.fieldsCount,
required this.isRandom,
this.height, this.height,
this.width, this.width,
this.onSubmit, this.onSubmit,
@ -60,6 +61,8 @@ class CustomPinPut extends StatefulWidget {
final CustomKey? customKey; final CustomKey? customKey;
final bool isRandom;
/// Displayed fields count. PIN code length. /// Displayed fields count. PIN code length.
final int fieldsCount; final int fieldsCount;

View file

@ -32,9 +32,9 @@ class CustomPinPutState extends State<CustomPinPut>
} catch (e) { } catch (e) {
_textControllerValue = ValueNotifier(_controller.value.text); _textControllerValue = ValueNotifier(_controller.value.text);
} }
if (pin.length == widget.fieldsCount) { // if (pin.length == widget.fieldsCount) {
widget.onSubmit?.call(pin); // widget.onSubmit?.call(pin);
} // }
} }
} }
@ -50,6 +50,9 @@ 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,
@ -69,6 +72,7 @@ class CustomPinPutState extends State<CustomPinPut>
), ),
Center( Center(
child: PinKeyboard( child: PinKeyboard(
isRandom: widget.isRandom,
customKey: widget.customKey, customKey: widget.customKey,
onNumberKeyPressed: (number) { onNumberKeyPressed: (number) {
if (_controller.text.length < widget.fieldsCount) { if (_controller.text.length < widget.fieldsCount) {

View file

@ -1,5 +1,6 @@
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_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
@ -140,15 +141,19 @@ class _BackspaceKeyState extends State<BackspaceKey> {
} }
}); });
}, },
child: Center( child: Semantics(
child: SvgPicture.asset( label: "Backspace Button. Deletes The Last Digit.",
Assets.svg.delete, excludeSemantics: true,
width: 20, child: Center(
height: 20, child: SvgPicture.asset(
color: Assets.svg.delete,
Theme.of(context).extension<StackColors>()!.numpadTextDefault, width: 20,
height: 20,
color:
Theme.of(context).extension<StackColors>()!.numpadTextDefault,
),
), ),
), )
), ),
); );
} }
@ -198,10 +203,12 @@ class CustomKey extends StatelessWidget {
Key? key, Key? key,
required this.onPressed, required this.onPressed,
this.iconAssetName, this.iconAssetName,
this.semanticsLabel = "Button",
}) : super(key: key); }) : super(key: key);
final VoidCallback onPressed; final VoidCallback onPressed;
final String? iconAssetName; final String? iconAssetName;
final String semanticsLabel;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -213,32 +220,197 @@ class CustomKey extends StatelessWidget {
color: Theme.of(context).extension<StackColors>()!.numpadBackDefault, color: Theme.of(context).extension<StackColors>()!.numpadBackDefault,
shadows: const [], shadows: const [],
), ),
child: MaterialButton( child: Semantics(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight, label: semanticsLabel,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, excludeSemantics: true,
shape: const StadiumBorder(), child: MaterialButton(
onPressed: () { // splashColor: Theme.of(context).extension<StackColors>()!.highlight,
onPressed.call(); materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
}, shape: const StadiumBorder(),
child: Center( onPressed: () {
child: iconAssetName == null onPressed.call();
? null },
: SvgPicture.asset( child: Center(
iconAssetName!, child: iconAssetName == null
width: 20, ? null
height: 20, : SvgPicture.asset(
color: Theme.of(context) iconAssetName!,
.extension<StackColors>()! width: 20,
.numpadTextDefault, height: 20,
), color: Theme.of(context)
.extension<StackColors>()!
.numpadTextDefault,
),
),
), ),
)
);
}
}
class PinKeyboard extends ConsumerWidget {
const PinKeyboard({
Key? key,
required this.onNumberKeyPressed,
required this.onBackPressed,
required this.onSubmitPressed,
required this.isRandom,
this.backgroundColor,
this.width = 264,
this.height = 360,
this.customKey,
}) : super(key: key);
final ValueSetter<String> onNumberKeyPressed;
final VoidCallback onBackPressed;
final VoidCallback onSubmitPressed;
final Color? backgroundColor;
final double? width;
final double? height;
final CustomKey? customKey;
final bool isRandom;
void _backHandler() {
onBackPressed.call();
}
void _submitHandler() {
onSubmitPressed.call();
}
void _numberHandler(String number) {
onNumberKeyPressed.call(number);
HapticFeedback.lightImpact();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final list = [
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"0",
];
// final isRandom = ref.read(prefsChangeNotifierProvider).randomizePIN;
if (isRandom) list.shuffle();
return Container(
width: width,
height: height,
color: backgroundColor ?? Colors.transparent,
child: Column(
children: [
Row(
children: [
NumberKey(
number: list[0],
onPressed: _numberHandler,
),
const SizedBox(
width: 24,
),
NumberKey(
number: list[1],
onPressed: _numberHandler,
),
const SizedBox(
width: 24,
),
NumberKey(
number: list[2],
onPressed: _numberHandler,
),
],
),
const SizedBox(
height: 24,
),
Row(
children: [
NumberKey(
number: list[3],
onPressed: _numberHandler,
),
const SizedBox(
width: 24,
),
NumberKey(
number: list[4],
onPressed: _numberHandler,
),
const SizedBox(
width: 24,
),
NumberKey(
number: list[5],
onPressed: _numberHandler,
),
],
),
const SizedBox(
height: 24,
),
Row(
children: [
NumberKey(
number: list[6],
onPressed: _numberHandler,
),
const SizedBox(
width: 24,
),
NumberKey(
number: list[7],
onPressed: _numberHandler,
),
const SizedBox(
width: 24,
),
NumberKey(
number: list[8],
onPressed: _numberHandler,
),
],
),
const SizedBox(
height: 24,
),
Row(
children: [
BackspaceKey(
onPressed: _backHandler,
),
const SizedBox(
width: 24,
),
NumberKey(
number: list[9],
onPressed: _numberHandler,
),
const SizedBox(
width: 24,
),
SubmitKey(
onPressed: _submitHandler,
),
],
)
],
), ),
); );
} }
} }
class PinKeyboard extends StatelessWidget { class RandomKeyboard extends StatelessWidget {
const PinKeyboard({ const RandomKeyboard({
Key? key, Key? key,
required this.onNumberKeyPressed, required this.onNumberKeyPressed,
required this.onBackPressed, required this.onBackPressed,
@ -268,10 +440,24 @@ class PinKeyboard extends StatelessWidget {
void _numberHandler(String number) { void _numberHandler(String number) {
onNumberKeyPressed.call(number); onNumberKeyPressed.call(number);
HapticFeedback.lightImpact(); HapticFeedback.lightImpact();
debugPrint("NUMBER: $number");
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final list = [
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"0",
];
list.shuffle();
return Container( return Container(
width: width, width: width,
height: height, height: height,
@ -281,21 +467,21 @@ class PinKeyboard extends StatelessWidget {
Row( Row(
children: [ children: [
NumberKey( NumberKey(
number: "1", number: list[0],
onPressed: _numberHandler, onPressed: _numberHandler,
), ),
const SizedBox( const SizedBox(
width: 24, width: 24,
), ),
NumberKey( NumberKey(
number: "2", number: list[1],
onPressed: _numberHandler, onPressed: _numberHandler,
), ),
const SizedBox( const SizedBox(
width: 24, width: 24,
), ),
NumberKey( NumberKey(
number: "3", number: list[2],
onPressed: _numberHandler, onPressed: _numberHandler,
), ),
], ],
@ -306,21 +492,21 @@ class PinKeyboard extends StatelessWidget {
Row( Row(
children: [ children: [
NumberKey( NumberKey(
number: "4", number: list[3],
onPressed: _numberHandler, onPressed: _numberHandler,
), ),
const SizedBox( const SizedBox(
width: 24, width: 24,
), ),
NumberKey( NumberKey(
number: "5", number: list[4],
onPressed: _numberHandler, onPressed: _numberHandler,
), ),
const SizedBox( const SizedBox(
width: 24, width: 24,
), ),
NumberKey( NumberKey(
number: "6", number: list[5],
onPressed: _numberHandler, onPressed: _numberHandler,
), ),
], ],
@ -331,21 +517,21 @@ class PinKeyboard extends StatelessWidget {
Row( Row(
children: [ children: [
NumberKey( NumberKey(
number: "7", number: list[6],
onPressed: _numberHandler, onPressed: _numberHandler,
), ),
const SizedBox( const SizedBox(
width: 24, width: 24,
), ),
NumberKey( NumberKey(
number: "8", number: list[7],
onPressed: _numberHandler, onPressed: _numberHandler,
), ),
const SizedBox( const SizedBox(
width: 24, width: 24,
), ),
NumberKey( NumberKey(
number: "9", number: list[8],
onPressed: _numberHandler, onPressed: _numberHandler,
), ),
], ],
@ -355,28 +541,22 @@ class PinKeyboard extends StatelessWidget {
), ),
Row( Row(
children: [ children: [
customKey == null
? const SizedBox(
height: 72,
width: 72,
)
: customKey!,
const SizedBox(
width: 24,
),
NumberKey(
number: "0",
onPressed: _numberHandler,
),
const SizedBox(
width: 24,
),
BackspaceKey( BackspaceKey(
onPressed: _backHandler, onPressed: _backHandler,
), ),
// SubmitKey( const SizedBox(
// onPressed: _submitHandler, width: 24,
// ), ),
NumberKey(
number: list[9],
onPressed: _numberHandler,
),
const SizedBox(
width: 24,
),
SubmitKey(
onPressed: _submitHandler,
),
], ],
) )
], ],

View file

@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
class BasicDialog extends StatelessWidget {
const BasicDialog({
Key? key,
this.leftButton,
this.rightButton,
this.icon,
required this.title,
this.message,
this.desktopHeight = 474,
this.desktopWidth = 641,
this.canPopWithBackButton = false,
}) : super(key: key);
final Widget? leftButton;
final Widget? rightButton;
final Widget? icon;
final String title;
final String? message;
final double? desktopHeight;
final double desktopWidth;
final bool canPopWithBackButton;
@override
Widget build(BuildContext context) {
final isDesktop = Util.isDesktop;
if (isDesktop) {
return DesktopDialog(
maxHeight: desktopHeight,
maxWidth: desktopWidth,
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 32),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: STextStyles.desktopH3(context),
),
const DesktopDialogCloseButton(),
],
),
),
if (message != null)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text(
message!,
style: STextStyles.desktopTextSmall(context),
),
),
if (leftButton != null || rightButton != null)
const SizedBox(
height: 32,
),
if (leftButton != null || rightButton != null)
Padding(
padding: const EdgeInsets.all(32),
child: Row(
children: [
leftButton != null
? Expanded(child: leftButton!)
: const Spacer(),
const SizedBox(
width: 16,
),
rightButton != null
? Expanded(child: rightButton!)
: const Spacer(),
],
),
)
],
),
);
} else {
return WillPopScope(
onWillPop: () async {
return canPopWithBackButton;
},
child: StackDialog(
title: title,
leftButton: leftButton,
rightButton: rightButton,
icon: icon,
message: message,
),
);
}
}
}

View file

@ -8,6 +8,7 @@ class TextFieldIconButton extends StatefulWidget {
this.onTap, this.onTap,
required this.child, required this.child,
this.color = Colors.transparent, this.color = Colors.transparent,
this.semanticsLabel = "Button",
}) : super(key: key); }) : super(key: key);
final double width; final double width;
@ -15,6 +16,7 @@ class TextFieldIconButton extends StatefulWidget {
final VoidCallback? onTap; final VoidCallback? onTap;
final Widget child; final Widget child;
final Color color; final Color color;
final String semanticsLabel;
@override @override
State<TextFieldIconButton> createState() => _TextFieldIconButtonState(); State<TextFieldIconButton> createState() => _TextFieldIconButtonState();
@ -36,21 +38,25 @@ class _TextFieldIconButtonState extends State<TextFieldIconButton> {
width: widget.width, width: widget.width,
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(100), borderRadius: BorderRadius.circular(100),
child: RawMaterialButton( child: Semantics(
constraints: BoxConstraints( label: widget.semanticsLabel,
minWidth: widget.width, excludeSemantics: true,
minHeight: widget.height, child: RawMaterialButton(
), constraints: BoxConstraints(
onPressed: onTap, minWidth: widget.width,
child: Container( minHeight: widget.height,
width: widget.width, ),
height: widget.height, onPressed: onTap,
color: widget.color, child: Container(
child: Center( width: widget.width,
child: widget.child, height: widget.height,
color: widget.color,
child: Center(
child: widget.child,
),
), ),
), ),
), )
), ),
); );
} }

View file

@ -19,6 +19,8 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/show_loading.dart'; import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/dialogs/basic_dialog.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/wallet_info_row/wallet_info_row.dart'; import 'package:stackwallet/widgets/wallet_info_row/wallet_info_row.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
@ -37,7 +39,7 @@ class SimpleWalletCard extends ConsumerWidget {
final bool popPrevious; final bool popPrevious;
final NavigatorState? desktopNavigatorState; final NavigatorState? desktopNavigatorState;
Future<void> _loadTokenWallet( Future<bool> _loadTokenWallet(
BuildContext context, BuildContext context,
WidgetRef ref, WidgetRef ref,
Manager manager, Manager manager,
@ -52,7 +54,31 @@ class SimpleWalletCard extends ConsumerWidget {
), ),
); );
await ref.read(tokenServiceProvider)!.initialize(); try {
await ref.read(tokenServiceProvider)!.initialize();
return true;
} catch (_) {
await showDialog<void>(
barrierDismissible: false,
context: context,
builder: (context) => BasicDialog(
title: "Failed to load token data",
desktopHeight: double.infinity,
desktopWidth: 450,
rightButton: PrimaryButton(
label: "OK",
onPressed: () {
Navigator.of(context).pop();
Navigator.of(context).pop();
if (desktopNavigatorState == null) {
Navigator.of(context).pop();
}
},
),
),
);
return false;
}
} }
void _openWallet(BuildContext context, WidgetRef ref) async { void _openWallet(BuildContext context, WidgetRef ref) async {
@ -91,13 +117,21 @@ class SimpleWalletCard extends ConsumerWidget {
final contract = final contract =
ref.read(mainDBProvider).getEthContractSync(contractAddress!)!; ref.read(mainDBProvider).getEthContractSync(contractAddress!)!;
await showLoading<void>( final success = await showLoading<bool>(
whileFuture: _loadTokenWallet(context, ref, manager, contract), whileFuture: _loadTokenWallet(
context: context, desktopNavigatorState?.context ?? context,
ref,
manager,
contract),
context: desktopNavigatorState?.context ?? context,
opaqueBG: true, opaqueBG: true,
message: "Loading ${contract.name}", message: "Loading ${contract.name}",
); );
if (!success) {
return;
}
if (desktopNavigatorState == null) { if (desktopNavigatorState == null) {
// pop loading // pop loading
nav.pop(); nav.pop();

View file

@ -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.4+164 version: 1.7.4+165
environment: environment:
sdk: ">=2.17.0 <3.0.0" sdk: ">=2.17.0 <3.0.0"

View file

@ -7,9 +7,13 @@ 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';
void main() { void main() {
group("CustomPinPut tests", () { group("CustomPinPut tests, non-random PIN", () {
testWidgets("CustomPinPut with 4 fields builds correctly", (tester) async { testWidgets("CustomPinPut with 4 fields builds correctly, non-random PIN",
const pinPut = CustomPinPut(fieldsCount: 4); (tester) async {
const pinPut = CustomPinPut(
fieldsCount: 4,
isRandom: false,
);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
@ -31,12 +35,17 @@ void main() {
expect(find.byType(NumberKey), findsNWidgets(10)); expect(find.byType(NumberKey), findsNWidgets(10));
}); });
testWidgets("CustomPinPut entering a pin successfully", (tester) async { testWidgets("CustomPinPut entering a pin successfully, non-random PIN",
(tester) async {
bool submittedPinMatches = false; bool submittedPinMatches = false;
final pinPut = CustomPinPut( final pinPut = CustomPinPut(
fieldsCount: 4, fieldsCount: 4,
onSubmit: (pin) => submittedPinMatches = pin == "1234", onSubmit: (pin) {
submittedPinMatches = pin == "1234";
print("pin entered: $pin");
},
useNativeKeyboard: false, useNativeKeyboard: false,
isRandom: false,
); );
await tester.pumpWidget( await tester.pumpWidget(
@ -69,16 +78,20 @@ void main() {
await tester.tap(find.byWidgetPredicate( await tester.tap(find.byWidgetPredicate(
(widget) => widget is NumberKey && widget.number == "4")); (widget) => widget is NumberKey && widget.number == "4"));
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", (tester) async { testWidgets("CustomPinPut pin enter fade animation, non-random PIN",
(tester) async {
final controller = TextEditingController(); final controller = TextEditingController();
final pinPut = CustomPinPut( final pinPut = CustomPinPut(
fieldsCount: 4, fieldsCount: 4,
pinAnimationType: PinAnimationType.fade, pinAnimationType: PinAnimationType.fade,
controller: controller, controller: controller,
isRandom: false,
); );
await tester.pumpWidget( await tester.pumpWidget(
@ -104,12 +117,14 @@ void main() {
expect(controller.text, ""); expect(controller.text, "");
}); });
testWidgets("CustomPinPut pin enter scale animation", (tester) async { testWidgets("CustomPinPut pin enter scale animation, non-random PIN",
(tester) async {
final controller = TextEditingController(); final controller = TextEditingController();
final pinPut = CustomPinPut( final pinPut = CustomPinPut(
fieldsCount: 4, fieldsCount: 4,
pinAnimationType: PinAnimationType.scale, pinAnimationType: PinAnimationType.scale,
controller: controller, controller: controller,
isRandom: false,
); );
await tester.pumpWidget( await tester.pumpWidget(
@ -135,12 +150,14 @@ void main() {
expect(controller.text, ""); expect(controller.text, "");
}); });
testWidgets("CustomPinPut pin enter rotate animation", (tester) async { testWidgets("CustomPinPut pin enter rotate animation, non-random PIN",
(tester) async {
final controller = TextEditingController(); final controller = TextEditingController();
final pinPut = CustomPinPut( final pinPut = CustomPinPut(
fieldsCount: 4, fieldsCount: 4,
pinAnimationType: PinAnimationType.rotation, pinAnimationType: PinAnimationType.rotation,
controller: controller, controller: controller,
isRandom: false,
); );
await tester.pumpWidget( await tester.pumpWidget(
@ -167,11 +184,12 @@ void main() {
}); });
}); });
testWidgets("PinKeyboard builds correctly", (tester) async { testWidgets("PinKeyboard builds correctly, non-random PIN", (tester) async {
final keyboard = PinKeyboard( final keyboard = PinKeyboard(
onNumberKeyPressed: (_) {}, onNumberKeyPressed: (_) {},
onBackPressed: () {}, onBackPressed: () {},
onSubmitPressed: () {}, onSubmitPressed: () {},
isRandom: false,
); );
await tester.pumpWidget( await tester.pumpWidget(
@ -188,6 +206,7 @@ void main() {
); );
expect(find.byType(BackspaceKey), findsOneWidget); expect(find.byType(BackspaceKey), findsOneWidget);
expect(find.byType(SubmitKey), findsOneWidget);
expect(find.byType(NumberKey), findsNWidgets(10)); expect(find.byType(NumberKey), findsNWidgets(10));
expect(find.text("0"), findsOneWidget); expect(find.text("0"), findsOneWidget);
expect(find.text("1"), findsOneWidget); expect(find.text("1"), findsOneWidget);
@ -199,6 +218,220 @@ void main() {
expect(find.text("7"), findsOneWidget); expect(find.text("7"), findsOneWidget);
expect(find.text("8"), findsOneWidget); expect(find.text("8"), findsOneWidget);
expect(find.text("9"), findsOneWidget); expect(find.text("9"), findsOneWidget);
expect(find.byType(SvgPicture), findsOneWidget); expect(find.byType(SvgPicture), findsNWidgets(2));
});
group("CustomPinPut tests, with random PIN", () {
testWidgets("CustomPinPut with 4 fields builds correctly, with random PIN",
(tester) async {
const pinPut = CustomPinPut(
fieldsCount: 4,
isRandom: true,
);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
extensions: [
StackColors.fromStackColorTheme(LightColors()),
],
),
home: const Material(
child: pinPut,
),
),
);
// expects 5 here. Four + the actual text field text
expect(find.text(""), findsNWidgets(5));
expect(find.byType(PinKeyboard), findsOneWidget);
expect(find.byType(BackspaceKey), findsOneWidget);
expect(find.byType(NumberKey), findsNWidgets(10));
});
testWidgets("CustomPinPut entering a pin successfully, with random PIN",
(tester) async {
bool submittedPinMatches = false;
final pinPut = CustomPinPut(
fieldsCount: 4,
onSubmit: (pin) {
submittedPinMatches = pin == "1234";
print("pin entered: $pin");
},
useNativeKeyboard: false,
isRandom: true,
);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
extensions: [
StackColors.fromStackColorTheme(LightColors()),
],
),
home: Material(
child: pinPut,
),
),
);
await tester.tap(find.byWidgetPredicate(
(widget) => widget is NumberKey && widget.number == "1"));
await tester.pumpAndSettle();
await tester.tap(find.byWidgetPredicate(
(widget) => widget is NumberKey && widget.number == "2"));
await tester.pumpAndSettle();
await tester.tap(find.byWidgetPredicate(
(widget) => widget is NumberKey && widget.number == "6"));
await tester.pumpAndSettle();
await tester.tap(find.byType(BackspaceKey));
await tester.pumpAndSettle();
await tester.tap(find.byWidgetPredicate(
(widget) => widget is NumberKey && widget.number == "3"));
await tester.pumpAndSettle();
await tester.tap(find.byWidgetPredicate(
(widget) => widget is NumberKey && widget.number == "4"));
await tester.pumpAndSettle();
await tester.tap(find.byType(SubmitKey));
await tester.pumpAndSettle();
expect(submittedPinMatches, true);
});
testWidgets("CustomPinPut pin enter fade animation, with random PIN",
(tester) async {
final controller = TextEditingController();
final pinPut = CustomPinPut(
fieldsCount: 4,
pinAnimationType: PinAnimationType.fade,
controller: controller,
isRandom: true,
);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
extensions: [
StackColors.fromStackColorTheme(LightColors()),
],
),
home: Material(
child: pinPut,
),
),
);
await tester.tap(find.byWidgetPredicate(
(widget) => widget is NumberKey && widget.number == "1"));
await tester.pumpAndSettle();
expect(controller.text, "1");
await tester.tap(find.byType(BackspaceKey));
await tester.pumpAndSettle();
expect(controller.text, "");
});
testWidgets("CustomPinPut pin enter scale animation, with random PIN",
(tester) async {
final controller = TextEditingController();
final pinPut = CustomPinPut(
fieldsCount: 4,
pinAnimationType: PinAnimationType.scale,
controller: controller,
isRandom: true,
);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
extensions: [
StackColors.fromStackColorTheme(LightColors()),
],
),
home: Material(
child: pinPut,
),
),
);
await tester.tap(find.byWidgetPredicate(
(widget) => widget is NumberKey && widget.number == "1"));
await tester.pumpAndSettle();
expect(controller.text, "1");
await tester.tap(find.byType(BackspaceKey));
await tester.pumpAndSettle();
expect(controller.text, "");
});
testWidgets("CustomPinPut pin enter rotate animation, with random PIN",
(tester) async {
final controller = TextEditingController();
final pinPut = CustomPinPut(
fieldsCount: 4,
pinAnimationType: PinAnimationType.rotation,
controller: controller,
isRandom: true,
);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
extensions: [
StackColors.fromStackColorTheme(LightColors()),
],
),
home: Material(
child: pinPut,
),
),
);
await tester.tap(find.byWidgetPredicate(
(widget) => widget is NumberKey && widget.number == "1"));
await tester.pumpAndSettle();
expect(controller.text, "1");
await tester.tap(find.byType(BackspaceKey));
await tester.pumpAndSettle();
expect(controller.text, "");
});
});
testWidgets("PinKeyboard builds correctly, with random PIN", (tester) async {
final keyboard = PinKeyboard(
onNumberKeyPressed: (_) {},
onBackPressed: () {},
onSubmitPressed: () {},
isRandom: true,
);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
extensions: [
StackColors.fromStackColorTheme(LightColors()),
],
),
home: Material(
child: keyboard,
),
),
);
expect(find.byType(BackspaceKey), findsOneWidget);
expect(find.byType(SubmitKey), findsOneWidget);
expect(find.byType(NumberKey), findsNWidgets(10));
expect(find.text("0"), findsOneWidget);
expect(find.text("1"), findsOneWidget);
expect(find.text("2"), findsOneWidget);
expect(find.text("3"), findsOneWidget);
expect(find.text("4"), findsOneWidget);
expect(find.text("5"), findsOneWidget);
expect(find.text("6"), findsOneWidget);
expect(find.text("7"), findsOneWidget);
expect(find.text("8"), findsOneWidget);
expect(find.text("9"), findsOneWidget);
expect(find.byType(SvgPicture), findsNWidgets(2));
}); });
} }