desktop_shell.dart
1 import 'package:flutter/material.dart'; 2 import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 4 import '../core/theme/app_theme.dart'; 5 import 'contacts/contacts_screen.dart'; 6 7 /// Side-by-side layout for wide screens: contacts sidebar + content area. 8 class DesktopShell extends ConsumerWidget { 9 final Widget child; 10 final String location; 11 12 const DesktopShell({super.key, required this.child, required this.location}); 13 14 @override 15 Widget build(BuildContext context, WidgetRef ref) { 16 final colors = AppColors.of(context); 17 18 // Right panel shows placeholder when on /contacts (sidebar already shows it) 19 final hasContent = location != '/contacts'; 20 21 final rightPanel = hasContent ? child : _EmptyConversation(); 22 23 return Scaffold( 24 body: SafeArea( 25 child: Row( 26 children: [ 27 // Left panel: contacts sidebar 28 SizedBox( 29 width: 300, 30 child: ContactsScreen(sidebarMode: true), 31 ), 32 VerticalDivider(width: 1, thickness: 1, color: colors.outline), 33 // Right panel: animated content area 34 Expanded( 35 child: AnimatedSwitcher( 36 duration: const Duration(milliseconds: 800), 37 switchInCurve: Curves.easeOut, 38 switchOutCurve: Curves.easeIn, 39 transitionBuilder: (child, animation) { 40 return FadeTransition( 41 opacity: animation, 42 child: SlideTransition( 43 position: Tween<Offset>( 44 begin: const Offset(0.02, 0), 45 end: Offset.zero, 46 ).animate(animation), 47 child: child, 48 ), 49 ); 50 }, 51 child: KeyedSubtree( 52 key: ValueKey(location), 53 child: rightPanel, 54 ), 55 ), 56 ), 57 ], 58 ), 59 ), 60 ); 61 } 62 } 63 64 class _EmptyConversation extends StatelessWidget { 65 @override 66 Widget build(BuildContext context) { 67 final colors = AppColors.of(context); 68 return Center( 69 child: Column( 70 mainAxisSize: MainAxisSize.min, 71 children: [ 72 Icon( 73 Icons.chat_bubble_outline, 74 size: 64, 75 color: colors.onBackgroundSecondary, 76 ), 77 const SizedBox(height: 16), 78 Text( 79 'Select a conversation', 80 style: Theme.of(context).textTheme.titleMedium?.copyWith( 81 color: colors.onBackgroundSecondary, 82 ), 83 ), 84 ], 85 ), 86 ); 87 } 88 }