diff --git a/lib/features/chat/screens/chat_rooms_list.dart b/lib/features/chat/screens/chat_rooms_list.dart index 8bc3c7bd..0655d0b9 100644 --- a/lib/features/chat/screens/chat_rooms_list.dart +++ b/lib/features/chat/screens/chat_rooms_list.dart @@ -10,7 +10,7 @@ import 'package:mostro_mobile/shared/providers/legible_handle_provider.dart'; import 'package:mostro_mobile/shared/providers/session_notifier_provider.dart'; import 'package:mostro_mobile/shared/widgets/bottom_nav_bar.dart'; import 'package:mostro_mobile/shared/widgets/mostro_app_bar.dart'; -import 'package:mostro_mobile/shared/widgets/mostro_app_drawer.dart'; +import 'package:mostro_mobile/shared/widgets/custom_drawer_overlay.dart'; class ChatRoomsScreen extends ConsumerWidget { const ChatRoomsScreen({super.key}); @@ -22,27 +22,28 @@ class ChatRoomsScreen extends ConsumerWidget { return Scaffold( backgroundColor: AppTheme.dark1, appBar: const MostroAppBar(), - drawer: const MostroAppDrawer(), - body: Container( - margin: const EdgeInsets.fromLTRB(16, 16, 16, 16), - decoration: BoxDecoration( - color: const Color(0xFF303544), - borderRadius: BorderRadius.circular(20), - ), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: Text( - 'CHAT', - style: TextStyle(color: AppTheme.mostroGreen), + body: CustomDrawerOverlay( + child: Container( + margin: const EdgeInsets.fromLTRB(16, 16, 16, 16), + decoration: BoxDecoration( + color: const Color(0xFF303544), + borderRadius: BorderRadius.circular(20), + ), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'CHAT', + style: TextStyle(color: AppTheme.mostroGreen), + ), ), - ), - Expanded( - child: _buildBody(chatListState), - ), - const BottomNavBar(), - ], + Expanded( + child: _buildBody(chatListState), + ), + const BottomNavBar(), + ], + ), ), ), ); diff --git a/lib/features/home/screens/home_screen.dart b/lib/features/home/screens/home_screen.dart index e495a610..349a4339 100644 --- a/lib/features/home/screens/home_screen.dart +++ b/lib/features/home/screens/home_screen.dart @@ -9,7 +9,7 @@ import 'package:mostro_mobile/shared/widgets/add_order_button.dart'; import 'package:mostro_mobile/shared/widgets/bottom_nav_bar.dart'; import 'package:mostro_mobile/shared/widgets/mostro_app_bar.dart'; import 'package:mostro_mobile/shared/widgets/order_filter.dart'; -import 'package:mostro_mobile/shared/widgets/mostro_app_drawer.dart'; +import 'package:mostro_mobile/shared/widgets/custom_drawer_overlay.dart'; class HomeScreen extends ConsumerWidget { const HomeScreen({super.key}); @@ -21,69 +21,95 @@ class HomeScreen extends ConsumerWidget { return Scaffold( backgroundColor: AppTheme.backgroundDark, appBar: MostroAppBar(), - drawer: const MostroAppDrawer(), - body: Stack( - children: [ - RefreshIndicator( - onRefresh: () async { - return await ref.refresh(filteredOrdersProvider); - }, - child: Column( + body: CustomDrawerOverlay( + child: Stack( + children: [ + // Main content column with bottom navigation + Column( children: [ - _buildTabs(ref), - _buildFilterButton(context, ref), + // Content area that expands to fill available space Expanded( - child: Container( - color: const Color(0xFF1D212C), - child: filteredOrders.isEmpty - ? const Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.search_off, - color: Colors.white30, - size: 48, - ), - SizedBox(height: 16), - Text( - 'No orders available', - style: TextStyle( - color: Colors.white60, - fontSize: 16, - ), - ), - Text( - 'Try changing filter settings or check back later', - style: TextStyle( - color: Colors.white38, - fontSize: 14, - ), - textAlign: TextAlign.center, - ), - ], + child: RefreshIndicator( + onRefresh: () async { + return await ref.refresh(filteredOrdersProvider); + }, + child: GestureDetector( + onHorizontalDragEnd: (details) { + if (details.primaryVelocity != null && + details.primaryVelocity! < 0) { + ref.read(homeOrderTypeProvider.notifier).state = + OrderType.buy; + } else if (details.primaryVelocity != null && + details.primaryVelocity! > 0) { + ref.read(homeOrderTypeProvider.notifier).state = + OrderType.sell; + } + }, + child: Column( + children: [ + _buildTabs(ref), + _buildFilterButton(context, ref), + Expanded( + child: Container( + color: const Color(0xFF1D212C), + child: filteredOrders.isEmpty + ? const Center( + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon( + Icons.search_off, + color: Colors.white30, + size: 48, + ), + SizedBox(height: 16), + Text( + 'No orders available', + style: TextStyle( + color: Colors.white60, + fontSize: 16, + ), + ), + Text( + 'Try changing filter settings or check back later', + style: TextStyle( + color: Colors.white38, + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + ], + ), + ) + : ListView.builder( + itemCount: filteredOrders.length, + padding: const EdgeInsets.only( + bottom: 100, top: 6), + itemBuilder: (context, index) { + final order = filteredOrders[index]; + return OrderListItem(order: order); + }, + ), ), - ) - : ListView.builder( - itemCount: filteredOrders.length, - padding: const EdgeInsets.only(bottom: 155, top: 6), - itemBuilder: (context, index) { - final order = filteredOrders[index]; - return OrderListItem(order: order); - }, ), + ], + ), + ), ), ), + // Bottom navigation bar fixed at the bottom const BottomNavBar(), ], ), - ), - Positioned( - bottom: 80 + MediaQuery.of(context).viewPadding.bottom + 16, - right: 16, - child: const AddOrderButton(), - ), - ], + // Floating action button positioned above bottom nav bar + Positioned( + bottom: 80 + MediaQuery.of(context).viewPadding.bottom + 16, + right: 16, + child: const AddOrderButton(), + ), + ], + ), ), ); } diff --git a/lib/features/trades/screens/trades_screen.dart b/lib/features/trades/screens/trades_screen.dart index b3fb50a1..723f8614 100644 --- a/lib/features/trades/screens/trades_screen.dart +++ b/lib/features/trades/screens/trades_screen.dart @@ -7,7 +7,7 @@ import 'package:mostro_mobile/features/trades/providers/trades_provider.dart'; import 'package:mostro_mobile/features/trades/widgets/trades_list.dart'; import 'package:mostro_mobile/shared/widgets/bottom_nav_bar.dart'; import 'package:mostro_mobile/shared/widgets/mostro_app_bar.dart'; -import 'package:mostro_mobile/shared/widgets/mostro_app_drawer.dart'; +import 'package:mostro_mobile/shared/widgets/custom_drawer_overlay.dart'; class TradesScreen extends ConsumerWidget { const TradesScreen({super.key}); @@ -20,103 +20,104 @@ class TradesScreen extends ConsumerWidget { return Scaffold( backgroundColor: AppTheme.backgroundDark, appBar: const MostroAppBar(), - drawer: const MostroAppDrawer(), - body: RefreshIndicator( - onRefresh: () async { - // Force reload the orders repository first - ref.read(orderRepositoryProvider).reloadData(); - // Then refresh the filtered trades provider - ref.invalidate(filteredTradesProvider); - }, - child: Column( - children: [ - Expanded( - child: Column( - children: [ - // Header with dark background - Container( - width: double.infinity, - padding: const EdgeInsets.all(16.0), - decoration: BoxDecoration( - color: AppTheme.backgroundDark, - border: Border( - bottom: BorderSide(color: Colors.white24, width: 0.5), + body: CustomDrawerOverlay( + child: RefreshIndicator( + onRefresh: () async { + // Force reload the orders repository first + ref.read(orderRepositoryProvider).reloadData(); + // Then refresh the filtered trades provider + ref.invalidate(filteredTradesProvider); + }, + child: Column( + children: [ + Expanded( + child: Column( + children: [ + // Header with dark background + Container( + width: double.infinity, + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + color: AppTheme.backgroundDark, + border: Border( + bottom: BorderSide(color: Colors.white24, width: 0.5), + ), ), - ), - child: const Text( - 'My Active Trades', - style: TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.bold, + child: const Text( + 'My Active Trades', + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.bold, + ), ), ), - ), - // Content area with dark background - Expanded( - child: Container( - decoration: const BoxDecoration( - color: AppTheme.backgroundDark, - ), - child: Column( - children: [ - // Espacio superior - const SizedBox(height: 16.0), - Expanded( - child: tradesAsync.when( - data: (trades) => _buildOrderList(trades), - loading: () => const Center( - child: CircularProgressIndicator(), - ), - error: (error, _) => Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.error_outline, - color: Colors.red, - size: 60, - ), - const SizedBox(height: 16), - Text( - 'Error loading trades', - style: TextStyle(color: AppTheme.cream1), - ), - Text( - error.toString(), - style: TextStyle( - color: AppTheme.cream1, fontSize: 12), - textAlign: TextAlign.center, - ), - const SizedBox(height: 16), - ElevatedButton( - onPressed: () { - ref.invalidate(orderEventsProvider); - ref.invalidate(filteredTradesProvider); - }, - child: const Text('Retry'), - ), - ], + // Content area with dark background + Expanded( + child: Container( + decoration: const BoxDecoration( + color: AppTheme.backgroundDark, + ), + child: Column( + children: [ + const SizedBox(height: 16.0), + Expanded( + child: tradesAsync.when( + data: (trades) => _buildOrderList(trades), + loading: () => const Center( + child: CircularProgressIndicator(), + ), + error: (error, _) => Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.error_outline, + color: Colors.red, + size: 60, + ), + const SizedBox(height: 16), + Text( + 'Error loading trades', + style: + TextStyle(color: AppTheme.cream1), + ), + Text( + error.toString(), + style: TextStyle( + color: AppTheme.cream1, + fontSize: 12), + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () { + ref.invalidate(orderEventsProvider); + ref.invalidate( + filteredTradesProvider); + }, + child: const Text('Retry'), + ), + ], + ), ), ), ), - ), - ], + ], + ), ), ), - ), - ], + ], + ), ), - ), - const BottomNavBar(), - ], + const BottomNavBar(), + ], + ), ), ), ); } - // Función eliminada: _buildFilterButton - Widget _buildOrderList(List trades) { if (trades.isEmpty) { return const Center( diff --git a/lib/shared/providers/drawer_provider.dart b/lib/shared/providers/drawer_provider.dart new file mode 100644 index 00000000..d474c76b --- /dev/null +++ b/lib/shared/providers/drawer_provider.dart @@ -0,0 +1,13 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class DrawerNotifier extends StateNotifier { + DrawerNotifier() : super(false); + + void openDrawer() => state = true; + void closeDrawer() => state = false; + void toggleDrawer() => state = !state; +} + +final drawerProvider = StateNotifierProvider((ref) { + return DrawerNotifier(); +}); diff --git a/lib/shared/widgets/bottom_nav_bar.dart b/lib/shared/widgets/bottom_nav_bar.dart index 64825d5a..1a8f6cc3 100644 --- a/lib/shared/widgets/bottom_nav_bar.dart +++ b/lib/shared/widgets/bottom_nav_bar.dart @@ -3,7 +3,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:lucide_icons/lucide_icons.dart'; import 'package:mostro_mobile/core/app_theme.dart'; -import 'package:google_fonts/google_fonts.dart'; final chatCountProvider = StateProvider((ref) => 0); final orderBookNotificationCountProvider = StateProvider((ref) => 0); @@ -20,6 +19,7 @@ class BottomNavBar extends ConsumerWidget { return SafeArea( top: false, + bottom: true, child: Container( width: double.infinity, height: 80, @@ -35,26 +35,26 @@ class BottomNavBar extends ConsumerWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - _buildNavItem( - context, - LucideIcons.book, - 'Order Book', - 0, - ), - _buildNavItem( - context, - LucideIcons.zap, - 'My Trades', - 1, - notificationCount: orderNotificationCount, - ), - _buildNavItem( - context, - LucideIcons.messageSquare, - 'Chat', - 2, - notificationCount: chatCount, - ), + _buildNavItem( + context, + LucideIcons.book, + 'Order Book', + 0, + ), + _buildNavItem( + context, + LucideIcons.zap, + 'My Trades', + 1, + notificationCount: orderNotificationCount, + ), + _buildNavItem( + context, + LucideIcons.messageSquare, + 'Chat', + 2, + notificationCount: chatCount, + ), ], ), ), @@ -70,9 +70,11 @@ class BottomNavBar extends ConsumerWidget { Color textColor = isActive ? AppTheme.activeColor : Colors.white; return Expanded( - child: Material( - color: Colors.transparent, - child: InkWell( + child: Semantics( + button: true, + enabled: true, + label: 'Navigate to $label', + child: GestureDetector( onTap: () => _onItemTapped(context, index), child: SizedBox( height: double.infinity, @@ -111,7 +113,7 @@ class BottomNavBar extends ConsumerWidget { const SizedBox(height: 4), Text( label, - style: GoogleFonts.inter( + style: TextStyle( fontSize: 12, fontWeight: FontWeight.w400, color: textColor, diff --git a/lib/shared/widgets/custom_drawer_overlay.dart b/lib/shared/widgets/custom_drawer_overlay.dart new file mode 100644 index 00000000..fe09dfb6 --- /dev/null +++ b/lib/shared/widgets/custom_drawer_overlay.dart @@ -0,0 +1,165 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:lucide_icons/lucide_icons.dart'; +import 'package:mostro_mobile/core/app_theme.dart'; +import 'package:mostro_mobile/shared/providers/drawer_provider.dart'; + +class CustomDrawerOverlay extends ConsumerWidget { + final Widget child; + + const CustomDrawerOverlay({super.key, required this.child}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isDrawerOpen = ref.watch(drawerProvider); + final statusBarHeight = MediaQuery.of(context).padding.top; + final appBarHeight = AppBar().preferredSize.height; + + return Stack( + children: [ + // Main content + child, + + // Overlay background + if (isDrawerOpen) + GestureDetector( + onTap: () => ref.read(drawerProvider.notifier).closeDrawer(), + child: Container( + width: double.infinity, + height: double.infinity, + color: Colors.black.withOpacity(0.3), + ), + ), + + // Drawer + WillPopScope( + onWillPop: () async { + if (isDrawerOpen) { + // Close drawer if it's open + ref.read(drawerProvider.notifier).closeDrawer(); + return false; // Prevent route pop + } + return true; // Allow route pop + }, + child: AnimatedPositioned( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + left: isDrawerOpen ? 0 : -MediaQuery.of(context).size.width * 0.7, + top: 0, + bottom: 0, + child: GestureDetector( + onHorizontalDragEnd: (details) { + if (details.primaryVelocity != null && + details.primaryVelocity! < 0) { + ref.read(drawerProvider.notifier).closeDrawer(); + } + }, + child: Container( + width: MediaQuery.of(context).size.width * 0.7, + decoration: BoxDecoration( + color: AppTheme.dark1, + border: Border( + right: BorderSide( + color: Colors.white.withValues(alpha: 0.1), + width: 1.0, + ), + ), + ), + child: Padding( + padding: EdgeInsets.only(top: statusBarHeight), + child: Column( + children: [ + SizedBox(height: 24), + + // Logo header + Container( + height: 60, + width: double.infinity, + alignment: Alignment.center, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/logo.png'), + fit: BoxFit.contain, + ), + ), + ), + + SizedBox(height: 24), + + Divider( + height: 1, + thickness: 1, + color: Colors.white.withOpacity(0.1), + ), + + SizedBox(height: 16), + + // Menu items + _buildMenuItem( + context, + ref, + icon: LucideIcons.user, + title: 'Account', + route: '/key_management', + ), + _buildMenuItem( + context, + ref, + icon: LucideIcons.settings, + title: 'Settings', + route: '/settings', + ), + _buildMenuItem( + context, + ref, + icon: LucideIcons.info, + title: 'About', + route: '/about', + ), + _buildMenuItem( + context, + ref, + icon: LucideIcons.bookOpen, + title: 'Walkthrough', + route: '/walkthrough', + ), + ], + ), + ), + ), + ), + ), + ), + ], + ); + } + + Widget _buildMenuItem( + BuildContext context, + WidgetRef ref, { + required IconData icon, + required String title, + required String route, + }) { + return ListTile( + dense: true, + leading: Icon( + icon, + color: AppTheme.cream1, + size: 22, + ), + title: Text( + title, + style: AppTheme.theme.textTheme.bodyLarge?.copyWith( + color: AppTheme.cream1, + fontWeight: FontWeight.w500, + ), + ), + onTap: () { + ref.read(drawerProvider.notifier).closeDrawer(); + context.push(route); + }, + ); + } +} diff --git a/lib/shared/widgets/mostro_app_bar.dart b/lib/shared/widgets/mostro_app_bar.dart index 361044f7..74a57812 100644 --- a/lib/shared/widgets/mostro_app_bar.dart +++ b/lib/shared/widgets/mostro_app_bar.dart @@ -1,8 +1,8 @@ -// lib/shared/widgets/mostro_app_bar.dart import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:heroicons/heroicons.dart'; import 'package:mostro_mobile/core/app_theme.dart'; +import 'package:mostro_mobile/shared/providers/drawer_provider.dart'; class MostroAppBar extends ConsumerWidget implements PreferredSizeWidget { const MostroAppBar({super.key}); @@ -10,9 +10,17 @@ class MostroAppBar extends ConsumerWidget implements PreferredSizeWidget { @override Widget build(BuildContext context, WidgetRef ref) { return AppBar( - backgroundColor: AppTheme.dark1, + backgroundColor: AppTheme.backgroundDark, elevation: 0, leadingWidth: 70, + // Add bottom border similar to bottom navbar + bottom: PreferredSize( + preferredSize: const Size.fromHeight(1.0), + child: Container( + height: 1.0, + color: Colors.white.withOpacity(0.1), + ), + ), // Use a custom IconButton with specific padding leading: Padding( padding: const EdgeInsets.only(left: 16.0), @@ -24,7 +32,7 @@ class MostroAppBar extends ConsumerWidget implements PreferredSizeWidget { size: 28, ), onPressed: () { - Scaffold.of(context).openDrawer(); + ref.read(drawerProvider.notifier).toggleDrawer(); }, ), ), @@ -52,5 +60,5 @@ class MostroAppBar extends ConsumerWidget implements PreferredSizeWidget { } @override - Size get preferredSize => const Size.fromHeight(kToolbarHeight); + Size get preferredSize => const Size.fromHeight(kToolbarHeight + 1.0); } diff --git a/lib/shared/widgets/mostro_app_drawer.dart b/lib/shared/widgets/mostro_app_drawer.dart deleted file mode 100644 index 91ac8cb3..00000000 --- a/lib/shared/widgets/mostro_app_drawer.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:mostro_mobile/core/app_theme.dart'; - -class MostroAppDrawer extends StatelessWidget { - const MostroAppDrawer({super.key}); - - @override - Widget build(BuildContext context) { - return Drawer( - backgroundColor: AppTheme.dark2, - child: ListView( - children: [ - DrawerHeader( - decoration: BoxDecoration( - color: AppTheme.dark1, - image: const DecorationImage( - image: AssetImage('assets/images/logo.png'), - fit: BoxFit.scaleDown)), - child: Stack(), - ), - ListTile( - leading: Icon( - Icons.person_outline_sharp, - color: AppTheme.cream1, - ), - title: Text( - 'Account', - style: AppTheme.theme.textTheme.headlineMedium, - ), - onTap: () { - context.push('/key_management'); - }, - ), - ListTile( - leading: Icon( - Icons.settings_outlined, - color: AppTheme.cream1, - ), - title: Text( - 'Settings', - style: AppTheme.theme.textTheme.headlineMedium, - ), - onTap: () { - context.push('/settings'); - }, - ), - ListTile( - leading: Icon( - Icons.info_outlined, - color: AppTheme.cream1, - ), - title: Text( - 'About', - style: AppTheme.theme.textTheme.headlineMedium, - ), - onTap: () { - context.push('/about'); - }, - ), - ListTile( - leading: Icon( - Icons.menu_book_sharp, - color: AppTheme.cream1, - ), - title: Text( - 'Walkthrough', - style: AppTheme.theme.textTheme.headlineMedium, - ), - onTap: () { - context.push('/walkthrough'); - }, - ), - ], - ), - ); - } -}