diff --git a/lib/frontend/screens/chats/chat_list_screen.dart b/lib/frontend/screens/chats/chat_list_screen.dart index 06326fc..678ac56 100644 --- a/lib/frontend/screens/chats/chat_list_screen.dart +++ b/lib/frontend/screens/chats/chat_list_screen.dart @@ -2,6 +2,9 @@ import 'package:flutter/material.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'dart:math'; import 'dart:ui' as ui; +import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart'; +import 'chat_screen.dart'; import '../calls/calls_tab.dart'; import '../contacts/contacts_tab.dart'; @@ -20,6 +23,27 @@ class _ChatListScreenState extends State int _currentNavIndex = 0; bool _isFabOpen = false; late AnimationController _fabController; + final Set _selectedChats = {}; + final ScrollController _scrollController = ScrollController(); + double _pullRatio = 0.0; // 0.0 = folded (hidden row), 1.0 = fully expanded + + bool get _isSelectionMode => _selectedChats.isNotEmpty; + + void _toggleSelection(String chatId) { + setState(() { + if (_selectedChats.contains(chatId)) { + _selectedChats.remove(chatId); + } else { + _selectedChats.add(chatId); + } + }); + } + + void _clearSelection() { + setState(() { + _selectedChats.clear(); + }); + } @override void initState() { @@ -28,11 +52,31 @@ class _ChatListScreenState extends State vsync: this, duration: const Duration(milliseconds: 350), ); + _scrollController.addListener(_onScroll); + } + + void _onScroll() { + if (_scrollController.hasClients) { + final double offset = _scrollController.offset; + if (offset < 0) { + final newRatio = (offset.abs() / 80.0).clamp(0.0, 1.0); + if (newRatio != _pullRatio) { + setState(() { + _pullRatio = newRatio; + }); + } + } else if (_pullRatio > 0) { + setState(() { + _pullRatio = 0.0; + }); + } + } } @override void dispose() { _fabController.dispose(); + _scrollController.dispose(); super.dispose(); } @@ -59,226 +103,328 @@ class _ChatListScreenState extends State IndexedStack( index: _currentNavIndex, children: [ - CustomScrollView( - slivers: [ - SliverPadding( - padding: const EdgeInsets.fromLTRB(20, 16, 20, 8), - sliver: SliverToBoxAdapter( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Подключение...', - style: TextStyle( - color: cs.onSurface, - fontSize: 20, - fontWeight: FontWeight.w600, - fontFamily: 'Outfit', - ), + Listener( + onPointerSignal: (pointerSignal) { + if (pointerSignal is PointerScrollEvent) { + if (_scrollController.hasClients && + _scrollController.offset <= 0) { + if (pointerSignal.scrollDelta.dy < 0) { + // Scrolled UP (pulling down) + setState(() { + _pullRatio = (_pullRatio + 0.2).clamp(0.0, 1.0); + }); + } else if (pointerSignal.scrollDelta.dy > 0 && + _pullRatio > 0) { + // Scrolled DOWN (folding up) + setState(() { + _pullRatio = (_pullRatio - 0.2).clamp(0.0, 1.0); + }); + } + } + } + }, + child: CustomScrollView( + controller: _scrollController, + physics: const BouncingScrollPhysics( + parent: AlwaysScrollableScrollPhysics(), + ), + slivers: [ + SliverToBoxAdapter( + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + curve: Curves.easeOutCubic, + height: _isSelectionMode + ? 0 + : (132 + (96 * _pullRatio)), + color: Colors.transparent, + clipBehavior: Clip.hardEdge, + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: Curves.easeOutCubic, + transform: Matrix4.translationValues( + 0, + _isSelectionMode ? -100 : 0, + 0, ), - PopupMenuButton( - icon: Icon( - Symbols.more_vert, - color: cs.outline, - weight: 400, - ), - offset: const Offset(0, 48), - elevation: 4, - color: cs.surfaceContainerHigh, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - itemBuilder: (context) => [ - _buildPopupMenuItem( - 1, - 'Кнопка 1', - Symbols.settings, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB( + 20, + 12, + 20, + 4, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + if (_pullRatio < 0.8) + Opacity( + opacity: 1.0 - _pullRatio, + child: Container( + width: 50 * (1.0 - _pullRatio), + height: 32, + margin: const EdgeInsets.only( + right: 8, + ), + child: Stack( + children: [ + _buildFoldedStory( + 'https://i.pravatar.cc/150?u=dasha', + 0, + ), + _buildFoldedStory( + 'https://i.pravatar.cc/150?u=mastika', + 1, + ), + _buildFoldedStory( + 'https://i.pravatar.cc/150?u=stas', + 2, + ), + ], + ), + ), + ), + Text( + 'Подключение...', + style: TextStyle( + color: cs.onSurface, + fontSize: 20, + fontWeight: FontWeight.w600, + fontFamily: 'Outfit', + ), + ), + ], + ), + PopupMenuButton( + icon: Icon( + Symbols.more_vert, + color: cs.outline, + weight: 400, + ), + offset: const Offset(0, 48), + elevation: 4, + color: cs.surfaceContainerHigh, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 16, + ), + ), + itemBuilder: (context) => [ + _buildPopupMenuItem( + 1, + 'Кнопка 1', + Symbols.settings, + ), + _buildPopupMenuItem( + 2, + 'Кнопка 2', + Symbols.notifications, + ), + _buildPopupMenuItem( + 3, + 'Кнопка 3', + Symbols.shield, + ), + _buildPopupMenuItem( + 4, + 'Кнопка 4', + Symbols.info, + ), + ], + ), + ], + ), ), - _buildPopupMenuItem( - 2, - 'Кнопка 2', - Symbols.notifications, + SizedBox( + height: 96 * _pullRatio, + child: Opacity( + opacity: _pullRatio, + child: ListView( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric( + horizontal: 20, + ), + children: [ + _buildStoryItem( + 'Даша', + 'https://i.pravatar.cc/150?u=dasha', + true, + ), + _buildStoryItem( + 'Мастика', + 'https://i.pravatar.cc/150?u=mastika', + false, + ), + ], + ), + ), ), - _buildPopupMenuItem( - 3, - 'Кнопка 3', - Symbols.shield, - ), - _buildPopupMenuItem( - 4, - 'Кнопка 4', - Symbols.info, + Padding( + padding: const EdgeInsets.fromLTRB( + 20, + 4, + 20, + 12, + ), + child: Container( + height: 44, + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(50), + ), + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: Row( + children: [ + Icon( + Symbols.search, + color: cs.outline, + size: 20, + weight: 400, + ), + const SizedBox(width: 10), + Expanded( + child: TextField( + style: TextStyle( + color: cs.onSurface, + fontSize: 15, + ), + decoration: InputDecoration( + hintText: 'Поиск', + hintStyle: TextStyle( + color: cs.outline, + fontSize: 15, + ), + border: InputBorder.none, + isDense: true, + contentPadding: EdgeInsets.zero, + ), + ), + ), + ], + ), + ), ), ], - onSelected: (value) { - // Action handler - }, ), - ], - ), - ), - ), - SliverToBoxAdapter( - child: SizedBox( - height: 96, - child: ListView( - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: 20), - children: [ - _buildStoryItem( - 'Даша', - 'https://i.pravatar.cc/150?u=dasha', - true, - ), - _buildStoryItem( - 'Мастика', - 'https://i.pravatar.cc/150?u=mastika', - false, - ), - ], - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 16, - ), - sliver: SliverToBoxAdapter( - child: Container( - height: 44, - decoration: BoxDecoration( - color: cs.surfaceContainerHighest, - borderRadius: BorderRadius.circular(50), ), - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Row( - children: [ - Icon( - Symbols.search, - color: cs.outline, - size: 20, - weight: 400, - ), - const SizedBox(width: 10), - Expanded( - child: TextField( - style: TextStyle( - color: cs.onSurface, - fontSize: 15, - ), - decoration: InputDecoration( - hintText: 'Поиск', - hintStyle: TextStyle( - color: cs.outline, - fontSize: 15, - ), - border: InputBorder.none, - isDense: true, - contentPadding: EdgeInsets.zero, + ), + ), + SliverPadding( + padding: EdgeInsets.only( + top: _isSelectionMode ? 64 : 0, + ), + sliver: SliverToBoxAdapter( + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: Curves.easeOutCubic, + height: 48, + child: ScrollConfiguration( + behavior: ScrollConfiguration.of(context) + .copyWith( + dragDevices: { + ui.PointerDeviceKind.touch, + ui.PointerDeviceKind.mouse, + ui.PointerDeviceKind.trackpad, + }, ), + child: ListView( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 8, ), + physics: const BouncingScrollPhysics(), + children: [ + _buildFolderChip('Все чаты'), + const SizedBox(width: 8), + _buildFolderChip('Контакты'), + const SizedBox(width: 8), + _buildFolderChip('Пидоры'), + const SizedBox(width: 8), + _buildFolderChip('Каналы'), + const SizedBox(width: 8), + _buildFolderChip('Группы'), + const SizedBox(width: 8), + _buildFolderChip('Боты'), + const SizedBox(width: 8), + _buildFolderChip('Избранное'), + const SizedBox(width: 8), + _buildFolderChip('Архив'), + ], ), - ], - ), - ), - ), - ), - SliverToBoxAdapter( - child: SizedBox( - height: 48, - child: ScrollConfiguration( - behavior: ScrollConfiguration.of(context).copyWith( - dragDevices: { - ui.PointerDeviceKind.touch, - ui.PointerDeviceKind.mouse, - ui.PointerDeviceKind.trackpad, - }, - ), - child: ListView( - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 8, ), - physics: const BouncingScrollPhysics(), - children: [ - _buildFolderChip('Все чаты'), - const SizedBox(width: 8), - _buildFolderChip('Контакты'), - const SizedBox(width: 8), - _buildFolderChip('Пидоры'), - const SizedBox(width: 8), - _buildFolderChip('Каналы'), - const SizedBox(width: 8), - _buildFolderChip('Группы'), - const SizedBox(width: 8), - _buildFolderChip('Боты'), - const SizedBox(width: 8), - _buildFolderChip('Избранное'), - const SizedBox(width: 8), - _buildFolderChip('Архив'), - ], ), ), ), - ), - SliverList( - delegate: SliverChildListDelegate([ - _buildChatItem( - 'Станислав', - 'Хорошо', - '10:07', - 'https://i.pravatar.cc/150?u=stas', - isOnline: true, - isRead: true, - ), - _buildChatItem( - 'Илья', - 'печатает...', - '10:07', - 'https://i.pravatar.cc/150?u=ilya', - isOnline: true, - isTyping: true, - unreadCount: 1, - ), - _buildChatItem( - 'Вероника', - 'Спасибо', - '09:56', - 'https://i.pravatar.cc/150?u=veronika', - isRead: true, - ), - _buildChatItem( - 'Komet Client', - 'Кстати. Смотрите, какую шту...', - '09:56', - 'https://i.pravatar.cc/150?u=komet', - unreadCount: 5, - isMuted: true, - ), - _buildChatItem( - '4-й подъезд', - 'Людмила: Сколько?', - '09:34', - 'https://i.pravatar.cc/150?u=podezd', - unreadCount: 78, - isMuted: true, - ), - const SizedBox(height: 100), - ]), - ), - ], - ), + SliverList( + delegate: SliverChildListDelegate([ + _buildChatItem( + 'stas', + 'Станислав', + 'Хорошо', + '10:07', + 'https://i.pravatar.cc/150?u=stas', + isOnline: true, + isRead: true, + ), + _buildChatItem( + 'ilya', + 'Илья', + 'печатает...', + '10:07', + 'https://i.pravatar.cc/150?u=ilya', + isOnline: true, + isTyping: true, + unreadCount: 1, + ), + _buildChatItem( + 'veronika', + 'Вероника', + 'Спасибо', + '09:56', + 'https://i.pravatar.cc/150?u=veronika', + isRead: true, + ), + _buildChatItem( + 'komet', + 'Komet Client', + 'Кстати. Смотрите, какую шту...', + '09:56', + 'https://i.pravatar.cc/150?u=komet', + unreadCount: 5, + isMuted: true, + ), + _buildChatItem( + 'podezd', + '4-й подъезд', + 'Людмила: Сколько?', + '09:34', + 'https://i.pravatar.cc/150?u=podezd', + unreadCount: 78, + isMuted: true, + ), + ]), + ), + ], + ), // CustomScrollView + ), // Listener const CallsTab(), const ContactsTab(), const SettingsTab(), ], ), - Positioned( + AnimatedPositioned( + duration: const Duration(milliseconds: 300), + curve: Curves.easeOutCubic, left: 8, right: 8, - bottom: 24.0, + bottom: _isSelectionMode ? -100 : 24.0, child: RepaintBoundary( child: Container( height: 68, @@ -386,46 +532,99 @@ class _ChatListScreenState extends State ), ), ), - if (_fabController.value > 0) + if (!_isSelectionMode) ...[ + if (_fabController.value > 0) + Positioned( + right: 20, + bottom: 110 + 74, + child: RepaintBoundary( + child: Transform.scale( + scale: val, + alignment: Alignment.bottomRight, + child: Opacity( + opacity: val > 0.5 ? (val - 0.5) * 2 : 0, + child: _buildFabMenu(), + ), + ), + ), + ), Positioned( right: 20, - bottom: 110 + 74, - child: RepaintBoundary( - child: Transform.scale( - scale: val, - alignment: Alignment.bottomRight, - child: Opacity( - opacity: val > 0.5 ? (val - 0.5) * 2 : 0, - child: _buildFabMenu(), + bottom: 110, + child: FloatingActionButton( + onPressed: _toggleFab, + backgroundColor: cs.primaryContainer, + elevation: 4, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: Transform.rotate( + angle: val * (pi / 4), + child: Icon( + Symbols.add, + color: cs.onPrimaryContainer, + size: 28, + weight: 400, ), ), ), ), - Positioned( - right: 20, - bottom: 110, - child: FloatingActionButton( - onPressed: _toggleFab, - backgroundColor: cs.primaryContainer, - elevation: 4, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - child: Transform.rotate( - angle: val * (pi / 4), - child: Icon( - Symbols.add, - color: cs.onPrimaryContainer, - size: 28, - weight: 400, - ), - ), - ), - ), + ], ], ); }, ), + AnimatedPositioned( + duration: const Duration(milliseconds: 300), + curve: Curves.easeOutCubic, + top: _isSelectionMode ? 0 : -80, + left: 0, + right: 0, + child: Container( + height: 64, + padding: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: cs.surface, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + IconButton( + icon: Icon(Symbols.arrow_back, color: cs.onSurface), + onPressed: _clearSelection, + ), + const SizedBox(width: 8), + Text( + _selectedChats.length.toString(), + style: TextStyle( + color: cs.onSurface, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + const Spacer(), + IconButton( + icon: Icon(Symbols.delete, color: cs.onSurface), + onPressed: () {}, + ), + IconButton( + icon: Icon(Symbols.archive, color: cs.onSurface), + onPressed: () {}, + ), + IconButton( + icon: Icon(Symbols.volume_off, color: cs.onSurface), + onPressed: () {}, + ), + ], + ), + ), + ), ], ), ), @@ -489,6 +688,7 @@ class _ChatListScreenState extends State } Widget _buildChatItem( + String id, String name, String message, String time, @@ -500,87 +700,139 @@ class _ChatListScreenState extends State bool isMuted = false, }) { final cs = Theme.of(context).colorScheme; - return ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 2), - leading: Stack( - children: [ - CircleAvatar(radius: 24, backgroundImage: NetworkImage(imageUrl)), - if (isOnline) - Positioned( - right: 0, - bottom: 0, - child: Container( - width: 12, - height: 12, - decoration: BoxDecoration( - color: cs.primary, - shape: BoxShape.circle, - border: Border.all(color: cs.surface, width: 2), - ), - ), - ), - ], - ), - title: Row( - children: [ - Expanded( - child: Text( - name, - style: TextStyle( - color: cs.onSurface, - fontSize: 15, - fontWeight: FontWeight.w600, - ), + final isSelected = _selectedChats.contains(id); + + return InkWell( + onTap: () { + if (_isSelectionMode) { + _toggleSelection(id); + } else { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ChatScreen(name: name, imageUrl: imageUrl), ), + ); + } + }, + onLongPress: () => _toggleSelection(id), + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + color: isSelected ? cs.primary.withOpacity(0.08) : Colors.transparent, + child: ListTile( + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 2, ), - if (isMuted) - Icon( - Symbols.notifications_off, - color: cs.outlineVariant, - size: 14, - weight: 400, - ), - const SizedBox(width: 8), - Text(time, style: TextStyle(color: cs.outline, fontSize: 12)), - ], - ), - subtitle: Padding( - padding: const EdgeInsets.only(top: 4), - child: Row( - children: [ - Expanded( - child: Text( - message, - style: TextStyle( - color: isTyping ? cs.primary : cs.outline, - fontSize: 14, - fontWeight: isTyping ? FontWeight.w500 : FontWeight.w400, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - if (unreadCount > 0) - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: isMuted - ? cs.surfaceContainerHighest - : cs.surfaceContainerHigh, - borderRadius: BorderRadius.circular(10), + leading: Stack( + children: [ + CircleAvatar(radius: 24, backgroundImage: NetworkImage(imageUrl)), + if (isSelected) + Positioned( + right: -2, + bottom: -2, + child: Container( + width: 20, + height: 20, + decoration: BoxDecoration( + color: cs.primary, + shape: BoxShape.circle, + border: Border.all(color: cs.surface, width: 2), + ), + child: Icon( + Symbols.check, + color: cs.onPrimary, + size: 14, + weight: 600, + ), + ), + ) + else if (isOnline) + Positioned( + right: 0, + bottom: 0, + child: Container( + width: 12, + height: 12, + decoration: BoxDecoration( + color: cs.primary, + shape: BoxShape.circle, + border: Border.all(color: cs.surface, width: 2), + ), + ), ), + ], + ), + title: Row( + children: [ + Expanded( child: Text( - unreadCount.toString(), + name, style: TextStyle( - color: isMuted ? cs.outline : cs.onSurface, - fontSize: 11, + color: cs.onSurface, + fontSize: 15, fontWeight: FontWeight.w600, ), ), - ) - else if (isRead) - Icon(Symbols.done_all, color: cs.primary, size: 16, weight: 400), - ], + ), + if (isMuted) + Icon( + Symbols.notifications_off, + color: cs.outlineVariant, + size: 14, + weight: 400, + ), + const SizedBox(width: 8), + Text(time, style: TextStyle(color: cs.outline, fontSize: 12)), + ], + ), + subtitle: Padding( + padding: const EdgeInsets.only(top: 4), + child: Row( + children: [ + Expanded( + child: Text( + message, + style: TextStyle( + color: isTyping ? cs.primary : cs.outline, + fontSize: 14, + fontWeight: isTyping ? FontWeight.w500 : FontWeight.w400, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + if (unreadCount > 0) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + color: isMuted + ? cs.surfaceContainerHighest + : cs.surfaceContainerHigh, + borderRadius: BorderRadius.circular(10), + ), + child: Text( + unreadCount.toString(), + style: TextStyle( + color: isMuted ? cs.outline : cs.onSurface, + fontSize: 11, + fontWeight: FontWeight.w600, + ), + ), + ) + else if (isRead) + Icon( + Symbols.done_all, + color: cs.primary, + size: 16, + weight: 400, + ), + ], + ), + ), ), ), ); @@ -714,4 +966,20 @@ class _ChatListScreenState extends State ), ); } + + Widget _buildFoldedStory(String imageUrl, int index) { + return Positioned( + left: index * 12.0, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.black, width: 2), + ), + child: CircleAvatar( + radius: 12, + backgroundImage: NetworkImage(imageUrl), + ), + ), + ); + } } diff --git a/lib/frontend/screens/chats/chat_screen.dart b/lib/frontend/screens/chats/chat_screen.dart index e69de29..50f586d 100644 --- a/lib/frontend/screens/chats/chat_screen.dart +++ b/lib/frontend/screens/chats/chat_screen.dart @@ -0,0 +1,240 @@ +import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'dart:ui'; + +class ChatScreen extends StatefulWidget { + final String name; + final String imageUrl; + + const ChatScreen({super.key, required this.name, required this.imageUrl}); + + @override + State createState() => _ChatScreenState(); +} + +class _ChatScreenState extends State { + final TextEditingController _messageController = TextEditingController(); + bool _hasText = false; + + @override + void initState() { + super.initState(); + _messageController.addListener(_onTextChanged); + } + + @override + void dispose() { + _messageController.removeListener(_onTextChanged); + _messageController.dispose(); + super.dispose(); + } + + void _onTextChanged() { + final bool newHasText = _messageController.text.trim().isNotEmpty; + if (newHasText != _hasText) { + setState(() { + _hasText = newHasText; + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + extendBodyBehindAppBar: true, + appBar: AppBar( + backgroundColor: const Color(0xFF1B1B1B).withOpacity(0.8), + elevation: 0, + surfaceTintColor: Colors.transparent, + leading: IconButton( + icon: const Icon( + Symbols.arrow_back, + color: Colors.white, + weight: 400, + ), + onPressed: () => Navigator.pop(context), + ), + titleSpacing: 0, + title: Row( + children: [ + CircleAvatar( + radius: 18, + backgroundImage: NetworkImage(widget.imageUrl), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + widget.name, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w600, + fontFamily: 'Outfit', + ), + ), + ], + ), + const Text( + 'Connecting...', + style: TextStyle( + color: Colors.grey, + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ], + ), + actions: [ + IconButton( + icon: const Icon(Symbols.call, color: Colors.white, weight: 400), + onPressed: () {}, + ), + IconButton( + icon: const Icon( + Symbols.more_vert, + color: Colors.white, + weight: 400, + ), + onPressed: () {}, + ), + ], + ), + body: Stack( + children: [ + Positioned.fill( + child: Image.network( + 'https://images.unsplash.com/photo-1579546929518-9e396f3cc809', + fit: BoxFit.cover, + opacity: const AlwaysStoppedAnimation(0.4), + ), + ), + const Positioned.fill( + child: DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.transparent, Colors.black87], + ), + ), + ), + ), + Column(children: [const Spacer(), _buildInputArea(context)]), + ], + ), + ); + } + + Widget _buildInputArea(BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + curve: Curves.easeOutCubic, + constraints: const BoxConstraints( + minHeight: 54, + maxHeight: 180, + ), + decoration: BoxDecoration( + color: const Color(0xFF1B1B1B).withOpacity(0.9), + borderRadius: BorderRadius.circular(28), + border: Border.all( + color: Colors.white.withOpacity(0.1), + width: 0.5, + ), + ), + padding: const EdgeInsets.symmetric( + horizontal: 14, + vertical: 0, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Symbols.face, + color: Colors.white.withOpacity(0.7), + size: 24, + weight: 400, + ), + const SizedBox(width: 12), + Expanded( + child: TextField( + controller: _messageController, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + ), + maxLines: null, + keyboardType: TextInputType.multiline, + textAlignVertical: TextAlignVertical.center, + decoration: const InputDecoration( + hintText: 'Message', + hintStyle: TextStyle( + color: Colors.grey, + fontSize: 16, + ), + border: InputBorder.none, + isDense: true, + contentPadding: EdgeInsets.zero, + ), + ), + ), + AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: _hasText ? 0 : 36, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 200), + opacity: _hasText ? 0 : 1, + child: _hasText + ? const SizedBox.shrink() + : Padding( + padding: const EdgeInsets.only(left: 12), + child: Icon( + Symbols.attachment, + color: Colors.white.withOpacity(0.7), + size: 24, + weight: 400, + ), + ), + ), + ), + ], + ), + ), + ), + const SizedBox(width: 8), + Container( + width: 54, + height: 54, + alignment: Alignment.center, + decoration: const BoxDecoration( + color: Color(0xFF2B2B2B), + shape: BoxShape.circle, + ), + child: Icon( + _hasText ? Symbols.send : Symbols.mic, + color: Colors.white, + size: 24, + weight: 400, + ), + ), + ], + ), + ), + ); + } +}