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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/core/app_routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart';
import 'package:mostro/features/account/screens/account_screen.dart';
import 'package:mostro/features/home/screens/home_screen.dart';
import 'package:mostro/features/notifications/screens/notifications_screen.dart';
import 'package:mostro/features/order/screens/add_order_screen.dart';
import 'package:mostro/features/walkthrough/providers/first_run_provider.dart';
import 'package:mostro/features/walkthrough/screens/walkthrough_screen.dart';

Expand Down Expand Up @@ -99,7 +100,10 @@ final GoRouter appRouter = GoRouter(
),
GoRoute(
path: AppRoute.addOrder,
builder: (_, __) => const _Stub('Add Order'),
builder: (context, state) {
final type = state.uri.queryParameters['type'] ?? 'sell';
return AddOrderScreen(orderType: type);
},
),
GoRoute(
path: AppRoute.takeSell,
Expand Down
7 changes: 2 additions & 5 deletions lib/features/home/screens/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:mostro/features/home/widgets/order_list_item.dart';
import 'package:mostro/shared/widgets/bottom_nav_bar.dart';
import 'package:mostro/shared/utils/fiat_currencies.dart';
import 'package:mostro/shared/widgets/notification_bell.dart';
import 'package:mostro/shared/widgets/add_order_button.dart';
import 'package:mostro/shared/widgets/order_filter.dart';

/// Home screen — public order book with BUY/SELL tabs, filter, and drawer.
Expand Down Expand Up @@ -194,11 +195,7 @@ class _HomeScreenState extends ConsumerState<HomeScreen>
DrawerMenu(onClose: () => setState(() => _drawerOpen = false)),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.push(AppRoute.addOrder),
backgroundColor: green,
child: const Icon(Icons.add, color: Colors.black),
),
floatingActionButton: const AddOrderButton(),
bottomNavigationBar: const BottomNavBar(),
);
}
Expand Down
291 changes: 291 additions & 0 deletions lib/features/order/screens/add_order_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';

import 'package:mostro/core/app_routes.dart';
import 'package:mostro/core/app_theme.dart';
import 'package:mostro/features/order/widgets/currency_section.dart';
import 'package:mostro/features/order/widgets/payment_method_section.dart';
import 'package:mostro/features/order/widgets/price_section.dart';

/// Create order screen — Route `/add_order`.
///
/// 4 cards: order type + amount + currency, payment methods,
/// price type, premium slider. Bottom bar: Cancel + Submit.
class AddOrderScreen extends ConsumerStatefulWidget {
const AddOrderScreen({super.key, this.orderType = 'sell'});

final String orderType;

@override
ConsumerState<AddOrderScreen> createState() => _AddOrderScreenState();
}

class _AddOrderScreenState extends ConsumerState<AddOrderScreen> {
final _amountController = TextEditingController();
final _minController = TextEditingController();
final _maxController = TextEditingController();
bool _isRange = false;
bool _submitting = false;

bool get _isBuy => widget.orderType == 'buy';

@override
void initState() {
super.initState();
// Reset form providers so each new screen starts fresh.
Future.microtask(() {
ref.read(selectedPaymentMethodsProvider.notifier).state = [];
ref.read(customPaymentMethodProvider.notifier).state = '';
ref.read(selectedFiatCodeProvider.notifier).state = 'USD';
ref.read(isMarketPriceProvider.notifier).state = true;
ref.read(premiumValueProvider.notifier).state = 0.0;
ref.read(fixedSatsProvider.notifier).state = '';
});
}

@override
void dispose() {
_amountController.dispose();
_minController.dispose();
_maxController.dispose();
super.dispose();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

bool _checkValid(List<String> selectedMethods, String customMethod) {
final hasPayment = selectedMethods.isNotEmpty || customMethod.isNotEmpty;
if (!hasPayment) return false;

if (_isRange) {
final min = double.tryParse(_minController.text);
final max = double.tryParse(_maxController.text);
return min != null && max != null && min > 0 && min < max;
} else {
final amount = double.tryParse(_amountController.text);
return amount != null && amount > 0;
}
}

Future<void> _submit() async {
final selectedMethods = ref.read(selectedPaymentMethodsProvider);
final customMethod = ref.read(customPaymentMethodProvider);
if (_submitting || !_checkValid(selectedMethods, customMethod)) return;
setState(() => _submitting = true);

try {
// TODO (Phase 7): Call create_order() via Rust bridge.
// For now, simulate a short delay and navigate to My Trades.
await Future.delayed(const Duration(milliseconds: 300));

if (!mounted) return;

// Navigate to My Trades tab (order book / trades screen).
context.go(AppRoute.orderBook);
} finally {
if (mounted) setState(() => _submitting = false);
}
}

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colors = theme.extension<AppColors>();
final green = colors?.mostroGreen ?? const Color(0xFF8CC63F);
final cardBg = colors?.backgroundCard ?? const Color(0xFF1E2230);
final inputBg = colors?.backgroundInput ?? const Color(0xFF252A3A);
final selectedMethods = ref.watch(selectedPaymentMethodsProvider);
final customMethod = ref.watch(customPaymentMethodProvider);
final isValid = _checkValid(selectedMethods, customMethod);

return Scaffold(
appBar: AppBar(title: const Text('CREATING NEW ORDER')),
body: ListView(
padding: const EdgeInsets.all(AppSpacing.lg),
children: [
// Card 1: Order type + amount + currency
_Card(
color: cardBg,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'You want to ${_isBuy ? 'buy' : 'sell'} Bitcoin',
style: theme.textTheme.headlineSmall,
),
const SizedBox(height: AppSpacing.md),

// Range toggle
Row(
children: [
Text(
'Range order',
style: TextStyle(
color: colors?.textSecondary,
fontSize: 13,
),
),
const SizedBox(width: AppSpacing.sm),
Switch(
value: _isRange,
activeThumbColor: green,
onChanged: (v) => setState(() => _isRange = v),
),
],
),
const SizedBox(height: AppSpacing.sm),

// Amount input(s)
if (_isRange) ...[
Row(
children: [
Expanded(
child: TextField(
controller: _minController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: 'Min',
filled: true,
fillColor: inputBg,
border: OutlineInputBorder(
borderRadius:
BorderRadius.circular(AppRadius.input),
borderSide: BorderSide.none,
),
),
onChanged: (_) => setState(() {}),
),
),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: TextField(
controller: _maxController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: 'Max',
filled: true,
fillColor: inputBg,
border: OutlineInputBorder(
borderRadius:
BorderRadius.circular(AppRadius.input),
borderSide: BorderSide.none,
),
),
onChanged: (_) => setState(() {}),
),
),
],
),
] else
TextField(
controller: _amountController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: 'Fiat amount',
filled: true,
fillColor: inputBg,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(AppRadius.input),
borderSide: BorderSide.none,
),
),
onChanged: (_) => setState(() {}),
),
const SizedBox(height: AppSpacing.md),

// Currency selector
const CurrencySection(),
],
),
),
const SizedBox(height: AppSpacing.lg),

// Card 2: Payment methods
_Card(
color: cardBg,
child: const PaymentMethodSection(),
),
const SizedBox(height: AppSpacing.lg),

// Card 3 + 4: Price type + premium
_Card(
color: cardBg,
child: const PriceSection(),
),
const SizedBox(height: AppSpacing.xxl),
],
),

// Bottom bar: Cancel + Submit
bottomNavigationBar: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.lg,
vertical: AppSpacing.md,
),
child: Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => context.pop(),
style: OutlinedButton.styleFrom(
foregroundColor: colors?.textSecondary,
side: BorderSide(
color: colors?.textSecondary ?? Colors.grey,
),
minimumSize: const Size(0, 48),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadius.button),
),
),
child: const Text('Cancel'),
),
),
const SizedBox(width: AppSpacing.md),
Expanded(
child: FilledButton(
onPressed: isValid ? _submit : null,
style: FilledButton.styleFrom(
backgroundColor: green,
foregroundColor: Colors.black,
disabledBackgroundColor: green.withValues(alpha: 0.3),
minimumSize: const Size(0, 48),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppRadius.button),
),
),
child: _submitting
? const SizedBox(
width: 20,
height: 20,
child:
CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Submit'),
),
),
],
),
),
),
);
}
}

class _Card extends StatelessWidget {
const _Card({required this.color, required this.child});

final Color color;
final Widget child;

@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(AppSpacing.lg),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(AppRadius.card),
),
child: child,
);
}
}
Loading