mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-17 09:47:37 +00:00
add ba/nano wallet representative change option to wallet settings, remove coin control from ba/nano, and some other clean up
This commit is contained in:
parent
b8fcc98ac6
commit
e7b480e585
14 changed files with 669 additions and 27 deletions
|
@ -142,7 +142,7 @@ class _AddCustomTokenViewState extends ConsumerState<AddCustomTokenView> {
|
||||||
context: context,
|
context: context,
|
||||||
message: "Looking up contract",
|
message: "Looking up contract",
|
||||||
);
|
);
|
||||||
currentToken = response.value;
|
currentToken = response!.value;
|
||||||
if (currentToken != null) {
|
if (currentToken != null) {
|
||||||
nameController.text = currentToken!.name;
|
nameController.text = currentToken!.name;
|
||||||
symbolController.text = currentToken!.symbol;
|
symbolController.text = currentToken!.symbol;
|
||||||
|
@ -157,7 +157,7 @@ class _AddCustomTokenViewState extends ConsumerState<AddCustomTokenView> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => StackOkDialog(
|
builder: (context) => StackOkDialog(
|
||||||
title: "Failed to look up token",
|
title: "Failed to look up token",
|
||||||
message: response.exception?.message,
|
message: response!.exception?.message,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -164,7 +164,7 @@ class _InstallThemeFromFileDialogState
|
||||||
);
|
);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
if (!result) {
|
if (!result!) {
|
||||||
unawaited(
|
unawaited(
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
|
@ -72,11 +72,11 @@ class _StackThemeCardState extends ConsumerState<StackThemeCard> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _downloadPressed() async {
|
Future<void> _downloadPressed() async {
|
||||||
final result = await showLoading(
|
final result = (await showLoading(
|
||||||
whileFuture: _downloadAndInstall(),
|
whileFuture: _downloadAndInstall(),
|
||||||
context: context,
|
context: context,
|
||||||
message: "Downloading and installing theme...",
|
message: "Downloading and installing theme...",
|
||||||
);
|
))!;
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
final message = result
|
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/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/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/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/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_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_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/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/global/wallets_provider.dart';
|
||||||
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
|
import 'package:stackwallet/providers/ui/transaction_filter_provider.dart';
|
||||||
import 'package:stackwallet/route_generator.dart';
|
import 'package:stackwallet/route_generator.dart';
|
||||||
|
@ -231,7 +232,7 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
|
||||||
.mnemonic;
|
.mnemonic;
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
RouteGenerator.getRoute(
|
RouteGenerator.getRoute(
|
||||||
shouldUseMaterialRoute:
|
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(
|
const SizedBox(
|
||||||
height: 8,
|
height: 8,
|
||||||
),
|
),
|
||||||
|
@ -434,18 +454,20 @@ class _EpiBoxInfoFormState extends ConsumerState<EpicBoxInfoForm> {
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
try {
|
try {
|
||||||
wallet.updateEpicboxConfig(
|
await wallet.updateEpicboxConfig(
|
||||||
hostController.text,
|
hostController.text,
|
||||||
int.parse(portController.text),
|
int.parse(portController.text),
|
||||||
);
|
);
|
||||||
showFloatingFlushBar(
|
if (mounted) {
|
||||||
context: context,
|
await showFloatingFlushBar(
|
||||||
message: "Epicbox info saved!",
|
context: context,
|
||||||
type: FlushBarType.success,
|
message: "Epicbox info saved!",
|
||||||
);
|
type: FlushBarType.success,
|
||||||
wallet.refresh();
|
);
|
||||||
|
}
|
||||||
|
unawaited(wallet.refresh());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showFloatingFlushBar(
|
await showFloatingFlushBar(
|
||||||
context: context,
|
context: context,
|
||||||
message: "Failed to save epicbox info: $e",
|
message: "Failed to save epicbox info: $e",
|
||||||
type: FlushBarType.warning,
|
type: FlushBarType.warning,
|
||||||
|
|
|
@ -0,0 +1,367 @@
|
||||||
|
/*
|
||||||
|
* 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...",
|
||||||
|
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(
|
||||||
|
"${ref.watch(walletsChangeNotifierProvider.select((value) => value.getManager(widget.walletId).walletName))} xPub",
|
||||||
|
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: 44),
|
||||||
|
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: [
|
||||||
|
RoundedWhiteContainer(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
representative!,
|
||||||
|
style: STextStyles.itemSubtitle(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 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}",
|
message: "Loading ${widget.token.name}",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!success) {
|
if (!success!) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:stackwallet/pages/settings_views/global_settings_view/xpub_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/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/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart';
|
||||||
import 'package:stackwallet/providers/providers.dart';
|
import 'package:stackwallet/providers/providers.dart';
|
||||||
|
|
|
@ -103,14 +103,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_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/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/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_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_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_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_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/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/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/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/stack_privacy_calls.dart';
|
||||||
import 'package:stackwallet/pages/token_view/my_tokens_view.dart';
|
import 'package:stackwallet/pages/token_view/my_tokens_view.dart';
|
||||||
import 'package:stackwallet/pages/token_view/token_contract_details_view.dart';
|
import 'package:stackwallet/pages/token_view/token_contract_details_view.dart';
|
||||||
|
@ -564,6 +565,20 @@ class RouteGenerator {
|
||||||
}
|
}
|
||||||
return _routeError("${settings.name} invalid args: ${args.toString()}");
|
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:
|
case AppearanceSettingsView.routeName:
|
||||||
return getRoute(
|
return getRoute(
|
||||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||||
|
|
|
@ -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/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/events/global/wallet_sync_status_changed_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/global_event_bus.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_cache.dart';
|
||||||
import 'package:stackwallet/services/mixins/wallet_db.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/node_service.dart';
|
||||||
import 'package:stackwallet/services/transaction_notification_tracker.dart';
|
import 'package:stackwallet/services/transaction_notification_tracker.dart';
|
||||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
|
@ -32,8 +32,7 @@ const int MINIMUM_CONFIRMATIONS = 1;
|
||||||
const String DEFAULT_REPRESENTATIVE =
|
const String DEFAULT_REPRESENTATIVE =
|
||||||
"ban_1ka1ium4pfue3uxtntqsrib8mumxgazsjf58gidh1xeo5te3whsq8z476goo";
|
"ban_1ka1ium4pfue3uxtntqsrib8mumxgazsjf58gidh1xeo5te3whsq8z476goo";
|
||||||
|
|
||||||
class BananoWallet extends CoinServiceAPI
|
class BananoWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
||||||
with WalletCache, WalletDB, CoinControlInterface {
|
|
||||||
BananoWallet({
|
BananoWallet({
|
||||||
required String walletId,
|
required String walletId,
|
||||||
required String walletName,
|
required String walletName,
|
||||||
|
@ -925,4 +924,51 @@ class BananoWallet extends CoinServiceAPI
|
||||||
);
|
);
|
||||||
await updateCachedChainHeight(height ?? 0);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/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/events/global/wallet_sync_status_changed_event.dart';
|
||||||
import 'package:stackwallet/services/event_bus/global_event_bus.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_cache.dart';
|
||||||
import 'package:stackwallet/services/mixins/wallet_db.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/node_service.dart';
|
||||||
import 'package:stackwallet/services/transaction_notification_tracker.dart';
|
import 'package:stackwallet/services/transaction_notification_tracker.dart';
|
||||||
import 'package:stackwallet/utilities/amount/amount.dart';
|
import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
|
@ -42,8 +42,7 @@ const int MINIMUM_CONFIRMATIONS = 1;
|
||||||
const String DEFAULT_REPRESENTATIVE =
|
const String DEFAULT_REPRESENTATIVE =
|
||||||
"nano_38713x95zyjsqzx6nm1dsom1jmm668owkeb9913ax6nfgj15az3nu8xkx579";
|
"nano_38713x95zyjsqzx6nm1dsom1jmm668owkeb9913ax6nfgj15az3nu8xkx579";
|
||||||
|
|
||||||
class NanoWallet extends CoinServiceAPI
|
class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB {
|
||||||
with WalletCache, WalletDB, CoinControlInterface {
|
|
||||||
NanoWallet({
|
NanoWallet({
|
||||||
required String walletId,
|
required String walletId,
|
||||||
required String walletName,
|
required String walletName,
|
||||||
|
@ -937,4 +936,51 @@ class NanoWallet extends CoinServiceAPI
|
||||||
);
|
);
|
||||||
await updateCachedChainHeight(height ?? 0);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
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:flutter/material.dart';
|
||||||
import 'package:stackwallet/themes/stack_colors.dart';
|
import 'package:stackwallet/themes/stack_colors.dart';
|
||||||
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
|
import 'package:stackwallet/widgets/custom_loading_overlay.dart';
|
||||||
|
|
||||||
Future<T> showLoading<T>({
|
Future<T?> showLoading<T>({
|
||||||
required Future<T> whileFuture,
|
required Future<T> whileFuture,
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required String message,
|
required String message,
|
||||||
String? subMessage,
|
String? subMessage,
|
||||||
bool isDesktop = false,
|
bool isDesktop = false,
|
||||||
bool opaqueBG = false,
|
bool opaqueBG = false,
|
||||||
|
void Function(Exception)? onException,
|
||||||
}) async {
|
}) async {
|
||||||
unawaited(
|
unawaited(
|
||||||
showDialog<void>(
|
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) {
|
if (context.mounted) {
|
||||||
Navigator.of(context, rootNavigator: isDesktop).pop();
|
Navigator.of(context, rootNavigator: isDesktop).pop();
|
||||||
|
if (ex != null) {
|
||||||
|
onException?.call(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -140,7 +140,7 @@ class SimpleWalletCard extends ConsumerWidget {
|
||||||
isDesktop: Util.isDesktop,
|
isDesktop: Util.isDesktop,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!success) {
|
if (!success!) {
|
||||||
// TODO: show error dialog here?
|
// TODO: show error dialog here?
|
||||||
Logging.instance.log(
|
Logging.instance.log(
|
||||||
"Failed to load token wallet for $contract",
|
"Failed to load token wallet for $contract",
|
||||||
|
|
Loading…
Reference in a new issue