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/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:isar/isar.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/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/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/transaction_views/tx_v2/transaction_v2_list_item.dart';
import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
import 'package:stackwallet/providers/db/main_db_provider.dart'; import 'package:stackwallet/providers/db/main_db_provider.dart';
@ -102,6 +104,59 @@ class _TransactionsV2ListState extends ConsumerState<TransactionsV2List> {
return const NoTransActionsFound(); return const NoTransActionsFound();
} else { } else {
_transactions.sort((a, b) => b.timestamp - a.timestamp); _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( return RefreshIndicator(
onRefresh: () async { onRefresh: () async {
final managerProvider = ref final managerProvider = ref
@ -116,16 +171,16 @@ class _TransactionsV2ListState extends ConsumerState<TransactionsV2List> {
shrinkWrap: true, shrinkWrap: true,
itemBuilder: (context, index) { itemBuilder: (context, index) {
BorderRadius? radius; BorderRadius? radius;
if (_transactions.length == 1) { if (_txns.length == 1) {
radius = BorderRadius.circular( radius = BorderRadius.circular(
Constants.size.circularBorderRadius, Constants.size.circularBorderRadius,
); );
} else if (index == _transactions.length - 1) { } else if (index == _txns.length - 1) {
radius = _borderRadiusLast; radius = _borderRadiusLast;
} else if (index == 0) { } else if (index == 0) {
radius = _borderRadiusFirst; radius = _borderRadiusFirst;
} }
final tx = _transactions[index]; final tx = _txns[index];
return TxListItem( return TxListItem(
tx: tx, tx: tx,
coin: manager.coin, coin: manager.coin,
@ -141,24 +196,24 @@ class _TransactionsV2ListState extends ConsumerState<TransactionsV2List> {
.background, .background,
); );
}, },
itemCount: _transactions.length, itemCount: _txns.length,
) )
: ListView.builder( : ListView.builder(
itemCount: _transactions.length, itemCount: _txns.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
BorderRadius? radius; BorderRadius? radius;
bool shouldWrap = false; bool shouldWrap = false;
if (_transactions.length == 1) { if (_txns.length == 1) {
radius = BorderRadius.circular( radius = BorderRadius.circular(
Constants.size.circularBorderRadius, Constants.size.circularBorderRadius,
); );
} else if (index == _transactions.length - 1) { } else if (index == _txns.length - 1) {
radius = _borderRadiusLast; radius = _borderRadiusLast;
shouldWrap = true; shouldWrap = true;
} else if (index == 0) { } else if (index == 0) {
radius = _borderRadiusFirst; radius = _borderRadiusFirst;
} }
final tx = _transactions[index]; final tx = _txns[index];
if (shouldWrap) { if (shouldWrap) {
return Column( return Column(
children: [ 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/blockchain_data/v2/transaction_v2.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/pages/exchange_view/trade_details_view.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/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/trades_service_provider.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart';
@ -24,137 +25,156 @@ class TxListItem extends ConsumerWidget {
required this.tx, required this.tx,
this.radius, this.radius,
required this.coin, required this.coin,
}); }) : assert(tx is TransactionV2 || tx is FusionTxGroup);
final TransactionV2 tx; final Object tx;
final BorderRadius? radius; final BorderRadius? radius;
final Coin coin; final Coin coin;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final matchingTrades = ref if (tx is TransactionV2) {
.read(tradesServiceProvider) final _tx = tx as TransactionV2;
.trades
.where((e) => e.payInTxid == tx.txid || e.payOutTxid == tx.txid);
if (tx.type == TransactionType.outgoing && matchingTrades.isNotEmpty) { final matchingTrades = ref
final trade = matchingTrades.first; .read(tradesServiceProvider)
return Container( .trades
decoration: BoxDecoration( .where((e) => e.payInTxid == _tx.txid || e.payOutTxid == _tx.txid);
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: radius, if (_tx.type == TransactionType.outgoing && matchingTrades.isNotEmpty) {
), final trade = matchingTrades.first;
child: Column( return Container(
mainAxisSize: MainAxisSize.min, decoration: BoxDecoration(
children: [ color: Theme.of(context).extension<StackColors>()!.popupBG,
TransactionCardV2( borderRadius: radius,
key: UniqueKey(), ),
transaction: tx, child: Column(
), mainAxisSize: MainAxisSize.min,
TradeCard( children: [
key: Key(tx.txid + TransactionCardV2(
tx.type.name + key: UniqueKey(),
tx.hashCode.toString() + transaction: _tx,
trade.uuid), // ),
trade: trade, TradeCard(
onTap: () async { key: Key(_tx.txid +
if (Util.isDesktop) { _tx.type.name +
await showDialog<void>( _tx.hashCode.toString() +
context: context, trade.uuid), //
builder: (context) => Navigator( trade: trade,
initialRoute: TradeDetailsView.routeName, onTap: () async {
onGenerateRoute: RouteGenerator.generateRoute, if (Util.isDesktop) {
onGenerateInitialRoutes: (_, __) { await showDialog<void>(
return [ context: context,
FadePageRoute( builder: (context) => Navigator(
DesktopDialog( initialRoute: TradeDetailsView.routeName,
maxHeight: null, onGenerateRoute: RouteGenerator.generateRoute,
maxWidth: 580, onGenerateInitialRoutes: (_, __) {
child: Column( return [
mainAxisSize: MainAxisSize.min, FadePageRoute(
children: [ DesktopDialog(
Padding( maxHeight: null,
padding: const EdgeInsets.only( maxWidth: 580,
left: 32, child: Column(
bottom: 16, mainAxisSize: MainAxisSize.min,
), children: [
child: Row( Padding(
mainAxisAlignment: padding: const EdgeInsets.only(
MainAxisAlignment.spaceBetween, left: 32,
children: [ bottom: 16,
Text( ),
"Trade details", child: Row(
style: STextStyles.desktopH3(context), mainAxisAlignment:
), MainAxisAlignment.spaceBetween,
DesktopDialogCloseButton( children: [
onPressedOverride: Navigator.of( Text(
context, "Trade details",
rootNavigator: true, style:
).pop, STextStyles.desktopH3(context),
), ),
], DesktopDialogCloseButton(
), onPressedOverride: Navigator.of(
), context,
Flexible( rootNavigator: true,
child: TradeDetailsView( ).pop,
tradeId: trade.tradeId, ),
// TODO ],
// transactionIfSentFromStack: tx,
transactionIfSentFromStack: null,
walletName: ref.watch(
walletsChangeNotifierProvider.select(
(value) => value
.getManager(tx.walletId)
.walletName,
),
), ),
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 {
} unawaited(
}, Navigator.of(context).pushNamed(
) TradeDetailsView.routeName,
], arguments: Tuple4(
), trade.tradeId,
); _tx,
} else { _tx.walletId,
return Container( ref
decoration: BoxDecoration( .read(walletsChangeNotifierProvider)
color: Theme.of(context).extension<StackColors>()!.popupBG, .getManager(_tx.walletId)
borderRadius: radius, .walletName,
), ),
child: TransactionCardV2( ),
// this may mess with combined firo transactions );
key: UniqueKey(), }
transaction: tx, },
), )
); ],
),
);
} 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) .getManager(widget.walletId)
.hasTokenSupport)) .hasTokenSupport))
? "Tokens" ? "Tokens"
: "Recent transactions", : "Recent activity",
style: STextStyles.desktopTextExtraSmall(context) style: STextStyles.desktopTextExtraSmall(context)
.copyWith( .copyWith(
color: Theme.of(context) color: Theme.of(context)

View file

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