v2
This commit is contained in:
parent
62a76f9e18
commit
4ce603fbf1
24 changed files with 809 additions and 284 deletions
|
@ -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()),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
176
lib/screens/drawer/payment_accounts_screen.dart
Normal file
176
lib/screens/drawer/payment_accounts_screen.dart
Normal 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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
156
lib/screens/drawer/settings_screen.dart
Normal file
156
lib/screens/drawer/settings_screen.dart
Normal 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) {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,53 +74,76 @@ 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.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Balances',
|
||||
style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 10.0),
|
||||
Text(
|
||||
'Available Balance: ${_formatXmr(balance.availableBalance)} XMR'),
|
||||
Text('Pending Balance: ${_formatXmr(balance.pendingBalance)} XMR'),
|
||||
const SizedBox(height: 10.0),
|
||||
Text(
|
||||
'Reserved Offer Balance: ${_formatXmr(balance.reservedOfferBalance)} XMR'),
|
||||
Text(
|
||||
'Reserved Trade Balance: ${_formatXmr(balance.reservedTradeBalance)} XMR'),
|
||||
const SizedBox(height: 16.0),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
// handle withdraw balance logic here
|
||||
},
|
||||
child: Text('Withdraw Balance'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildXmrAddressCard(String? xmrAddress) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'Monero',
|
||||
style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 10.0),
|
||||
Text(
|
||||
'Available Balance: ${_formatXmr(balance.availableBalance)} XMR'),
|
||||
Text('Pending Balance: ${_formatXmr(balance.pendingBalance)} XMR'),
|
||||
const SizedBox(height: 10.0),
|
||||
Text(
|
||||
'Reserved Offer Balance: ${_formatXmr(balance.reservedOfferBalance)} XMR'),
|
||||
Text(
|
||||
'Reserved Trade Balance: ${_formatXmr(balance.reservedTradeBalance)} XMR'),
|
||||
const SizedBox(height: 16.0),
|
||||
if (primaryAddress != null)
|
||||
Column(
|
||||
child: xmrAddress != null
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
primaryAddress,
|
||||
textAlign: TextAlign.center,
|
||||
const Text(
|
||||
'Addresses',
|
||||
style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
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: () {
|
||||
Clipboard.setData(ClipboardData(text: primaryAddress));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Address copied to clipboard')),
|
||||
);
|
||||
// request a new XMR address here
|
||||
},
|
||||
child: const Text('Copy Address'),
|
||||
child: Text('Request a new address'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget _buildXmrTransactionsList(List<XmrTx>? transactions) {
|
||||
if (transactions != null) {
|
||||
transactions.sort((a, b) => b.timestamp.compareTo(a.timestamp));
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -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,15 +106,26 @@ class _BuyTabState extends State<BuyTab> with SingleTickerProviderStateMixin {
|
|||
],
|
||||
),
|
||||
),
|
||||
body: _isLoadingPaymentMethods
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
BuyMarketOffersTab(),
|
||||
BuyMyOffersTab(),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
SizeTransition(
|
||||
sizeFactor: _filterAnimation,
|
||||
axisAlignment: -1.0,
|
||||
child: _buildFilterMenu(),
|
||||
),
|
||||
Expanded(
|
||||
child: _isLoadingPaymentMethods
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
BuyMarketOffersTab(),
|
||||
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
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -55,6 +55,7 @@ class _SellTabState extends State<SellTab> with SingleTickerProviderStateMixin {
|
|||
isScrollControlled: true,
|
||||
builder: (BuildContext context) {
|
||||
return NewTradeOfferForm(
|
||||
direction: 'SELL',
|
||||
paymentAccounts: _paymentAccounts,
|
||||
formKey: _formKey,
|
||||
);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
},
|
||||
);
|
|
@ -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> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
15
pubspec.lock
15
pubspec.lock
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue