diff --git a/assets/images/layer_img.png b/assets/images/layer_img.png index 8c2151c..d3fff76 100644 Binary files a/assets/images/layer_img.png and b/assets/images/layer_img.png differ diff --git a/assets/images/layer_img1.png b/assets/images/layer_img1.png new file mode 100644 index 0000000..8c2151c Binary files /dev/null and b/assets/images/layer_img1.png differ diff --git a/assets/videos/shy.mp4 b/assets/videos/shy.mp4 new file mode 100644 index 0000000..29fe3d2 Binary files /dev/null and b/assets/videos/shy.mp4 differ diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index 8ade767..e5f9042 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -5,6 +5,22 @@ import '../services/chat_service.dart'; import 'dart:math'; +// ===== 반응형 크기 계산 함수 ===== +double _getResponsiveFontSize(BuildContext context, double baseSize) { + double screenWidth = MediaQuery.of(context).size.width; + return baseSize * (screenWidth / 1366); // 기준: 1366px (14.1인치) +} + +double _getResponsivePadding(BuildContext context, double basePadding) { + double screenWidth = MediaQuery.of(context).size.width; + return basePadding * (screenWidth / 1366); +} + +double _getResponsiveSize(BuildContext context, double baseSize) { + double screenWidth = MediaQuery.of(context).size.width; + return baseSize * (screenWidth / 1366); +} + class ChatScreen extends StatefulWidget { const ChatScreen({Key? key}) : super(key: key); @@ -224,7 +240,7 @@ class _ChatScreenState extends State { ), child: Center( child: Container( - margin: const EdgeInsets.all(30), // 메인 컨테이너 여백 조정 + margin: EdgeInsets.all(_getResponsivePadding(context, 30)), // 메인 컨테이너 여백 조정 decoration: BoxDecoration( color: Colors.white.withOpacity(0.8), // 투명도 넣기 borderRadius: BorderRadius.circular(36), // 모서리 둥글게 @@ -235,37 +251,47 @@ class _ChatScreenState extends State { Expanded( flex: 5, child: Container( - margin: const EdgeInsets.fromLTRB(30, 0, 30, 50), + margin: EdgeInsets.fromLTRB( + _getResponsivePadding(context, 30), + _getResponsivePadding(context, 50), + _getResponsivePadding(context, 30), + _getResponsivePadding(context, 50), + ), child: Column( children: [ // 헤더 Padding( - padding: const EdgeInsets.fromLTRB(30, 66, 30, 30), + padding: EdgeInsets.fromLTRB( + _getResponsivePadding(context, 30), + _getResponsivePadding(context, 20), + _getResponsivePadding(context, 30), + _getResponsivePadding(context, 15), + ), child: Row( children: [ Container( - width: 44, - height: 44, + width: _getResponsiveSize(context, 44), + height: _getResponsiveSize(context, 44), decoration: BoxDecoration( color: Color(0xFFC8E6C9), - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(_getResponsiveSize(context, 12)), ), - child: const Center( + child: Center( child: Text( '🌱', - style: TextStyle(fontSize: 28), + style: TextStyle(fontSize: _getResponsiveFontSize(context, 28)), ), ), ), - const SizedBox(width: 16), + SizedBox(width: _getResponsivePadding(context, 16)), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( + Text( 'Smart Garden', style: TextStyle( - fontSize: 20, + fontSize: _getResponsiveFontSize(context, 20), fontWeight: FontWeight.w600, color: Color(0xFF212121), ), @@ -273,7 +299,7 @@ class _ChatScreenState extends State { Text( '스마트가든 챗봇', style: TextStyle( - fontSize: 16, + fontSize: _getResponsiveFontSize(context, 16), color: Color(0xFF757575), ), ), @@ -285,8 +311,11 @@ class _ChatScreenState extends State { // 캐릭터 영역 Expanded( child: Container( - margin: const EdgeInsets.symmetric( - horizontal: 30, + margin: EdgeInsets.fromLTRB( + _getResponsivePadding(context, 30), + _getResponsivePadding(context, 10), + _getResponsivePadding(context, 30), + _getResponsivePadding(context, 10), ), decoration: BoxDecoration( color: Color(0xFFF5F5F5), @@ -325,7 +354,7 @@ class _ChatScreenState extends State { width: 1, ), ), - child: const Center( + child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -333,11 +362,11 @@ class _ChatScreenState extends State { CircularProgressIndicator( color: Color(0xFF81C784), ), - SizedBox(height: 20), + SizedBox(height: _getResponsivePadding(context, 20)), Text( '비디오 로딩 중...', style: TextStyle( - fontSize: 12, + fontSize: _getResponsiveFontSize(context, 12), color: Color(0xFF757575), ), ), @@ -349,12 +378,18 @@ class _ChatScreenState extends State { ), ), ), - const SizedBox(height: 55), + SizedBox(height: _getResponsivePadding(context, 16)), + // SizedBox(height: _getResponsivePadding(context, 16)), //26.06.26 // 캐릭터 정보 + 설명 문구 // 1. 가장 바깥쪽 박스 추가 Container( - margin: const EdgeInsets.fromLTRB(30, 0, 30, 0), - padding: const EdgeInsets.all(24), + margin: EdgeInsets.fromLTRB( + _getResponsivePadding(context, 30), + 0, + _getResponsivePadding(context, 30), + 0, + ), + padding: EdgeInsets.all(_getResponsivePadding(context, 24)), decoration: BoxDecoration( color: Colors.white.withOpacity(0.9), borderRadius: BorderRadius.circular(30), @@ -384,24 +419,24 @@ class _ChatScreenState extends State { child: Image.asset('assets/images/profile.png', fit: BoxFit.cover), ), ), - const SizedBox(width: 12), - const Column( + SizedBox(width: _getResponsivePadding(context, 12)), + Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('푸미', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: Color(0xFF212121))), - Text('스마트가든 AI 가이드', style: TextStyle(fontSize: 14, color: Color(0xFF757575))), + Text('푸미', style: TextStyle(fontSize: _getResponsiveFontSize(context, 18), fontWeight: FontWeight.w600, color: Color(0xFF212121))), + Text('스마트가든 AI 가이드', style: TextStyle(fontSize: _getResponsiveFontSize(context, 14), color: Color(0xFF757575))), ], ), ], ), - const SizedBox(height: 20), + SizedBox(height: _getResponsivePadding(context, 20)), Container( width: double.infinity, - padding: const EdgeInsets.all(16), + padding: EdgeInsets.all(_getResponsivePadding(context, 16)), decoration: BoxDecoration( color: const Color(0xFFF1F8E9), - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(_getResponsiveSize(context, 12)), //border: Border.all(color: const Color(0xFFC8E6C9), width: 1), boxShadow: [ BoxShadow( @@ -411,9 +446,9 @@ class _ChatScreenState extends State { ), ], ), - child: const Text( + child: Text( '안녕하세요!\n스마트가든의 식물들이 건강하게 자랄 수 있도록 도와드릴게요.\n궁금한 점을 언제든 물어보세요!', - style: TextStyle(fontSize: 14, color: Colors.black, height: 1.6), + style: TextStyle(fontSize: _getResponsiveFontSize(context, 13), color: Color(0xFF558B2F), height: 1.6), ), ), ], @@ -427,15 +462,15 @@ class _ChatScreenState extends State { Expanded( flex: 5, child: Container( - margin: const EdgeInsets.only( - top: 50, - bottom: 50, - right: 50, - left: 10, + margin: EdgeInsets.only( + top: _getResponsivePadding(context, 50), + bottom: _getResponsivePadding(context, 50), + right: _getResponsivePadding(context, 50), + left: _getResponsivePadding(context, 10), ), decoration: BoxDecoration( color: Color(0xFFfbfdf8), - borderRadius: BorderRadius.circular(30), + borderRadius: BorderRadius.circular(_getResponsiveSize(context, 30)), // border: Border.all( // color: Color(0xFFE0E0E0), // width: 1, @@ -452,20 +487,20 @@ class _ChatScreenState extends State { children: [ // 헤더 Container( - padding: const EdgeInsets.all(20), + padding: EdgeInsets.all(_getResponsivePadding(context, 20)), decoration: BoxDecoration(), child: Row( children: [ - const Icon( + Icon( Icons.eco, color: Color(0xFF81C784), - size: 24, + size: _getResponsiveSize(context, 24), ), - const SizedBox(width: 12), - const Text( + SizedBox(width: _getResponsivePadding(context, 12)), + Text( '푸미 일지', style: TextStyle( - fontSize: 22, + fontSize: _getResponsiveFontSize(context, 22), fontWeight: FontWeight.w600, color: Color(0xFF212121), ), @@ -477,8 +512,8 @@ class _ChatScreenState extends State { // 채팅 메시지 영역 Expanded( child: Container( - margin: const EdgeInsets.symmetric( - horizontal: 16, + margin: EdgeInsets.symmetric( + horizontal: _getResponsivePadding(context, 16), ), // decoration: BoxDecoration( // image: DecorationImage( @@ -532,7 +567,7 @@ class _ChatScreenState extends State { ), child: ListView.builder( controller: _scrollController, - padding: const EdgeInsets.all(16), + padding: EdgeInsets.all(_getResponsivePadding(context, 16)), itemCount: _messages.length, itemBuilder: (context, index) { final message = _messages[index]; @@ -549,11 +584,19 @@ class _ChatScreenState extends State { ), // 자주하는 질문 버튼들을 감싸는 컨테이너 Container( - margin: const EdgeInsets.fromLTRB(16, 12, 16, 12), - padding: const EdgeInsets.all(12), + margin: EdgeInsets.fromLTRB( + _getResponsivePadding(context, 16), + _getResponsivePadding(context, 12), + _getResponsivePadding(context, 16), + _getResponsivePadding(context, 12), + ), + padding: EdgeInsets.symmetric( + horizontal: _getResponsivePadding(context, 6), + vertical: _getResponsivePadding(context, 12), + ), decoration: BoxDecoration( color: Color(0xFFecf6df).withOpacity(0.8), // 배경 - borderRadius: BorderRadius.circular(22), + borderRadius: BorderRadius.circular(_getResponsiveSize(context, 22)), ), child: Row( children: [ @@ -562,7 +605,7 @@ class _ChatScreenState extends State { child: Container( decoration: BoxDecoration( color: Colors.white, // ← 흰색 - borderRadius: BorderRadius.circular(25), + borderRadius: BorderRadius.circular(_getResponsiveSize(context, 25)), ), child: Material( @@ -571,39 +614,39 @@ class _ChatScreenState extends State { onTap: () => _sendQuickQuestion('현재 온도는?'), borderRadius: BorderRadius.circular( - 25, + _getResponsiveSize(context, 25), ), child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 12, + padding: EdgeInsets.symmetric( + horizontal: _getResponsivePadding(context, 12), + vertical: _getResponsivePadding(context, 12), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( - width: 32, - height: 32, + width: _getResponsiveSize(context, 32), + height: _getResponsiveSize(context, 32), decoration: BoxDecoration( borderRadius: BorderRadius.circular( - 6, + _getResponsiveSize(context, 6), ), ), - child: const Center( + child: Center( child: Icon( Icons.thermostat, color: Color(0xFF4CAF50), - size: 25, + size: _getResponsiveSize(context, 25), ), ), ), - const SizedBox(width: 8), + SizedBox(width: _getResponsivePadding(context, 8)), Text( '온도 정보', style: TextStyle( - fontSize: 18, + fontSize: _getResponsiveFontSize(context, 18), fontWeight: FontWeight.w600, color: Colors.black, ), @@ -615,13 +658,13 @@ class _ChatScreenState extends State { ), ), ), - const SizedBox(width: 8), + SizedBox(width: _getResponsivePadding(context, 8)), // 습도 정보 Expanded( child: Container( decoration: BoxDecoration( color: Colors.white, // ← 흰색 - borderRadius: BorderRadius.circular(25), + borderRadius: BorderRadius.circular(_getResponsiveSize(context, 25)), ), child: Material( @@ -630,39 +673,39 @@ class _ChatScreenState extends State { onTap: () => _sendQuickQuestion('현재 습도는?'), borderRadius: BorderRadius.circular( - 25, + _getResponsiveSize(context, 25), ), child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 12, + padding: EdgeInsets.symmetric( + horizontal: _getResponsivePadding(context, 12), + vertical: _getResponsivePadding(context, 12), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( - width: 32, - height: 32, + width: _getResponsiveSize(context, 32), + height: _getResponsiveSize(context, 32), decoration: BoxDecoration( borderRadius: BorderRadius.circular( - 6, + _getResponsiveSize(context, 6), ), ), - child: const Center( + child: Center( child: Icon( Icons.water_drop, color: Color(0xFF4CAF50), - size: 25, + size: _getResponsiveSize(context, 25), ), ), ), - const SizedBox(width: 8), + SizedBox(width: _getResponsivePadding(context, 8)), Text( '습도 정보', style: TextStyle( - fontSize: 18, + fontSize: _getResponsiveFontSize(context, 18), fontWeight: FontWeight.w600, color: Color(0xFF000000), ), @@ -674,13 +717,13 @@ class _ChatScreenState extends State { ), ), ), - const SizedBox(width: 8), + SizedBox(width: _getResponsivePadding(context, 8)), // 물 주기 Expanded( child: Container( decoration: BoxDecoration( color: Colors.white, // ← 흰색 - borderRadius: BorderRadius.circular(25), + borderRadius: BorderRadius.circular(_getResponsiveSize(context, 25)), ), child: Material( color: Colors.transparent, @@ -688,39 +731,39 @@ class _ChatScreenState extends State { onTap: () => _sendQuickQuestion('물을 주세요'), borderRadius: BorderRadius.circular( - 25, + _getResponsiveSize(context, 25), ), child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 12, + padding: EdgeInsets.symmetric( + horizontal: _getResponsivePadding(context, 12), + vertical: _getResponsivePadding(context, 12), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( - width: 32, - height: 32, + width: _getResponsiveSize(context, 32), + height: _getResponsiveSize(context, 32), decoration: BoxDecoration( borderRadius: BorderRadius.circular( - 6, + _getResponsiveSize(context, 6), ), ), - child: const Center( + child: Center( child: Icon( Icons.waves, color: Color(0xFF4CAF50), - size: 25, + size: _getResponsiveSize(context, 25), ), ), ), - const SizedBox(width: 8), + SizedBox(width: _getResponsivePadding(context, 8)), Text( '물 주기', style: TextStyle( - fontSize: 18, + fontSize: _getResponsiveFontSize(context, 18), fontWeight: FontWeight.w600, color: Color(0xFF000000), ), @@ -737,7 +780,12 @@ class _ChatScreenState extends State { ), // 입력 필드 + 전송 버튼 (같은 줄) Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + padding: EdgeInsets.fromLTRB( + _getResponsivePadding(context, 16), + 0, + _getResponsivePadding(context, 16), + _getResponsivePadding(context, 16), + ), child: Row( children: [ // 입력 필드 @@ -745,7 +793,7 @@ class _ChatScreenState extends State { child: Container( decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(22), + borderRadius: BorderRadius.circular(_getResponsiveSize(context, 22)), // border: Border.all( // color: Color(0xFFE0E0E0), // width: 0.5, @@ -768,26 +816,26 @@ class _ChatScreenState extends State { decoration: InputDecoration( hintText: '메시지를 입력하세요...', hintStyle: TextStyle( - fontSize: 16, + fontSize: _getResponsiveFontSize(context, 16), color: Color(0xFF9E9E9E), ), border: InputBorder.none, contentPadding: - const EdgeInsets.symmetric( - horizontal: 16, - vertical: 22, + EdgeInsets.symmetric( + horizontal: _getResponsivePadding(context, 16), + vertical: _getResponsivePadding(context, 22), ), ), - style: const TextStyle(fontSize: 20), + style: TextStyle(fontSize: _getResponsiveFontSize(context, 20)), maxLines: 1, ), ), ), - const SizedBox(width: 12), + SizedBox(width: _getResponsivePadding(context, 12)), // 전송 버튼 SizedBox( - height: 60, - width: 60, + height: _getResponsiveSize(context, 60), + width: _getResponsiveSize(context, 60), child: ElevatedButton( onPressed: () => _sendMessage(_textController.text), style: ElevatedButton.styleFrom( @@ -795,14 +843,14 @@ class _ChatScreenState extends State { elevation: 6, // 그림자 깊이 shadowColor: Colors.black.withOpacity(0.4), // 그림자 색상 shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(100), + borderRadius: BorderRadius.circular(_getResponsiveSize(context, 100)), ), padding: EdgeInsets.zero, ), child: ImageIcon( AssetImage('assets/images/send.png'), color: Colors.white, - size: 28, + size: _getResponsiveSize(context, 28), ), ), ), @@ -854,7 +902,9 @@ class _ChatBubble extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.only(bottom: 30), + padding: EdgeInsets.only( + bottom: _getResponsivePadding(context, 30), + ), child: Row( mainAxisAlignment: message.isUser ? MainAxisAlignment.end @@ -876,7 +926,7 @@ class _ChatBubble extends StatelessWidget { ), ), ), - const SizedBox(width: 10), + SizedBox(width: _getResponsivePadding(context, 10)), ], Flexible( child: Row( @@ -888,20 +938,23 @@ class _ChatBubble extends StatelessWidget { if (message.isUser) Text( _formatTime(message.timestamp), - style: TextStyle(fontSize: 12, color: Color(0xFF9E9E9E)), + style: TextStyle( + fontSize: _getResponsiveFontSize(context, 12), + color: Color(0xFF9E9E9E), + ), ), - if (message.isUser) const SizedBox(width: 8), + if (message.isUser) SizedBox(width: _getResponsivePadding(context, 8)), Flexible( child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 10, + padding: EdgeInsets.symmetric( + horizontal: _getResponsivePadding(context, 16), + vertical: _getResponsivePadding(context, 10), ), decoration: BoxDecoration( color: message.isUser ? Color(0xFFecf6df) : Colors.white, - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(_getResponsiveSize(context, 8)), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), @@ -915,7 +968,7 @@ class _ChatBubble extends StatelessWidget { : SelectableText( message.text, style: TextStyle( - fontSize: 20, + fontSize: _getResponsiveFontSize(context, 20), color: message.isUser ? Color(0xFF000000) : Color(0xFF424242), @@ -924,11 +977,14 @@ class _ChatBubble extends StatelessWidget { ), ), ), - if (!message.isUser) const SizedBox(width: 8), + if (!message.isUser) SizedBox(width: _getResponsivePadding(context, 8)), if (!message.isUser) Text( _formatTime(message.timestamp), - style: TextStyle(fontSize: 12, color: Color(0xFF9E9E9E)), + style: TextStyle( + fontSize: _getResponsiveFontSize(context, 12), + color: Color(0xFF9E9E9E), + ), ), ], ), @@ -990,13 +1046,13 @@ class _LoadingAnimationState extends State<_LoadingAnimation> index: 0, getOffset: _getOffset, ), - const SizedBox(width: 3), + SizedBox(width: _getResponsiveSize(context, 3)), _AnimatedDot( progress: progress, index: 1, getOffset: _getOffset, ), - const SizedBox(width: 3), + SizedBox(width: _getResponsiveSize(context, 3)), _AnimatedDot( progress: progress, index: 2, diff --git a/pubspec.yaml b/pubspec.yaml index 50ec724..22a6ab5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -69,6 +69,7 @@ flutter: - assets/videos/angry.mp4 - assets/videos/thirst.mp4 - assets/videos/cold.mp4 + - assets/videos/shy.mp4 - assets/images/profile.png - assets/images/background.png