-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
436 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
enum OrderType { | ||
market, | ||
limit; | ||
|
||
String get asString { | ||
switch (this) { | ||
case OrderType.market: | ||
return "Market"; | ||
case OrderType.limit: | ||
return "Limit"; | ||
} | ||
} | ||
|
||
// Factory method to convert a String to OrderType | ||
static OrderType fromString(String value) { | ||
switch (value.toLowerCase()) { | ||
case 'market': | ||
return OrderType.market; | ||
case 'limit': | ||
return OrderType.limit; | ||
default: | ||
throw ArgumentError('Invalid OrderType: $value'); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import 'dart:async'; | ||
|
||
import 'package:flutter/material.dart'; | ||
import 'package:get_10101/logger/logger.dart'; | ||
import 'package:get_10101/trade/order_service.dart'; | ||
import 'package:get_10101/trade/position_service.dart'; | ||
|
||
class OrderChangeNotifier extends ChangeNotifier { | ||
final OrderService service; | ||
late Timer timer; | ||
|
||
List<Order>? _orders; | ||
|
||
OrderChangeNotifier(this.service) { | ||
_refresh(); | ||
Timer.periodic(const Duration(seconds: 2), (timer) async { | ||
_refresh(); | ||
}); | ||
} | ||
|
||
void _refresh() async { | ||
try { | ||
final orders = await service.fetchOrders(); | ||
_orders = orders; | ||
|
||
super.notifyListeners(); | ||
} catch (error) { | ||
logger.e(error); | ||
} | ||
} | ||
|
||
List<Order>? getOrders() => _orders; | ||
|
||
@override | ||
void dispose() { | ||
super.dispose(); | ||
timer.cancel(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter/widgets.dart'; | ||
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||
import 'package:get_10101/common/color.dart'; | ||
import 'package:get_10101/common/direction.dart'; | ||
import 'package:get_10101/common/model.dart'; | ||
import 'package:get_10101/trade/order_change_notifier.dart'; | ||
import 'package:get_10101/trade/order_service.dart'; | ||
import 'package:get_10101/trade/position_change_notifier.dart'; | ||
import 'package:get_10101/trade/position_service.dart'; | ||
import 'package:get_10101/trade/quote_change_notifier.dart'; | ||
import 'package:get_10101/trade/quote_service.dart'; | ||
import 'package:get_10101/trade/trade_confirmation_dialog.dart'; | ||
import 'package:intl/intl.dart'; | ||
import 'package:provider/provider.dart'; | ||
|
||
class OrderHistoryTable extends StatelessWidget { | ||
const OrderHistoryTable({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final orderChangeNotified = context.watch<OrderChangeNotifier>(); | ||
final orders = orderChangeNotified.getOrders(); | ||
|
||
if (orders == null) { | ||
return const Center(child: CircularProgressIndicator()); | ||
} | ||
|
||
if (orders.isEmpty) { | ||
return const Center(child: Text('No data available')); | ||
} else { | ||
return buildTable(orders, context); | ||
} | ||
} | ||
|
||
Widget buildTable(List<Order> orders, BuildContext context) { | ||
orders.sort((a, b) => b.creationTimestamp.compareTo(a.creationTimestamp)); | ||
|
||
return Table( | ||
border: TableBorder.symmetric(inside: const BorderSide(width: 2, color: Colors.black)), | ||
defaultVerticalAlignment: TableCellVerticalAlignment.middle, | ||
columnWidths: const { | ||
0: MinColumnWidth(FixedColumnWidth(100.0), FractionColumnWidth(0.1)), | ||
1: MinColumnWidth(FixedColumnWidth(100.0), FractionColumnWidth(0.1)), | ||
2: MinColumnWidth(FixedColumnWidth(100.0), FractionColumnWidth(0.1)), | ||
3: MinColumnWidth(FixedColumnWidth(100.0), FractionColumnWidth(0.1)), | ||
4: MinColumnWidth(FixedColumnWidth(200.0), FractionColumnWidth(0.2)), | ||
}, | ||
children: [ | ||
TableRow( | ||
decoration: BoxDecoration( | ||
color: tenTenOnePurple.shade300, | ||
border: Border.all( | ||
width: 1, | ||
), | ||
borderRadius: const BorderRadius.only( | ||
topLeft: Radius.circular(10), topRight: Radius.circular(10)), | ||
), | ||
children: [ | ||
buildHeaderCell('State'), | ||
buildHeaderCell('Price'), | ||
buildHeaderCell('Quantity'), | ||
buildHeaderCell('Leverage'), | ||
buildHeaderCell('Timestamp'), | ||
], | ||
), | ||
for (var order in orders) | ||
TableRow( | ||
children: [ | ||
buildTableCell(Tooltip(message: order.state.asString, child: stateToIcon(order))), | ||
// buildTableCell(Text(order.id)), | ||
buildTableCell(Text(order.price != null ? order.price.toString() : "NaN")), | ||
buildTableCell( | ||
Text(order.direction == "Short" ? "-${order.quantity}" : "+${order.quantity}")), | ||
buildTableCell(Text("${order.leverage.formatted()}x")), | ||
buildTableCell( | ||
Text("${DateFormat('dd-MM-yyyy – HH:mm').format(order.creationTimestamp)} UTC")), | ||
], | ||
), | ||
], | ||
); | ||
} | ||
|
||
Widget stateToIcon(Order order) { | ||
const double size = 16.0; | ||
var icon = switch (order.state) { | ||
OrderState.initial => | ||
const SizedBox(width: size, height: size, child: CircularProgressIndicator()), | ||
OrderState.rejected => const Icon( | ||
FontAwesomeIcons.circleExclamation, | ||
size: size, | ||
), | ||
OrderState.open => | ||
const SizedBox(width: size, height: size, child: CircularProgressIndicator()), | ||
OrderState.filling => | ||
const SizedBox(width: size, height: size, child: CircularProgressIndicator()), | ||
OrderState.failed => const Icon( | ||
FontAwesomeIcons.circleExclamation, | ||
color: Colors.red, | ||
size: size, | ||
), | ||
OrderState.filled => const Icon( | ||
FontAwesomeIcons.check, | ||
size: size, | ||
), | ||
OrderState.unknown => const Icon( | ||
FontAwesomeIcons.circleExclamation, | ||
size: size, | ||
) | ||
}; | ||
return icon; | ||
} | ||
|
||
TableCell buildHeaderCell(String text) { | ||
return TableCell( | ||
child: Container( | ||
padding: const EdgeInsets.all(10), | ||
alignment: Alignment.center, | ||
child: Text(text, | ||
textAlign: TextAlign.center, | ||
style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.white)))); | ||
} | ||
|
||
TableCell buildTableCell(Widget child) => TableCell( | ||
child: Center( | ||
child: Container( | ||
padding: const EdgeInsets.all(10), alignment: Alignment.center, child: child))); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import 'dart:convert'; | ||
import 'package:flutter/cupertino.dart'; | ||
import 'package:get_10101/common/contract_symbol.dart'; | ||
import 'package:get_10101/common/direction.dart'; | ||
import 'package:get_10101/common/http_client.dart'; | ||
import 'package:get_10101/common/model.dart'; | ||
import 'package:get_10101/common/order_type.dart'; | ||
|
||
class OrderService { | ||
const OrderService(); | ||
|
||
Future<List<Order>> fetchOrders() async { | ||
final response = await HttpClientManager.instance.get(Uri(path: '/api/orders')); | ||
|
||
if (response.statusCode == 200) { | ||
final List<dynamic> jsonData = jsonDecode(response.body); | ||
return jsonData.map((orderData) => Order.fromJson(orderData)).toList(); | ||
} else { | ||
throw FlutterError("Could not fetch orders"); | ||
} | ||
} | ||
} | ||
|
||
class Order { | ||
final String id; //: Uuid, | ||
final Leverage leverage; //: f32, | ||
final Usd quantity; //: f32, | ||
final Usd? price; //: f32, | ||
final ContractSymbol contractSymbol; //: ContractSymbol, | ||
final Direction direction; //: Direction, | ||
final OrderType orderType; //: OrderType, | ||
// TODO: define a state | ||
final OrderState state; //: OrderState, | ||
final DateTime creationTimestamp; //: OffsetDateTime, | ||
// TODO: define failure reason | ||
final String? failureReason; //: Option<FailureReason>, | ||
|
||
Order( | ||
{required this.id, | ||
required this.leverage, | ||
required this.quantity, | ||
required this.price, | ||
required this.contractSymbol, | ||
required this.direction, | ||
required this.orderType, | ||
required this.state, | ||
required this.creationTimestamp, | ||
required this.failureReason}); | ||
|
||
factory Order.fromJson(Map<String, dynamic> json) { | ||
return Order( | ||
id: json['id'] as String, | ||
leverage: Leverage(json['leverage'] as double), | ||
quantity: Usd(json['quantity'] as double), | ||
price: json['price'] != null ? Usd(json['price'] as double) : null, | ||
contractSymbol: ContractSymbol.btcusd, | ||
direction: Direction.fromString(json['direction']), | ||
creationTimestamp: DateTime.parse(json['creation_timestamp'] as String), | ||
orderType: OrderType.fromString(json['order_type'] as String), | ||
state: OrderState.fromString(json['state'] as String), //json['state'] as String, | ||
failureReason: json['failure_reason'], // json['failure_reason'], | ||
); | ||
} | ||
} | ||
|
||
enum OrderState { | ||
initial, | ||
rejected, | ||
open, | ||
filling, | ||
failed, | ||
filled, | ||
unknown; | ||
|
||
String get asString { | ||
switch (this) { | ||
case OrderState.initial: | ||
return "Initial"; | ||
case OrderState.rejected: | ||
return "Rejected"; | ||
case OrderState.open: | ||
return "Open"; | ||
case OrderState.filling: | ||
return "Filling"; | ||
case OrderState.failed: | ||
return "Failed"; | ||
case OrderState.filled: | ||
return "Filled"; | ||
case OrderState.unknown: | ||
return "Unknown"; | ||
} | ||
} | ||
|
||
static OrderState fromString(String value) { | ||
switch (value.toLowerCase()) { | ||
case 'initial': | ||
return OrderState.initial; | ||
case 'rejected': | ||
return OrderState.rejected; | ||
case 'open': | ||
return OrderState.open; | ||
case 'filling': | ||
return OrderState.filling; | ||
case 'filled': | ||
return OrderState.filled; | ||
case 'failed': | ||
return OrderState.failed; | ||
default: | ||
throw ArgumentError('Invalid OrderState: $json'); | ||
} | ||
} | ||
} |
Oops, something went wrong.