mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-25 05:58:42 +01:00
CW-1138-Add-CakePay-orders-to-the-transaction-history-screen (#2446)
* migrate to new backend API * hide value_type filter * Improve vendor card parsing * Update country.dart * Update lib/cake_pay/src/models/cake_pay_card.dart Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * Update lib/cake_pay/src/models/cake_pay_card.dart Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * address review comments * fix min limit value * Update lib/view_model/cake_pay/cake_pay_buy_card_view_model.dart Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * cake pay order row * order provider adapter * add Cake Pay order filtering * Update cake_pay_buy_card_view_model.dart * fix hidden order balance * Update lib/cake_pay/src/models/cake_pay_card.dart Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * Update lib/cake_pay/src/services/cake_pay_api.dart Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * address review comments --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
||||
import 'package:cake_wallet/order/order_source_description.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/anonpay_transaction_row.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/order_row.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/payjoin_transaction_row.dart';
|
||||
@@ -189,19 +191,32 @@ class TransactionsPage extends StatelessWidget {
|
||||
}
|
||||
if (item is OrderListItem) {
|
||||
final order = item.order;
|
||||
|
||||
return Observer(
|
||||
builder: (_) => OrderRow(
|
||||
builder: (_) {
|
||||
|
||||
// TODO: Refactor Amount Hiding Logic it is not working properly for Orders and Trades
|
||||
final hide = dashboardViewModel.balanceViewModel.displayMode ==
|
||||
BalanceDisplayMode.hiddenBalance;
|
||||
|
||||
final formattedAmount = hide ? '---' : order.amountFormatted();
|
||||
final formattedReceiveAmount = hide ? '---' : order.receiveAmount;
|
||||
|
||||
return OrderRow(
|
||||
key: item.key,
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.orderDetails, arguments: order),
|
||||
provider: order.provider,
|
||||
from: order.from!,
|
||||
to: order.to!,
|
||||
createdAtFormattedDate:
|
||||
DateFormat('HH:mm').format(order.createdAt),
|
||||
formattedAmount: item.orderFormattedAmount,
|
||||
),
|
||||
providerTitle: order.providerTitle,
|
||||
providerIconPath: order.providerIcon,
|
||||
from: order.from ?? '',
|
||||
to: order.to ?? '',
|
||||
createdAtFormattedDate: DateFormat('HH:mm').format(order.createdAt),
|
||||
formattedAmount: formattedAmount,
|
||||
formattedReceiveAmount: dashboardViewModel.balanceViewModel.isFiatDisabled &&
|
||||
order.source == OrderSourceDescription.order
|
||||
? ''
|
||||
: formattedReceiveAmount,
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
return Container(color: Colors.transparent, height: 1);
|
||||
|
||||
@@ -1,77 +1,110 @@
|
||||
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
||||
import 'package:cake_wallet/buy/get_buy_provider_icon.dart';
|
||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/utils/image_utill.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class OrderRow extends StatelessWidget {
|
||||
OrderRow({
|
||||
required this.provider,
|
||||
const OrderRow({
|
||||
Key? key,
|
||||
required this.providerTitle,
|
||||
required this.providerIconPath,
|
||||
required this.from,
|
||||
required this.to,
|
||||
required this.createdAtFormattedDate,
|
||||
this.state,
|
||||
this.onTap,
|
||||
this.formattedAmount,
|
||||
super.key,
|
||||
});
|
||||
this.formattedReceiveAmount,
|
||||
}) : super(key: key);
|
||||
|
||||
final VoidCallback? onTap;
|
||||
final BuyProviderDescription provider;
|
||||
|
||||
final String providerTitle;
|
||||
final String providerIconPath;
|
||||
|
||||
final String from;
|
||||
final String to;
|
||||
final String createdAtFormattedDate;
|
||||
final String? createdAtFormattedDate;
|
||||
final String? formattedAmount;
|
||||
final String? formattedReceiveAmount;
|
||||
final TradeState? state;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final iconColor = Theme.of(context).colorScheme.onSurfaceVariant;
|
||||
|
||||
final providerIcon = getBuyProviderIcon(provider, iconColor: iconColor);
|
||||
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
|
||||
padding: const EdgeInsets.fromLTRB(24, 8, 24, 8),
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (providerIcon != null)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 12),
|
||||
child: providerIcon,
|
||||
),
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
_ProviderIcon(path: providerIconPath, title: providerTitle),
|
||||
if (state != null)
|
||||
Positioned(
|
||||
right: 0,
|
||||
bottom: 2,
|
||||
child: Container(
|
||||
height: 8,
|
||||
width: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: _statusColor(context, state!),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
|
||||
Text(
|
||||
'$from → $to',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
formattedAmount != null
|
||||
? Text(
|
||||
formattedAmount! + ' ' + to,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
)
|
||||
: Container()
|
||||
]),
|
||||
SizedBox(height: 5),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
createdAtFormattedDate,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
'$from → $to',
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
if (formattedAmount != null)
|
||||
Text(
|
||||
formattedAmount!,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
if (createdAtFormattedDate != null)
|
||||
Text(
|
||||
createdAtFormattedDate!,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
if (formattedReceiveAmount != null)
|
||||
Text(
|
||||
formattedReceiveAmount!,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
@@ -82,4 +115,54 @@ class OrderRow extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Color _statusColor(BuildContext context, TradeState status) {
|
||||
switch (status) {
|
||||
case TradeState.complete:
|
||||
case TradeState.completed:
|
||||
case TradeState.finished:
|
||||
case TradeState.success:
|
||||
case TradeState.settled:
|
||||
return PaletteDark.brightGreen;
|
||||
case TradeState.failed:
|
||||
case TradeState.expired:
|
||||
case TradeState.notFound:
|
||||
return Palette.darkRed;
|
||||
default:
|
||||
return const Color(0xffff6600);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _ProviderIcon extends StatelessWidget {
|
||||
const _ProviderIcon({required this.path, required this.title});
|
||||
|
||||
final String path;
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (path.isNotEmpty) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
child: ImageUtil.getImageFromPath(imagePath: path, height: 36, width: 36),
|
||||
);
|
||||
}
|
||||
return Container(
|
||||
height: 36,
|
||||
width: 36,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
title.isNotEmpty ? title.characters.first.toUpperCase() : '?',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:cake_wallet/cake_pay/src/models/cake_pay_order.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CakePayDetailsListCardItem extends StandartListItem {
|
||||
CakePayDetailsListCardItem({
|
||||
required String title,
|
||||
required String value,
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
this.price,
|
||||
this.quantity,
|
||||
required this.from,
|
||||
required this.to,
|
||||
required this.onTap,
|
||||
required this.cards,
|
||||
}) : super(title: title, value: value);
|
||||
|
||||
final String id;
|
||||
final String createdAt;
|
||||
final String? price;
|
||||
final String? quantity;
|
||||
final String from;
|
||||
final String to;
|
||||
final void Function(BuildContext) onTap;
|
||||
final List<OrderCard> cards;
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
import 'package:cake_wallet/src/screens/order_details/cake_pay_detail_list_card_item.dart';
|
||||
import 'package:cake_wallet/src/screens/trade_details/trade_details_status_item.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_pay_order_list_card.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list_status_row.dart';
|
||||
import 'package:cake_wallet/themes/core/material_base_theme.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/view_model/order_details_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -19,24 +24,25 @@ class OrderDetailsPage extends BasePage {
|
||||
final OrderDetailsViewModel orderDetailsViewModel;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) =>
|
||||
OrderDetailsPageBody(orderDetailsViewModel);
|
||||
Widget body(BuildContext context) => OrderDetailsPageBody(orderDetailsViewModel, currentTheme);
|
||||
}
|
||||
|
||||
class OrderDetailsPageBody extends StatefulWidget {
|
||||
OrderDetailsPageBody(this.orderDetailsViewModel);
|
||||
OrderDetailsPageBody(this.orderDetailsViewModel, this.currentTheme);
|
||||
|
||||
final OrderDetailsViewModel orderDetailsViewModel;
|
||||
final MaterialThemeBase currentTheme;
|
||||
|
||||
@override
|
||||
OrderDetailsPageBodyState createState() =>
|
||||
OrderDetailsPageBodyState(orderDetailsViewModel);
|
||||
OrderDetailsPageBodyState(orderDetailsViewModel, currentTheme);
|
||||
}
|
||||
|
||||
class OrderDetailsPageBodyState extends State<OrderDetailsPageBody> {
|
||||
OrderDetailsPageBodyState(this.orderDetailsViewModel);
|
||||
OrderDetailsPageBodyState(this.orderDetailsViewModel, this.currentTheme);
|
||||
|
||||
final OrderDetailsViewModel orderDetailsViewModel;
|
||||
final MaterialThemeBase currentTheme;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -47,28 +53,45 @@ class OrderDetailsPageBodyState extends State<OrderDetailsPageBody> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(builder: (_) {
|
||||
final itemsCount = orderDetailsViewModel.items.length;
|
||||
return SectionStandardList(
|
||||
sectionCount: 1,
|
||||
itemCounter: (int _) => orderDetailsViewModel.items.length,
|
||||
itemCounter: (int _) => itemsCount,
|
||||
itemBuilder: (__, index) {
|
||||
final item = orderDetailsViewModel.items[index];
|
||||
|
||||
if (item is DetailsListStatusItem) {
|
||||
return StandardListStatusRow(title: item.title, value: item.value);
|
||||
}
|
||||
|
||||
if (item is CakePayDetailsListCardItem) {
|
||||
final cardImagePath = (item.cards.isNotEmpty) ? item.cards.first.cardImagePath : null;
|
||||
|
||||
return CakePayOrderListCard(
|
||||
id: item.id,
|
||||
create: item.createdAt,
|
||||
price: item.price,
|
||||
quantity: item.quantity,
|
||||
pair: '${item.from} → ${item.to}',
|
||||
currentTheme: widget.currentTheme.type,
|
||||
onTap: item.onTap,
|
||||
backgroundImage: cardImagePath,
|
||||
);
|
||||
}
|
||||
|
||||
if (item is TrackTradeListItem) {
|
||||
return GestureDetector(
|
||||
onTap: item.onTap,
|
||||
child: ListRow(
|
||||
title: '${item.title}', value: '${item.value}'));
|
||||
child: ListRow(title: '${item.title}', value: '${item.value}'));
|
||||
} else {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: '${item.value}'));
|
||||
showBar<void>(context, S.of(context).copied_to_clipboard);
|
||||
},
|
||||
child: ListRow(
|
||||
title: '${item.title}', value: '${item.value}'));
|
||||
child: ListRow(title: '${item.title}', value: '${item.value}'));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/themes/core/material_base_theme.dart';
|
||||
|
||||
class CakePayOrderListCard extends StatelessWidget {
|
||||
const CakePayOrderListCard({
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.create,
|
||||
this.quantity,
|
||||
this.price,
|
||||
required this.pair,
|
||||
required this.onTap,
|
||||
required this.currentTheme,
|
||||
this.backgroundImage,
|
||||
this.aspectRatio = 16 / 9,
|
||||
}) : super(key: key);
|
||||
|
||||
final String id;
|
||||
final String create;
|
||||
final String? quantity;
|
||||
final String? price;
|
||||
final String pair;
|
||||
final ThemeType currentTheme;
|
||||
final void Function(BuildContext) onTap;
|
||||
final String? backgroundImage;
|
||||
final double aspectRatio;
|
||||
|
||||
ImageProvider? _imageProviderFor(String? path) {
|
||||
if (path == null || path.isEmpty) return null;
|
||||
if (path.startsWith('http://') || path.startsWith('https://')) return NetworkImage(path);
|
||||
return AssetImage(path);
|
||||
}
|
||||
|
||||
Color _labelBackground(BuildContext context) {
|
||||
final surface = Theme.of(context).colorScheme.surface;
|
||||
return surface.withOpacity(0.75);
|
||||
}
|
||||
|
||||
Color _titleLabelBackground(BuildContext context) {
|
||||
final surface = Theme.of(context).colorScheme.surface;
|
||||
return surface.withOpacity(0.85);
|
||||
}
|
||||
|
||||
Widget _labelBox(
|
||||
BuildContext context, {
|
||||
required String text,
|
||||
EdgeInsets padding = const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
double radius = 10,
|
||||
Color? background,
|
||||
TextStyle? style,
|
||||
}) {
|
||||
return Container(
|
||||
padding: padding,
|
||||
decoration: BoxDecoration(
|
||||
color: background ?? _labelBackground(context),
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: style ??
|
||||
Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final image = _imageProviderFor(backgroundImage);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
onTap: () => onTap(context),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: AspectRatio(
|
||||
aspectRatio: aspectRatio,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
if (image != null)
|
||||
Image(image: image, fit: BoxFit.cover)
|
||||
else
|
||||
Container(color: Theme.of(context).colorScheme.surfaceContainerHighest),
|
||||
if (image != null)
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
stops: [0.0, 0.6, 1.0],
|
||||
colors: [Colors.transparent, Colors.black26, Colors.black54],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(14.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_labelBox(
|
||||
context,
|
||||
text: 'Create • $create',
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
radius: 8,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 10,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
_labelBox(
|
||||
context,
|
||||
text: 'Qty • $quantity Total • $price',
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
radius: 8,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 10,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
_labelBox(
|
||||
context,
|
||||
text: pair,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
radius: 12,
|
||||
background: _titleLabelBackground(context),
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
_labelBox(
|
||||
context,
|
||||
text: 'Order • $id',
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
radius: 10,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IgnorePointer(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(currentTheme == ThemeType.dark ? 0.08 : 0.12),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user