Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions assets/languages/strings_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,10 @@
"transactionBuy": "Kauf",
"transactionHistory": "Transaktionshistorie",
"transactionPending": "In Bearbeitung",
"transactionReceived": "Empfangen",
"transactions": "Transaktionen",
"transactionSell": "Verkauf",
"transactionSent": "Gesendet",
"transactionWaitingForPayment": "Warte auf Zahlung",
"twoFa": "2-Faktor Authentifizierung",
"twoFaCodeRequired": "Code ist erforderlich",
Expand Down
2 changes: 2 additions & 0 deletions assets/languages/strings_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,10 @@
"transactionBuy": "Buy",
"transactionHistory": "Transaction history",
"transactionPending": "Processing",
"transactionReceived": "Received",
"transactions": "Transactions",
"transactionSell": "Sell",
"transactionSent": "Sent",
"transactionWaitingForPayment": "Waiting for payment",
"twoFa": "Two-factor authentication",
"twoFaCodeRequired": "Code is required",
Expand Down
1 change: 1 addition & 0 deletions lib/models/dfx_transaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class DfxTransaction extends Transaction {
required super.amount,
required super.asset,
required super.type,
super.category,
required super.note,
required super.data,
required super.timestamp,
Expand Down
24 changes: 24 additions & 0 deletions lib/models/transaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,28 @@ import 'package:realunit_wallet/packages/service/transaction_history_service.dar

enum TransactionTypes { transfer, genericContractCall, tokenTransfer, savingsAdd, savingsRemove }

/// Business classification of a REALU transfer, resolved by the API (decision authority):
/// the Brokerbot as counterparty marks a share purchase or sale, everything else is a plain
/// token movement. Unknown or missing values stay null, so the UI falls back to the
/// direction-based labels used before the category existed.
enum TransferCategory {
purchase('purchase'),
sale('sale'),
transferIn('transferIn'),
transferOut('transferOut');

final String value;
const TransferCategory(this.value);

static TransferCategory? fromValue(String? value) {
if (value == null) return null;
return TransferCategory.values.cast<TransferCategory?>().firstWhere(
(e) => e?.value == value,
orElse: () => null,
);
}
}

/// Transaction with on-chain metadata
class Transaction {
final int height;
Expand All @@ -13,6 +35,7 @@ class Transaction {
final BigInt amount;
final Asset asset;
final TransactionTypes type;
final TransferCategory? category;
final String? note;
final String? data;
final DateTime timestamp;
Expand All @@ -26,6 +49,7 @@ class Transaction {
required this.amount,
required this.asset,
required this.type,
this.category,
required this.note,
required this.data,
required this.timestamp,
Expand Down
6 changes: 6 additions & 0 deletions lib/packages/repository/transaction_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class TransactionRepository {
transaction.amount.toRadixString(16),
transaction.asset.id,
transaction.type.index,
transaction.category?.value ?? '',
transaction.note ?? '',
transaction.data ?? '',
transaction.timestamp,
Expand All @@ -42,6 +43,7 @@ class TransactionRepository {
amount: transaction.amount.toRadixString(16),
asset: transaction.asset.id,
type: transaction.type.index,
category: transaction.category?.value ?? '',
note: transaction.note ?? '',
data: transaction.data ?? '',
timeStamp: transaction.timestamp,
Expand Down Expand Up @@ -110,6 +112,7 @@ class TransactionRepository {
amount: BigInt.parse(txData.amount, radix: 16),
asset: asset,
type: txType,
category: TransferCategory.fromValue(txData.category),
note: txData.note,
data: txData.data,
timestamp: txData.timeStamp,
Expand All @@ -125,6 +128,7 @@ class TransactionRepository {
amount: BigInt.parse(txData.amount, radix: 16),
asset: asset,
type: txType,
category: TransferCategory.fromValue(txData.category),
note: txData.note,
data: txData.data,
timestamp: txData.timeStamp,
Expand Down Expand Up @@ -203,6 +207,7 @@ class TransactionRepository {
amount: BigInt.parse(transactionData.amount, radix: 16),
asset: asset,
type: txType,
category: TransferCategory.fromValue(transactionData.category),
note: transactionData.note,
data: transactionData.data,
timestamp: transactionData.timeStamp,
Expand All @@ -219,6 +224,7 @@ class TransactionRepository {
amount: BigInt.parse(transactionData.amount, radix: 16),
asset: asset,
type: txType,
category: TransferCategory.fromValue(transactionData.category),
note: transactionData.note,
data: transactionData.data,
timestamp: transactionData.timeStamp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ class HistoryEventDto {
final DateTime timestamp;
final String txHash;
final TransferDto? transfer;
final String? category;

const HistoryEventDto({
required this.timestamp,
required this.txHash,
this.transfer,
this.category,
});

factory HistoryEventDto.fromJson(Map<String, dynamic> json) {
Expand All @@ -38,6 +40,7 @@ class HistoryEventDto {
transfer: json['transfer'] != null
? TransferDto.fromJson(json['transfer'] as Map<String, dynamic>)
: null,
category: json['category'] as String?,
);
}
}
Expand Down
2 changes: 2 additions & 0 deletions lib/packages/service/transaction_history_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class TransactionHistoryService extends DFXAuthService {
amount: BigInt.parse(transfer.value),
asset: appStore.apiConfig.asset,
type: TransactionTypes.tokenTransfer,
category: TransferCategory.fromValue(entry.category),
note: '',
data: null,
timestamp: entry.timestamp,
Expand All @@ -73,6 +74,7 @@ class TransactionHistoryService extends DFXAuthService {
amount: BigInt.parse(transfer.value),
asset: appStore.apiConfig.asset,
type: TransactionTypes.tokenTransfer,
category: TransferCategory.fromValue(entry.category),
note: '',
data: null,
timestamp: entry.timestamp,
Expand Down
5 changes: 4 additions & 1 deletion lib/packages/storage/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase.forTesting(super.executor);

@override
int get schemaVersion => 2;
int get schemaVersion => 3;

@override
MigrationStrategy get migration => MigrationStrategy(
Expand All @@ -81,6 +81,9 @@ class AppDatabase extends _$AppDatabase {
if (from < 2) {
await m.createTable(dfxTransactionDetails);
}
if (from < 3) {
await m.addColumn(transactions, transactions.category);
}
},
);

Expand Down
8 changes: 8 additions & 0 deletions lib/packages/storage/transaction_storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ extension TransactionStorage on AppDatabase {
String amount,
int asset,
int type,
String category,
String note,
String data,
DateTime timeStamp,
Expand All @@ -25,6 +26,7 @@ extension TransactionStorage on AppDatabase {
amount: amount,
asset: asset,
type: type,
category: Value(category),
note: note,
data: data,
timeStamp: timeStamp,
Expand All @@ -40,6 +42,7 @@ extension TransactionStorage on AppDatabase {
String? amount,
int? asset,
int? type,
String? category,
String? note,
String? data,
DateTime? timeStamp,
Expand All @@ -52,6 +55,7 @@ extension TransactionStorage on AppDatabase {
amount: Value.absentIfNull(amount),
asset: Value.absentIfNull(asset),
type: Value.absentIfNull(type),
category: Value.absentIfNull(category),
note: Value.absentIfNull(note),
data: Value.absentIfNull(data),
timeStamp: Value.absentIfNull(timeStamp),
Expand Down Expand Up @@ -156,6 +160,10 @@ class Transactions extends Table {

IntColumn get type => integer()(); // coverage:ignore-line

// Business category of the transfer as delivered by the API ('' = uncategorized, legacy
// rows and events without a category). Added in schema v3.
TextColumn get category => text().withDefault(const Constant(''))(); // coverage:ignore-line

TextColumn get note => text()(); // coverage:ignore-line

TextColumn get data => text()(); // coverage:ignore-line
Expand Down
8 changes: 3 additions & 5 deletions lib/screens/dashboard/widgets/transaction_row.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:realunit_wallet/models/transaction.dart';
import 'package:realunit_wallet/styles/colors.dart';
import 'package:realunit_wallet/styles/icons.dart';
import 'package:realunit_wallet/widgets/hide_amount_text.dart';
import 'package:realunit_wallet/widgets/transaction_title_label.dart';

class TransactionRow extends StatelessWidget {
final Transaction transaction;
Expand Down Expand Up @@ -77,11 +78,8 @@ class TransactionRow extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_isOutbound
? S.of(context).transactionSell
: S.of(context).transactionBuy,
style: const TextStyle(
fontSize: 16,
transactionTitleLabel(context, transaction, isOutbound: _isOutbound),
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w600,
height: 20 / 16,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:open_file/open_file.dart';
import 'package:realunit_wallet/generated/i18n.dart';
import 'package:realunit_wallet/models/transaction.dart';
import 'package:realunit_wallet/packages/service/dfx/real_unit_pdf_service.dart';
import 'package:realunit_wallet/screens/settings/bloc/settings_bloc.dart';
import 'package:realunit_wallet/screens/transaction_history/cubits/receipt/transaction_history_receipt_cubit.dart';
import 'package:realunit_wallet/setup/di.dart';
import 'package:realunit_wallet/styles/colors.dart';
import 'package:realunit_wallet/widgets/hide_amount_text.dart';
import 'package:realunit_wallet/widgets/transaction_title_label.dart';

class TransactionHistoryRow extends StatelessWidget {
final Transaction transaction;
Expand Down Expand Up @@ -101,9 +101,8 @@ class TransactionHistoryRowView extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isOutbound ? S.of(context).transactionSell : S.of(context).transactionBuy,
style: const TextStyle(
fontSize: 16,
transactionTitleLabel(context, transaction, isOutbound: isOutbound),
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w600,
height: 20 / 16,
),
Expand Down
32 changes: 32 additions & 0 deletions lib/widgets/transaction_title_label.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:flutter/widgets.dart';
import 'package:realunit_wallet/generated/i18n.dart';
import 'package:realunit_wallet/models/dfx_transaction.dart';
import 'package:realunit_wallet/models/transaction.dart';

/// Resolves the list title of a transaction.
///
/// DFX-backed transactions (bank buys and sells) keep the direction-based Buy/Sell labels.
/// For pure on-chain transfers the API-provided [TransferCategory] decides: Brokerbot
/// counterparty means a share purchase or sale, everything else is a plain received/sent
/// token movement. Without a category (legacy rows, older API) the previous direction-based
/// labels remain, so nothing breaks while backend and app roll out independently.
String transactionTitleLabel(BuildContext context, Transaction transaction, {required bool isOutbound}) {
final s = S.of(context);

if (transaction is! DfxTransaction) {
switch (transaction.category) {
case TransferCategory.purchase:
return s.transactionBuy;
case TransferCategory.sale:
return s.transactionSell;
case TransferCategory.transferIn:
return s.transactionReceived;
case TransferCategory.transferOut:
return s.transactionSent;
case null:
break;
}
}

return isOutbound ? s.transactionSell : s.transactionBuy;
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading