mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-25 05:58:42 +01:00
Cw 467 mark change outputs in unspent outputs list (#1137)
* CW-490 Use native Coin Freeze * CW-467 Code cleanup * CW-467 Fix native Code * CW-467 Extend Unspend * CW-467 Add isChange * CW-467 Minor Fixes * CW-467 Add isChange to Electrum Unspents * CW-467 Localize Change Tag * CW-467 Fix frozen balance showing on other monero wallets * CW-467 Fix frozen balance showing on other monero wallets
This commit is contained in:
@@ -321,10 +321,7 @@ class CWMonero extends Monero {
|
||||
@override
|
||||
List<Unspent> getUnspents(Object wallet) {
|
||||
final moneroWallet = wallet as MoneroWallet;
|
||||
return moneroWallet.unspentCoins
|
||||
.map((MoneroUnspent moneroUnspent) => Unspent(moneroUnspent.address, moneroUnspent.hash,
|
||||
moneroUnspent.value, 0, moneroUnspent.keyImage))
|
||||
.toList();
|
||||
return moneroWallet.unspentCoins;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/unspent_coins/widgets/unspent_coins_list_item.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class UnspentCoinsListPage extends BasePage {
|
||||
UnspentCoinsListPage({required this.unspentCoinsListViewModel});
|
||||
@@ -17,31 +15,10 @@ class UnspentCoinsListPage extends BasePage {
|
||||
@override
|
||||
String get title => S.current.unspent_coins_title;
|
||||
|
||||
//@override
|
||||
//Widget trailing(BuildContext context) {
|
||||
// final questionImage = Image.asset('assets/images/question_mark.png',
|
||||
// color: Theme.of(context).extension<CakeTextTheme>()!.titleColor);
|
||||
|
||||
// return SizedBox(
|
||||
// height: 20.0,
|
||||
// width: 20.0,
|
||||
// child: ButtonTheme(
|
||||
// minWidth: double.minPositive,
|
||||
// child: FlatButton(
|
||||
// highlightColor: Colors.transparent,
|
||||
// splashColor: Colors.transparent,
|
||||
// padding: EdgeInsets.all(0),
|
||||
// onPressed: () => showUnspentCoinsAlert(context),
|
||||
// child: questionImage),
|
||||
// ),
|
||||
// );
|
||||
//}
|
||||
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) =>
|
||||
UnspentCoinsListForm(unspentCoinsListViewModel);
|
||||
Widget body(BuildContext context) => UnspentCoinsListForm(unspentCoinsListViewModel);
|
||||
}
|
||||
|
||||
class UnspentCoinsListForm extends StatefulWidget {
|
||||
@@ -50,8 +27,7 @@ class UnspentCoinsListForm extends StatefulWidget {
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
|
||||
@override
|
||||
UnspentCoinsListFormState createState() =>
|
||||
UnspentCoinsListFormState(unspentCoinsListViewModel);
|
||||
UnspentCoinsListFormState createState() => UnspentCoinsListFormState(unspentCoinsListViewModel);
|
||||
}
|
||||
|
||||
class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
|
||||
@@ -59,16 +35,6 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
|
||||
|
||||
final UnspentCoinsListViewModel unspentCoinsListViewModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback(afterLayout);
|
||||
}
|
||||
|
||||
void afterLayout(dynamic _) {
|
||||
//showUnspentCoinsAlert(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
@@ -76,8 +42,7 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
|
||||
child: Observer(
|
||||
builder: (_) => ListView.separated(
|
||||
itemCount: unspentCoinsListViewModel.items.length,
|
||||
separatorBuilder: (_, __) =>
|
||||
SizedBox(height: 15),
|
||||
separatorBuilder: (_, __) => SizedBox(height: 15),
|
||||
itemBuilder: (_, int index) {
|
||||
return Observer(builder: (_) {
|
||||
final item = unspentCoinsListViewModel.items[index];
|
||||
@@ -86,38 +51,22 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
|
||||
: item.address;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () =>
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.unspentCoinsDetails,
|
||||
arguments: [item, unspentCoinsListViewModel]),
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.unspentCoinsDetails,
|
||||
arguments: [item, unspentCoinsListViewModel]),
|
||||
child: UnspentCoinsListItem(
|
||||
note: item.note,
|
||||
amount: item.amount,
|
||||
address: address,
|
||||
isSending: item.isSending,
|
||||
isFrozen: item.isFrozen,
|
||||
isChange: item.isChange,
|
||||
onCheckBoxTap: item.isFrozen
|
||||
? null
|
||||
: () async {
|
||||
item.isSending = !item.isSending;
|
||||
await unspentCoinsListViewModel
|
||||
.saveUnspentCoinInfo(item);}));
|
||||
? null
|
||||
: () async {
|
||||
item.isSending = !item.isSending;
|
||||
await unspentCoinsListViewModel.saveUnspentCoinInfo(item);
|
||||
}));
|
||||
});
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
void showUnspentCoinsAlert(BuildContext context) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: '',
|
||||
alertContent: 'Information about unspent coins',
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class UnspentCoinsListItem extends StatelessWidget {
|
||||
UnspentCoinsListItem({
|
||||
@@ -11,6 +11,7 @@ class UnspentCoinsListItem extends StatelessWidget {
|
||||
required this.address,
|
||||
required this.isSending,
|
||||
required this.isFrozen,
|
||||
required this.isChange,
|
||||
this.onCheckBoxTap,
|
||||
});
|
||||
|
||||
@@ -19,6 +20,7 @@ class UnspentCoinsListItem extends StatelessWidget {
|
||||
final String address;
|
||||
final bool isSending;
|
||||
final bool isFrozen;
|
||||
final bool isChange;
|
||||
final Function()? onCheckBoxTap;
|
||||
|
||||
@override
|
||||
@@ -27,9 +29,8 @@ class UnspentCoinsListItem extends StatelessWidget {
|
||||
final selectedItemColor = Theme.of(context).primaryColor;
|
||||
final itemColor = isSending ? selectedItemColor : unselectedItemColor;
|
||||
|
||||
final amountColor = isSending
|
||||
? Colors.white
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
|
||||
final amountColor =
|
||||
isSending ? Colors.white : Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
|
||||
final addressColor = isSending
|
||||
? Colors.white.withOpacity(0.5)
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonSecondaryTextColor;
|
||||
@@ -47,7 +48,8 @@ class UnspentCoinsListItem extends StatelessWidget {
|
||||
child: StandardCheckbox(
|
||||
iconColor: amountColor,
|
||||
borderColor: addressColor,
|
||||
value: isSending, onChanged: (value) => onCheckBoxTap?.call())),
|
||||
value: isSending,
|
||||
onChanged: (value) => onCheckBoxTap?.call())),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@@ -57,9 +59,7 @@ class UnspentCoinsListItem extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
if (note.isNotEmpty)
|
||||
AutoSizeText(
|
||||
note,
|
||||
@@ -69,8 +69,8 @@ class UnspentCoinsListItem extends StatelessWidget {
|
||||
),
|
||||
AutoSizeText(
|
||||
amount,
|
||||
style:
|
||||
TextStyle(color: amountColor, fontSize: 15, fontWeight: FontWeight.w600),
|
||||
style: TextStyle(
|
||||
color: amountColor, fontSize: 15, fontWeight: FontWeight.w600),
|
||||
maxLines: 1,
|
||||
)
|
||||
]),
|
||||
@@ -84,23 +84,41 @@ class UnspentCoinsListItem extends StatelessWidget {
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
S.of(context).frozen,
|
||||
style:
|
||||
TextStyle(color: amountColor, fontSize: 7, fontWeight: FontWeight.w600),
|
||||
))
|
||||
style: TextStyle(
|
||||
color: amountColor, fontSize: 7, fontWeight: FontWeight.w600),
|
||||
)),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
AutoSizeText(
|
||||
'${address.substring(0, 5)}...${address.substring(address.length-5)}', // ToDo: Maybe use address label
|
||||
'${address.substring(0, 5)}...${address.substring(address.length - 5)}', // ToDo: Maybe use address label
|
||||
style: TextStyle(
|
||||
color: addressColor,
|
||||
fontSize: 12,
|
||||
),
|
||||
maxLines: 1,
|
||||
),
|
||||
if (isChange)
|
||||
Container(
|
||||
height: 17,
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.5)),
|
||||
color: Colors.white),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
S.of(context).unspent_change,
|
||||
style: TextStyle(
|
||||
color: itemColor,
|
||||
fontSize: 7,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -175,10 +175,8 @@ abstract class BalanceViewModelBase with Store {
|
||||
return '---';
|
||||
}
|
||||
|
||||
return _getFiatBalance(
|
||||
price: price,
|
||||
cryptoAmount: getFormattedFrozenBalance(walletBalance)) + ' ' + fiatCurrency.toString();
|
||||
|
||||
return _getFiatBalance(price: price, cryptoAmount: getFormattedFrozenBalance(walletBalance)) +
|
||||
' ${fiatCurrency.toString()}';
|
||||
}
|
||||
|
||||
@computed
|
||||
@@ -201,10 +199,8 @@ abstract class BalanceViewModelBase with Store {
|
||||
return '---';
|
||||
}
|
||||
|
||||
return _getFiatBalance(
|
||||
price: price,
|
||||
cryptoAmount: walletBalance.formattedAvailableBalance) + ' ' + fiatCurrency.toString();
|
||||
|
||||
return _getFiatBalance(price: price, cryptoAmount: walletBalance.formattedAvailableBalance) +
|
||||
' ${fiatCurrency.toString()}';
|
||||
}
|
||||
|
||||
@computed
|
||||
@@ -216,10 +212,8 @@ abstract class BalanceViewModelBase with Store {
|
||||
return '---';
|
||||
}
|
||||
|
||||
return _getFiatBalance(
|
||||
price: price,
|
||||
cryptoAmount: walletBalance.formattedAdditionalBalance) + ' ' + fiatCurrency.toString();
|
||||
|
||||
return _getFiatBalance(price: price, cryptoAmount: walletBalance.formattedAdditionalBalance) +
|
||||
' ${fiatCurrency.toString()}';
|
||||
}
|
||||
|
||||
@computed
|
||||
|
||||
@@ -12,6 +12,7 @@ abstract class UnspentCoinsItemBase with Store {
|
||||
required this.isFrozen,
|
||||
required this.note,
|
||||
required this.isSending,
|
||||
required this.isChange,
|
||||
required this.amountRaw,
|
||||
required this.vout,
|
||||
required this.keyImage
|
||||
@@ -35,6 +36,9 @@ abstract class UnspentCoinsItemBase with Store {
|
||||
@observable
|
||||
bool isSending;
|
||||
|
||||
@observable
|
||||
bool isChange;
|
||||
|
||||
@observable
|
||||
int amountRaw;
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cw_core/unspent_transaction_output.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/unspent_transaction_output.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
@@ -26,66 +24,49 @@ abstract class UnspentCoinsListViewModelBase with Store {
|
||||
|
||||
@computed
|
||||
ObservableList<UnspentCoinsItem> get items => ObservableList.of(_getUnspents().map((elem) {
|
||||
final amount = formatAmountToString(elem.value) + ' ${wallet.currency.title}';
|
||||
|
||||
final info =
|
||||
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
|
||||
|
||||
return UnspentCoinsItem(
|
||||
address: elem.address,
|
||||
amount: amount,
|
||||
amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}',
|
||||
hash: elem.hash,
|
||||
isFrozen: info?.isFrozen ?? false,
|
||||
note: info?.note ?? '',
|
||||
isSending: info?.isSending ?? true,
|
||||
isFrozen: info.isFrozen,
|
||||
note: info.note,
|
||||
isSending: info.isSending,
|
||||
amountRaw: elem.value,
|
||||
vout: elem.vout,
|
||||
keyImage: elem.keyImage);
|
||||
keyImage: elem.keyImage,
|
||||
isChange: elem.isChange,
|
||||
);
|
||||
}));
|
||||
|
||||
Future<void> saveUnspentCoinInfo(UnspentCoinsItem item) async {
|
||||
try {
|
||||
final info =
|
||||
getUnspentCoinInfo(item.hash, item.address, item.amountRaw, item.vout, item.keyImage);
|
||||
if (info == null) {
|
||||
final newInfo = UnspentCoinsInfo(
|
||||
walletId: wallet.id,
|
||||
hash: item.hash,
|
||||
address: item.address,
|
||||
value: item.amountRaw,
|
||||
vout: item.vout,
|
||||
isFrozen: item.isFrozen,
|
||||
isSending: item.isSending,
|
||||
noteRaw: item.note,
|
||||
keyImage: item.keyImage);
|
||||
|
||||
await _unspentCoinsInfo.add(newInfo);
|
||||
_updateUnspents();
|
||||
wallet.updateBalance();
|
||||
return;
|
||||
}
|
||||
info.isFrozen = item.isFrozen;
|
||||
info.isSending = item.isSending;
|
||||
info.note = item.note;
|
||||
|
||||
await info.save();
|
||||
_updateUnspents();
|
||||
wallet.updateBalance();
|
||||
await _updateUnspents();
|
||||
await wallet.updateBalance();
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
UnspentCoinsInfo? getUnspentCoinInfo(
|
||||
String hash, String address, int value, int vout, String? keyImage) {
|
||||
return _unspentCoinsInfo.values.firstWhereOrNull((element) =>
|
||||
element.walletId == wallet.id &&
|
||||
element.hash == hash &&
|
||||
element.address == address &&
|
||||
element.value == value &&
|
||||
element.vout == vout &&
|
||||
element.keyImage == keyImage);
|
||||
}
|
||||
UnspentCoinsInfo getUnspentCoinInfo(
|
||||
String hash, String address, int value, int vout, String? keyImage) =>
|
||||
_unspentCoinsInfo.values.firstWhere((element) =>
|
||||
element.walletId == wallet.id &&
|
||||
element.hash == hash &&
|
||||
element.address == address &&
|
||||
element.value == value &&
|
||||
element.vout == vout &&
|
||||
element.keyImage == keyImage);
|
||||
|
||||
String formatAmountToString(int fullBalance) {
|
||||
if (wallet.type == WalletType.monero)
|
||||
@@ -95,7 +76,7 @@ abstract class UnspentCoinsListViewModelBase with Store {
|
||||
return '';
|
||||
}
|
||||
|
||||
void _updateUnspents() {
|
||||
Future<void> _updateUnspents() async {
|
||||
if (wallet.type == WalletType.monero) return monero!.updateUnspents(wallet);
|
||||
if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type))
|
||||
return bitcoin!.updateUnspents(wallet);
|
||||
|
||||
Reference in New Issue
Block a user