mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-12-22 19:39:22 +00:00
desktop all trades view
This commit is contained in:
parent
37bacf8c7f
commit
4c45487e6e
5 changed files with 479 additions and 9 deletions
|
@ -16,7 +16,6 @@ import 'package:stackwallet/providers/ui/transaction_filter_provider.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/enums/flush_bar_type.dart';
|
||||
import 'package:stackwallet/utilities/format.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
|
|
|
@ -0,0 +1,454 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart';
|
||||
import 'package:stackwallet/models/exchange/response_objects/trade.dart';
|
||||
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
|
||||
import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart';
|
||||
import 'package:stackwallet/providers/global/trades_service_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/utilities/assets.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/format.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart';
|
||||
import 'package:stackwallet/widgets/icon_widgets/x_icon.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';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class DesktopAllTradesView extends ConsumerStatefulWidget {
|
||||
const DesktopAllTradesView({Key? key}) : super(key: key);
|
||||
|
||||
static const String routeName = "/desktopAllTrades";
|
||||
|
||||
@override
|
||||
ConsumerState<DesktopAllTradesView> createState() =>
|
||||
_DesktopAllTradesViewState();
|
||||
}
|
||||
|
||||
class _DesktopAllTradesViewState extends ConsumerState<DesktopAllTradesView> {
|
||||
late final TextEditingController _searchController;
|
||||
late final FocusNode searchFieldFocusNode;
|
||||
|
||||
String _searchString = "";
|
||||
|
||||
List<Tuple2<String, List<Trade>>> groupTransactionsByMonth(
|
||||
List<Trade> trades) {
|
||||
Map<String, List<Trade>> map = {};
|
||||
|
||||
for (var trade in trades) {
|
||||
final date = trade.timestamp;
|
||||
final monthYear = "${Constants.monthMap[date.month]} ${date.year}";
|
||||
if (map[monthYear] == null) {
|
||||
map[monthYear] = [];
|
||||
}
|
||||
map[monthYear]!.add(trade);
|
||||
}
|
||||
|
||||
List<Tuple2<String, List<Trade>>> result = [];
|
||||
map.forEach((key, value) {
|
||||
result.add(Tuple2(key, value));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_searchController = TextEditingController();
|
||||
searchFieldFocusNode = FocusNode();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
searchFieldFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DesktopScaffold(
|
||||
appBar: DesktopAppBar(
|
||||
isCompactHeight: true,
|
||||
background: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
leading: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 32,
|
||||
),
|
||||
AppBarIconButton(
|
||||
size: 32,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
shadows: const [],
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.arrowLeft,
|
||||
width: 18,
|
||||
height: 18,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.topNavIconPrimary,
|
||||
),
|
||||
onPressed: Navigator.of(context).pop,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
"Trades",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 20,
|
||||
top: 20,
|
||||
right: 20,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 570,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
controller: _searchController,
|
||||
focusNode: searchFieldFocusNode,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_searchString = value;
|
||||
});
|
||||
},
|
||||
style:
|
||||
STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveText,
|
||||
height: 1.8,
|
||||
),
|
||||
decoration: standardInputDecoration(
|
||||
"Search...",
|
||||
searchFieldFocusNode,
|
||||
context,
|
||||
desktopMed: true,
|
||||
).copyWith(
|
||||
prefixIcon: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 18,
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.search,
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
),
|
||||
suffixIcon: _searchController.text.isNotEmpty
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(right: 0),
|
||||
child: UnconstrainedBox(
|
||||
child: Row(
|
||||
children: [
|
||||
TextFieldIconButton(
|
||||
child: const XIcon(),
|
||||
onTap: () async {
|
||||
setState(() {
|
||||
_searchController.text = "";
|
||||
_searchString = "";
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Expanded(
|
||||
child: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
List<Trade> trades = ref.watch(
|
||||
tradesServiceProvider.select((value) => value.trades));
|
||||
|
||||
if (_searchString.isNotEmpty) {
|
||||
final term = _searchString.toLowerCase();
|
||||
trades = trades
|
||||
.where((e) => e.toString().toLowerCase().contains(term))
|
||||
.toList(growable: false);
|
||||
}
|
||||
final monthlyList = groupTransactionsByMonth(trades);
|
||||
|
||||
return ListView.builder(
|
||||
primary: false,
|
||||
itemCount: monthlyList.length,
|
||||
itemBuilder: (_, index) {
|
||||
final month = monthlyList[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (index != 0)
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Text(
|
||||
month.item1,
|
||||
style: STextStyles.smallMed12(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
primary: false,
|
||||
separatorBuilder: (context, _) => Container(
|
||||
height: 1,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.background,
|
||||
),
|
||||
itemCount: month.item2.length,
|
||||
itemBuilder: (context, index) => Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: DesktopTradeRowCard(
|
||||
key: Key(
|
||||
"transactionCard_key_${month.item2[index].tradeId}"),
|
||||
tradeId: month.item2[index].tradeId,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopTradeRowCard extends ConsumerStatefulWidget {
|
||||
const DesktopTradeRowCard({
|
||||
Key? key,
|
||||
required this.tradeId,
|
||||
}) : super(key: key);
|
||||
|
||||
final String tradeId;
|
||||
|
||||
@override
|
||||
ConsumerState<DesktopTradeRowCard> createState() =>
|
||||
_DesktopTradeRowCardState();
|
||||
}
|
||||
|
||||
class _DesktopTradeRowCardState extends ConsumerState<DesktopTradeRowCard> {
|
||||
late final String tradeId;
|
||||
|
||||
String _fetchIconAssetForStatus(String statusString, BuildContext context) {
|
||||
ChangeNowTransactionStatus? status;
|
||||
try {
|
||||
if (statusString.toLowerCase().startsWith("waiting")) {
|
||||
statusString = "waiting";
|
||||
}
|
||||
status = changeNowTransactionStatusFromStringIgnoreCase(statusString);
|
||||
} on ArgumentError catch (_) {
|
||||
status = ChangeNowTransactionStatus.Failed;
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case ChangeNowTransactionStatus.New:
|
||||
case ChangeNowTransactionStatus.Waiting:
|
||||
case ChangeNowTransactionStatus.Confirming:
|
||||
case ChangeNowTransactionStatus.Exchanging:
|
||||
case ChangeNowTransactionStatus.Sending:
|
||||
case ChangeNowTransactionStatus.Refunded:
|
||||
case ChangeNowTransactionStatus.Verifying:
|
||||
return Assets.svg.txExchangePending(context);
|
||||
case ChangeNowTransactionStatus.Finished:
|
||||
return Assets.svg.txExchange(context);
|
||||
case ChangeNowTransactionStatus.Failed:
|
||||
return Assets.svg.txExchangeFailed(context);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
tradeId = widget.tradeId;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final String? txid =
|
||||
ref.read(tradeSentFromStackLookupProvider).getTxidForTradeId(tradeId);
|
||||
final List<String>? walletIds = ref
|
||||
.read(tradeSentFromStackLookupProvider)
|
||||
.getWalletIdsForTradeId(tradeId);
|
||||
|
||||
final trade =
|
||||
ref.watch(tradesServiceProvider.select((value) => value.get(tradeId)))!;
|
||||
|
||||
return Material(
|
||||
color: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(Constants.size.circularBorderRadius),
|
||||
),
|
||||
child: RawMaterialButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
if (txid != null && walletIds != null && walletIds.isNotEmpty) {
|
||||
final manager = ref
|
||||
.read(walletsChangeNotifierProvider)
|
||||
.getManager(walletIds.first);
|
||||
|
||||
debugPrint("name: ${manager.walletName}");
|
||||
|
||||
// TODO store tx data completely locally in isar so we don't lock up ui here when querying txData
|
||||
final txData = await manager.transactionData;
|
||||
|
||||
final tx = txData.getAllTransactions()[txid];
|
||||
|
||||
if (mounted) {
|
||||
unawaited(
|
||||
Navigator.of(context).pushNamed(
|
||||
TradeDetailsView.routeName,
|
||||
arguments: Tuple4(
|
||||
tradeId,
|
||||
tx,
|
||||
walletIds.first,
|
||||
manager.walletName,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
unawaited(
|
||||
Navigator.of(context).pushNamed(
|
||||
TradeDetailsView.routeName,
|
||||
arguments: Tuple4(
|
||||
tradeId,
|
||||
null,
|
||||
walletIds?.first,
|
||||
null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 16,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
),
|
||||
child: Center(
|
||||
child: SvgPicture.asset(
|
||||
_fetchIconAssetForStatus(
|
||||
trade.status,
|
||||
context,
|
||||
),
|
||||
width: 32,
|
||||
height: 32,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Text(
|
||||
"${trade.payInCurrency.toUpperCase()} → ${trade.payOutCurrency.toUpperCase()}",
|
||||
style:
|
||||
STextStyles.desktopTextExtraExtraSmall(context).copyWith(
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Text(
|
||||
Format.extractDateFrom(
|
||||
trade.timestamp.millisecondsSinceEpoch ~/ 1000),
|
||||
style: STextStyles.desktopTextExtraExtraSmall(context),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: Text(
|
||||
"-${Decimal.tryParse(trade.payInAmount)?.toStringAsFixed(8) ?? "..."} ${trade.payInCurrency.toUpperCase()}",
|
||||
style:
|
||||
STextStyles.desktopTextExtraExtraSmall(context).copyWith(
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Text(
|
||||
trade.exchangeName,
|
||||
style: STextStyles.desktopTextExtraExtraSmall(context),
|
||||
),
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.circleInfo,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.textSubtitle2,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -3,20 +3,20 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:stackwallet/pages/exchange_view/trade_details_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart';
|
||||
import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart';
|
||||
import 'package:stackwallet/providers/global/trades_service_provider.dart';
|
||||
import 'package:stackwallet/providers/global/wallets_provider.dart';
|
||||
import 'package:stackwallet/route_generator.dart';
|
||||
import 'package:stackwallet/utilities/constants.dart';
|
||||
import 'package:stackwallet/utilities/text_styles.dart';
|
||||
import 'package:stackwallet/utilities/theme/stack_colors.dart';
|
||||
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
|
||||
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import 'package:stackwallet/widgets/rounded_white_container.dart';
|
||||
import 'package:stackwallet/widgets/trade_card.dart';
|
||||
|
||||
import '../../../route_generator.dart';
|
||||
import '../../../widgets/desktop/desktop_dialog.dart';
|
||||
import '../../../widgets/desktop/desktop_dialog_close_button.dart';
|
||||
|
||||
class DesktopTradeHistory extends ConsumerStatefulWidget {
|
||||
const DesktopTradeHistory({Key? key}) : super(key: key);
|
||||
|
||||
|
@ -71,7 +71,8 @@ class _DesktopTradeHistoryState extends ConsumerState<DesktopTradeHistory> {
|
|||
BlueTextButton(
|
||||
text: "See all",
|
||||
onTap: () {
|
||||
// todo display all trades
|
||||
Navigator.of(context)
|
||||
.pushNamed(DesktopAllTradesView.routeName);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
|
|
@ -85,6 +85,7 @@ import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_sear
|
|||
import 'package:stackwallet/pages/wallet_view/wallet_view.dart';
|
||||
import 'package:stackwallet/pages/wallets_view/wallets_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/create_password/create_password_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/forgot_password_desktop_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/address_book_view/desktop_address_book.dart';
|
||||
|
@ -96,12 +97,14 @@ import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_vie
|
|||
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/qr_code_desktop_popup_content.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/notifications/desktop_notifications_view.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/settings_menu/advanced_settings/advanced_settings.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/settings_menu/appearance_settings.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/settings_menu/backup_and_restore/backup_and_restore_settings.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/settings_menu/currency_settings/currency_settings.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/settings_menu/nodes_settings.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/settings_menu/security_settings.dart';
|
||||
import 'package:stackwallet/pages_desktop_specific/home/settings_menu/settings_menu.dart';
|
||||
|
@ -115,9 +118,6 @@ import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart';
|
|||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import 'pages_desktop_specific/home/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart';
|
||||
import 'pages_desktop_specific/home/settings_menu/language_settings/language_settings.dart';
|
||||
|
||||
class RouteGenerator {
|
||||
static const bool useMaterialPageRoute = true;
|
||||
|
||||
|
@ -1029,6 +1029,12 @@ class RouteGenerator {
|
|||
builder: (_) => const DesktopExchangeView(),
|
||||
settings: RouteSettings(name: settings.name));
|
||||
|
||||
case DesktopAllTradesView.routeName:
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
builder: (_) => const DesktopAllTradesView(),
|
||||
settings: RouteSettings(name: settings.name));
|
||||
|
||||
case DesktopSettingsView.routeName:
|
||||
return getRoute(
|
||||
shouldUseMaterialRoute: useMaterialPageRoute,
|
||||
|
|
|
@ -11,6 +11,16 @@ class TradesService extends ChangeNotifier {
|
|||
return list;
|
||||
}
|
||||
|
||||
Trade? get(String tradeId) {
|
||||
try {
|
||||
return DB.instance
|
||||
.values<Trade>(boxName: DB.boxNameTradesV2)
|
||||
.firstWhere((e) => e.tradeId == tradeId);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> add({
|
||||
required Trade trade,
|
||||
required bool shouldNotifyListeners,
|
||||
|
|
Loading…
Reference in a new issue