mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-23 03:49:22 +00:00
Merge branch 'staging' into add-monkey
# Conflicts: # lib/services/coins/banano/banano_wallet.dart
This commit is contained in:
commit
ea9f997d0b
18 changed files with 789 additions and 36 deletions
|
@ -1 +1 @@
|
|||
Subproject commit 407425c9fcf7a30c81f1345246c7225bc18b5cd5
|
||||
Subproject commit e48952185556a10f182184fd572bcb04365f5831
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -164,7 +164,7 @@ class _InstallThemeFromFileDialogState
|
|||
);
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
if (!result) {
|
||||
if (!result!) {
|
||||
unawaited(
|
||||
showDialog(
|
||||
context: context,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -100,7 +100,7 @@ class _MyTokenSelectItemState extends ConsumerState<MyTokenSelectItem> {
|
|||
message: "Loading ${widget.token.name}",
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
if (!success!) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -104,14 +104,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';
|
||||
|
@ -595,6 +596,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,
|
||||
|
|
|
@ -15,9 +15,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';
|
||||
|
@ -33,8 +33,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,
|
||||
|
@ -941,4 +940,51 @@ class BananoWallet extends CoinServiceAPI
|
|||
key: "monkeyImageBytesKey",
|
||||
) as List<int>?;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
@ -431,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
130
lib/services/nano_api.dart
Normal 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});
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue