From d9d8f9a17cce574eb17eb42bf13b682bacc73395 Mon Sep 17 00:00:00 2001 From: Justin Enerio Date: Tue, 5 Dec 2023 04:29:48 +0800 Subject: [PATCH] feat: update offramp status screen (#1154) Co-authored-by: ookami-kb --- .../espressocash_app/lib/core/currency.dart | 18 ++ packages/espressocash_app/lib/data/db/db.dart | 12 +- .../activities/widgets/off_ramp_tile.dart | 62 ++-- .../features/profile/screens/help_screen.dart | 8 +- .../features/profile/widgets/extensions.dart | 12 + .../scalex_off_ramp_order_watcher.dart | 12 +- .../ramp/screens/off_ramp_order_screen.dart | 290 ++++++++++++++---- .../ramp/services/off_ramp_order_service.dart | 61 +++- .../src/widgets/off_ramp_confirmation.dart | 166 ++++++++++ .../ramp/src/widgets/partners/scalex.dart | 7 + .../ramp/widgets/off_ramp_order_details.dart | 63 ++++ .../espressocash_app/lib/l10n/intl_en.arb | 58 +++- .../espressocash_app/lib/storybook/main.dart | 2 + .../screens/off_ramp_order_screen.dart | 31 ++ packages/espressocash_app/lib/ui/button.dart | 2 +- .../espressocash_app/lib/ui/timeline.dart | 32 +- .../moor_schemas/moor_schema_v43.json | 1 + 17 files changed, 718 insertions(+), 119 deletions(-) create mode 100644 packages/espressocash_app/lib/features/profile/widgets/extensions.dart create mode 100644 packages/espressocash_app/lib/features/ramp/src/widgets/off_ramp_confirmation.dart create mode 100644 packages/espressocash_app/lib/features/ramp/widgets/off_ramp_order_details.dart create mode 100644 packages/espressocash_app/lib/storybook/stories/screens/off_ramp_order_screen.dart create mode 100644 packages/espressocash_app/moor_schemas/moor_schema_v43.json diff --git a/packages/espressocash_app/lib/core/currency.dart b/packages/espressocash_app/lib/core/currency.dart index 9c9d5b5697..d3fe16981d 100644 --- a/packages/espressocash_app/lib/core/currency.dart +++ b/packages/espressocash_app/lib/core/currency.dart @@ -35,6 +35,13 @@ sealed class Currency with _$Currency { decimals: 2, ); + static const FiatCurrency ngn = FiatCurrency( + symbol: 'NGN', + sign: '₦', + name: 'Nigerian Naira', + decimals: 2, + ); + String get name => switch (this) { FiatCurrency(:final name) => name, CryptoCurrency(:final token) => token.name, @@ -55,3 +62,14 @@ sealed class Currency with _$Currency { } const defaultFiatCurrency = Currency.usd; + +FiatCurrency currencyFromString(String currency) { + switch (currency) { + case 'USD': + return Currency.usd; + case 'NGN': + return Currency.ngn; + default: + return defaultFiatCurrency; + } +} diff --git a/packages/espressocash_app/lib/data/db/db.dart b/packages/espressocash_app/lib/data/db/db.dart index d20120818e..1c4ae0340c 100644 --- a/packages/espressocash_app/lib/data/db/db.dart +++ b/packages/espressocash_app/lib/data/db/db.dart @@ -29,7 +29,7 @@ class OutgoingTransferRows extends Table { Set> get primaryKey => {id}; } -const int latestVersion = 42; +const int latestVersion = 43; const _tables = [ OutgoingTransferRows, @@ -179,6 +179,11 @@ class MyDatabase extends _$MyDatabase { if (from >= 40 && from < 42) { await m.addColumn(offRampOrderRows, offRampOrderRows.partner); } + if (from >= 40 && from < 43) { + await m.addColumn(offRampOrderRows, offRampOrderRows.resolvedAt); + await m.addColumn(offRampOrderRows, offRampOrderRows.receiveAmount); + await m.addColumn(offRampOrderRows, offRampOrderRows.fiatSymbol); + } }, ); @@ -232,6 +237,9 @@ class OffRampOrderRows extends Table with AmountMixin, EntityMixin { TextColumn get transaction => text()(); TextColumn get depositAddress => text()(); Int64Column get slot => int64()(); + DateTimeColumn get resolvedAt => dateTime().nullable()(); + IntColumn get receiveAmount => integer().nullable()(); + TextColumn get fiatSymbol => text().nullable()(); TextColumn get partner => textEnum().withDefault(const Constant('kado'))(); } @@ -242,7 +250,9 @@ enum OffRampOrderStatus { depositTxReady, sendingDepositTx, depositError, + depositTxConfirmError, waitingForPartner, failure, completed, + cancelled, } diff --git a/packages/espressocash_app/lib/features/activities/widgets/off_ramp_tile.dart b/packages/espressocash_app/lib/features/activities/widgets/off_ramp_tile.dart index 19e4a77b20..9a15bd36c4 100644 --- a/packages/espressocash_app/lib/features/activities/widgets/off_ramp_tile.dart +++ b/packages/espressocash_app/lib/features/activities/widgets/off_ramp_tile.dart @@ -5,52 +5,46 @@ import 'package:flutter/widgets.dart'; import '../../../core/presentation/format_amount.dart'; import '../../../core/presentation/format_date.dart'; import '../../../data/db/db.dart'; -import '../../../di.dart'; import '../../../gen/assets.gen.dart'; import '../../../l10n/device_locale.dart'; import '../../../l10n/l10n.dart'; import '../../ramp/screens/off_ramp_order_screen.dart'; -import '../../ramp/services/off_ramp_order_service.dart'; +import '../../ramp/widgets/off_ramp_order_details.dart'; import '../models/activity.dart'; import 'activity_tile.dart'; -class OffRampTile extends StatefulWidget { +class OffRampTile extends StatelessWidget { const OffRampTile({super.key, required this.activity}); final OffRampActivity activity; @override - State createState() => _OffRampTileState(); -} - -class _OffRampTileState extends State { - late final _stream = sl().watch(widget.activity.id); - - @override - Widget build(BuildContext context) => StreamBuilder( - stream: _stream, - builder: (context, snpahost) { - final order = snpahost.data; - - return CpActivityTile( - title: order?.amount.let( - (amount) => context.l10n.activities_lblSoldToken( - amount.format( - context.locale, - maxDecimals: 2, - roundInteger: true, - ), + Widget build(BuildContext context) => OffRampOrderDetails( + orderId: activity.id, + builder: (context, order) => CpActivityTile( + title: order?.amount.let( + (amount) => context.l10n.activities_lblSoldToken( + amount.format( + context.locale, + maxDecimals: 2, + roundInteger: true, ), - ) ?? - '', - icon: Assets.icons.paymentIcon.svg(), - status: order?.status == OffRampOrderStatus.completed - ? CpActivityTileStatus.success - : CpActivityTileStatus.inProgress, - timestamp: context.formatDate(widget.activity.created), - onTap: () => context.router - .push(OffRampOrderScreen.route(orderId: widget.activity.id)), - ); - }, + ), + ) ?? + '', + incomingAmount: order?.receiveAmount?.format( + context.locale, + maxDecimals: 2, + ), + icon: Assets.icons.paymentIcon.svg(), + status: order?.status == OffRampOrderStatus.completed + ? CpActivityTileStatus.success + : order?.status == OffRampOrderStatus.failure + ? CpActivityTileStatus.failure + : CpActivityTileStatus.inProgress, + timestamp: context.formatDate(activity.created), + onTap: () => context.router + .push(OffRampOrderScreen.route(orderId: activity.id)), + ), ); } diff --git a/packages/espressocash_app/lib/features/profile/screens/help_screen.dart b/packages/espressocash_app/lib/features/profile/screens/help_screen.dart index 5686ede868..e94efc5565 100644 --- a/packages/espressocash_app/lib/features/profile/screens/help_screen.dart +++ b/packages/espressocash_app/lib/features/profile/screens/help_screen.dart @@ -1,12 +1,12 @@ import 'package:auto_route/annotations.dart'; import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; import '../../../../../config.dart'; import '../../../../../core/presentation/utils.dart'; import '../../../../../l10n/l10n.dart'; import '../../../../../ui/app_bar.dart'; import '../../../routes.gr.dart'; +import '../widgets/extensions.dart'; import '../widgets/profile_button.dart'; @RoutePage() @@ -32,11 +32,7 @@ class HelpScreen extends StatelessWidget { ), ProfileButton( label: context.l10n.contactUs, - onPressed: () { - final email = Uri.encodeComponent(contactEmail); - final mail = Uri.parse('mailto:$email'); - launchUrl(mail); - }, + onPressed: context.launchContactUs, ), ], ), diff --git a/packages/espressocash_app/lib/features/profile/widgets/extensions.dart b/packages/espressocash_app/lib/features/profile/widgets/extensions.dart new file mode 100644 index 0000000000..35786469c5 --- /dev/null +++ b/packages/espressocash_app/lib/features/profile/widgets/extensions.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../../config.dart'; + +extension BuildContextExt on BuildContext { + void launchContactUs() { + final email = Uri.encodeComponent(contactEmail); + final mail = Uri.parse('mailto:$email'); + launchUrl(mail); + } +} diff --git a/packages/espressocash_app/lib/features/ramp/scalex/services/scalex_off_ramp_order_watcher.dart b/packages/espressocash_app/lib/features/ramp/scalex/services/scalex_off_ramp_order_watcher.dart index 6d92782abd..44cf409e44 100644 --- a/packages/espressocash_app/lib/features/ramp/scalex/services/scalex_off_ramp_order_watcher.dart +++ b/packages/espressocash_app/lib/features/ramp/scalex/services/scalex_off_ramp_order_watcher.dart @@ -26,9 +26,11 @@ class ScalexOffRampOrderWatcher implements RampWatcher { .whereNotNull() .asyncMap((order) => _client.fetchStatus(order.partnerOrderId)) .listen((status) async { - final statement = _db.update(_db.onRampOrderRows) + final statement = _db.update(_db.offRampOrderRows) ..where( - (tbl) => tbl.id.equals(orderId) & tbl.isCompleted.equals(false), + (tbl) => + tbl.id.equals(orderId) & + tbl.status.equals(OffRampOrderStatus.waitingForPartner.name), ); final isCompleted = status == ScalexOrderStatus.completed; @@ -36,10 +38,12 @@ class ScalexOffRampOrderWatcher implements RampWatcher { if (isCompleted) await _subscription?.cancel(); await statement.write( - OnRampOrderRowsCompanion( + OffRampOrderRowsCompanion( humanStatus: Value(status.name), machineStatus: Value(status.name), - isCompleted: Value(isCompleted), + status: isCompleted + ? const Value(OffRampOrderStatus.completed) + : const Value(OffRampOrderStatus.waitingForPartner), ), ); }); diff --git a/packages/espressocash_app/lib/features/ramp/screens/off_ramp_order_screen.dart b/packages/espressocash_app/lib/features/ramp/screens/off_ramp_order_screen.dart index 7ecaef451b..3fe53765ca 100644 --- a/packages/espressocash_app/lib/features/ramp/screens/off_ramp_order_screen.dart +++ b/packages/espressocash_app/lib/features/ramp/screens/off_ramp_order_screen.dart @@ -1,18 +1,27 @@ +import 'dart:async'; + import 'package:auto_route/auto_route.dart'; -import 'package:flutter/widgets.dart'; +import 'package:dfunc/dfunc.dart'; +import 'package:flutter/material.dart'; +import '../../../core/presentation/format_amount.dart'; +import '../../../core/presentation/format_date.dart'; import '../../../data/db/db.dart'; import '../../../di.dart'; +import '../../../l10n/device_locale.dart'; import '../../../l10n/l10n.dart'; import '../../../routes.gr.dart'; import '../../../ui/button.dart'; +import '../../../ui/content_padding.dart'; +import '../../../ui/dialogs.dart'; import '../../../ui/status_screen.dart'; import '../../../ui/status_widget.dart'; -import '../kado/services/kado_off_ramp_order_watcher.dart'; -import '../models/ramp_partner.dart'; -import '../scalex/services/scalex_off_ramp_order_watcher.dart'; +import '../../../ui/text_button.dart'; +import '../../../ui/timeline.dart'; +import '../../profile/widgets/extensions.dart'; +import '../../transactions/widgets/transfer_progress.dart'; import '../services/off_ramp_order_service.dart'; -import '../src/models/ramp_watcher.dart'; +import '../src/widgets/off_ramp_confirmation.dart'; @RoutePage() class OffRampOrderScreen extends StatefulWidget { @@ -28,35 +37,17 @@ class OffRampOrderScreen extends StatefulWidget { class _OffRampOrderScreenState extends State { late final Stream _stream; - RampWatcher? _watcher; + StreamSubscription? _confirmationSubscription; @override void initState() { super.initState(); _stream = sl().watch(widget.orderId); - - _initWatcher(); - } - - Future _initWatcher() async { - if (_watcher != null) return; - - final onRamp = await _stream.first; - - _watcher = switch (onRamp.partner) { - RampPartner.kado => sl(), - RampPartner.scalex => sl(), - RampPartner.rampNetwork || - RampPartner.coinflow || - RampPartner.guardarian => - throw ArgumentError('Not implemented'), - } - ..watch(widget.orderId); } @override void dispose() { - _watcher?.close(); + _confirmationSubscription?.cancel(); super.dispose(); } @@ -66,40 +57,221 @@ class _OffRampOrderScreenState extends State { builder: (context, snapshot) { final order = snapshot.data; - return StatusScreen( - statusType: order?.status == OffRampOrderStatus.completed - ? CpStatusType.success - : order?.status == OffRampOrderStatus.depositError - ? CpStatusType.error - : CpStatusType.info, - statusContent: Text(order?.status.name ?? ''), - content: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Column( - children: [ - const SizedBox(height: 140), - if (order?.status == OffRampOrderStatus.depositError) - CpButton( - size: CpButtonSize.big, - width: double.infinity, - text: context.l10n.retry, - onPressed: () { - sl().retry(widget.orderId); - }, - ), - if (order?.status == OffRampOrderStatus.depositTxRequired) - CpButton( - size: CpButtonSize.big, - width: double.infinity, - text: 'Create deposit tx', - onPressed: () { - sl().retry(widget.orderId); - }, - ), - ], - ), - ), - ); + return order == null + ? TransferProgress(onBack: () => context.router.pop()) + : OffRampOrderScreenContent(order: order); }, ); } + +class OffRampOrderScreenContent extends StatelessWidget { + const OffRampOrderScreenContent({ + super.key, + required this.order, + }); + + final OffRampOrder order; + + @override + Widget build(BuildContext context) { + if (order.status == OffRampOrderStatus.depositTxRequired) { + return OffRampConfirmation(order: order); + } + + final locale = DeviceLocale.localeOf(context); + + void onCancel() => showConfirmationDialog( + context, + title: context.l10n.offRampCancelTitle, + message: context.l10n.offRampCancelSubtitle, + onConfirm: () { + sl().cancel(order.id); + }, + ); + + final retryButton = CpButton( + size: CpButtonSize.big, + width: double.infinity, + text: context.l10n.retry, + onPressed: () { + sl().retry(order.id); + }, + ); + + final contactUsButton = CpButton( + size: CpButtonSize.big, + width: double.infinity, + text: context.l10n.contactUs, + onPressed: context.launchContactUs, + ); + + final cancelButton = Padding( + padding: EdgeInsets.only( + top: 24, + bottom: MediaQuery.paddingOf(context).bottom + 16, + ), + child: CpTextButton( + text: context.l10n.outgoingSplitKeyPayments_btnCancel, + variant: CpTextButtonVariant.light, + onPressed: onCancel, + ), + ); + + final CpStatusType statusType = switch (order.status) { + OffRampOrderStatus.depositTxRequired || + OffRampOrderStatus.creatingDepositTx || + OffRampOrderStatus.depositTxReady || + OffRampOrderStatus.sendingDepositTx || + OffRampOrderStatus.waitingForPartner => + CpStatusType.info, + OffRampOrderStatus.depositError || + OffRampOrderStatus.depositTxConfirmError || + OffRampOrderStatus.failure => + CpStatusType.error, + OffRampOrderStatus.completed => CpStatusType.success, + OffRampOrderStatus.cancelled => CpStatusType.neutral, + }; + + final String? statusTitle = order.status == OffRampOrderStatus.completed + ? context.l10n.transferSuccessTitle + : null; + + final String statusContent = switch (order.status) { + OffRampOrderStatus.depositTxRequired || + OffRampOrderStatus.creatingDepositTx || + OffRampOrderStatus.depositTxReady || + OffRampOrderStatus.sendingDepositTx => + context.l10n.offRampWithdrawOngoing( + order.amount.format(locale), + ), + OffRampOrderStatus.waitingForPartner => + context.l10n.offRampWaitingForPartner, + OffRampOrderStatus.depositTxConfirmError || + OffRampOrderStatus.depositError => + context.l10n.offRampDepositError, + OffRampOrderStatus.failure => context.l10n.offRampWithdrawalFailure, + OffRampOrderStatus.completed => context.l10n.offRampWithdrawSuccess, + OffRampOrderStatus.cancelled => context.l10n.offRampWithdrawCancelled( + order.amount.format(locale), + ), + }; + + final CpTimelineStatus timelineStatus = switch (order.status) { + OffRampOrderStatus.depositTxRequired || + OffRampOrderStatus.creatingDepositTx || + OffRampOrderStatus.depositTxReady || + OffRampOrderStatus.sendingDepositTx || + OffRampOrderStatus.waitingForPartner => + CpTimelineStatus.inProgress, + OffRampOrderStatus.depositTxConfirmError || + OffRampOrderStatus.depositError || + OffRampOrderStatus.failure => + CpTimelineStatus.failure, + OffRampOrderStatus.completed => CpTimelineStatus.success, + OffRampOrderStatus.cancelled => CpTimelineStatus.neutral, + }; + + final animated = timelineStatus == CpTimelineStatus.inProgress && + order.status != OffRampOrderStatus.waitingForPartner; + + final int activeItem = switch (order.status) { + OffRampOrderStatus.depositTxRequired || + OffRampOrderStatus.creatingDepositTx || + OffRampOrderStatus.depositTxReady || + OffRampOrderStatus.sendingDepositTx || + OffRampOrderStatus.waitingForPartner || + OffRampOrderStatus.depositError || + OffRampOrderStatus.depositTxConfirmError || + OffRampOrderStatus.cancelled => + 1, + OffRampOrderStatus.failure || OffRampOrderStatus.completed => 2, + }; + + final withdrawInitiated = CpTimelineItem( + title: context.l10n.offRampWithdrawInitiated, + trailing: order.amount.format(locale), + subtitle: order.created.let((t) => context.formatDate(t)), + ); + final amountSent = CpTimelineItem( + title: context.l10n.offRampWithdrawSent, + ); + final paymentSuccess = CpTimelineItem( + title: context.l10n.offRampWithdrawReceived, + subtitle: order.resolved?.let((t) => context.formatDate(t)), + ); + final paymentCanceled = CpTimelineItem( + title: context.l10n.offRampWithdrawCancelledTitle, + subtitle: order.resolved?.let((t) => context.formatDate(t)), + ); + + final normalItems = [ + withdrawInitiated, + amountSent, + paymentSuccess, + ]; + final cancelingItems = [ + withdrawInitiated, + paymentCanceled, + ]; + + final items = order.status == OffRampOrderStatus.cancelled + ? cancelingItems + : normalItems; + + final Widget? primaryButton = switch (order.status) { + OffRampOrderStatus.depositError || + OffRampOrderStatus.depositTxConfirmError => + retryButton, + OffRampOrderStatus.failure => contactUsButton, + OffRampOrderStatus.depositTxRequired || + OffRampOrderStatus.creatingDepositTx || + OffRampOrderStatus.depositTxReady || + OffRampOrderStatus.sendingDepositTx || + OffRampOrderStatus.waitingForPartner || + OffRampOrderStatus.completed || + OffRampOrderStatus.cancelled => + null, + }; + + return StatusScreen( + title: context.l10n.offRampWithdrawTitle.toUpperCase(), + statusType: statusType, + statusTitle: statusTitle?.let(Text.new), + statusContent: Text(statusContent), + content: CpContentPadding( + child: Column( + children: [ + const Spacer(flex: 1), + CpTimeline( + status: timelineStatus, + items: items, + active: activeItem, + animated: animated, + ), + const Spacer(flex: 4), + SizedBox( + height: CpButtonSize.big.height, + child: Center( + child: Text( + context.l10n.orderId(order.partnerOrderId), + style: const TextStyle( + color: Color(0xFF979593), + fontSize: 14, + ), + ), + ), + ), + if (primaryButton != null) ...[ + const SizedBox(height: 12), + primaryButton, + ], + Opacity( + opacity: order.status == OffRampOrderStatus.depositError ? 1 : 0, + child: cancelButton, + ), + ], + ), + ), + ); + } +} diff --git a/packages/espressocash_app/lib/features/ramp/services/off_ramp_order_service.dart b/packages/espressocash_app/lib/features/ramp/services/off_ramp_order_service.dart index 2c2f45eb8a..3046d8aeed 100644 --- a/packages/espressocash_app/lib/features/ramp/services/off_ramp_order_service.dart +++ b/packages/espressocash_app/lib/features/ramp/services/off_ramp_order_service.dart @@ -30,6 +30,9 @@ typedef OffRampOrder = ({ OffRampOrderStatus status, CryptoAmount amount, RampPartner partner, + DateTime? resolved, + FiatAmount? receiveAmount, + String partnerOrderId, }); @Singleton(scope: authScope) @@ -88,12 +91,23 @@ class OffRampOrderService implements Disposable { ), ); + final receiveAmount = row.receiveAmount?.let( + (it) => Amount( + value: it, + // ignore: avoid-non-null-assertion, checked amount + currency: currencyFromString(row.fiatSymbol!), + ) as FiatAmount, + ); + return ( id: row.id, created: row.created, status: row.status, amount: amount, partner: row.partner, + resolved: row.resolvedAt, + receiveAmount: receiveAmount, + partnerOrderId: row.partnerOrderId, ); }); } @@ -114,6 +128,7 @@ class OffRampOrderService implements Disposable { ), ); case OffRampOrderStatus.depositError: + case OffRampOrderStatus.depositTxConfirmError: final tx = order.transaction; if (tx.isEmpty) { await updateQuery.write( @@ -134,6 +149,36 @@ class OffRampOrderService implements Disposable { case OffRampOrderStatus.waitingForPartner: case OffRampOrderStatus.failure: case OffRampOrderStatus.completed: + case OffRampOrderStatus.cancelled: + break; + } + } + + Future cancel(String orderId) async { + final query = _db.select(_db.offRampOrderRows) + ..where((tbl) => tbl.id.equals(orderId)); + final order = await query.getSingle(); + + final updateQuery = _db.update(_db.offRampOrderRows) + ..where((tbl) => tbl.id.equals(orderId)); + + switch (order.status) { + case OffRampOrderStatus.depositError: + await updateQuery.write( + const OffRampOrderRowsCompanion( + status: Value(OffRampOrderStatus.cancelled), + ), + ); + + case OffRampOrderStatus.depositTxRequired: + case OffRampOrderStatus.creatingDepositTx: + case OffRampOrderStatus.depositTxReady: + case OffRampOrderStatus.sendingDepositTx: + case OffRampOrderStatus.waitingForPartner: + case OffRampOrderStatus.failure: + case OffRampOrderStatus.completed: + case OffRampOrderStatus.cancelled: + case OffRampOrderStatus.depositTxConfirmError: break; } } @@ -144,6 +189,7 @@ class OffRampOrderService implements Disposable { required CryptoAmount amount, required RampPartner partner, required String depositAddress, + FiatAmount? receiveAmount, }) => tryEitherAsync((_) async { { @@ -160,6 +206,8 @@ class OffRampOrderService implements Disposable { status: OffRampOrderStatus.depositTxRequired, depositAddress: depositAddress, partner: partner, + receiveAmount: receiveAmount?.value, + fiatSymbol: receiveAmount?.currency.symbol, ); await _db.into(_db.offRampOrderRows).insert(order); @@ -177,6 +225,7 @@ class OffRampOrderService implements Disposable { switch (order.status) { case OffRampOrderStatus.depositTxRequired: case OffRampOrderStatus.depositError: + case OffRampOrderStatus.depositTxConfirmError: case OffRampOrderStatus.waitingForPartner: return const Stream.empty(); case OffRampOrderStatus.creatingDepositTx: @@ -201,6 +250,8 @@ class OffRampOrderService implements Disposable { status: Value(OffRampOrderStatus.sendingDepositTx), ), ); + case OffRampOrderStatus.cancelled: + return Stream.fromIterable([_cancelled]); case OffRampOrderStatus.failure: case OffRampOrderStatus.completed: _subscriptions[orderId]?.cancel(); @@ -285,6 +336,12 @@ class OffRampOrderService implements Disposable { } } - static const _depositError = - OffRampOrderRowsCompanion(status: Value(OffRampOrderStatus.depositError)); + static final _cancelled = OffRampOrderRowsCompanion( + status: const Value(OffRampOrderStatus.cancelled), + resolvedAt: Value(DateTime.now()), + ); + + static const _depositError = OffRampOrderRowsCompanion( + status: Value(OffRampOrderStatus.depositTxConfirmError), + ); } diff --git a/packages/espressocash_app/lib/features/ramp/src/widgets/off_ramp_confirmation.dart b/packages/espressocash_app/lib/features/ramp/src/widgets/off_ramp_confirmation.dart new file mode 100644 index 0000000000..382a6a63e0 --- /dev/null +++ b/packages/espressocash_app/lib/features/ramp/src/widgets/off_ramp_confirmation.dart @@ -0,0 +1,166 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; + +import '../../../../core/amount.dart'; +import '../../../../core/currency.dart'; +import '../../../../core/fee_label.dart'; +import '../../../../core/presentation/format_amount.dart'; +import '../../../../di.dart'; +import '../../../../l10n/device_locale.dart'; +import '../../../../l10n/l10n.dart'; +import '../../../../ui/app_bar.dart'; +import '../../../../ui/back_button.dart'; +import '../../../../ui/button.dart'; +import '../../../../ui/chip.dart'; +import '../../../../ui/content_padding.dart'; +import '../../../../ui/info_widget.dart'; +import '../../../../ui/theme.dart'; +import '../../services/off_ramp_order_service.dart'; + +class OffRampConfirmation extends StatelessWidget { + const OffRampConfirmation({ + super.key, + required this.order, + }); + + final OffRampOrder order; + + @override + Widget build(BuildContext context) => CpTheme.black( + child: Scaffold( + appBar: CpAppBar( + title: Text( + context.l10n.offRampWithdrawTitle.toUpperCase(), + style: const TextStyle( + fontWeight: FontWeight.w700, + fontSize: 17, + ), + ), + leading: CpBackButton( + onPressed: () => context.router.popUntilRoot(), + ), + ), + body: CpContentPadding( + child: _TokenCreateLinkContent( + withdrawAmount: order.amount, + receiveAmount: order.receiveAmount, + fee: Amount.fromDecimal( + // TODO(KB): Replace with fee from backend. + value: Decimal.parse('0.1'), + currency: Currency.usdc, + ), + ), + ), + bottomNavigationBar: SafeArea( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const FeeLabel(type: FeeType.splitKey()), + const SizedBox(height: 21), + CpButton( + width: double.infinity, + onPressed: () => sl().retry(order.id), + text: context.l10n.ramp_btnContinue, + ), + ], + ), + ), + ), + ), + ); +} + +class _TokenCreateLinkContent extends StatelessWidget { + const _TokenCreateLinkContent({ + required this.withdrawAmount, + required this.fee, + this.receiveAmount, + }); + + final Amount withdrawAmount; + final Amount? receiveAmount; + final Amount fee; + + @override + Widget build(BuildContext context) => Column( + children: [ + const SizedBox(height: 60), + _AmountView( + withdrawAmount: withdrawAmount, + receiveAmount: receiveAmount, + ), + Padding( + padding: const EdgeInsets.all(16), + child: CpInfoWidget( + message: Text(context.l10n.offRampWithdrawNotice), + variant: CpInfoVariant.black, + ), + ), + const Spacer(), + ], + ); +} + +class _AmountView extends StatelessWidget { + const _AmountView({required this.withdrawAmount, this.receiveAmount}); + + final Amount withdrawAmount; + final Amount? receiveAmount; + + @override + Widget build(BuildContext context) { + final locale = DeviceLocale.localeOf(context); + final formattedAmount = withdrawAmount.format( + locale, + maxDecimals: withdrawAmount.currency.decimals, + ); + + final formattedReceiveAmount = receiveAmount?.format( + locale, + maxDecimals: withdrawAmount.currency.decimals, + ); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + FittedBox( + child: Text( + formattedAmount, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 55, + ), + ), + ), + Container( + margin: const EdgeInsets.symmetric(vertical: 24), + child: CpChip( + // TODO(KB): Check if needed + // ignore: avoid-single-child-column-or-row + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (formattedReceiveAmount != null) + FittedBox( + child: Text( + context.l10n + .offRampReceiveAmount(formattedReceiveAmount) + .toUpperCase(), + maxLines: 1, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ), + ], + ); + } +} diff --git a/packages/espressocash_app/lib/features/ramp/src/widgets/partners/scalex.dart b/packages/espressocash_app/lib/features/ramp/src/widgets/partners/scalex.dart index 6fb7f39ebc..7ee07ef612 100644 --- a/packages/espressocash_app/lib/features/ramp/src/widgets/partners/scalex.dart +++ b/packages/espressocash_app/lib/features/ramp/src/widgets/partners/scalex.dart @@ -72,6 +72,7 @@ extension BuildContextExt on BuildContext { case { 'reference': final String reference, 'from_amount': final num fromAmount, + 'to_amount': final num toAmount, // ignore: avoid-missing-interpolation, similar names 'address': final String address, }) { @@ -80,11 +81,17 @@ extension BuildContextExt on BuildContext { Amount.fromDecimal(value: decimal, currency: Currency.usdc) as CryptoAmount; + final receiveAmount = Amount.fromDecimal( + value: Decimal.parse(toAmount.toString()), + currency: Currency.ngn, + ) as FiatAmount; + await sl() .create( partnerOrderId: reference, amount: amount, partner: RampPartner.scalex, + receiveAmount: receiveAmount, depositAddress: address, ) .then((order) { diff --git a/packages/espressocash_app/lib/features/ramp/widgets/off_ramp_order_details.dart b/packages/espressocash_app/lib/features/ramp/widgets/off_ramp_order_details.dart new file mode 100644 index 0000000000..a35fbbfb19 --- /dev/null +++ b/packages/espressocash_app/lib/features/ramp/widgets/off_ramp_order_details.dart @@ -0,0 +1,63 @@ +import 'package:flutter/widgets.dart'; + +import '../../../di.dart'; +import '../kado/services/kado_off_ramp_order_watcher.dart'; +import '../models/ramp_partner.dart'; +import '../scalex/services/scalex_off_ramp_order_watcher.dart'; +import '../services/off_ramp_order_service.dart'; +import '../src/models/ramp_watcher.dart'; + +class OffRampOrderDetails extends StatefulWidget { + const OffRampOrderDetails({ + super.key, + required this.orderId, + required this.builder, + }); + + final String orderId; + final Widget Function(BuildContext context, OffRampOrder? order) builder; + + @override + State createState() => _OffRampOrderDetailsState(); +} + +class _OffRampOrderDetailsState extends State { + late final Stream _stream; + RampWatcher? _watcher; + + @override + void initState() { + super.initState(); + _stream = sl().watch(widget.orderId); + + _initWatcher(); + } + + Future _initWatcher() async { + if (_watcher != null) return; + + final ramp = await _stream.first; + + _watcher = switch (ramp.partner) { + RampPartner.kado => sl(), + RampPartner.scalex => sl(), + RampPartner.rampNetwork || + RampPartner.coinflow || + RampPartner.guardarian => + throw ArgumentError('Not implemented'), + } + ..watch(widget.orderId); + } + + @override + void dispose() { + _watcher?.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => StreamBuilder( + stream: _stream, + builder: (context, snapshot) => widget.builder(context, snapshot.data), + ); +} diff --git a/packages/espressocash_app/lib/l10n/intl_en.arb b/packages/espressocash_app/lib/l10n/intl_en.arb index 906b4e764f..9e70f73d9c 100644 --- a/packages/espressocash_app/lib/l10n/intl_en.arb +++ b/packages/espressocash_app/lib/l10n/intl_en.arb @@ -865,5 +865,61 @@ } }, "zeroAmountTitle": "Invalid amount", - "@zeroAmountTitle": {} + "@zeroAmountTitle": {}, + "offRampWithdrawTitle": "Withdrawal Progress", + "@offRampWithdrawTitle": {}, + "offRampCancelTitle": "Cancel Withdrawal", + "@offRampCancelTitle": {}, + "offRampCancelSubtitle": "Are you sure you want to cancel this withdrawal?", + "@offRampCancelSubtitle": {}, + "offRampWithdrawInitiated": "Withdrawal initiated", + "@offRampWithdrawInitiated": {}, + "offRampWithdrawSent": "USDC sent to partner", + "@offRampWithdrawSent": {}, + "offRampWithdrawReceived": "Money has been received", + "@offRampWithdrawReceived": {}, + "offRampWithdrawCancelledTitle": "Withdrawal has been canceled", + "@offRampWithdrawCancelledTitle": {}, + "offRampDepositError": "There was an issue with your withdrawal.", + "@offRampDepositError": {}, + "offRampWithdrawalFailure": "There was an issue with your withdrawal. Please contact us.", + "@offRampWithdrawalFailure": {}, + "offRampWaitingForPartner": "Your money is on the way", + "@offRampWaitingForPartner": {}, + "offRampWithdrawSuccess": "Your withdrawal was successful.", + "@offRampWithdrawSuccess": {}, + "offRampWithdrawOngoing": "Sending {amount}", + "@offRampWithdrawOngoing": { + "placeholders": { + "amount": { + "type": "String" + } + } + }, + "offRampWithdrawCancelled": "{amount} withdrawal successfully canceled.", + "@offRampWithdrawCancelled": { + "placeholders": { + "amount": { + "type": "String" + } + } + }, + "offRampWithdrawNotice": "Withdrawals can normally take up to 3 business days to receive.", + "@offRampWithdrawNotice": {}, + "offRampReceiveAmount": "You'll receive {value}", + "@offRampReceiveAmount": { + "placeholders": { + "value": { + "type": "String" + } + } + }, + "orderId": "Order ID: {orderId}", + "@orderId": { + "placeholders": { + "orderId": { + "type": "String" + } + } + } } diff --git a/packages/espressocash_app/lib/storybook/main.dart b/packages/espressocash_app/lib/storybook/main.dart index 8daf475b08..66601d902c 100644 --- a/packages/espressocash_app/lib/storybook/main.dart +++ b/packages/espressocash_app/lib/storybook/main.dart @@ -22,6 +22,7 @@ import 'stories/loader.dart'; import 'stories/navigation_bar.dart'; import 'stories/rounded_rectangle.dart'; import 'stories/screens/off_ramp_amount_screen.dart'; +import 'stories/screens/off_ramp_order_screen.dart'; import 'stories/screens/ramp_partner_select_screen.dart'; import 'stories/screens/wallet_main_screen.dart'; import 'stories/shake.dart'; @@ -68,6 +69,7 @@ class StorybookApp extends StatelessWidget { ), stories: [ offRampAmountScreenStory, + offRampOrderScreenStory, onRampPartnerSelectScreen, walletMainScreen, cpActivityTile, diff --git a/packages/espressocash_app/lib/storybook/stories/screens/off_ramp_order_screen.dart b/packages/espressocash_app/lib/storybook/stories/screens/off_ramp_order_screen.dart new file mode 100644 index 0000000000..e1ed23f883 --- /dev/null +++ b/packages/espressocash_app/lib/storybook/stories/screens/off_ramp_order_screen.dart @@ -0,0 +1,31 @@ +import 'package:storybook_flutter/storybook_flutter.dart'; + +import '../../../core/amount.dart'; +import '../../../core/currency.dart'; +import '../../../data/db/db.dart'; +import '../../../features/ramp/models/ramp_partner.dart'; +import '../../../features/ramp/screens/off_ramp_order_screen.dart'; +import '../../utils.dart'; + +final offRampOrderScreenStory = Story( + name: 'Screens/OffRampOrderScreen', + builder: (context) => OffRampOrderScreenContent( + order: ( + id: 'ORDER_ID', + created: DateTime.now(), + status: context.knobs.options( + label: 'Status', + initial: OffRampOrderStatus.depositTxRequired, + options: OffRampOrderStatus.values.toOptions(), + ), + amount: const CryptoAmount( + value: 10000000, + cryptoCurrency: Currency.usdc, + ), + receiveAmount: null, + partner: RampPartner.scalex, + resolved: null, + partnerOrderId: 'PARTNER_ORDER_ID', + ), + ), +); diff --git a/packages/espressocash_app/lib/ui/button.dart b/packages/espressocash_app/lib/ui/button.dart index cd0b8e693b..abf8e3cb73 100644 --- a/packages/espressocash_app/lib/ui/button.dart +++ b/packages/espressocash_app/lib/ui/button.dart @@ -166,7 +166,7 @@ extension on CpButtonAlignment { } } -extension on CpButtonSize { +extension CpButtonSizeExt on CpButtonSize { double get height { switch (this) { case CpButtonSize.normal: diff --git a/packages/espressocash_app/lib/ui/timeline.dart b/packages/espressocash_app/lib/ui/timeline.dart index 2c6a87e406..a788431e60 100644 --- a/packages/espressocash_app/lib/ui/timeline.dart +++ b/packages/espressocash_app/lib/ui/timeline.dart @@ -79,6 +79,8 @@ class _State extends State with SingleTickerProviderStateMixin { _AnimationTransformer? indicatorTransformer; _AnimationTransformer? connectorTransformer; + final isActive = index == widget.active; + if (widget.animated) { if (index == widget.active) { indicatorTransformer = _lowerIndicatorTransformer; @@ -111,7 +113,11 @@ class _State extends State with SingleTickerProviderStateMixin { ? Colors.white : CpColors.darkBackground, ), - child: index <= lastIconIndex ? widget.status.icon : null, + child: index <= lastIconIndex + ? isActive + ? widget.status.icon + : _successIcon + : null, ), ), ), @@ -302,23 +308,27 @@ extension on CpTimelineStatus { Widget get icon { switch (this) { case CpTimelineStatus.failure: - return const Icon( - Icons.close, - color: Colors.white, - size: 22, - ); + return _failIcon; case CpTimelineStatus.success: case CpTimelineStatus.inProgress: case CpTimelineStatus.neutral: - return const Icon( - Icons.check, - color: Colors.white, - size: 22, - ); + return _successIcon; } } } +const _successIcon = Icon( + Icons.check, + color: Colors.white, + size: 22, +); + +const _failIcon = Icon( + Icons.close, + color: Colors.white, + size: 22, +); + double _sinoidalTransformer(double value) => sin(2 * pi * value) / 2; double _connectorTransformer(double value) => value * _connectorHeight; diff --git a/packages/espressocash_app/moor_schemas/moor_schema_v43.json b/packages/espressocash_app/moor_schemas/moor_schema_v43.json new file mode 100644 index 0000000000..68fb7b4773 --- /dev/null +++ b/packages/espressocash_app/moor_schemas/moor_schema_v43.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.1.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"outgoing_transfer_rows","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created","getter_name":"created","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["id"]}},{"id":1,"references":[],"type":"table","data":{"name":"payment_request_rows","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created","getter_name":"created","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"dynamic_link","getter_name":"dynamicLink","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"state","getter_name":"state","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(PaymentRequestStateDto.values)","dart_type_name":"PaymentRequestStateDto"}},{"name":"transaction_id","getter_name":"transactionId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"recipient","getter_name":"recipient","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"amount","getter_name":"amount","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"splt_token","getter_name":"spltToken","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"reference","getter_name":"reference","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"message","getter_name":"message","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"memo","getter_name":"memo","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["id"]}},{"id":2,"references":[],"type":"table","data":{"name":"o_d_p_rows","was_declared_in_moor":false,"columns":[{"name":"amount","getter_name":"amount","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"token","getter_name":"token","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created","getter_name":"created","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"receiver","getter_name":"receiver","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"reference","getter_name":"reference","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"status","getter_name":"status","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(ODPStatusDto.values)","dart_type_name":"ODPStatusDto"}},{"name":"tx","getter_name":"tx","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx_id","getter_name":"txId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx_failure_reason","getter_name":"txFailureReason","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(TxFailureReason.values)","dart_type_name":"TxFailureReason"}},{"name":"slot","getter_name":"slot","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["id"]}},{"id":3,"references":[],"type":"table","data":{"name":"o_s_k_p_rows","was_declared_in_moor":false,"columns":[{"name":"amount","getter_name":"amount","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"token","getter_name":"token","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created","getter_name":"created","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"status","getter_name":"status","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(OSKPStatusDto.values)","dart_type_name":"OSKPStatusDto"}},{"name":"tx","getter_name":"tx","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx_id","getter_name":"txId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"withdraw_tx_id","getter_name":"withdrawTxId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"private_key","getter_name":"privateKey","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"link1","getter_name":"link1","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"link2","getter_name":"link2","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"link3","getter_name":"link3","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx_failure_reason","getter_name":"txFailureReason","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(TxFailureReason.values)","dart_type_name":"TxFailureReason"}},{"name":"cancel_tx","getter_name":"cancelTx","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"cancel_tx_id","getter_name":"cancelTxId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"generated_links_at","getter_name":"generatedLinksAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"resolved_at","getter_name":"resolvedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"slot","getter_name":"slot","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"api_version","getter_name":"apiVersion","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const Constant(0)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(OskpApiVersionDto.values)","dart_type_name":"OskpApiVersionDto"}}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["id"]}},{"id":4,"references":[],"type":"table","data":{"name":"i_s_k_p_rows","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created","getter_name":"created","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx","getter_name":"tx","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx_id","getter_name":"txId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx_failure_reason","getter_name":"txFailureReason","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(TxFailureReason.values)","dart_type_name":"TxFailureReason"}},{"name":"slot","getter_name":"slot","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"private_key","getter_name":"privateKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"status","getter_name":"status","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(ISKPStatusDto.values)","dart_type_name":"ISKPStatusDto"}},{"name":"api_version","getter_name":"apiVersion","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const Constant(0)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(IskpApiVersionDto.values)","dart_type_name":"IskpApiVersionDto"}}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["id"]}},{"id":5,"references":[],"type":"table","data":{"name":"swap_rows","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created","getter_name":"created","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx","getter_name":"tx","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx_id","getter_name":"txId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx_failure_reason","getter_name":"txFailureReason","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(TxFailureReason.values)","dart_type_name":"TxFailureReason"}},{"name":"slot","getter_name":"slot","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"status","getter_name":"status","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(SwapStatusDto.values)","dart_type_name":"SwapStatusDto"}},{"name":"amount","getter_name":"amount","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"token","getter_name":"token","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"input_mint","getter_name":"inputMint","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"output_mint","getter_name":"outputMint","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"slippage","getter_name":"slippage","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(SlippageDto.values)","dart_type_name":"SlippageDto"}}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["id"]}},{"id":6,"references":[],"type":"table","data":{"name":"transaction_rows","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created","getter_name":"created","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"encoded_tx","getter_name":"encodedTx","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"status","getter_name":"status","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(TxCommonStatus.values)","dart_type_name":"TxCommonStatus"}}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["id"]}},{"id":7,"references":[],"type":"table","data":{"name":"favorite_token_rows","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"symbol","getter_name":"symbol","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"logo_uri","getter_name":"logoUri","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["id"]}},{"id":8,"references":[],"type":"table","data":{"name":"popular_token_rows","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"symbol","getter_name":"symbol","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"logo_uri","getter_name":"logoUri","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"price","getter_name":"price","moor_type":"double","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["id"]}},{"id":9,"references":[],"type":"table","data":{"name":"o_t_rows","was_declared_in_moor":false,"columns":[{"name":"amount","getter_name":"amount","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"token","getter_name":"token","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created","getter_name":"created","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"status","getter_name":"status","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(OTStatusDto.values)","dart_type_name":"OTStatusDto"}},{"name":"tx","getter_name":"tx","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx_id","getter_name":"txId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"withdraw_tx_id","getter_name":"withdrawTxId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"private_key","getter_name":"privateKey","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"link","getter_name":"link","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx_failure_reason","getter_name":"txFailureReason","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(TxFailureReason.values)","dart_type_name":"TxFailureReason"}},{"name":"cancel_tx","getter_name":"cancelTx","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"cancel_tx_id","getter_name":"cancelTxId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["id"]}},{"id":10,"references":[],"type":"table","data":{"name":"i_t_rows","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created","getter_name":"created","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"private_key","getter_name":"privateKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"status","getter_name":"status","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(ITStatusDto.values)","dart_type_name":"ITStatusDto"}},{"name":"tx","getter_name":"tx","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx_id","getter_name":"txId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["id"]}},{"id":11,"references":[],"type":"table","data":{"name":"o_l_p_rows","was_declared_in_moor":false,"columns":[{"name":"amount","getter_name":"amount","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"token","getter_name":"token","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created","getter_name":"created","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"status","getter_name":"status","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(OLPStatusDto.values)","dart_type_name":"OLPStatusDto"}},{"name":"tx","getter_name":"tx","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx_id","getter_name":"txId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"withdraw_tx_id","getter_name":"withdrawTxId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"private_key","getter_name":"privateKey","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"link","getter_name":"link","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx_failure_reason","getter_name":"txFailureReason","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(TxFailureReason.values)","dart_type_name":"TxFailureReason"}},{"name":"cancel_tx","getter_name":"cancelTx","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"cancel_tx_id","getter_name":"cancelTxId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"generated_links_at","getter_name":"generatedLinksAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"resolved_at","getter_name":"resolvedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"slot","getter_name":"slot","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["id"]}},{"id":12,"references":[],"type":"table","data":{"name":"i_l_p_rows","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created","getter_name":"created","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx","getter_name":"tx","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx_id","getter_name":"txId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx_failure_reason","getter_name":"txFailureReason","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(TxFailureReason.values)","dart_type_name":"TxFailureReason"}},{"name":"slot","getter_name":"slot","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"private_key","getter_name":"privateKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"status","getter_name":"status","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumIndexConverter(ILPStatusDto.values)","dart_type_name":"ILPStatusDto"}}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["id"]}},{"id":13,"references":[],"type":"table","data":{"name":"on_ramp_order_rows","was_declared_in_moor":false,"columns":[{"name":"amount","getter_name":"amount","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"token","getter_name":"token","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created","getter_name":"created","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"is_completed","getter_name":"isCompleted","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"is_completed\" IN (0, 1))","default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"human_status","getter_name":"humanStatus","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"machine_status","getter_name":"machineStatus","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"partner_order_id","getter_name":"partnerOrderId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"receive_amount","getter_name":"receiveAmount","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"tx_hash","getter_name":"txHash","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"partner","getter_name":"partner","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant('kado')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(RampPartner.values)","dart_type_name":"RampPartner"}}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["id"]}},{"id":14,"references":[],"type":"table","data":{"name":"off_ramp_order_rows","was_declared_in_moor":false,"columns":[{"name":"amount","getter_name":"amount","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"token","getter_name":"token","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"id","getter_name":"id","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created","getter_name":"created","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"status","getter_name":"status","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(OffRampOrderStatus.values)","dart_type_name":"OffRampOrderStatus"}},{"name":"human_status","getter_name":"humanStatus","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"machine_status","getter_name":"machineStatus","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"partner_order_id","getter_name":"partnerOrderId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"transaction","getter_name":"transaction","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"deposit_address","getter_name":"depositAddress","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"slot","getter_name":"slot","moor_type":"bigInt","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"resolved_at","getter_name":"resolvedAt","moor_type":"dateTime","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"receive_amount","getter_name":"receiveAmount","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"fiat_symbol","getter_name":"fiatSymbol","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"partner","getter_name":"partner","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant('kado')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(RampPartner.values)","dart_type_name":"RampPartner"}}],"is_virtual":false,"without_rowid":false,"constraints":[],"explicit_pk":["id"]}}]} \ No newline at end of file