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/prices_provider.dart';
|
||||||
import 'package:haveno_flutter_app/providers/trades_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/providers/wallets_provider.dart';
|
||||||
import 'package:haveno_flutter_app/screens/accounts_screen.dart';
|
import 'package:haveno_flutter_app/screens/drawer/payment_accounts_screen.dart';
|
||||||
import 'package:haveno_flutter_app/screens/settings_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/trades_tab.dart';
|
||||||
import 'package:haveno_flutter_app/tabs/buy_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/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:logging/logging.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:haveno_flutter_app/services/haveno_service.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/http_service.dart';
|
||||||
import 'package:haveno_flutter_app/services/monero_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/get_version_provider.dart';
|
||||||
import 'package:haveno_flutter_app/providers/account_provider.dart';
|
import 'package:haveno_flutter_app/providers/account_provider.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:badges/badges.dart' as badges; // Import the badges package
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
@ -30,18 +31,18 @@ void main() async {
|
||||||
_setupLogging();
|
_setupLogging();
|
||||||
|
|
||||||
// Initialize services
|
// Initialize services
|
||||||
final torService = TorService();
|
//final torService = TorService();
|
||||||
await torService.initializeTor();
|
//await torService.initializeTor();
|
||||||
|
|
||||||
final httpService = HttpService();
|
final httpService = HttpService();
|
||||||
final moneroService = MoneroService();
|
final moneroService = MoneroService();
|
||||||
|
|
||||||
final havenoService = HavenoService('192.168.0.18', 3201, 'apitest');
|
final havenoService = HavenoService('127.0.0.1', 3201, 'apitest');
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
Provider(create: (_) => torService),
|
//Provider(create: (_) => torService),
|
||||||
Provider(create: (_) => httpService),
|
Provider(create: (_) => httpService),
|
||||||
Provider(create: (_) => moneroService),
|
Provider(create: (_) => moneroService),
|
||||||
Provider(create: (_) => havenoService),
|
Provider(create: (_) => havenoService),
|
||||||
|
@ -95,6 +96,7 @@ class MyApp extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
colorScheme: ColorScheme.fromSeed(
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
@ -104,13 +106,13 @@ class MyApp extends StatelessWidget {
|
||||||
// Ensures dark mode with light text
|
// Ensures dark mode with light text
|
||||||
),
|
),
|
||||||
scaffoldBackgroundColor: Color(0xFF303030),
|
scaffoldBackgroundColor: Color(0xFF303030),
|
||||||
appBarTheme: AppBarTheme(
|
appBarTheme: const AppBarTheme(
|
||||||
backgroundColor: Color(0xFF303030),
|
backgroundColor: Color(0xFF303030),
|
||||||
),
|
),
|
||||||
drawerTheme: DrawerThemeData(
|
drawerTheme: const DrawerThemeData(
|
||||||
backgroundColor: Color(0xFF303030),
|
backgroundColor: Color(0xFF303030),
|
||||||
),
|
),
|
||||||
bottomNavigationBarTheme: BottomNavigationBarThemeData(
|
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||||
backgroundColor: Color(0xFF303030),
|
backgroundColor: Color(0xFF303030),
|
||||||
selectedItemColor: Color(0xFFF4511E),
|
selectedItemColor: Color(0xFFF4511E),
|
||||||
unselectedItemColor: Colors.white,
|
unselectedItemColor: Colors.white,
|
||||||
|
@ -124,7 +126,7 @@ class MyApp extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
cardTheme: CardTheme(
|
cardTheme: const CardTheme(
|
||||||
color: Color(0xFF424242), // Card background color
|
color: Color(0xFF424242), // Card background color
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -142,6 +144,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
String _statusMessage = "Connecting to Tor...";
|
String _statusMessage = "Connecting to Tor...";
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
int _selectedIndex = 0;
|
int _selectedIndex = 0;
|
||||||
|
int _notificationCount = 5; // Mock notification count
|
||||||
|
|
||||||
static final List<Widget> _widgetOptions = <Widget>[
|
static final List<Widget> _widgetOptions = <Widget>[
|
||||||
BuyTab(),
|
BuyTab(),
|
||||||
|
@ -152,17 +155,17 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final torService = context.read<TorService>();
|
// final torService = context.read<TorService>();
|
||||||
torService.statusStream.listen((String status) {
|
// torService.statusStream.listen((String status) {
|
||||||
print(status);
|
// print(status);
|
||||||
setState(() {
|
// setState(() {
|
||||||
if (status.contains("started")) {
|
// if (status.contains("started")) {
|
||||||
_statusMessage = "Connecting to the Monero network...";
|
// _statusMessage = "Connecting to the Monero network...";
|
||||||
} else {
|
// } else {
|
||||||
_statusMessage = status;
|
// _statusMessage = status;
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
_initializeServices();
|
_initializeServices();
|
||||||
}
|
}
|
||||||
|
@ -201,7 +204,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
context.read<TorService>().dispose();
|
//context.read<TorService>().dispose();
|
||||||
context.read<HttpService>().close();
|
context.read<HttpService>().close();
|
||||||
context.read<MoneroService>().close();
|
context.read<MoneroService>().close();
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
|
@ -213,7 +216,25 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: AppBar(
|
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),
|
drawer: _buildDrawer(context),
|
||||||
body: Center(
|
body: Center(
|
||||||
|
@ -275,7 +296,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => AccountsScreen()),
|
MaterialPageRoute(builder: (context) => PaymentAccountsScreen()),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -54,11 +54,11 @@ class OffersProvider with ChangeNotifier {
|
||||||
required String direction,
|
required String direction,
|
||||||
required String price,
|
required String price,
|
||||||
required bool useMarketBasedPrice,
|
required bool useMarketBasedPrice,
|
||||||
required double marketPriceMarginPct,
|
double? marketPriceMarginPct,
|
||||||
required fixnum.Int64 amount,
|
required fixnum.Int64 amount,
|
||||||
required fixnum.Int64 minAmount,
|
required fixnum.Int64 minAmount,
|
||||||
required double buyerSecurityDepositPct,
|
required double buyerSecurityDepositPct,
|
||||||
required String triggerPrice,
|
String? triggerPrice,
|
||||||
required bool reserveExactAmount,
|
required bool reserveExactAmount,
|
||||||
required String paymentAccountId,
|
required String paymentAccountId,
|
||||||
}) async {
|
}) async {
|
||||||
|
|
|
@ -14,6 +14,7 @@ class PaymentAccountsProvider with ChangeNotifier {
|
||||||
final Logger _logger = Logger('PaymentAccountsProvider');
|
final Logger _logger = Logger('PaymentAccountsProvider');
|
||||||
final HavenoService _havenoService;
|
final HavenoService _havenoService;
|
||||||
List<PaymentMethod>? _paymentMethods;
|
List<PaymentMethod>? _paymentMethods;
|
||||||
|
List<PaymentMethod>? _cryptoCurrencyPaymentMethods;
|
||||||
List<PaymentAccount>? _paymentAccounts;
|
List<PaymentAccount>? _paymentAccounts;
|
||||||
PaymentAccountForm _paymentAccountForm = PaymentAccountForm();
|
PaymentAccountForm _paymentAccountForm = PaymentAccountForm();
|
||||||
bool _isLoadingPaymentMethods = false;
|
bool _isLoadingPaymentMethods = false;
|
||||||
|
@ -62,6 +63,7 @@ class PaymentAccountsProvider with ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
List<PaymentMethod>? get paymentMethods => _paymentMethods;
|
List<PaymentMethod>? get paymentMethods => _paymentMethods;
|
||||||
|
List<PaymentMethod>? get cryptoCurrencyPaymentMethods => _cryptoCurrencyPaymentMethods;
|
||||||
List<PaymentAccount>? get paymentAccounts => _paymentAccounts;
|
List<PaymentAccount>? get paymentAccounts => _paymentAccounts;
|
||||||
PaymentAccountForm get paymentAccountForm => _paymentAccountForm;
|
PaymentAccountForm get paymentAccountForm => _paymentAccountForm;
|
||||||
bool get isLoadingPaymentMethods => _isLoadingPaymentMethods;
|
bool get isLoadingPaymentMethods => _isLoadingPaymentMethods;
|
||||||
|
@ -76,6 +78,9 @@ class PaymentAccountsProvider with ChangeNotifier {
|
||||||
final getPaymentMethodsReply = await _havenoService.paymentAccountsClient
|
final getPaymentMethodsReply = await _havenoService.paymentAccountsClient
|
||||||
.getPaymentMethods(GetPaymentMethodsRequest());
|
.getPaymentMethods(GetPaymentMethodsRequest());
|
||||||
_paymentMethods = getPaymentMethodsReply.paymentMethods;
|
_paymentMethods = getPaymentMethodsReply.paymentMethods;
|
||||||
|
// _paymentMethods?.forEach((method) {
|
||||||
|
// debugPrint("Payment Method: ${jsonEncode(method.toProto3Json())}");
|
||||||
|
// });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Failed to get payment methods: $e");
|
print("Failed to get payment methods: $e");
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -92,9 +97,9 @@ class PaymentAccountsProvider with ChangeNotifier {
|
||||||
final getPaymentAccountsReply = await _havenoService.paymentAccountsClient
|
final getPaymentAccountsReply = await _havenoService.paymentAccountsClient
|
||||||
.getPaymentAccounts(GetPaymentAccountsRequest());
|
.getPaymentAccounts(GetPaymentAccountsRequest());
|
||||||
_paymentAccounts = getPaymentAccountsReply.paymentAccounts;
|
_paymentAccounts = getPaymentAccountsReply.paymentAccounts;
|
||||||
_paymentAccounts?.forEach((account) {
|
// _paymentAccounts?.forEach((account) {
|
||||||
debugPrint(jsonEncode(account.toProto3Json()));
|
// debugPrint(jsonEncode(account.toProto3Json()));
|
||||||
});
|
// });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Failed to get payment accounts: $e");
|
print("Failed to get payment accounts: $e");
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -104,6 +109,24 @@ class PaymentAccountsProvider with ChangeNotifier {
|
||||||
return paymentAccounts;
|
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(
|
Future<PaymentAccountForm> getPaymentAcountForm(
|
||||||
String paymentMethodId) async {
|
String paymentMethodId) async {
|
||||||
final traditionalCurrencyPaymentAccountForm =
|
final traditionalCurrencyPaymentAccountForm =
|
||||||
|
|
|
@ -4,18 +4,18 @@ import 'package:haveno_flutter_app/services/haveno_service.dart';
|
||||||
|
|
||||||
class PricesProvider with ChangeNotifier {
|
class PricesProvider with ChangeNotifier {
|
||||||
final HavenoService _havenoService;
|
final HavenoService _havenoService;
|
||||||
List<MarketPriceInfo>? _marketPrices;
|
List<MarketPriceInfo> _marketPrices = [];
|
||||||
|
|
||||||
PricesProvider(this._havenoService);
|
PricesProvider(this._havenoService);
|
||||||
|
|
||||||
List<MarketPriceInfo>? get prices => _marketPrices;
|
List<MarketPriceInfo> get prices => _marketPrices;
|
||||||
|
|
||||||
Future<void> getPrices() async {
|
Future<void> getXmrMarketPrices() async {
|
||||||
try {
|
try {
|
||||||
final getMarketPricesReply = await _havenoService.priceClient
|
final getMarketPricesReply = await _havenoService.priceClient
|
||||||
.getMarketPrices(MarketPricesRequest());
|
.getMarketPrices(MarketPricesRequest());
|
||||||
_marketPrices = getMarketPricesReply.marketPrice;
|
_marketPrices = getMarketPricesReply.marketPrice;
|
||||||
_marketPrices?.forEach((price) {
|
_marketPrices.forEach((price) {
|
||||||
print("Price: $price");
|
print("Price: $price");
|
||||||
});
|
});
|
||||||
notifyListeners();
|
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 'dart:convert';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flutter/material.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:provider/provider.dart';
|
||||||
import 'package:haveno_flutter_app/providers/trades_provider.dart';
|
import 'package:haveno_flutter_app/providers/trades_provider.dart';
|
||||||
import 'package:haveno_flutter_app/proto/compiled/grpc.pbgrpc.dart';
|
import 'package:haveno_flutter_app/proto/compiled/grpc.pbgrpc.dart';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flutter/material.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:provider/provider.dart';
|
||||||
import 'package:haveno_flutter_app/providers/trades_provider.dart';
|
import 'package:haveno_flutter_app/providers/trades_provider.dart';
|
||||||
import 'package:haveno_flutter_app/proto/compiled/grpc.pbgrpc.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: [
|
children: [
|
||||||
if (balances.hasXmr())
|
if (balances.hasXmr())
|
||||||
_buildXmrBalanceCard(
|
_buildXmrBalanceCard(
|
||||||
'XMR', balances.xmr, walletsProvider.xmrPrimaryAddress),
|
'XMR', balances.xmr),
|
||||||
const SizedBox(height: 16.0),
|
const SizedBox(height: 10.0),
|
||||||
|
_buildXmrAddressCard(walletsProvider.xmrPrimaryAddress),
|
||||||
|
const SizedBox(height: 10.0),
|
||||||
_buildXmrTransactionsList(walletsProvider.xmrTxs),
|
_buildXmrTransactionsList(walletsProvider.xmrTxs),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -72,53 +74,76 @@ class _WalletsScreenState extends State<WalletScreen> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildXmrBalanceCard(
|
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(
|
return Card(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: xmrAddress != null
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
? Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
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(
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
const Text(
|
||||||
primaryAddress,
|
'Addresses',
|
||||||
textAlign: TextAlign.center,
|
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(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Clipboard.setData(ClipboardData(text: primaryAddress));
|
// request a new XMR address here
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('Address copied to clipboard')),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: const Text('Copy Address'),
|
child: Text('Request a new address'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Widget _buildXmrTransactionsList(List<XmrTx>? transactions) {
|
Widget _buildXmrTransactionsList(List<XmrTx>? transactions) {
|
||||||
if (transactions != null) {
|
if (transactions != null) {
|
||||||
transactions.sort((a, b) => b.timestamp.compareTo(a.timestamp));
|
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(
|
Card(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
@ -241,7 +241,7 @@ class _OfferDetailScreenState extends State<OfferDetailScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.0),
|
SizedBox(height: 10.0),
|
||||||
Card(
|
Card(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
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:flutter/material.dart';
|
||||||
|
import 'package:haveno_flutter_app/providers/prices_provider.dart';
|
||||||
import 'package:haveno_flutter_app/widgets/offer_card_widget.dart';
|
import 'package:haveno_flutter_app/widgets/offer_card_widget.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:haveno_flutter_app/providers/offers_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 {
|
class BuyMarketOffersTab extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final offersProvider = Provider.of<OffersProvider>(context, listen: false);
|
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>(
|
return FutureBuilder<void>(
|
||||||
future: offersProvider.getOffers(), // Fetch offers
|
future: fetchData(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
@ -17,16 +29,39 @@ class BuyMarketOffersTab extends StatelessWidget {
|
||||||
return Center(child: Text('Error: ${snapshot.error}'));
|
return Center(child: Text('Error: ${snapshot.error}'));
|
||||||
} else {
|
} else {
|
||||||
final offers = offersProvider.marketBuyOffers;
|
final offers = offersProvider.marketBuyOffers;
|
||||||
|
final marketPrices = pricesProvider.prices;
|
||||||
|
|
||||||
if (offers == null || offers.isEmpty) {
|
if (offers == null || offers.isEmpty) {
|
||||||
return const Center(child: Text('No offers available'));
|
return const Center(child: Text('No offers available'));
|
||||||
} else {
|
} 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(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(top: 2.0), // Add 2 pixels of padding at the top
|
||||||
top: 2.0), // Add 2 pixels of padding at the top
|
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: offers.length,
|
itemCount: sortedOffers.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final offer = offers[index];
|
final offer = sortedOffers[index]['offer'] as OfferInfo;
|
||||||
return OfferCard(offer: offer);
|
return OfferCard(offer: offer);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -12,16 +12,27 @@ class BuyTab extends StatefulWidget {
|
||||||
_BuyTabState createState() => _BuyTabState();
|
_BuyTabState createState() => _BuyTabState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BuyTabState extends State<BuyTab> with SingleTickerProviderStateMixin {
|
class _BuyTabState extends State<BuyTab> with TickerProviderStateMixin {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
TabController? _tabController;
|
TabController? _tabController;
|
||||||
bool _isLoadingPaymentMethods = true;
|
bool _isLoadingPaymentMethods = true;
|
||||||
List<PaymentAccount> _paymentAccounts = [];
|
List<PaymentAccount> _paymentAccounts = [];
|
||||||
|
bool _isFilterVisible = false;
|
||||||
|
late AnimationController _filterAnimationController;
|
||||||
|
late Animation<double> _filterAnimation;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_tabController = TabController(length: 2, vsync: this);
|
_tabController = TabController(length: 2, vsync: this);
|
||||||
|
_filterAnimationController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_filterAnimation = CurvedAnimation(
|
||||||
|
parent: _filterAnimationController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
_initializeData();
|
_initializeData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +57,7 @@ class _BuyTabState extends State<BuyTab> with SingleTickerProviderStateMixin {
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_tabController?.dispose();
|
_tabController?.dispose();
|
||||||
|
_filterAnimationController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +67,7 @@ class _BuyTabState extends State<BuyTab> with SingleTickerProviderStateMixin {
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return NewTradeOfferForm(
|
return NewTradeOfferForm(
|
||||||
|
direction: 'BUY',
|
||||||
paymentAccounts: _paymentAccounts,
|
paymentAccounts: _paymentAccounts,
|
||||||
formKey: _formKey,
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Buy Monero'),
|
title: const Text('Buy Monero'),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.filter_list),
|
||||||
|
onPressed: _toggleFilter,
|
||||||
|
),
|
||||||
|
],
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
tabs: const [
|
tabs: const [
|
||||||
|
@ -76,15 +106,26 @@ class _BuyTabState extends State<BuyTab> with SingleTickerProviderStateMixin {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: _isLoadingPaymentMethods
|
body: Column(
|
||||||
? const Center(child: CircularProgressIndicator())
|
children: [
|
||||||
: TabBarView(
|
SizeTransition(
|
||||||
controller: _tabController,
|
sizeFactor: _filterAnimation,
|
||||||
children: [
|
axisAlignment: -1.0,
|
||||||
BuyMarketOffersTab(),
|
child: _buildFilterMenu(),
|
||||||
BuyMyOffersTab(),
|
),
|
||||||
],
|
Expanded(
|
||||||
),
|
child: _isLoadingPaymentMethods
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: TabBarView(
|
||||||
|
controller: _tabController,
|
||||||
|
children: [
|
||||||
|
BuyMarketOffersTab(),
|
||||||
|
BuyMyOffersTab(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: _showNewTradeForm,
|
onPressed: _showNewTradeForm,
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
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:flutter/material.dart';
|
||||||
|
import 'package:haveno_flutter_app/providers/prices_provider.dart';
|
||||||
import 'package:haveno_flutter_app/widgets/offer_card_widget.dart';
|
import 'package:haveno_flutter_app/widgets/offer_card_widget.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:haveno_flutter_app/providers/offers_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 {
|
class SaleMarketOffersTab extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final offersProvider = Provider.of<OffersProvider>(context, listen: false);
|
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>(
|
return FutureBuilder<void>(
|
||||||
future: offersProvider.getOffers(), // Fetch offers
|
future: fetchData(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
@ -17,16 +29,39 @@ class SaleMarketOffersTab extends StatelessWidget {
|
||||||
return Center(child: Text('Error: ${snapshot.error}'));
|
return Center(child: Text('Error: ${snapshot.error}'));
|
||||||
} else {
|
} else {
|
||||||
final offers = offersProvider.marketSellOffers;
|
final offers = offersProvider.marketSellOffers;
|
||||||
|
final marketPrices = pricesProvider.prices;
|
||||||
|
|
||||||
if (offers == null || offers.isEmpty) {
|
if (offers == null || offers.isEmpty) {
|
||||||
return const Center(child: Text('No offers available'));
|
return const Center(child: Text('No offers available'));
|
||||||
} else {
|
} 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(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(top: 2.0), // Add 2 pixels of padding at the top
|
||||||
top: 2.0), // Add 2 pixels of padding at the top
|
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: offers.length,
|
itemCount: sortedOffers.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final offer = offers[index];
|
final offer = sortedOffers[index]['offer'] as OfferInfo;
|
||||||
return OfferCard(offer: offer);
|
return OfferCard(offer: offer);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -55,6 +55,7 @@ class _SellTabState extends State<SellTab> with SingleTickerProviderStateMixin {
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return NewTradeOfferForm(
|
return NewTradeOfferForm(
|
||||||
|
direction: 'SELL',
|
||||||
paymentAccounts: _paymentAccounts,
|
paymentAccounts: _paymentAccounts,
|
||||||
formKey: _formKey,
|
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/providers/account_provider.dart';
|
||||||
import 'package:haveno_flutter_app/screens/active_buyer_trade_timeline_screen.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/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:intl/intl.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:haveno_flutter_app/providers/trades_provider.dart';
|
import 'package:haveno_flutter_app/providers/trades_provider.dart';
|
||||||
|
|
|
@ -65,25 +65,6 @@ bool isFiatCurrency(String currencyCode) {
|
||||||
'IMP', // Isle of Man Pound
|
'IMP', // Isle of Man Pound
|
||||||
'INR', // Indian Rupee
|
'INR', // Indian Rupee
|
||||||
'IQD', // Iraqi Dinar
|
'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
|
'LSL', // Lesotho Loti
|
||||||
'LYD', // Libyan Dinar
|
'LYD', // Libyan Dinar
|
||||||
'MAD', // Moroccan Dirham
|
'MAD', // Moroccan Dirham
|
||||||
|
@ -163,3 +144,65 @@ bool isFiatCurrency(String currencyCode) {
|
||||||
};
|
};
|
||||||
return fiatCurrencies.contains(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: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:provider/provider.dart';
|
||||||
import 'package:haveno_flutter_app/providers/payment_accounts_provider.dart';
|
import 'package:haveno_flutter_app/providers/payment_accounts_provider.dart';// Import the utils file
|
||||||
import 'package:haveno_flutter_app/proto/compiled/pb.pb.dart';
|
|
||||||
import 'payment_account_form.dart';
|
|
||||||
|
|
||||||
class PaymentMethodSelectionForm extends StatefulWidget {
|
class PaymentMethodSelectionForm extends StatefulWidget {
|
||||||
|
final String accountType;
|
||||||
|
|
||||||
|
PaymentMethodSelectionForm({required this.accountType});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_PaymentMethodSelectionFormState createState() =>
|
_PaymentMethodSelectionFormState createState() =>
|
||||||
_PaymentMethodSelectionFormState();
|
_PaymentMethodSelectionFormState();
|
||||||
|
@ -18,7 +22,9 @@ class _PaymentMethodSelectionFormState
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final paymentAccountsProvider =
|
final paymentAccountsProvider =
|
||||||
Provider.of<PaymentAccountsProvider>(context);
|
Provider.of<PaymentAccountsProvider>(context);
|
||||||
final paymentMethods = paymentAccountsProvider.paymentMethods ?? [];
|
final paymentMethods = widget.accountType == 'FIAT'
|
||||||
|
? paymentAccountsProvider.paymentMethods
|
||||||
|
: paymentAccountsProvider.cryptoCurrencyPaymentMethods;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: MediaQuery.of(context).viewInsets,
|
padding: MediaQuery.of(context).viewInsets,
|
||||||
|
@ -34,10 +40,10 @@ class _PaymentMethodSelectionFormState
|
||||||
labelText: 'Payment Method',
|
labelText: 'Payment Method',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
items: paymentMethods.map((method) {
|
items: paymentMethods?.map((method) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
value: method.id,
|
value: method.id,
|
||||||
child: Text(method.id),
|
child: Text(getPaymentMethodLabel(method.id)),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
|
@ -56,7 +62,7 @@ class _PaymentMethodSelectionFormState
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return DynamicPaymentAccountForm(
|
return DynamicPaymentAccountForm(
|
||||||
paymentAccountForm: form,
|
paymentAccountForm: form,
|
||||||
paymentMethodLabel: value,
|
paymentMethodLabel: getPaymentMethodLabel(value),
|
||||||
paymentMethodId: value);
|
paymentMethodId: value);
|
||||||
},
|
},
|
||||||
);
|
);
|
|
@ -8,10 +8,12 @@ import 'dart:convert'; // Import the dart:convert library
|
||||||
class NewTradeOfferForm extends StatefulWidget {
|
class NewTradeOfferForm extends StatefulWidget {
|
||||||
final GlobalKey<FormState> formKey;
|
final GlobalKey<FormState> formKey;
|
||||||
final List<PaymentAccount> paymentAccounts;
|
final List<PaymentAccount> paymentAccounts;
|
||||||
|
final String direction; // New argument for direction ('BUY' or 'SELL')
|
||||||
|
|
||||||
const NewTradeOfferForm({
|
const NewTradeOfferForm({
|
||||||
required this.formKey,
|
required this.formKey,
|
||||||
required this.paymentAccounts,
|
required this.paymentAccounts,
|
||||||
|
required this.direction,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -22,6 +24,7 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
|
||||||
PaymentAccount? _selectedPaymentAccount;
|
PaymentAccount? _selectedPaymentAccount;
|
||||||
TradeCurrency? _selectedTradeCurrency;
|
TradeCurrency? _selectedTradeCurrency;
|
||||||
int _selectedPricingTypeIndex = 0; // 0 for Fixed, 1 for Dynamic
|
int _selectedPricingTypeIndex = 0; // 0 for Fixed, 1 for Dynamic
|
||||||
|
bool _reserveExactAmount = false;
|
||||||
|
|
||||||
final TextEditingController _priceController = TextEditingController();
|
final TextEditingController _priceController = TextEditingController();
|
||||||
final TextEditingController _depositController =
|
final TextEditingController _depositController =
|
||||||
|
@ -34,6 +37,8 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final isBuy = widget.direction == 'BUY';
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: MediaQuery.of(context).viewInsets,
|
padding: MediaQuery.of(context).viewInsets,
|
||||||
child: Container(
|
child: Container(
|
||||||
|
@ -44,7 +49,10 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
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),
|
const SizedBox(height: 16.0),
|
||||||
ToggleButtons(
|
ToggleButtons(
|
||||||
isSelected: [
|
isSelected: [
|
||||||
|
@ -70,7 +78,7 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
|
||||||
const SizedBox(height: 16.0),
|
const SizedBox(height: 16.0),
|
||||||
DropdownButtonFormField<PaymentAccount>(
|
DropdownButtonFormField<PaymentAccount>(
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Payment Account',
|
labelText: 'Your Sender Account',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
value: _selectedPaymentAccount,
|
value: _selectedPaymentAccount,
|
||||||
|
@ -138,9 +146,11 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
|
||||||
if (_selectedPricingTypeIndex == 1)
|
if (_selectedPricingTypeIndex == 1)
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _marginController,
|
controller: _marginController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Market Price Below Margin (%)',
|
labelText: isBuy
|
||||||
border: OutlineInputBorder(),
|
? 'Market Price Below Margin (%)'
|
||||||
|
: 'Market Price Above Margin (%)',
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
|
@ -156,13 +166,13 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
|
||||||
const SizedBox(height: 16.0),
|
const SizedBox(height: 16.0),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _amountController,
|
controller: _amountController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Amount of XMR',
|
labelText: 'Amount of XMR to ${isBuy ? 'Buy' : 'Sell'}',
|
||||||
border: OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
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;
|
return null;
|
||||||
},
|
},
|
||||||
|
@ -171,7 +181,7 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _minAmountController,
|
controller: _minAmountController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Minimum Transaction Amount (Of XMR)',
|
labelText: 'Minimum Transaction Amount (XMR)',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
|
@ -185,12 +195,12 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _depositController,
|
controller: _depositController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Buyer Security Deposit (%)',
|
labelText: 'Mutual Security Deposit (%)',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
return 'Please enter the buyer security deposit';
|
return 'Please enter the mutual security deposit';
|
||||||
}
|
}
|
||||||
final deposit = double.tryParse(value);
|
final deposit = double.tryParse(value);
|
||||||
if (deposit == null || deposit < 0 || deposit > 50) {
|
if (deposit == null || deposit < 0 || deposit > 50) {
|
||||||
|
@ -204,9 +214,10 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
|
||||||
if (_selectedPricingTypeIndex == 1)
|
if (_selectedPricingTypeIndex == 1)
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _triggerPriceController,
|
controller: _triggerPriceController,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Stop Trigger Price',
|
labelText:
|
||||||
border: OutlineInputBorder(),
|
'Delist If Market Price Goes Above (${_selectedTradeCurrency?.code ?? ''})',
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
|
@ -216,13 +227,43 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16.0),
|
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(
|
ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (widget.formKey.currentState?.validate() ?? false) {
|
if (widget.formKey.currentState?.validate() ?? false) {
|
||||||
// Prepare the data to be sent
|
// Prepare the data to be sent
|
||||||
final offerData = {
|
final offerData = {
|
||||||
'currencyCode': _selectedTradeCurrency?.code ?? '',
|
'currencyCode': _selectedTradeCurrency?.code ?? '',
|
||||||
'direction': 'BUY',
|
'direction': widget.direction,
|
||||||
'price': _selectedPricingTypeIndex == 0
|
'price': _selectedPricingTypeIndex == 0
|
||||||
? _priceController.text
|
? _priceController.text
|
||||||
: '',
|
: '',
|
||||||
|
@ -248,7 +289,7 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
|
||||||
'triggerPrice': _selectedPricingTypeIndex == 1
|
'triggerPrice': _selectedPricingTypeIndex == 1
|
||||||
? _triggerPriceController.text
|
? _triggerPriceController.text
|
||||||
: '',
|
: '',
|
||||||
'reserveExactAmount': true,
|
'reserveExactAmount': _reserveExactAmount,
|
||||||
'paymentAccountId': _selectedPaymentAccount?.id ?? '',
|
'paymentAccountId': _selectedPaymentAccount?.id ?? '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -261,7 +302,7 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
|
||||||
Provider.of<OffersProvider>(context, listen: false);
|
Provider.of<OffersProvider>(context, listen: false);
|
||||||
offersProvider.postOffer(
|
offersProvider.postOffer(
|
||||||
currencyCode: _selectedTradeCurrency?.code ?? '',
|
currencyCode: _selectedTradeCurrency?.code ?? '',
|
||||||
direction: 'BUY',
|
direction: widget.direction,
|
||||||
price: _selectedPricingTypeIndex == 0
|
price: _selectedPricingTypeIndex == 0
|
||||||
? _priceController.text
|
? _priceController.text
|
||||||
: '', // Use the price from the controller
|
: '', // Use the price from the controller
|
||||||
|
@ -284,7 +325,7 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
|
||||||
triggerPrice: _selectedPricingTypeIndex == 1
|
triggerPrice: _selectedPricingTypeIndex == 1
|
||||||
? _triggerPriceController.text
|
? _triggerPriceController.text
|
||||||
: '', // Use the trigger price from the controller
|
: '', // Use the trigger price from the controller
|
||||||
reserveExactAmount: false,
|
reserveExactAmount: _reserveExactAmount,
|
||||||
paymentAccountId: _selectedPaymentAccount?.id ?? '',
|
paymentAccountId: _selectedPaymentAccount?.id ?? '',
|
||||||
);
|
);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
@ -307,3 +348,4 @@ class __NewTradeOfferFormState extends State<NewTradeOfferForm> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:haveno_flutter_app/utils/payment_utils.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:haveno_flutter_app/proto/compiled/grpc.pbgrpc.dart';
|
import 'package:haveno_flutter_app/proto/compiled/grpc.pbgrpc.dart';
|
||||||
import 'package:haveno_flutter_app/screens/my_offer_detail_screen.dart';
|
import 'package:haveno_flutter_app/screens/my_offer_detail_screen.dart';
|
||||||
|
@ -43,8 +44,12 @@ class OfferCard extends StatelessWidget {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Card(
|
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,
|
color: Theme.of(context).cardTheme.color,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.zero, // No border radius
|
||||||
|
),
|
||||||
|
elevation: 0, // No elevation
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@ -104,7 +109,7 @@ class OfferCard extends StatelessWidget {
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Center(
|
Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
_isFiatCurrency(offer.counterCurrencyCode)
|
isFiatCurrency(offer.counterCurrencyCode)
|
||||||
? '${double.parse(offer.price).toStringAsFixed(2)} ${offer.counterCurrencyCode}'
|
? '${double.parse(offer.price).toStringAsFixed(2)} ${offer.counterCurrencyCode}'
|
||||||
: '${offer.price}',
|
: '${offer.price}',
|
||||||
style: const TextStyle(
|
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"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
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:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -541,13 +549,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
tor:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: "../flutter_plugins/tor"
|
|
||||||
relative: true
|
|
||||||
source: path
|
|
||||||
version: "0.0.1"
|
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -29,11 +29,12 @@ dependencies:
|
||||||
font_awesome_flutter: ^10.7.0
|
font_awesome_flutter: ^10.7.0
|
||||||
timeline_tile: ^2.0.0
|
timeline_tile: ^2.0.0
|
||||||
# Add your plugin dependency here
|
# Add your plugin dependency here
|
||||||
tor:
|
##tor:
|
||||||
path: ../flutter_plugins/tor
|
## path: ../flutter_plugins/tor
|
||||||
protoc_plugin: ^21.1.2
|
protoc_plugin: ^21.1.2
|
||||||
fixnum: ^1.1.0
|
fixnum: ^1.1.0
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
|
badges: ^3.1.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in a new issue