This commit is contained in:
unknown 2024-07-15 15:14:30 -04:00 committed by KewbitXMR
parent 62a76f9e18
commit 4ce603fbf1
24 changed files with 809 additions and 284 deletions

View file

@ -6,22 +6,23 @@ import 'package:haveno_flutter_app/providers/payment_accounts_provider.dart';
import 'package:haveno_flutter_app/providers/prices_provider.dart';
import 'package:haveno_flutter_app/providers/trades_provider.dart';
import 'package:haveno_flutter_app/providers/wallets_provider.dart';
import 'package:haveno_flutter_app/screens/accounts_screen.dart';
import 'package:haveno_flutter_app/screens/settings_screen.dart';
import 'package:haveno_flutter_app/screens/drawer/payment_accounts_screen.dart';
import 'package:haveno_flutter_app/screens/drawer/settings_screen.dart';
import 'package:haveno_flutter_app/tabs/trades_tab.dart';
import 'package:haveno_flutter_app/tabs/buy_tab.dart';
import 'package:haveno_flutter_app/tabs/sell_tab.dart';
import 'package:haveno_flutter_app/screens/wallet_screen.dart';
import 'package:haveno_flutter_app/screens/drawer/wallet_screen.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:haveno_flutter_app/services/haveno_service.dart';
import 'package:haveno_flutter_app/services/http_service.dart';
import 'package:haveno_flutter_app/services/monero_service.dart';
import 'package:haveno_flutter_app/services/tor_service.dart';
//import 'package:haveno_flutter_app/services/tor_service.dart';
import 'package:haveno_flutter_app/providers/get_version_provider.dart';
import 'package:haveno_flutter_app/providers/account_provider.dart';
import 'dart:async';
import 'package:badges/badges.dart' as badges; // Import the badges package
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@ -30,18 +31,18 @@ void main() async {
_setupLogging();
// Initialize services
final torService = TorService();
await torService.initializeTor();
//final torService = TorService();
//await torService.initializeTor();
final httpService = HttpService();
final moneroService = MoneroService();
final havenoService = HavenoService('192.168.0.18', 3201, 'apitest');
final havenoService = HavenoService('127.0.0.1', 3201, 'apitest');
runApp(
MultiProvider(
providers: [
Provider(create: (_) => torService),
//Provider(create: (_) => torService),
Provider(create: (_) => httpService),
Provider(create: (_) => moneroService),
Provider(create: (_) => havenoService),
@ -95,6 +96,7 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
@ -104,13 +106,13 @@ class MyApp extends StatelessWidget {
// Ensures dark mode with light text
),
scaffoldBackgroundColor: Color(0xFF303030),
appBarTheme: AppBarTheme(
appBarTheme: const AppBarTheme(
backgroundColor: Color(0xFF303030),
),
drawerTheme: DrawerThemeData(
drawerTheme: const DrawerThemeData(
backgroundColor: Color(0xFF303030),
),
bottomNavigationBarTheme: BottomNavigationBarThemeData(
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: Color(0xFF303030),
selectedItemColor: Color(0xFFF4511E),
unselectedItemColor: Colors.white,
@ -124,7 +126,7 @@ class MyApp extends StatelessWidget {
),
),
),
cardTheme: CardTheme(
cardTheme: const CardTheme(
color: Color(0xFF424242), // Card background color
),
),
@ -142,6 +144,7 @@ class _HomeScreenState extends State<HomeScreen> {
String _statusMessage = "Connecting to Tor...";
Timer? _timer;
int _selectedIndex = 0;
int _notificationCount = 5; // Mock notification count
static final List<Widget> _widgetOptions = <Widget>[
BuyTab(),
@ -152,17 +155,17 @@ class _HomeScreenState extends State<HomeScreen> {
@override
void initState() {
super.initState();
final torService = context.read<TorService>();
torService.statusStream.listen((String status) {
print(status);
setState(() {
if (status.contains("started")) {
_statusMessage = "Connecting to the Monero network...";
} else {
_statusMessage = status;
}
});
});
// final torService = context.read<TorService>();
// torService.statusStream.listen((String status) {
// print(status);
// setState(() {
// if (status.contains("started")) {
// _statusMessage = "Connecting to the Monero network...";
// } else {
// _statusMessage = status;
// }
// });
// });
_initializeServices();
}
@ -201,7 +204,7 @@ class _HomeScreenState extends State<HomeScreen> {
@override
void dispose() {
context.read<TorService>().dispose();
//context.read<TorService>().dispose();
context.read<HttpService>().close();
context.read<MoneroService>().close();
_timer?.cancel();
@ -213,7 +216,25 @@ class _HomeScreenState extends State<HomeScreen> {
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: AppBar(
title: Text('Haveno'),
title: const Text('Haveno'),
actions: [
Padding(
padding: const EdgeInsets.only(right: 15.0), // Adjust padding to move the bell and badge
child: badges.Badge(
position: badges.BadgePosition.topEnd(top: 5, end: 5),
badgeContent: Text(
'$_notificationCount',
style: TextStyle(color: Colors.white, fontSize: 10),
),
child: IconButton(
icon: Icon(Icons.notifications),
onPressed: () {
// Handle notification bell tap
},
),
),
),
],
),
drawer: _buildDrawer(context),
body: Center(
@ -275,7 +296,7 @@ class _HomeScreenState extends State<HomeScreen> {
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AccountsScreen()),
MaterialPageRoute(builder: (context) => PaymentAccountsScreen()),
);
},
),

View file

@ -54,11 +54,11 @@ class OffersProvider with ChangeNotifier {
required String direction,
required String price,
required bool useMarketBasedPrice,
required double marketPriceMarginPct,
double? marketPriceMarginPct,
required fixnum.Int64 amount,
required fixnum.Int64 minAmount,
required double buyerSecurityDepositPct,
required String triggerPrice,
String? triggerPrice,
required bool reserveExactAmount,
required String paymentAccountId,
}) async {

View file

@ -14,6 +14,7 @@ class PaymentAccountsProvider with ChangeNotifier {
final Logger _logger = Logger('PaymentAccountsProvider');
final HavenoService _havenoService;
List<PaymentMethod>? _paymentMethods;
List<PaymentMethod>? _cryptoCurrencyPaymentMethods;
List<PaymentAccount>? _paymentAccounts;
PaymentAccountForm _paymentAccountForm = PaymentAccountForm();
bool _isLoadingPaymentMethods = false;
@ -62,6 +63,7 @@ class PaymentAccountsProvider with ChangeNotifier {
}
List<PaymentMethod>? get paymentMethods => _paymentMethods;
List<PaymentMethod>? get cryptoCurrencyPaymentMethods => _cryptoCurrencyPaymentMethods;
List<PaymentAccount>? get paymentAccounts => _paymentAccounts;
PaymentAccountForm get paymentAccountForm => _paymentAccountForm;
bool get isLoadingPaymentMethods => _isLoadingPaymentMethods;
@ -76,6 +78,9 @@ class PaymentAccountsProvider with ChangeNotifier {
final getPaymentMethodsReply = await _havenoService.paymentAccountsClient
.getPaymentMethods(GetPaymentMethodsRequest());
_paymentMethods = getPaymentMethodsReply.paymentMethods;
// _paymentMethods?.forEach((method) {
// debugPrint("Payment Method: ${jsonEncode(method.toProto3Json())}");
// });
} catch (e) {
print("Failed to get payment methods: $e");
} finally {
@ -92,9 +97,9 @@ class PaymentAccountsProvider with ChangeNotifier {
final getPaymentAccountsReply = await _havenoService.paymentAccountsClient
.getPaymentAccounts(GetPaymentAccountsRequest());
_paymentAccounts = getPaymentAccountsReply.paymentAccounts;
_paymentAccounts?.forEach((account) {
debugPrint(jsonEncode(account.toProto3Json()));
});
// _paymentAccounts?.forEach((account) {
// debugPrint(jsonEncode(account.toProto3Json()));
// });
} catch (e) {
print("Failed to get payment accounts: $e");
} finally {
@ -104,6 +109,24 @@ class PaymentAccountsProvider with ChangeNotifier {
return paymentAccounts;
}
Future<List<PaymentMethod>?> getCryptoCurrencyPaymentMethods() async {
_isLoadingPaymentAccounts = true;
notifyListeners();
try {
final getCryptoCurrencyPaymentMethodsReply = await _havenoService.paymentAccountsClient.getCryptoCurrencyPaymentMethods(GetCryptoCurrencyPaymentMethodsRequest());
_cryptoCurrencyPaymentMethods = getCryptoCurrencyPaymentMethodsReply.paymentMethods;
// _cryptoCurrencyPaymentMethods?.forEach((method) {
// debugPrint("Crypto Method: ${jsonEncode(method.toProto3Json())}");
// });
} catch (e) {
print("Failed to get payment accounts: $e");
} finally {
_isLoadingPaymentAccounts = false;
notifyListeners();
}
return paymentMethods;
}
Future<PaymentAccountForm> getPaymentAcountForm(
String paymentMethodId) async {
final traditionalCurrencyPaymentAccountForm =

View file

@ -4,18 +4,18 @@ import 'package:haveno_flutter_app/services/haveno_service.dart';
class PricesProvider with ChangeNotifier {
final HavenoService _havenoService;
List<MarketPriceInfo>? _marketPrices;
List<MarketPriceInfo> _marketPrices = [];
PricesProvider(this._havenoService);
List<MarketPriceInfo>? get prices => _marketPrices;
List<MarketPriceInfo> get prices => _marketPrices;
Future<void> getPrices() async {
Future<void> getXmrMarketPrices() async {
try {
final getMarketPricesReply = await _havenoService.priceClient
.getMarketPrices(MarketPricesRequest());
_marketPrices = getMarketPricesReply.marketPrice;
_marketPrices?.forEach((price) {
_marketPrices.forEach((price) {
print("Price: $price");
});
notifyListeners();

View file

@ -1,99 +0,0 @@
import 'package:flutter/material.dart';
import 'package:haveno_flutter_app/widgets/payment_method_form.dart';
import 'package:provider/provider.dart';
import 'package:haveno_flutter_app/providers/payment_accounts_provider.dart';
class AccountsScreen extends StatefulWidget {
@override
_AccountsScreenState createState() => _AccountsScreenState();
}
class _AccountsScreenState extends State<AccountsScreen> {
@override
void initState() {
super.initState();
final paymentAccountsProvider =
Provider.of<PaymentAccountsProvider>(context, listen: false);
paymentAccountsProvider.getPaymentAccounts();
}
void _showCreateAccountForm(BuildContext context) {
final paymentAccountsProvider =
Provider.of<PaymentAccountsProvider>(context, listen: false);
paymentAccountsProvider.getPaymentMethods().then((_) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (BuildContext context) {
return PaymentMethodSelectionForm();
},
);
});
}
@override
Widget build(BuildContext context) {
final paymentAccountsProvider =
Provider.of<PaymentAccountsProvider>(context);
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: AppBar(
title: const Text('Accounts'),
),
body: Consumer<PaymentAccountsProvider>(
builder: (context, provider, child) {
if (provider.isLoadingPaymentAccounts) {
return const Center(child: CircularProgressIndicator());
} else if (provider.paymentAccounts?.isEmpty ?? true) {
return const Center(
child: Text(
'You do not currently have any accounts',
style: TextStyle(color: Colors.white70, fontSize: 18),
),
);
} else {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListView.builder(
itemCount: provider.paymentAccounts?.length ?? 0,
itemBuilder: (context, index) {
final account = provider.paymentAccounts![index];
return Card(
color: Theme.of(context).cardTheme.color,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${account.accountName}',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold),
),
const SizedBox(height: 8.0),
Text(
account.paymentMethod.id,
style: const TextStyle(color: Colors.white70),
),
],
),
),
);
},
),
);
}
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => _showCreateAccountForm(context),
backgroundColor: Theme.of(context).colorScheme.primary,
child: Icon(Icons.add),
),
);
}
}

View file

@ -1,7 +1,7 @@
import 'dart:convert';
import 'package:fixnum/fixnum.dart';
import 'package:flutter/material.dart';
import 'package:haveno_flutter_app/utils/check_fiat.dart';
import 'package:haveno_flutter_app/utils/payment_utils.dart';
import 'package:provider/provider.dart';
import 'package:haveno_flutter_app/providers/trades_provider.dart';
import 'package:haveno_flutter_app/proto/compiled/grpc.pbgrpc.dart';

View file

@ -1,7 +1,7 @@
import 'dart:convert';
import 'package:fixnum/fixnum.dart';
import 'package:flutter/material.dart';
import 'package:haveno_flutter_app/utils/check_fiat.dart';
import 'package:haveno_flutter_app/utils/payment_utils.dart';
import 'package:provider/provider.dart';
import 'package:haveno_flutter_app/providers/trades_provider.dart';
import 'package:haveno_flutter_app/proto/compiled/grpc.pbgrpc.dart';

View file

@ -0,0 +1,176 @@
import 'package:flutter/material.dart';
import 'package:haveno_flutter_app/utils/payment_utils.dart';
import 'package:haveno_flutter_app/widgets/add_payment_method_form.dart';
import 'package:provider/provider.dart';
import 'package:haveno_flutter_app/providers/payment_accounts_provider.dart';
import 'package:badges/badges.dart' as badges; // Import the badges package
class PaymentAccountsScreen extends StatefulWidget {
@override
_PaymentAccountsScreenState createState() => _PaymentAccountsScreenState();
}
class _PaymentAccountsScreenState extends State<PaymentAccountsScreen>
with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
final paymentAccountsProvider =
Provider.of<PaymentAccountsProvider>(context, listen: false);
paymentAccountsProvider.getPaymentAccounts();
paymentAccountsProvider.getPaymentMethods();
paymentAccountsProvider.getCryptoCurrencyPaymentMethods();
}
void _showCreateAccountForm(BuildContext context) {
final paymentAccountsProvider =
Provider.of<PaymentAccountsProvider>(context, listen: false);
final isFiat = _tabController.index == 0;
final getPaymentMethods = isFiat
? paymentAccountsProvider.getPaymentMethods()
: paymentAccountsProvider.getCryptoCurrencyPaymentMethods();
getPaymentMethods.then((_) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (BuildContext context) {
return PaymentMethodSelectionForm(accountType: isFiat ? 'FIAT' : 'CRYPTO');
},
);
});
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final paymentAccountsProvider =
Provider.of<PaymentAccountsProvider>(context);
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: AppBar(
title: const Text('Accounts'),
bottom: TabBar(
controller: _tabController,
tabs: const [
Tab(text: 'Fiat Accounts'),
Tab(text: 'Crypto Accounts'),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
_buildAccountList(context, paymentAccountsProvider, PaymentMethodType.FIAT),
_buildAccountList(context, paymentAccountsProvider, PaymentMethodType.CRYPTO),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () => _showCreateAccountForm(context),
backgroundColor: Theme.of(context).colorScheme.primary,
child: Icon(Icons.add),
),
);
}
Widget _buildAccountList(BuildContext context,
PaymentAccountsProvider provider, PaymentMethodType accountType) {
if (provider.isLoadingPaymentAccounts) {
return const Center(child: CircularProgressIndicator());
} else {
final accounts = provider.paymentAccounts?.where((account) {
final methodType = getPaymentMethodType(account.paymentMethod.id);
return methodType == accountType;
}).toList();
if (accounts == null || accounts.isEmpty) {
return const Center(
child: Text(
'You do not currently have any accounts',
style: TextStyle(color: Colors.white70, fontSize: 18),
),
);
} else {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListView.builder(
itemCount: accounts.length,
itemBuilder: (context, index) {
final account = accounts[index];
final accountSupportedCurrencies = account.tradeCurrencies;
return Card(
color: Theme.of(context).cardTheme.color,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${account.accountName}',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold),
),
badges.Badge(
badgeContent: Text(
getPaymentMethodLabel(account.paymentMethod.id),
style: const TextStyle(
color: Colors.white, fontSize: 10),
),
badgeStyle: badges.BadgeStyle(
shape: badges.BadgeShape.square,
badgeColor: Theme.of(context).colorScheme.primary,
borderRadius: BorderRadius.circular(8),
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
),
),
],
),
const SizedBox(height: 8.0),
Wrap(
spacing: 8.0,
runSpacing: 4.0,
children: accountSupportedCurrencies.map((currency) {
return badges.Badge(
badgeContent: Text(
currency.name,
style: const TextStyle(
color: Colors.white, fontSize: 10),
),
badgeStyle: badges.BadgeStyle(
shape: badges.BadgeShape.square,
badgeColor: Colors.blue,
borderRadius: BorderRadius.circular(8),
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
),
);
}).toList(),
),
],
),
),
);
},
),
);
}
}
}
}

View file

@ -0,0 +1,156 @@
import 'package:flutter/material.dart';
class SettingsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: AppBar(
title: Text('Settings'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: ListView(
children: [
Card(
color: Theme.of(context).cardTheme.color,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Preferences',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: 'Language',
border: OutlineInputBorder(),
labelStyle: TextStyle(color: Colors.white),
),
items: ['English', 'Spanish', 'French']
.map((language) => DropdownMenuItem(
value: language,
child: Text(language),
))
.toList(),
onChanged: (value) {},
),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: 'Country',
border: OutlineInputBorder(),
labelStyle: TextStyle(color: Colors.white),
),
items: ['USA', 'Canada', 'UK']
.map((country) => DropdownMenuItem(
value: country,
child: Text(country),
))
.toList(),
onChanged: (value) {},
),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: 'Preferred Currency',
border: OutlineInputBorder(),
labelStyle: TextStyle(color: Colors.white),
),
items: ['USD', 'EUR', 'GBP']
.map((currency) => DropdownMenuItem(
value: currency,
child: Text(currency),
))
.toList(),
onChanged: (value) {},
),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: 'Blockchain Explorer',
border: OutlineInputBorder(),
labelStyle: TextStyle(color: Colors.white),
),
items: ['XMRChain.net', 'Monero.com']
.map((explorer) => DropdownMenuItem(
value: explorer,
child: Text(explorer),
))
.toList(),
onChanged: (value) {},
),
const SizedBox(height: 16),
TextField(
decoration: InputDecoration(
labelText: 'Max Deviation from Market Price',
border: OutlineInputBorder(),
labelStyle: TextStyle(color: Colors.white),
suffixText: '%',
),
keyboardType: TextInputType.number,
),
],
),
),
),
const SizedBox(height: 16),
Card(
color: Theme.of(context).cardTheme.color,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Display Options',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 16),
SwitchListTile(
title: Text(
'Hide Non-Supported Payment Methods',
style: TextStyle(color: Colors.white),
),
value: true,
onChanged: (value) {},
),
const SizedBox(height: 16),
SwitchListTile(
title: Text(
'Sort Market Lists by Number of Offers/Trades',
style: TextStyle(color: Colors.white),
),
value: false,
onChanged: (value) {},
),
const SizedBox(height: 16),
SwitchListTile(
title: Text(
'User Dark Mode',
style: TextStyle(color: Colors.white),
),
value: true,
onChanged: (value) {},
),
],
),
),
),
],
),
),
);
}
}

View file

@ -59,8 +59,10 @@ class _WalletsScreenState extends State<WalletScreen> {
children: [
if (balances.hasXmr())
_buildXmrBalanceCard(
'XMR', balances.xmr, walletsProvider.xmrPrimaryAddress),
const SizedBox(height: 16.0),
'XMR', balances.xmr),
const SizedBox(height: 10.0),
_buildXmrAddressCard(walletsProvider.xmrPrimaryAddress),
const SizedBox(height: 10.0),
_buildXmrTransactionsList(walletsProvider.xmrTxs),
],
);
@ -72,15 +74,15 @@ class _WalletsScreenState extends State<WalletScreen> {
}
Widget _buildXmrBalanceCard(
String coin, XmrBalanceInfo balance, String? primaryAddress) {
String coin, XmrBalanceInfo balance) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Monero',
'Balances',
style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10.0),
@ -93,25 +95,11 @@ class _WalletsScreenState extends State<WalletScreen> {
Text(
'Reserved Trade Balance: ${_formatXmr(balance.reservedTradeBalance)} XMR'),
const SizedBox(height: 16.0),
if (primaryAddress != null)
Column(
children: [
Text(
primaryAddress,
textAlign: TextAlign.center,
),
const SizedBox(height: 8.0),
ElevatedButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: primaryAddress));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Address copied to clipboard')),
);
// handle withdraw balance logic here
},
child: const Text('Copy Address'),
),
],
child: Text('Withdraw Balance'),
),
],
),
@ -119,6 +107,43 @@ class _WalletsScreenState extends State<WalletScreen> {
);
}
Widget _buildXmrAddressCard(String? xmrAddress) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: xmrAddress != null
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Addresses',
style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10.0),
Text(xmrAddress),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'You currently don\'t have an XMR address',
style: TextStyle(fontSize: 16.0),
),
SizedBox(height: 10.0),
ElevatedButton(
onPressed: () {
// request a new XMR address here
},
child: Text('Request a new address'),
),
],
),
),
);
}
Widget _buildXmrTransactionsList(List<XmrTx>? transactions) {
if (transactions != null) {
transactions.sort((a, b) => b.timestamp.compareTo(a.timestamp));

View file

@ -202,7 +202,7 @@ class _OfferDetailScreenState extends State<OfferDetailScreen> {
),
),
),
SizedBox(height: 16.0),
SizedBox(height: 10.0),
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
@ -241,7 +241,7 @@ class _OfferDetailScreenState extends State<OfferDetailScreen> {
),
),
),
SizedBox(height: 16.0),
SizedBox(height: 10.0),
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),

View file

@ -1,19 +0,0 @@
import 'package:flutter/material.dart';
class SettingsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: AppBar(
title: Text('Settings'),
),
body: Center(
child: Text(
'Settings Screen',
style: TextStyle(color: Colors.white),
),
),
);
}
}

View file

@ -1,15 +1,27 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:haveno_flutter_app/providers/prices_provider.dart';
import 'package:haveno_flutter_app/widgets/offer_card_widget.dart';
import 'package:provider/provider.dart';
import 'package:haveno_flutter_app/providers/offers_provider.dart';
import 'package:haveno_flutter_app/proto/compiled/grpc.pbgrpc.dart'; // Ensure you have the correct import for OfferInfo
class BuyMarketOffersTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
final offersProvider = Provider.of<OffersProvider>(context, listen: false);
final pricesProvider = Provider.of<PricesProvider>(context, listen: false);
Future<void> fetchData() async {
if (pricesProvider.prices.isEmpty) {
await pricesProvider.getXmrMarketPrices();
}
await offersProvider.getOffers();
}
return FutureBuilder<void>(
future: offersProvider.getOffers(), // Fetch offers
future: fetchData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
@ -17,16 +29,39 @@ class BuyMarketOffersTab extends StatelessWidget {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
final offers = offersProvider.marketBuyOffers;
final marketPrices = pricesProvider.prices;
if (offers == null || offers.isEmpty) {
return const Center(child: Text('No offers available'));
} else {
// Calculate value score for each offer
final List<Map<String, dynamic>> sortedOffers = offers.map((offer) {
final marketPriceInfo = marketPrices.firstWhere(
(marketInfo) => marketInfo.currencyCode == offer.counterCurrencyCode,
orElse: () => MarketPriceInfo()..price = 0.0); // Provide a default value if not found
final marketPrice = marketPriceInfo.price;
final offerPrice = double.tryParse(offer.price) ?? 0.0;
double valueScore = 0;
if (marketPrice > 0) {
valueScore = (marketPrice - offerPrice) / marketPrice * 100;
}
return {
'offer': offer,
'valueScore': valueScore,
};
}).toList();
// Sort offers by value score (lowest value first)
sortedOffers.sort((a, b) => (a['valueScore'] as double).compareTo(b['valueScore'] as double));
return Padding(
padding: const EdgeInsets.only(
top: 2.0), // Add 2 pixels of padding at the top
padding: const EdgeInsets.only(top: 2.0), // Add 2 pixels of padding at the top
child: ListView.builder(
itemCount: offers.length,
itemCount: sortedOffers.length,
itemBuilder: (context, index) {
final offer = offers[index];
final offer = sortedOffers[index]['offer'] as OfferInfo;
return OfferCard(offer: offer);
},
),

View file

@ -12,16 +12,27 @@ class BuyTab extends StatefulWidget {
_BuyTabState createState() => _BuyTabState();
}
class _BuyTabState extends State<BuyTab> with SingleTickerProviderStateMixin {
class _BuyTabState extends State<BuyTab> with TickerProviderStateMixin {
final _formKey = GlobalKey<FormState>();
TabController? _tabController;
bool _isLoadingPaymentMethods = true;
List<PaymentAccount> _paymentAccounts = [];
bool _isFilterVisible = false;
late AnimationController _filterAnimationController;
late Animation<double> _filterAnimation;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
_filterAnimationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_filterAnimation = CurvedAnimation(
parent: _filterAnimationController,
curve: Curves.easeInOut,
);
_initializeData();
}
@ -46,6 +57,7 @@ class _BuyTabState extends State<BuyTab> with SingleTickerProviderStateMixin {
@override
void dispose() {
_tabController?.dispose();
_filterAnimationController.dispose();
super.dispose();
}
@ -55,6 +67,7 @@ class _BuyTabState extends State<BuyTab> with SingleTickerProviderStateMixin {
isScrollControlled: true,
builder: (BuildContext context) {
return NewTradeOfferForm(
direction: 'BUY',
paymentAccounts: _paymentAccounts,
formKey: _formKey,
);
@ -62,12 +75,29 @@ class _BuyTabState extends State<BuyTab> with SingleTickerProviderStateMixin {
);
}
void _toggleFilter() {
setState(() {
_isFilterVisible = !_isFilterVisible;
if (_isFilterVisible) {
_filterAnimationController.forward();
} else {
_filterAnimationController.reverse();
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: AppBar(
title: const Text('Buy Monero'),
actions: [
IconButton(
icon: Icon(Icons.filter_list),
onPressed: _toggleFilter,
),
],
bottom: TabBar(
controller: _tabController,
tabs: const [
@ -76,7 +106,15 @@ class _BuyTabState extends State<BuyTab> with SingleTickerProviderStateMixin {
],
),
),
body: _isLoadingPaymentMethods
body: Column(
children: [
SizeTransition(
sizeFactor: _filterAnimation,
axisAlignment: -1.0,
child: _buildFilterMenu(),
),
Expanded(
child: _isLoadingPaymentMethods
? const Center(child: CircularProgressIndicator())
: TabBarView(
controller: _tabController,
@ -85,6 +123,9 @@ class _BuyTabState extends State<BuyTab> with SingleTickerProviderStateMixin {
BuyMyOffersTab(),
],
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _showNewTradeForm,
backgroundColor: Theme.of(context).colorScheme.primary,
@ -92,4 +133,52 @@ class _BuyTabState extends State<BuyTab> with SingleTickerProviderStateMixin {
),
);
}
Widget _buildFilterMenu() {
return Container(
color: Theme.of(context).cardTheme.color,
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Filters',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
DropdownButtonFormField<String>(
decoration: const InputDecoration(
labelText: 'Currency',
border: OutlineInputBorder(),
),
items: ['USD', 'EUR', 'GBP']
.map((currency) => DropdownMenuItem(
value: currency,
child: Text(currency),
))
.toList(),
onChanged: (value) {
// Handle currency filter change
},
),
const SizedBox(height: 8),
DropdownButtonFormField<String>(
decoration: const InputDecoration(
labelText: 'Payment Method',
border: OutlineInputBorder(),
),
items: ['PayPal', 'Bank Transfer', 'Credit Card']
.map((method) => DropdownMenuItem(
value: method,
child: Text(method),
))
.toList(),
onChanged: (value) {
// Handle payment method filter change
},
),
],
),
);
}
}

View file

@ -1,15 +1,27 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:haveno_flutter_app/providers/prices_provider.dart';
import 'package:haveno_flutter_app/widgets/offer_card_widget.dart';
import 'package:provider/provider.dart';
import 'package:haveno_flutter_app/providers/offers_provider.dart';
import 'package:haveno_flutter_app/proto/compiled/grpc.pbgrpc.dart'; // Ensure you have the correct import for OfferInfo
class SaleMarketOffersTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
final offersProvider = Provider.of<OffersProvider>(context, listen: false);
final pricesProvider = Provider.of<PricesProvider>(context, listen: false);
Future<void> fetchData() async {
if (pricesProvider.prices.isEmpty) {
await pricesProvider.getXmrMarketPrices();
}
await offersProvider.getOffers();
}
return FutureBuilder<void>(
future: offersProvider.getOffers(), // Fetch offers
future: fetchData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
@ -17,16 +29,39 @@ class SaleMarketOffersTab extends StatelessWidget {
return Center(child: Text('Error: ${snapshot.error}'));
} else {
final offers = offersProvider.marketSellOffers;
final marketPrices = pricesProvider.prices;
if (offers == null || offers.isEmpty) {
return const Center(child: Text('No offers available'));
} else {
// Calculate value score for each offer
final List<Map<String, dynamic>> sortedOffers = offers.map((offer) {
final marketPriceInfo = marketPrices.firstWhere(
(marketInfo) => marketInfo.currencyCode == offer.counterCurrencyCode,
orElse: () => MarketPriceInfo()..price = 0.0); // Provide a default value if not found
final marketPrice = marketPriceInfo.price;
final offerPrice = double.tryParse(offer.price) ?? 0.0;
double valueScore = 0;
if (marketPrice > 0) {
valueScore = (offerPrice - marketPrice) / marketPrice * 100;
}
return {
'offer': offer,
'valueScore': valueScore,
};
}).toList();
// Sort offers by value score (highest value first)
sortedOffers.sort((a, b) => (b['valueScore'] as double).compareTo(a['valueScore'] as double));
return Padding(
padding: const EdgeInsets.only(
top: 2.0), // Add 2 pixels of padding at the top
padding: const EdgeInsets.only(top: 2.0), // Add 2 pixels of padding at the top
child: ListView.builder(
itemCount: offers.length,
itemCount: sortedOffers.length,
itemBuilder: (context, index) {
final offer = offers[index];
final offer = sortedOffers[index]['offer'] as OfferInfo;
return OfferCard(offer: offer);
},
),

View file

@ -55,6 +55,7 @@ class _SellTabState extends State<SellTab> with SingleTickerProviderStateMixin {
isScrollControlled: true,
builder: (BuildContext context) {
return NewTradeOfferForm(
direction: 'SELL',
paymentAccounts: _paymentAccounts,
formKey: _formKey,
);

View file

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:haveno_flutter_app/providers/account_provider.dart';
import 'package:haveno_flutter_app/screens/active_buyer_trade_timeline_screen.dart';
import 'package:haveno_flutter_app/screens/active_seller_trade_timeline_screen.dart';
import 'package:haveno_flutter_app/utils/check_fiat.dart';
import 'package:haveno_flutter_app/utils/payment_utils.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:haveno_flutter_app/providers/trades_provider.dart';

View file

@ -65,25 +65,6 @@ bool isFiatCurrency(String currencyCode) {
'IMP', // Isle of Man Pound
'INR', // Indian Rupee
'IQD', // Iraqi Dinar
'IRR', // Iranian Rial
'ISK', // Icelandic Króna
'JEP', // Jersey Pound
'JMD', // Jamaican Dollar
'JOD', // Jordanian Dinar
'JPY', // Japanese Yen
'KES', // Kenyan Shilling
'KGS', // Kyrgyzstani Som
'KHR', // Cambodian Riel
'KID', // Kiribati Dollar
'KMF', // Comorian Franc
'KRW', // South Korean Won
'KWD', // Kuwaiti Dinar
'KYD', // Cayman Islands Dollar
'KZT', // Kazakhstani Tenge
'LAK', // Lao Kip
'LBP', // Lebanese Pound
'LKR', // Sri Lankan Rupee
'LRD', // Liberian Dollar
'LSL', // Lesotho Loti
'LYD', // Libyan Dinar
'MAD', // Moroccan Dirham
@ -163,3 +144,65 @@ bool isFiatCurrency(String currencyCode) {
};
return fiatCurrencies.contains(currencyCode);
}
bool isCryptoCurrency(String currencyCode) {
const cryptoCurrencies = {
'BTC', // Bitcoin
'BCH', // Bitcoin Cash
'LTC', // Litecoin
'ETH' // Ethereum
};
return cryptoCurrencies.contains(currencyCode);
}
enum PaymentMethodType {
CRYPTO,
FIAT,
UNKNOWN,
}
// Payment Method Mappings
const Map<String, String> fiatPaymentMethodLabels = {
'AUSTRALIA_PAYID': 'Australia PayID',
'CASH_APP': 'Cash App',
'CASH_AT_ATM': 'Cash at ATM',
'F2F': 'Face to Face',
'FASTER_PAYMENTS': 'Faster Payments',
'MONEY_GRAM': 'MoneyGram',
'PAXUM': 'Paxum',
'PAYPAL': 'PayPal',
'PAY_BY_MAIL': 'Pay by Mail',
'REVOLUT': 'Revolut',
'SEPA': 'SEPA',
'SEPA_INSTANT': 'SEPA Instant',
'STRIKE': 'Strike',
'SWIFT': 'SWIFT',
'TRANSFERWISE': 'TransferWise',
'UPHOLD': 'Uphold',
'VENMO': 'Venmo',
'ZELLE': 'Zelle',
};
const Map<String, String> cryptoPaymentMethodLabels = {
'BLOCK_CHAINS': 'Blockchains'
};
// Combine both maps for easy lookup
const Map<String, String> paymentMethodLabels = {
...fiatPaymentMethodLabels,
...cryptoPaymentMethodLabels,
};
String getPaymentMethodLabel(String id) {
return paymentMethodLabels[id] ?? 'Unknown Payment Method';
}
PaymentMethodType getPaymentMethodType(String paymentMethodId) {
if (cryptoPaymentMethodLabels.containsKey(paymentMethodId)) {
return PaymentMethodType.CRYPTO;
} else if (fiatPaymentMethodLabels.containsKey(paymentMethodId)) {
return PaymentMethodType.FIAT;
} else {
return PaymentMethodType.UNKNOWN;
}
}

View file

@ -1,10 +1,14 @@
import 'package:flutter/material.dart';
import 'package:haveno_flutter_app/utils/payment_utils.dart';
import 'package:haveno_flutter_app/widgets/add_payment_account_form.dart';
import 'package:provider/provider.dart';
import 'package:haveno_flutter_app/providers/payment_accounts_provider.dart';
import 'package:haveno_flutter_app/proto/compiled/pb.pb.dart';
import 'payment_account_form.dart';
import 'package:haveno_flutter_app/providers/payment_accounts_provider.dart';// Import the utils file
class PaymentMethodSelectionForm extends StatefulWidget {
final String accountType;
PaymentMethodSelectionForm({required this.accountType});
@override
_PaymentMethodSelectionFormState createState() =>
_PaymentMethodSelectionFormState();
@ -18,7 +22,9 @@ class _PaymentMethodSelectionFormState
Widget build(BuildContext context) {
final paymentAccountsProvider =
Provider.of<PaymentAccountsProvider>(context);
final paymentMethods = paymentAccountsProvider.paymentMethods ?? [];
final paymentMethods = widget.accountType == 'FIAT'
? paymentAccountsProvider.paymentMethods
: paymentAccountsProvider.cryptoCurrencyPaymentMethods;
return Padding(
padding: MediaQuery.of(context).viewInsets,
@ -34,10 +40,10 @@ class _PaymentMethodSelectionFormState
labelText: 'Payment Method',
border: OutlineInputBorder(),
),
items: paymentMethods.map((method) {
items: paymentMethods?.map((method) {
return DropdownMenuItem<String>(
value: method.id,
child: Text(method.id),
child: Text(getPaymentMethodLabel(method.id)),
);
}).toList(),
onChanged: (value) {
@ -56,7 +62,7 @@ class _PaymentMethodSelectionFormState
builder: (BuildContext context) {
return DynamicPaymentAccountForm(
paymentAccountForm: form,
paymentMethodLabel: value,
paymentMethodLabel: getPaymentMethodLabel(value),
paymentMethodId: value);
},
);

View file

@ -8,10 +8,12 @@ import 'dart:convert'; // Import the dart:convert library
class NewTradeOfferForm extends StatefulWidget {
final GlobalKey<FormState> formKey;
final List<PaymentAccount> paymentAccounts;
final String direction; // New argument for direction ('BUY' or 'SELL')
const NewTradeOfferForm({
required this.formKey,
required this.paymentAccounts,
required this.direction,
});
@override
@ -22,6 +24,7 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
PaymentAccount? _selectedPaymentAccount;
TradeCurrency? _selectedTradeCurrency;
int _selectedPricingTypeIndex = 0; // 0 for Fixed, 1 for Dynamic
bool _reserveExactAmount = false;
final TextEditingController _priceController = TextEditingController();
final TextEditingController _depositController =
@ -34,6 +37,8 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
@override
Widget build(BuildContext context) {
final isBuy = widget.direction == 'BUY';
return Padding(
padding: MediaQuery.of(context).viewInsets,
child: Container(
@ -44,7 +49,10 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Open a New Offer', style: TextStyle(fontSize: 18)),
Text(
'Open a New XMR ${isBuy ? 'Buy' : 'Sell'} Offer',
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 16.0),
ToggleButtons(
isSelected: [
@ -70,7 +78,7 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
const SizedBox(height: 16.0),
DropdownButtonFormField<PaymentAccount>(
decoration: const InputDecoration(
labelText: 'Payment Account',
labelText: 'Your Sender Account',
border: OutlineInputBorder(),
),
value: _selectedPaymentAccount,
@ -138,9 +146,11 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
if (_selectedPricingTypeIndex == 1)
TextFormField(
controller: _marginController,
decoration: const InputDecoration(
labelText: 'Market Price Below Margin (%)',
border: OutlineInputBorder(),
decoration: InputDecoration(
labelText: isBuy
? 'Market Price Below Margin (%)'
: 'Market Price Above Margin (%)',
border: const OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
@ -156,13 +166,13 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
const SizedBox(height: 16.0),
TextFormField(
controller: _amountController,
decoration: const InputDecoration(
labelText: 'Amount of XMR',
border: OutlineInputBorder(),
decoration: InputDecoration(
labelText: 'Amount of XMR to ${isBuy ? 'Buy' : 'Sell'}',
border: const OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter the maximum amount you wish to buy';
return 'Please enter the maximum amount you wish to ${isBuy ? 'buy' : 'sell'}';
}
return null;
},
@ -171,7 +181,7 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
TextFormField(
controller: _minAmountController,
decoration: const InputDecoration(
labelText: 'Minimum Transaction Amount (Of XMR)',
labelText: 'Minimum Transaction Amount (XMR)',
border: OutlineInputBorder(),
),
validator: (value) {
@ -185,12 +195,12 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
TextFormField(
controller: _depositController,
decoration: const InputDecoration(
labelText: 'Buyer Security Deposit (%)',
labelText: 'Mutual Security Deposit (%)',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter the buyer security deposit';
return 'Please enter the mutual security deposit';
}
final deposit = double.tryParse(value);
if (deposit == null || deposit < 0 || deposit > 50) {
@ -204,9 +214,10 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
if (_selectedPricingTypeIndex == 1)
TextFormField(
controller: _triggerPriceController,
decoration: const InputDecoration(
labelText: 'Stop Trigger Price',
border: OutlineInputBorder(),
decoration: InputDecoration(
labelText:
'Delist If Market Price Goes Above (${_selectedTradeCurrency?.code ?? ''})',
border: const OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
@ -216,13 +227,43 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
},
),
const SizedBox(height: 16.0),
Row(
children: [
Expanded(
child: CheckboxListTile(
title: const Row(
children: [
Text('Reserve only the funds needed'),
SizedBox(width: 4),
Tooltip(
message:
'If selected, only the exact amount of funds needed for this trade will be reserved. This may also incur a mining fee and will require 10 confirmations therefore it will take ~20 minutes longer to post your trade.',
child: Icon(
Icons.info_outline,
color: Colors.white,
),
),
],
),
value: _reserveExactAmount,
onChanged: (value) {
setState(() {
_reserveExactAmount = value ?? false;
});
},
controlAffinity: ListTileControlAffinity.leading,
),
),
],
),
const SizedBox(height: 16.0),
ElevatedButton(
onPressed: () async {
if (widget.formKey.currentState?.validate() ?? false) {
// Prepare the data to be sent
final offerData = {
'currencyCode': _selectedTradeCurrency?.code ?? '',
'direction': 'BUY',
'direction': widget.direction,
'price': _selectedPricingTypeIndex == 0
? _priceController.text
: '',
@ -248,7 +289,7 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
'triggerPrice': _selectedPricingTypeIndex == 1
? _triggerPriceController.text
: '',
'reserveExactAmount': true,
'reserveExactAmount': _reserveExactAmount,
'paymentAccountId': _selectedPaymentAccount?.id ?? '',
};
@ -261,7 +302,7 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
Provider.of<OffersProvider>(context, listen: false);
offersProvider.postOffer(
currencyCode: _selectedTradeCurrency?.code ?? '',
direction: 'BUY',
direction: widget.direction,
price: _selectedPricingTypeIndex == 0
? _priceController.text
: '', // Use the price from the controller
@ -284,7 +325,7 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
triggerPrice: _selectedPricingTypeIndex == 1
? _triggerPriceController.text
: '', // Use the trigger price from the controller
reserveExactAmount: false,
reserveExactAmount: _reserveExactAmount,
paymentAccountId: _selectedPaymentAccount?.id ?? '',
);
Navigator.pop(context);
@ -307,3 +348,4 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
);
}
}

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:haveno_flutter_app/utils/payment_utils.dart';
import 'package:provider/provider.dart';
import 'package:haveno_flutter_app/proto/compiled/grpc.pbgrpc.dart';
import 'package:haveno_flutter_app/screens/my_offer_detail_screen.dart';
@ -43,8 +44,12 @@ class OfferCard extends StatelessWidget {
}
},
child: Card(
margin: const EdgeInsets.all(2.0),
margin: const EdgeInsets.only(top: 1.0), // 1 pixel margin at the top
color: Theme.of(context).cardTheme.color,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.zero, // No border radius
),
elevation: 0, // No elevation
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
@ -104,7 +109,7 @@ class OfferCard extends StatelessWidget {
const SizedBox(height: 10),
Center(
child: Text(
_isFiatCurrency(offer.counterCurrencyCode)
isFiatCurrency(offer.counterCurrencyCode)
? '${double.parse(offer.price).toStringAsFixed(2)} ${offer.counterCurrencyCode}'
: '${offer.price}',
style: const TextStyle(
@ -142,20 +147,4 @@ class OfferCard extends StatelessWidget {
},
);
}
bool _isFiatCurrency(String currencyCode) {
const fiatCurrencies = {
'GBP',
'USD',
'EUR',
'JPY',
'AUD',
'CAD',
'CHF',
'CNY',
'SEK',
'NZD'
};
return fiatCurrencies.contains(currencyCode);
}
}

View file

@ -25,6 +25,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.11.0"
badges:
dependency: "direct main"
description:
name: badges
sha256: a7b6bbd60dce418df0db3058b53f9d083c22cdb5132a052145dc267494df0b84
url: "https://pub.dev"
source: hosted
version: "3.1.2"
boolean_selector:
dependency: transitive
description:
@ -541,13 +549,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
tor:
dependency: "direct main"
description:
path: "../flutter_plugins/tor"
relative: true
source: path
version: "0.0.1"
typed_data:
dependency: transitive
description:

View file

@ -29,11 +29,12 @@ dependencies:
font_awesome_flutter: ^10.7.0
timeline_tile: ^2.0.0
# Add your plugin dependency here
tor:
path: ../flutter_plugins/tor
##tor:
## path: ../flutter_plugins/tor
protoc_plugin: ^21.1.2
fixnum: ^1.1.0
intl: ^0.17.0
badges: ^3.1.2
dev_dependencies:
flutter_test: