WIP group fusions on wallet home screen activity

This commit is contained in:
julian 2023-10-20 17:26:42 -06:00
parent 79d117d7f1
commit c6a370e4f5
5 changed files with 392 additions and 130 deletions

View file

@ -0,0 +1,176 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/format.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
class FusionTxGroup {
final List<TransactionV2> transactions;
FusionTxGroup(this.transactions);
}
class FusionTxGroupCard extends ConsumerWidget {
const FusionTxGroupCard({super.key, required this.group});
final FusionTxGroup group;
@override
Widget build(BuildContext context, WidgetRef ref) {
final walletId = group.transactions.first.walletId;
final coin = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletId).coin));
final currentHeight = ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(walletId).currentHeight));
return Material(
color: Theme.of(context).extension<StackColors>()!.popupBG,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(Constants.size.circularBorderRadius),
),
child: Padding(
padding: const EdgeInsets.all(6),
child: RawMaterialButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () async {
if (Util.isDesktop) {
// await showDialog<void>(
// context: context,
// builder: (context) => DesktopDialog(
// maxHeight: MediaQuery.of(context).size.height - 64,
// maxWidth: 580,
// child: TransactionV2DetailsView(
// transaction: _transaction,
// coin: coin,
// walletId: walletId,
// ),
// ),
// );
} else {
// unawaited(
// Navigator.of(context).pushNamed(
// TransactionV2DetailsView.routeName,
// arguments: (
// tx: _transaction,
// coin: coin,
// walletId: walletId,
// ),
// ),
// );
}
},
child: Padding(
padding: const EdgeInsets.all(8),
child: Row(
children: [
TxIcon(
transaction: group.transactions.first,
coin: coin,
currentHeight: currentHeight,
),
const SizedBox(
width: 14,
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
"Fusions",
style: STextStyles.itemSubtitle12(context),
),
),
),
const SizedBox(
width: 10,
),
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Builder(
builder: (_) {
return Text(
"${group.transactions.length} fusion transactions",
style: STextStyles.itemSubtitle12(context),
);
},
),
),
),
],
),
const SizedBox(
height: 4,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// crossAxisAlignment: CrossAxisAlignment.end,
children: [
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
Format.extractDateFrom(
group.transactions.last.timestamp,
),
style: STextStyles.label(context),
),
),
),
// if (ref.watch(prefsChangeNotifierProvider
// .select((value) => value.externalCalls)))
// const SizedBox(
// width: 10,
// ),
// if (ref.watch(prefsChangeNotifierProvider
// .select((value) => value.externalCalls)))
// Flexible(
// child: FittedBox(
// fit: BoxFit.scaleDown,
// child: Builder(
// builder: (_) {
// return Text(
// "$prefix${Amount.fromDecimal(
// amount.decimal * price,
// fractionDigits: 2,
// ).fiatString(
// locale: locale,
// )} $baseCurrency",
// style: STextStyles.label(context),
// );
// },
// ),
// ),
// ),
],
),
],
),
),
],
),
),
),
),
);
}
}

View file

@ -13,8 +13,10 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/fusion_tx_group_card.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list_item.dart';
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart';
@ -102,6 +104,59 @@ class _TransactionsV2ListState extends ConsumerState<TransactionsV2List> {
return const NoTransActionsFound();
} else {
_transactions.sort((a, b) => b.timestamp - a.timestamp);
final List<Object> _txns = [];
List<TransactionV2> fusions = [];
for (int i = 0; i < _transactions.length; i++) {
final tx = _transactions[i];
if (tx.subType == TransactionSubType.cashFusion) {
if (fusions.isNotEmpty) {
final prevTime = DateTime.fromMillisecondsSinceEpoch(
fusions.last.timestamp * 1000);
final thisTime =
DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000);
print(
"DIFFERERNCE: ${prevTime.difference(thisTime).inMinutes}");
if (prevTime.difference(thisTime).inMinutes > 30) {
_txns.add(FusionTxGroup(fusions));
fusions = [tx];
continue;
}
}
fusions.add(tx);
}
if (i + 1 < _transactions.length) {
final nextTx = _transactions[i + 1];
if (nextTx.subType != TransactionSubType.cashFusion &&
fusions.isNotEmpty) {
_txns.add(FusionTxGroup(fusions));
fusions = [];
}
}
if (tx.subType != TransactionSubType.cashFusion) {
_txns.add(tx);
}
}
// sanity check
int count = 0;
for (final e in _txns) {
if (e is TransactionV2) {
count++;
} else if (e is FusionTxGroup) {
count += e.transactions.length;
}
}
assert(count == _transactions.length);
return RefreshIndicator(
onRefresh: () async {
final managerProvider = ref
@ -116,16 +171,16 @@ class _TransactionsV2ListState extends ConsumerState<TransactionsV2List> {
shrinkWrap: true,
itemBuilder: (context, index) {
BorderRadius? radius;
if (_transactions.length == 1) {
if (_txns.length == 1) {
radius = BorderRadius.circular(
Constants.size.circularBorderRadius,
);
} else if (index == _transactions.length - 1) {
} else if (index == _txns.length - 1) {
radius = _borderRadiusLast;
} else if (index == 0) {
radius = _borderRadiusFirst;
}
final tx = _transactions[index];
final tx = _txns[index];
return TxListItem(
tx: tx,
coin: manager.coin,
@ -141,24 +196,24 @@ class _TransactionsV2ListState extends ConsumerState<TransactionsV2List> {
.background,
);
},
itemCount: _transactions.length,
itemCount: _txns.length,
)
: ListView.builder(
itemCount: _transactions.length,
itemCount: _txns.length,
itemBuilder: (context, index) {
BorderRadius? radius;
bool shouldWrap = false;
if (_transactions.length == 1) {
if (_txns.length == 1) {
radius = BorderRadius.circular(
Constants.size.circularBorderRadius,
);
} else if (index == _transactions.length - 1) {
} else if (index == _txns.length - 1) {
radius = _borderRadiusLast;
shouldWrap = true;
} else if (index == 0) {
radius = _borderRadiusFirst;
}
final tx = _transactions[index];
final tx = _txns[index];
if (shouldWrap) {
return Column(
children: [

View file

@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/fusion_tx_group_card.dart';
import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_card.dart';
import 'package:stackwallet/providers/global/trades_service_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
@ -24,137 +25,156 @@ class TxListItem extends ConsumerWidget {
required this.tx,
this.radius,
required this.coin,
});
}) : assert(tx is TransactionV2 || tx is FusionTxGroup);
final TransactionV2 tx;
final Object tx;
final BorderRadius? radius;
final Coin coin;
@override
Widget build(BuildContext context, WidgetRef ref) {
final matchingTrades = ref
.read(tradesServiceProvider)
.trades
.where((e) => e.payInTxid == tx.txid || e.payOutTxid == tx.txid);
if (tx is TransactionV2) {
final _tx = tx as TransactionV2;
if (tx.type == TransactionType.outgoing && matchingTrades.isNotEmpty) {
final trade = matchingTrades.first;
return Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TransactionCardV2(
key: UniqueKey(),
transaction: tx,
),
TradeCard(
key: Key(tx.txid +
tx.type.name +
tx.hashCode.toString() +
trade.uuid), //
trade: trade,
onTap: () async {
if (Util.isDesktop) {
await showDialog<void>(
context: context,
builder: (context) => Navigator(
initialRoute: TradeDetailsView.routeName,
onGenerateRoute: RouteGenerator.generateRoute,
onGenerateInitialRoutes: (_, __) {
return [
FadePageRoute(
DesktopDialog(
maxHeight: null,
maxWidth: 580,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(
left: 32,
bottom: 16,
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"Trade details",
style: STextStyles.desktopH3(context),
),
DesktopDialogCloseButton(
onPressedOverride: Navigator.of(
context,
rootNavigator: true,
).pop,
),
],
),
),
Flexible(
child: TradeDetailsView(
tradeId: trade.tradeId,
// TODO
// transactionIfSentFromStack: tx,
transactionIfSentFromStack: null,
walletName: ref.watch(
walletsChangeNotifierProvider.select(
(value) => value
.getManager(tx.walletId)
.walletName,
),
final matchingTrades = ref
.read(tradesServiceProvider)
.trades
.where((e) => e.payInTxid == _tx.txid || e.payOutTxid == _tx.txid);
if (_tx.type == TransactionType.outgoing && matchingTrades.isNotEmpty) {
final trade = matchingTrades.first;
return Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TransactionCardV2(
key: UniqueKey(),
transaction: _tx,
),
TradeCard(
key: Key(_tx.txid +
_tx.type.name +
_tx.hashCode.toString() +
trade.uuid), //
trade: trade,
onTap: () async {
if (Util.isDesktop) {
await showDialog<void>(
context: context,
builder: (context) => Navigator(
initialRoute: TradeDetailsView.routeName,
onGenerateRoute: RouteGenerator.generateRoute,
onGenerateInitialRoutes: (_, __) {
return [
FadePageRoute(
DesktopDialog(
maxHeight: null,
maxWidth: 580,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(
left: 32,
bottom: 16,
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"Trade details",
style:
STextStyles.desktopH3(context),
),
DesktopDialogCloseButton(
onPressedOverride: Navigator.of(
context,
rootNavigator: true,
).pop,
),
],
),
walletId: tx.walletId,
),
),
],
Flexible(
child: TradeDetailsView(
tradeId: trade.tradeId,
// TODO
// transactionIfSentFromStack: tx,
transactionIfSentFromStack: null,
walletName: ref.watch(
walletsChangeNotifierProvider.select(
(value) => value
.getManager(_tx.walletId)
.walletName,
),
),
walletId: _tx.walletId,
),
),
],
),
),
const RouteSettings(
name: TradeDetailsView.routeName,
),
),
const RouteSettings(
name: TradeDetailsView.routeName,
),
),
];
},
),
);
} else {
unawaited(
Navigator.of(context).pushNamed(
TradeDetailsView.routeName,
arguments: Tuple4(
trade.tradeId,
tx,
tx.walletId,
ref
.read(walletsChangeNotifierProvider)
.getManager(tx.walletId)
.walletName,
];
},
),
),
);
}
},
)
],
),
);
} else {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: TransactionCardV2(
// this may mess with combined firo transactions
key: UniqueKey(),
transaction: tx,
),
);
);
} else {
unawaited(
Navigator.of(context).pushNamed(
TradeDetailsView.routeName,
arguments: Tuple4(
trade.tradeId,
_tx,
_tx.walletId,
ref
.read(walletsChangeNotifierProvider)
.getManager(_tx.walletId)
.walletName,
),
),
);
}
},
)
],
),
);
} else {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: TransactionCardV2(
// this may mess with combined firo transactions
key: UniqueKey(),
transaction: _tx,
),
);
}
}
final group = tx as FusionTxGroup;
return Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius,
),
child: FusionTxGroupCard(
// this may mess with combined firo transactions
key: UniqueKey(),
group: group,
),
);
}
}

View file

@ -446,7 +446,7 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
.getManager(widget.walletId)
.hasTokenSupport))
? "Tokens"
: "Recent transactions",
: "Recent activity",
style: STextStyles.desktopTextExtraSmall(context)
.copyWith(
color: Theme.of(context)

View file

@ -50,16 +50,27 @@ abstract class Format {
// }
// format date string from unix timestamp
static String extractDateFrom(int timestamp, {bool localized = true}) {
static String extractDateFrom(
int timestamp, {
bool localized = true,
bool noTime = false,
}) {
var date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
if (!localized) {
date = date.toUtc();
}
final dayAndYear =
"${date.day} ${Constants.monthMapShort[date.month]} ${date.year}";
if (noTime) {
return dayAndYear;
}
final minutes =
date.minute < 10 ? "0${date.minute}" : date.minute.toString();
return "${date.day} ${Constants.monthMapShort[date.month]} ${date.year}, ${date.hour}:$minutes";
return "$dayAndYear, ${date.hour}:$minutes";
}
// static String localizedStringAsFixed({