Merge remote-tracking branch 'origin/ordinals' into ordinals-desktop

This commit is contained in:
sneurlax 2023-07-21 18:24:43 -05:00
commit 397942f4af
45 changed files with 1042 additions and 226 deletions

BIN
assets/icon/macos-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -1 +1 @@
Subproject commit 686559344a58f77732c3d0134fbf44d271a55229
Subproject commit f677dec0b34d3f9fe8fce2bc8ff5c508c3f3bb9a

@ -1 +1 @@
Subproject commit cdccef0e8dc10b7fe703b5bb9b41b59b25177e83
Subproject commit 9cd241b5ea142e21c01dd7639b42603281c43287

@ -1 +1 @@
Subproject commit c920c09df5e415bba4bbe95dd50e1f0085f040e6
Subproject commit e48952185556a10f182184fd572bcb04365f5831

View file

@ -46,7 +46,7 @@ class EthTokenTxExtraDTO {
),
gas: _amountFromJsonNum(map['gas']),
gasPrice: _amountFromJsonNum(map['gasPrice']),
nonce: map['nonce'] as int,
nonce: map['nonce'] as int?,
input: map['input'] as String,
gasCost: _amountFromJsonNum(map['gasCost']),
gasUsed: _amountFromJsonNum(map['gasUsed']),
@ -63,7 +63,7 @@ class EthTokenTxExtraDTO {
final Amount gas;
final Amount gasPrice;
final String input;
final int nonce;
final int? nonce;
final Amount gasCost;
final Amount gasUsed;

View file

@ -127,16 +127,16 @@ class EthTxDTO {
map['timestamp'] = timestamp;
map['from'] = from;
map['to'] = to;
map['value'] = value;
map['gas'] = gas;
map['gasPrice'] = gasPrice;
map['maxFeePerGas'] = maxFeePerGas;
map['maxPriorityFeePerGas'] = maxPriorityFeePerGas;
map['value'] = value.toString();
map['gas'] = gas.toString();
map['gasPrice'] = gasPrice.toString();
map['maxFeePerGas'] = maxFeePerGas.toString();
map['maxPriorityFeePerGas'] = maxPriorityFeePerGas.toString();
map['isError'] = isError;
map['hasToken'] = hasToken;
map['compressedTx'] = compressedTx;
map['gasCost'] = gasCost;
map['gasUsed'] = gasUsed;
map['gasCost'] = gasCost.toString();
map['gasUsed'] = gasUsed.toString();
return map;
}

View file

@ -2341,8 +2341,6 @@ class ThemeAssetsV3 implements IThemeAssets {
// Added some future proof params in case we want to add anything else
// This should provide some buffer in stead of creating assetsV4 etc
@Name("otherStringParam1")
late final String? dummy1;
@Name("otherStringParam2")
late final String? dummy2;
@Name("otherStringParam3")
@ -2388,6 +2386,19 @@ class ThemeAssetsV3 implements IThemeAssets {
Map<Coin, String>? _coinCardImages;
late final String? coinCardImagesString;
@ignore
Map<Coin, String>? get coinCardFavoritesImages =>
_coinCardFavoritesImages ??= coinCardFavoritesImagesString == null
? null
: parseCoinAssetsString(
coinCardFavoritesImagesString!,
placeHolder: coinPlaceholder,
);
@ignore
Map<Coin, String>? _coinCardFavoritesImages;
@Name("otherStringParam1")
late final String? coinCardFavoritesImagesString;
ThemeAssetsV3();
factory ThemeAssetsV3.fromJson({
@ -2443,13 +2454,18 @@ class ThemeAssetsV3 implements IThemeAssets {
Map<String, dynamic>.from(json["coins"]["cards"] as Map),
)
: null
..coinCardFavoritesImagesString = json["coins"]["favoriteCards"] is Map
? createCoinAssetsString(
"$themeId/assets",
Map<String, dynamic>.from(json["coins"]["favoriteCards"] as Map),
)
: null
..loadingGifRelative = json["loading_gif"] is String
? "$themeId/assets/${json["loading_gif"] as String}"
: null
..backgroundRelative = json["background"] is String
? "$themeId/assets/${json["background"] as String}"
: null
..dummy1 = null
..dummy2 = null
..dummy3 = null;
}
@ -2528,6 +2544,7 @@ class ThemeAssetsV3 implements IThemeAssets {
'coinImages: $coinImages, '
'coinSecondaryImages: $coinSecondaryImages, '
'coinCardImages: $coinCardImages'
'coinCardFavoritesImages: $coinCardFavoritesImages'
')';
}
}

View file

@ -29626,7 +29626,7 @@ int _themeAssetsV3EstimateSize(
}
}
{
final value = object.dummy1;
final value = object.coinCardFavoritesImagesString;
if (value != null) {
bytesCount += 3 + value.length * 3;
}
@ -29677,7 +29677,7 @@ void _themeAssetsV3Serialize(
writer.writeString(offsets[7], object.coinSecondaryImagesString);
writer.writeString(offsets[8], object.exchangeRelative);
writer.writeString(offsets[9], object.loadingGifRelative);
writer.writeString(offsets[10], object.dummy1);
writer.writeString(offsets[10], object.coinCardFavoritesImagesString);
writer.writeString(offsets[11], object.dummy2);
writer.writeString(offsets[12], object.dummy3);
writer.writeString(offsets[13], object.personaEasyRelative);
@ -29714,7 +29714,7 @@ ThemeAssetsV3 _themeAssetsV3Deserialize(
object.coinSecondaryImagesString = reader.readString(offsets[7]);
object.exchangeRelative = reader.readString(offsets[8]);
object.loadingGifRelative = reader.readStringOrNull(offsets[9]);
object.dummy1 = reader.readStringOrNull(offsets[10]);
object.coinCardFavoritesImagesString = reader.readStringOrNull(offsets[10]);
object.dummy2 = reader.readStringOrNull(offsets[11]);
object.dummy3 = reader.readStringOrNull(offsets[12]);
object.personaEasyRelative = reader.readString(offsets[13]);
@ -31224,7 +31224,7 @@ extension ThemeAssetsV3QueryFilter
}
QueryBuilder<ThemeAssetsV3, ThemeAssetsV3, QAfterFilterCondition>
dummy1IsNull() {
coinCardFavoritesImagesStringIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
property: r'otherStringParam1',
@ -31233,7 +31233,7 @@ extension ThemeAssetsV3QueryFilter
}
QueryBuilder<ThemeAssetsV3, ThemeAssetsV3, QAfterFilterCondition>
dummy1IsNotNull() {
coinCardFavoritesImagesStringIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNotNull(
property: r'otherStringParam1',
@ -31242,7 +31242,7 @@ extension ThemeAssetsV3QueryFilter
}
QueryBuilder<ThemeAssetsV3, ThemeAssetsV3, QAfterFilterCondition>
dummy1EqualTo(
coinCardFavoritesImagesStringEqualTo(
String? value, {
bool caseSensitive = true,
}) {
@ -31256,7 +31256,7 @@ extension ThemeAssetsV3QueryFilter
}
QueryBuilder<ThemeAssetsV3, ThemeAssetsV3, QAfterFilterCondition>
dummy1GreaterThan(
coinCardFavoritesImagesStringGreaterThan(
String? value, {
bool include = false,
bool caseSensitive = true,
@ -31272,7 +31272,7 @@ extension ThemeAssetsV3QueryFilter
}
QueryBuilder<ThemeAssetsV3, ThemeAssetsV3, QAfterFilterCondition>
dummy1LessThan(
coinCardFavoritesImagesStringLessThan(
String? value, {
bool include = false,
bool caseSensitive = true,
@ -31288,7 +31288,7 @@ extension ThemeAssetsV3QueryFilter
}
QueryBuilder<ThemeAssetsV3, ThemeAssetsV3, QAfterFilterCondition>
dummy1Between(
coinCardFavoritesImagesStringBetween(
String? lower,
String? upper, {
bool includeLower = true,
@ -31308,7 +31308,7 @@ extension ThemeAssetsV3QueryFilter
}
QueryBuilder<ThemeAssetsV3, ThemeAssetsV3, QAfterFilterCondition>
dummy1StartsWith(
coinCardFavoritesImagesStringStartsWith(
String value, {
bool caseSensitive = true,
}) {
@ -31322,7 +31322,7 @@ extension ThemeAssetsV3QueryFilter
}
QueryBuilder<ThemeAssetsV3, ThemeAssetsV3, QAfterFilterCondition>
dummy1EndsWith(
coinCardFavoritesImagesStringEndsWith(
String value, {
bool caseSensitive = true,
}) {
@ -31336,7 +31336,8 @@ extension ThemeAssetsV3QueryFilter
}
QueryBuilder<ThemeAssetsV3, ThemeAssetsV3, QAfterFilterCondition>
dummy1Contains(String value, {bool caseSensitive = true}) {
coinCardFavoritesImagesStringContains(String value,
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'otherStringParam1',
@ -31347,7 +31348,8 @@ extension ThemeAssetsV3QueryFilter
}
QueryBuilder<ThemeAssetsV3, ThemeAssetsV3, QAfterFilterCondition>
dummy1Matches(String pattern, {bool caseSensitive = true}) {
coinCardFavoritesImagesStringMatches(String pattern,
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'otherStringParam1',
@ -31358,7 +31360,7 @@ extension ThemeAssetsV3QueryFilter
}
QueryBuilder<ThemeAssetsV3, ThemeAssetsV3, QAfterFilterCondition>
dummy1IsEmpty() {
coinCardFavoritesImagesStringIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'otherStringParam1',
@ -31368,7 +31370,7 @@ extension ThemeAssetsV3QueryFilter
}
QueryBuilder<ThemeAssetsV3, ThemeAssetsV3, QAfterFilterCondition>
dummy1IsNotEmpty() {
coinCardFavoritesImagesStringIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'otherStringParam1',

View file

@ -142,7 +142,7 @@ class _AddCustomTokenViewState extends ConsumerState<AddCustomTokenView> {
context: context,
message: "Looking up contract",
);
currentToken = response.value;
currentToken = response!.value;
if (currentToken != null) {
nameController.text = currentToken!.name;
symbolController.text = currentToken!.symbol;
@ -157,7 +157,7 @@ class _AddCustomTokenViewState extends ConsumerState<AddCustomTokenView> {
context: context,
builder: (context) => StackOkDialog(
title: "Failed to look up token",
message: response.exception?.message,
message: response!.exception?.message,
),
),
);

View file

@ -493,51 +493,54 @@ class _ConfirmTransactionViewState
],
),
),
if (coin == Coin.epicCash)
if (coin == Coin.epicCash &&
(transactionInfo["onChainNote"] as String).isNotEmpty)
const SizedBox(
height: 12,
),
if (coin == Coin.epicCash)
if (coin == Coin.epicCash &&
(transactionInfo["onChainNote"] as String).isNotEmpty)
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"On chain note",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 4,
),
Text(
transactionInfo["onChainNote"] as String,
style: STextStyles.itemSubtitle12(context),
),
],
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"On chain note",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 4,
),
Text(
transactionInfo["onChainNote"] as String,
style: STextStyles.itemSubtitle12(context),
),
],
),
),
),
const SizedBox(
height: 12,
),
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
(coin == Coin.epicCash) ? "Local Note" :
"Note",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 4,
),
Text(
transactionInfo["note"] as String,
style: STextStyles.itemSubtitle12(context),
),
],
if ((transactionInfo["note"] as String).isNotEmpty)
const SizedBox(
height: 12,
),
if ((transactionInfo["note"] as String).isNotEmpty)
RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
(coin == Coin.epicCash) ? "Local Note" : "Note",
style: STextStyles.smallMed12(context),
),
const SizedBox(
height: 4,
),
Text(
transactionInfo["note"] as String,
style: STextStyles.itemSubtitle12(context),
),
],
),
),
),
],
),
if (isDesktop)

View file

@ -164,7 +164,7 @@ class _InstallThemeFromFileDialogState
);
if (mounted) {
Navigator.of(context).pop();
if (!result) {
if (!result!) {
unawaited(
showDialog(
context: context,

View file

@ -72,11 +72,11 @@ class _StackThemeCardState extends ConsumerState<StackThemeCard> {
}
Future<void> _downloadPressed() async {
final result = await showLoading(
final result = (await showLoading(
whileFuture: _downloadAndInstall(),
context: context,
message: "Downloading and installing theme...",
);
))!;
if (mounted) {
final message = result

View file

@ -20,11 +20,12 @@ import 'package:stackwallet/pages/home_view/home_view.dart';
import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_preferences_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/xpub_view.dart';
import 'package:stackwallet/pages/settings_views/sub_widgets/settings_list_button.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/change_representative_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
import 'package:stackwallet/route_generator.dart';
@ -231,7 +232,7 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
.mnemonic;
if (mounted) {
Navigator.push(
await Navigator.push(
context,
RouteGenerator.getRoute(
shouldUseMaterialRoute:
@ -305,6 +306,25 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
);
},
),
if (coin == Coin.nano || coin == Coin.banano)
const SizedBox(
height: 8,
),
if (coin == Coin.nano || coin == Coin.banano)
Consumer(
builder: (_, ref, __) {
return SettingsListButton(
iconAssetName: Assets.svg.eye,
title: "Change representative",
onPressed: () {
Navigator.of(context).pushNamed(
ChangeRepresentativeView.routeName,
arguments: widget.walletId,
);
},
);
},
),
const SizedBox(
height: 8,
),
@ -434,18 +454,20 @@ class _EpiBoxInfoFormState extends ConsumerState<EpicBoxInfoForm> {
TextButton(
onPressed: () async {
try {
wallet.updateEpicboxConfig(
await wallet.updateEpicboxConfig(
hostController.text,
int.parse(portController.text),
);
showFloatingFlushBar(
context: context,
message: "Epicbox info saved!",
type: FlushBarType.success,
);
wallet.refresh();
if (mounted) {
await showFloatingFlushBar(
context: context,
message: "Epicbox info saved!",
type: FlushBarType.success,
);
}
unawaited(wallet.refresh());
} catch (e) {
showFloatingFlushBar(
await showFloatingFlushBar(
context: context,
message: "Failed to save epicbox info: $e",
type: FlushBarType.warning,

View file

@ -0,0 +1,402 @@
/*
* This file is part of Stack Wallet.
*
* Copyright (c) 2023 Cypher Stack
* All Rights Reserved.
* The code is distributed under GPLv3 license, see LICENSE file for details.
* Generated by Cypher Stack on 2023-05-26
*
*/
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 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/services/coins/banano/banano_wallet.dart';
import 'package:stackwallet/services/coins/nano/nano_wallet.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
class ChangeRepresentativeView extends ConsumerStatefulWidget {
const ChangeRepresentativeView({
Key? key,
required this.walletId,
this.clipboardInterface = const ClipboardWrapper(),
}) : super(key: key);
final String walletId;
final ClipboardInterface clipboardInterface;
static const String routeName = "/changeRepresentative";
@override
ConsumerState<ChangeRepresentativeView> createState() => _XPubViewState();
}
class _XPubViewState extends ConsumerState<ChangeRepresentativeView> {
final _textController = TextEditingController();
final _textFocusNode = FocusNode();
final bool isDesktop = Util.isDesktop;
late ClipboardInterface _clipboardInterface;
String? representative;
Future<String> loadRepresentative() async {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(widget.walletId);
if (manager.coin == Coin.nano) {
return (manager.wallet as NanoWallet).getCurrentRepresentative();
} else if (manager.coin == Coin.banano) {
return (manager.wallet as BananoWallet).getCurrentRepresentative();
}
throw Exception("Unsupported wallet attempted to show representative!");
}
Future<void> _save() async {
final manager =
ref.read(walletsChangeNotifierProvider).getManager(widget.walletId);
final changeFuture = manager.coin == Coin.nano
? (manager.wallet as NanoWallet).changeRepresentative
: (manager.wallet as BananoWallet).changeRepresentative;
final result = await showLoading(
whileFuture: changeFuture(_textController.text),
context: context,
message: "Updating representative...",
isDesktop: Util.isDesktop,
onException: (ex) {
String msg = ex.toString();
while (msg.isNotEmpty && msg.startsWith("Exception:")) {
msg = msg.substring(10).trim();
}
showFloatingFlushBar(
type: FlushBarType.warning,
message: msg,
context: context,
);
});
if (mounted) {
if (result != null && result) {
setState(() {
representative = _textController.text;
_textController.text = "";
});
await showFloatingFlushBar(
type: FlushBarType.success,
message: "Representative changed",
context: context,
);
}
}
}
@override
void initState() {
_clipboardInterface = widget.clipboardInterface;
super.initState();
}
@override
void dispose() {
_textController.dispose();
_textFocusNode.dispose();
super.dispose();
}
Future<void> _copy() async {
await _clipboardInterface
.setData(ClipboardData(text: representative ?? ""));
if (mounted) {
unawaited(showFloatingFlushBar(
type: FlushBarType.info,
message: "Copied to clipboard",
iconAsset: Assets.svg.copy,
context: context,
));
}
}
@override
Widget build(BuildContext context) {
return ConditionalParent(
condition: !isDesktop,
builder: (child) => Background(
child: SafeArea(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () async {
Navigator.of(context).pop();
},
),
title: Text(
"Wallet representative",
style: STextStyles.navBarTitle(context),
),
actions: [
Padding(
padding: const EdgeInsets.all(10),
child: AspectRatio(
aspectRatio: 1,
child: AppBarIconButton(
color: Theme.of(context)
.extension<StackColors>()!
.background,
shadows: const [],
icon: SvgPicture.asset(
Assets.svg.copy,
width: 24,
height: 24,
color: Theme.of(context)
.extension<StackColors>()!
.topNavIconPrimary,
),
onPressed: () {
if (representative != null) {
_copy();
}
},
),
),
),
],
),
body: Padding(
padding: const EdgeInsets.only(
top: 12,
left: 16,
right: 16,
),
child: child,
),
),
),
),
child: ConditionalParent(
condition: isDesktop,
builder: (child) => DesktopDialog(
maxWidth: 600,
maxHeight: double.infinity,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(
left: 32,
),
child: Text(
"Change representative",
style: STextStyles.desktopH2(context),
),
),
DesktopDialogCloseButton(
onPressedOverride: Navigator.of(
context,
rootNavigator: true,
).pop,
),
],
),
AnimatedSize(
duration: const Duration(
milliseconds: 150,
),
child: Padding(
padding: const EdgeInsets.fromLTRB(32, 0, 32, 32),
child: child,
),
),
],
),
),
child: Column(
children: [
if (isDesktop) const SizedBox(height: 24),
ConditionalParent(
condition: !isDesktop,
builder: (child) => Expanded(
child: child,
),
child: FutureBuilder(
future: loadRepresentative(),
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
representative = snapshot.data!;
}
const height = 600.0;
Widget child;
if (representative == null) {
child = const SizedBox(
key: Key("loadingRepresentative"),
height: height,
child: Center(
child: LoadingIndicator(
width: 100,
),
),
);
} else {
child = Column(
children: [
ConditionalParent(
condition: !isDesktop,
builder: (child) => RoundedWhiteContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
child,
],
),
),
child: ConditionalParent(
condition: isDesktop,
builder: (child) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Current representative",
style: STextStyles.desktopTextExtraExtraSmall(
context),
),
const SizedBox(
height: 4,
),
Row(
children: [
child,
],
),
],
),
child: SelectableText(
representative!,
style: isDesktop
? STextStyles.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
)
: STextStyles.itemSubtitle12(context),
),
),
),
const SizedBox(
height: 24,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
controller: _textController,
style: isDesktop
? STextStyles.desktopTextExtraSmall(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveText,
height: 1.8,
)
: STextStyles.field(context),
focusNode: _textFocusNode,
decoration: standardInputDecoration(
"Enter new representative",
_textFocusNode,
context,
desktopMed: isDesktop,
).copyWith(
contentPadding: isDesktop
? const EdgeInsets.only(
left: 16,
top: 11,
bottom: 12,
right: 5,
)
: null,
suffixIcon: _textController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_textController.text = "";
});
},
),
],
),
),
)
: null,
),
),
),
if (isDesktop) const SizedBox(height: 60),
if (!isDesktop) const Spacer(),
PrimaryButton(
label: "Save",
onPressed: _save,
),
if (!isDesktop)
const SizedBox(
height: 16,
),
],
);
}
return AnimatedSwitcher(
duration: const Duration(
milliseconds: 200,
),
child: child,
);
},
),
),
],
),
),
);
}
}

View file

@ -100,7 +100,7 @@ class _MyTokenSelectItemState extends ConsumerState<MyTokenSelectItem> {
message: "Loading ${widget.token.name}",
);
if (!success) {
if (!success!) {
return;
}

View file

@ -52,6 +52,7 @@ class WalletSummary extends StatelessWidget {
walletId: walletId,
width: constraints.maxWidth,
height: constraints.maxHeight,
isFavorite: false,
),
Positioned.fill(
child: Padding(

View file

@ -358,7 +358,6 @@ class _TransactionDetailsViewState
final currentHeight = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletId).currentHeight));
print("THIS TRANSACTION IS $_transaction");
return ConditionalParent(
@ -474,7 +473,9 @@ class _TransactionDetailsViewState
),
SelectableText(
_transaction.isCancelled
? "Cancelled"
? coin == Coin.ethereum
? "Failed"
: "Cancelled"
: whatIsIt(
_transaction,
currentHeight,
@ -585,7 +586,9 @@ class _TransactionDetailsViewState
// child:
SelectableText(
_transaction.isCancelled
? "Cancelled"
? coin == Coin.ethereum
? "Failed"
: "Cancelled"
: whatIsIt(
_transaction,
currentHeight,
@ -781,8 +784,8 @@ class _TransactionDetailsViewState
isDesktop
? const _Divider()
: const SizedBox(
height: 12,
),
height: 12,
),
if (coin == Coin.epicCash)
RoundedWhiteContainer(
padding: isDesktop
@ -790,22 +793,22 @@ class _TransactionDetailsViewState
: const EdgeInsets.all(12),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
CrossAxisAlignment.start,
children: [
Text(
"On chain note",
style: isDesktop
? STextStyles
.desktopTextExtraExtraSmall(
context)
.desktopTextExtraExtraSmall(
context)
: STextStyles.itemSubtitle(
context),
context),
),
const SizedBox(
height: 8,
@ -814,18 +817,16 @@ class _TransactionDetailsViewState
_transaction.otherData ?? "",
style: isDesktop
? STextStyles
.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(
context)
.extension<
StackColors>()!
.textDark,
)
: STextStyles
.itemSubtitle12(
context),
.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<
StackColors>()!
.textDark,
)
: STextStyles.itemSubtitle12(
context),
),
],
),
@ -854,7 +855,9 @@ class _TransactionDetailsViewState
MainAxisAlignment.spaceBetween,
children: [
Text(
(coin == Coin.epicCash) ? "Local Note" : "Note ",
(coin == Coin.epicCash)
? "Local Note"
: "Note ",
style: isDesktop
? STextStyles
.desktopTextExtraExtraSmall(
@ -923,7 +926,9 @@ class _TransactionDetailsViewState
notesServiceChangeNotifierProvider(
walletId)
.select((value) => value.getNoteFor(
txid: (coin == Coin.epicCash)? _transaction.slateId! : _transaction.txid ))),
txid: (coin == Coin.epicCash)
? _transaction.slateId!
: _transaction.txid))),
builder: (builderContext,
AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState ==

View file

@ -149,6 +149,7 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
walletId: widget.walletId,
width: widget.width,
height: widget.height,
isFavorite: true,
),
child: Padding(
padding: const EdgeInsets.all(12.0),

View file

@ -13,7 +13,8 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/xpub_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/change_representative_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart';
import 'package:stackwallet/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart';
import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart';
import 'package:stackwallet/providers/providers.dart';
@ -21,11 +22,13 @@ import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
enum _WalletOptions {
addressList,
deleteWallet,
changeRepresentative,
showXpub;
String get prettyName {
@ -34,6 +37,8 @@ enum _WalletOptions {
return "Address list";
case _WalletOptions.deleteWallet:
return "Delete wallet";
case _WalletOptions.changeRepresentative:
return "Change representative";
case _WalletOptions.showXpub:
return "Show xPub";
}
@ -70,6 +75,9 @@ class WalletOptionsButton extends StatelessWidget {
onAddressListPressed: () async {
Navigator.of(context).pop(_WalletOptions.addressList);
},
onChangeRepPressed: () async {
Navigator.of(context).pop(_WalletOptions.changeRepresentative);
},
onShowXpubPressed: () async {
Navigator.of(context).pop(_WalletOptions.showXpub);
},
@ -134,6 +142,32 @@ class WalletOptionsButton extends StatelessWidget {
),
);
if (result == true) {
if (context.mounted) {
Navigator.of(context).pop();
}
}
break;
case _WalletOptions.changeRepresentative:
final result = await showDialog<bool?>(
context: context,
barrierDismissible: false,
builder: (context) => Navigator(
initialRoute: ChangeRepresentativeView.routeName,
onGenerateRoute: RouteGenerator.generateRoute,
onGenerateInitialRoutes: (_, __) {
return [
RouteGenerator.generateRoute(
RouteSettings(
name: ChangeRepresentativeView.routeName,
arguments: walletId,
),
),
];
},
),
);
if (result == true) {
if (context.mounted) {
Navigator.of(context).pop();
@ -171,18 +205,24 @@ class WalletOptionsPopupMenu extends ConsumerWidget {
required this.onDeletePressed,
required this.onAddressListPressed,
required this.onShowXpubPressed,
required this.onChangeRepPressed,
required this.walletId,
}) : super(key: key);
final VoidCallback onDeletePressed;
final VoidCallback onAddressListPressed;
final VoidCallback onShowXpubPressed;
final VoidCallback onChangeRepPressed;
final String walletId;
@override
Widget build(BuildContext context, WidgetRef ref) {
final bool xpubEnabled = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletId).hasXPub));
final manager = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletId)));
final bool xpubEnabled = manager.hasXPub;
final bool canChangeRep =
manager.coin == Coin.nano || manager.coin == Coin.banano;
return Stack(
children: [
@ -237,6 +277,43 @@ class WalletOptionsPopupMenu extends ConsumerWidget {
),
),
),
if (canChangeRep)
const SizedBox(
height: 8,
),
if (canChangeRep)
TransparentButton(
onPressed: onChangeRepPressed,
child: Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SvgPicture.asset(
Assets.svg.eye,
width: 20,
height: 20,
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconLeft,
),
const SizedBox(width: 14),
Expanded(
child: Text(
_WalletOptions.changeRepresentative.prettyName,
style: STextStyles.desktopTextExtraExtraSmall(
context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textDark,
),
),
),
],
),
),
),
if (xpubEnabled)
const SizedBox(
height: 8,

View file

@ -125,7 +125,7 @@ class _CreatePasswordViewState extends ConsumerState<CreatePasswordView> {
}
}
if (!widget.restoreFromSWB) {
if (!widget.restoreFromSWB && mounted) {
unawaited(showFloatingFlushBar(
type: FlushBarType.success,
message: "Your password is set up",

View file

@ -84,10 +84,10 @@ class _DesktopLoginViewState extends ConsumerState<DesktopLoginView> {
unawaited(
showDialog(
context: context,
builder: (context) => Column(
builder: (context) => const Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
children: [
LoadingIndicator(
width: 200,
height: 200,

View file

@ -109,14 +109,15 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/support_vi
import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_preferences_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/wallet_syncing_options_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/xpub_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/change_representative_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_warning_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart';
import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart';
import 'package:stackwallet/pages/stack_privacy_calls.dart';
import 'package:stackwallet/pages/token_view/my_tokens_view.dart';
import 'package:stackwallet/pages/token_view/token_contract_details_view.dart';
@ -618,6 +619,20 @@ class RouteGenerator {
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case ChangeRepresentativeView.routeName:
if (args is String) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => ChangeRepresentativeView(
walletId: args,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case AppearanceSettingsView.routeName:
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,

View file

@ -14,9 +14,9 @@ import 'package:stackwallet/services/event_bus/events/global/node_connection_sta
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/mixins/coin_control_interface.dart';
import 'package:stackwallet/services/mixins/wallet_cache.dart';
import 'package:stackwallet/services/mixins/wallet_db.dart';
import 'package:stackwallet/services/nano_api.dart';
import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
@ -32,8 +32,7 @@ const int MINIMUM_CONFIRMATIONS = 1;
const String DEFAULT_REPRESENTATIVE =
"ban_1ka1ium4pfue3uxtntqsrib8mumxgazsjf58gidh1xeo5te3whsq8z476goo";
class BananoWallet extends CoinServiceAPI
with WalletCache, WalletDB, CoinControlInterface {
class BananoWallet extends CoinServiceAPI with WalletCache, WalletDB {
BananoWallet({
required String walletId,
required String walletName,
@ -925,4 +924,51 @@ class BananoWallet extends CoinServiceAPI
);
await updateCachedChainHeight(height ?? 0);
}
Future<String> getCurrentRepresentative() async {
final serverURI = Uri.parse(getCurrentNode().host);
final address = await currentReceivingAddress;
final response = await NanoAPI.getAccountInfo(
server: serverURI,
representative: true,
account: address,
);
return response.accountInfo?.representative ?? DEFAULT_REPRESENTATIVE;
}
Future<bool> changeRepresentative(String newRepresentative) async {
try {
final serverURI = Uri.parse(getCurrentNode().host);
final balance = this.balance.spendable.raw.toString();
final String privateKey = await getPrivateKeyFromMnemonic();
final address = await currentReceivingAddress;
final response = await NanoAPI.getAccountInfo(
server: serverURI,
representative: true,
account: address,
);
if (response.accountInfo == null) {
throw response.exception ?? Exception("Failed to get account info");
}
final work = await requestWork(response.accountInfo!.frontier);
return await NanoAPI.changeRepresentative(
server: serverURI,
accountType: NanoAccountType.BANANO,
account: address,
newRepresentative: newRepresentative,
previousBlock: response.accountInfo!.frontier,
balance: balance,
privateKey: privateKey,
work: work!,
);
} catch (_) {
rethrow;
}
}
}

View file

@ -1023,6 +1023,7 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
final response = await EthereumAPI.getEthTransactions(
address: thisAddress,
firstBlock: isRescan ? 0 : firstBlock,
includeTokens: true,
);
if (response.value == null) {
@ -1057,8 +1058,10 @@ class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
txFailed = true;
}
isIncoming = false;
} else {
} else if (checksumEthereumAddress(element.to) == thisAddress) {
isIncoming = true;
} else {
continue;
}
//Calculate fees (GasLimit * gasPrice)

View file

@ -24,9 +24,9 @@ import 'package:stackwallet/services/event_bus/events/global/node_connection_sta
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/mixins/coin_control_interface.dart';
import 'package:stackwallet/services/mixins/wallet_cache.dart';
import 'package:stackwallet/services/mixins/wallet_db.dart';
import 'package:stackwallet/services/nano_api.dart';
import 'package:stackwallet/services/node_service.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
@ -42,8 +42,7 @@ const int MINIMUM_CONFIRMATIONS = 1;
const String DEFAULT_REPRESENTATIVE =
"nano_38713x95zyjsqzx6nm1dsom1jmm668owkeb9913ax6nfgj15az3nu8xkx579";
class NanoWallet extends CoinServiceAPI
with WalletCache, WalletDB, CoinControlInterface {
class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB {
NanoWallet({
required String walletId,
required String walletName,
@ -937,4 +936,51 @@ class NanoWallet extends CoinServiceAPI
);
await updateCachedChainHeight(height ?? 0);
}
Future<String> getCurrentRepresentative() async {
final serverURI = Uri.parse(getCurrentNode().host);
final address = await currentReceivingAddress;
final response = await NanoAPI.getAccountInfo(
server: serverURI,
representative: true,
account: address,
);
return response.accountInfo?.representative ?? DEFAULT_REPRESENTATIVE;
}
Future<bool> changeRepresentative(String newRepresentative) async {
try {
final serverURI = Uri.parse(getCurrentNode().host);
final balance = this.balance.spendable.raw.toString();
final String privateKey = await getPrivateKeyFromMnemonic();
final address = await currentReceivingAddress;
final response = await NanoAPI.getAccountInfo(
server: serverURI,
representative: true,
account: address,
);
if (response.accountInfo == null) {
throw response.exception ?? Exception("Failed to get account info");
}
final work = await requestWork(response.accountInfo!.frontier);
return await NanoAPI.changeRepresentative(
server: serverURI,
accountType: NanoAccountType.NANO,
account: address,
newRepresentative: newRepresentative,
previousBlock: response.accountInfo!.frontier,
balance: balance,
privateKey: privateKey,
work: work!,
);
} catch (_) {
rethrow;
}
}
}

View file

@ -10,7 +10,6 @@
import 'dart:convert';
import 'package:decimal/decimal.dart';
import 'package:http/http.dart';
import 'package:stackwallet/dto/ethereum/eth_token_tx_dto.dart';
import 'package:stackwallet/dto/ethereum/eth_token_tx_extra_dto.dart';
@ -50,6 +49,7 @@ abstract class EthereumAPI {
static Future<EthereumResponse<List<EthTxDTO>>> getEthTransactions({
required String address,
int firstBlock = 0,
bool includeTokens = false,
}) async {
try {
final response = await get(
@ -67,7 +67,7 @@ abstract class EthereumAPI {
for (final map in list!) {
final txn = EthTxDTO.fromMap(Map<String, dynamic>.from(map as Map));
if (txn.hasToken == 0) {
if (txn.hasToken == 0 || includeTokens) {
txns.add(txn);
}
}
@ -76,9 +76,11 @@ abstract class EthereumAPI {
null,
);
} else {
throw EthApiException(
"getEthTransactions($address) response is empty but status code is "
"${response.statusCode}",
// nice that the api returns an empty body instead of being
// consistent and returning a json object with no transactions
return EthereumResponse(
[],
null,
);
}
} else {
@ -196,9 +198,11 @@ abstract class EthereumAPI {
null,
);
} else {
throw EthApiException(
"getEthTransactionNonces($txns) response is empty but status code is "
"${response.statusCode}",
// nice that the api returns an empty body instead of being
// consistent and returning a json object with no transactions
return EthereumResponse(
[],
null,
);
}
} else {
@ -252,13 +256,13 @@ abstract class EthereumAPI {
);
} else {
throw EthApiException(
"getEthTransaction($txids) response is empty but status code is "
"getEthTokenTransactionsByTxids($txids) response is empty but status code is "
"${response.statusCode}",
);
}
} else {
throw EthApiException(
"getEthTransaction($txids) failed with status code: "
"getEthTokenTransactionsByTxids($txids) failed with status code: "
"${response.statusCode}",
);
}
@ -269,7 +273,7 @@ abstract class EthereumAPI {
);
} catch (e, s) {
Logging.instance.log(
"getEthTransaction($txids): $e\n$s",
"getEthTokenTransactionsByTxids($txids): $e\n$s",
level: LogLevel.Error,
);
return EthereumResponse(
@ -307,9 +311,11 @@ abstract class EthereumAPI {
null,
);
} else {
throw EthApiException(
"getTokenTransactions($address, $tokenContractAddress) response is empty but status code is "
"${response.statusCode}",
// nice that the api returns an empty body instead of being
// consistent and returning a json object with no transactions
return EthereumResponse(
[],
null,
);
}
} else {
@ -424,10 +430,10 @@ abstract class EthereumAPI {
final map = json["data"].first as Map;
final balance =
Decimal.tryParse(map["balance"].toString()) ?? Decimal.zero;
BigInt.tryParse(map["units"].toString()) ?? BigInt.zero;
return EthereumResponse(
Amount.fromDecimal(balance, fractionDigits: map["decimals"] as int),
Amount(rawValue: balance, fractionDigits: map["decimals"] as int),
null,
);
} else {

130
lib/services/nano_api.dart Normal file
View file

@ -0,0 +1,130 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:nanodart/nanodart.dart';
class NanoAPI {
static Future<
({
NAccountInfo? accountInfo,
Exception? exception,
})> getAccountInfo({
required Uri server,
required bool representative,
required String account,
}) async {
NAccountInfo? accountInfo;
Exception? exception;
try {
final response = await http.post(
server,
headers: {
"Content-Type": "application/json",
},
body: jsonEncode({
"action": "account_info",
"representative": "true",
"account": account,
}),
);
final map = jsonDecode(response.body);
if (map is Map && map["error"] != null) {
throw Exception(map["error"].toString());
}
accountInfo = NAccountInfo(
frontier: map["frontier"] as String,
representative: map["representative"] as String,
);
} on Exception catch (e) {
exception = e;
} catch (e) {
exception = Exception(e.toString());
}
return (accountInfo: accountInfo, exception: exception);
}
static Future<bool> changeRepresentative({
required Uri server,
required int accountType,
required String account,
required String newRepresentative,
required String previousBlock,
required String balance,
required String privateKey,
required String work,
}) async {
Map<String, String> block = {
"type": "state",
"account": account,
"previous": previousBlock,
"representative": newRepresentative,
"balance": balance,
"link":
"0000000000000000000000000000000000000000000000000000000000000000",
"work": work,
};
final String hash;
try {
hash = NanoBlocks.computeStateHash(
accountType,
account,
previousBlock,
newRepresentative,
BigInt.parse(balance),
block["link"] as String,
);
} catch (e) {
if (e is RangeError) {
throw Exception("Invalid representative format");
}
rethrow;
}
final signature = NanoSignatures.signBlock(hash, privateKey);
block["signature"] = signature;
final map = await postBlock(server: server, block: block);
if (map is Map && map["error"] != null) {
throw Exception(map["error"].toString());
}
return map["error"] == null;
}
// TODO: GET RID OF DYNAMIC AND USED TYPED DATA
static Future<dynamic> postBlock({
required Uri server,
required Map<String, dynamic> block,
}) async {
final response = await http.post(
server,
headers: {
"Content-Type": "application/json",
},
body: jsonEncode({
"action": "process",
"json_block": "true",
"subtype": "change",
"block": block,
}),
);
return jsonDecode(response.body);
}
}
class NAccountInfo {
final String frontier;
final String representative;
NAccountInfo({required this.frontier, required this.representative});
}

View file

@ -22,3 +22,14 @@ final coinCardProvider = Provider.family<String?, Coin>((ref, coin) {
return null;
}
});
final coinCardFavoritesProvider = Provider.family<String?, Coin>((ref, coin) {
final assets = ref.watch(themeAssetsProvider);
if (assets is ThemeAssetsV3) {
return assets.coinCardFavoritesImages?[coin.mainNetVersion] ??
assets.coinCardImages?[coin.mainNetVersion];
} else {
return null;
}
});

View file

@ -70,6 +70,11 @@ class AmountFormatter {
String string, {
EthContract? ethContract,
}) {
return unit.tryParse(string, locale: locale, coin: coin);
return unit.tryParse(
string,
locale: locale,
coin: coin,
tokenContract: ethContract,
);
}
}

View file

@ -12,15 +12,17 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
Future<T> showLoading<T>({
Future<T?> showLoading<T>({
required Future<T> whileFuture,
required BuildContext context,
required String message,
String? subMessage,
bool isDesktop = false,
bool opaqueBG = false,
void Function(Exception)? onException,
}) async {
unawaited(
showDialog<void>(
@ -43,10 +45,24 @@ Future<T> showLoading<T>({
),
);
final result = await whileFuture;
Exception? ex;
T? result;
try {
result = await whileFuture;
} catch (e, s) {
Logging.instance.log(
"showLoading caught: $e\n$s",
level: LogLevel.Warning,
);
ex = e is Exception ? e : Exception(e.toString());
}
if (context.mounted) {
Navigator.of(context, rootNavigator: isDesktop).pop();
if (ex != null) {
onException?.call(ex);
}
}
return result;

View file

@ -25,11 +25,13 @@ class CoinCard extends ConsumerWidget {
required this.walletId,
required this.width,
required this.height,
required this.isFavorite,
});
final String walletId;
final double width;
final double height;
final bool isFavorite;
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -38,7 +40,7 @@ class CoinCard extends ConsumerWidget {
.select((value) => value.getManager(walletId).coin),
);
final bool hasCardImageBg = ref.watch(coinCardProvider(coin)) != null;
final bool hasCardImageBg = (isFavorite) ? ref.watch(coinCardFavoritesProvider(coin)) != null : ref.watch(coinCardProvider(coin)) != null;
return Stack(
children: [
@ -54,7 +56,9 @@ class CoinCard extends ConsumerWidget {
fit: BoxFit.cover,
image: FileImage(
File(
ref.watch(coinCardProvider(coin))!,
(isFavorite)
? ref.watch(coinCardFavoritesProvider(coin))!
: ref.watch(coinCardProvider(coin))!,
),
),
),

View file

@ -230,7 +230,9 @@ class _TransactionCardState extends ConsumerState<TransactionCard> {
fit: BoxFit.scaleDown,
child: Text(
_transaction.isCancelled
? "Cancelled"
? coin == Coin.ethereum
? "Failed"
: "Cancelled"
: whatIsIt(
_transaction.type,
coin,

View file

@ -140,7 +140,7 @@ class SimpleWalletCard extends ConsumerWidget {
isDesktop: Util.isDesktop,
);
if (!success) {
if (!success!) {
// TODO: show error dialog here?
Logging.instance.log(
"Failed to load token wallet for $contract",

View file

@ -564,6 +564,7 @@
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
STRIP_INSTALLED_PRODUCT = NO;
SWIFT_COMPILATION_MODE = wholemodule;
@ -716,6 +717,7 @@
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
STRIP_INSTALLED_PRODUCT = NO;
SWIFT_COMPILATION_MODE = wholemodule;

View file

@ -1,68 +1,68 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_16.png",
"scale" : "1x"
"info": {
"version": 1,
"author": "xcode"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "app_icon_64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "app_icon_1024.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
"images": [
{
"size": "16x16",
"idiom": "mac",
"filename": "app_icon_16.png",
"scale": "1x"
},
{
"size": "16x16",
"idiom": "mac",
"filename": "app_icon_32.png",
"scale": "2x"
},
{
"size": "32x32",
"idiom": "mac",
"filename": "app_icon_32.png",
"scale": "1x"
},
{
"size": "32x32",
"idiom": "mac",
"filename": "app_icon_64.png",
"scale": "2x"
},
{
"size": "128x128",
"idiom": "mac",
"filename": "app_icon_128.png",
"scale": "1x"
},
{
"size": "128x128",
"idiom": "mac",
"filename": "app_icon_256.png",
"scale": "2x"
},
{
"size": "256x256",
"idiom": "mac",
"filename": "app_icon_256.png",
"scale": "1x"
},
{
"size": "256x256",
"idiom": "mac",
"filename": "app_icon_512.png",
"scale": "2x"
},
{
"size": "512x512",
"idiom": "mac",
"filename": "app_icon_512.png",
"scale": "1x"
},
{
"size": "512x512",
"idiom": "mac",
"filename": "app_icon_1024.png",
"scale": "2x"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 B

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -226,10 +226,10 @@ packages:
dependency: transitive
description:
name: cli_util
sha256: "66f86e916d285c1a93d3b79587d94bd71984a66aac4ff74e524cfa7877f1395c"
sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7
url: "https://pub.dev"
source: hosted
version: "0.3.5"
version: "0.4.0"
clock:
dependency: transitive
description:
@ -584,10 +584,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: ce0e501cfc258907842238e4ca605e74b7fd1cdf04b3b43e86c43f3e40a1592c
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
url: "https://pub.dev"
source: hosted
version: "0.11.0"
version: "0.13.1"
flutter_libepiccash:
dependency: "direct main"
description:
@ -646,10 +646,10 @@ packages:
dependency: "direct main"
description:
name: flutter_native_splash
sha256: "6777a3abb974021a39b5fdd2d46a03ca390e03903b6351f21d10e7ecc969f12d"
sha256: ba45d8cfbd778478a74696b012f33ffb6b1760c9bc531b21e2964444a4870dae
url: "https://pub.dev"
source: hosted
version: "2.2.16"
version: "2.3.1"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@ -853,10 +853,10 @@ packages:
dependency: transitive
description:
name: image
sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6"
sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf
url: "https://pub.dev"
source: hosted
version: "3.3.0"
version: "4.0.17"
import_sorter:
dependency: "direct dev"
description:
@ -1575,10 +1575,10 @@ packages:
dependency: transitive
description:
name: universal_io
sha256: "06866290206d196064fd61df4c7aea1ffe9a4e7c4ccaa8fcded42dd41948005d"
sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.2.2"
url_launcher:
dependency: "direct main"
description:

View file

@ -11,7 +11,7 @@ description: Stack Wallet
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.7.15+181
version: 1.7.16+182
environment:
sdk: ">=3.0.2 <4.0.0"
@ -145,7 +145,7 @@ dev_dependencies:
integration_test:
sdk: flutter
build_runner: ^2.1.7
flutter_launcher_icons: ^0.11.0
flutter_launcher_icons: ^0.13.1
hive_generator: ^2.0.0
dependency_validator: ^3.1.2
hive_test: ^1.0.1
@ -157,7 +157,7 @@ dev_dependencies:
flutter_lints: ^2.0.1
isar_generator: 3.0.5
flutter_icons:
flutter_launcher_icons:
android: true
ios: true
image_path: assets/icon/icon.png
@ -170,7 +170,7 @@ flutter_icons:
icon_size: 48 # min:48, max:256, default: 48
macos:
generate: true
image_path: assets/icon/icon.png
image_path: assets/icon/macos-icon.png
flutter_native_splash:
image: assets/images/splash.png