import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; void main() { runApp(const SmartGardenApp()); } class SmartGardenApp extends StatelessWidget { const SmartGardenApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Smart Garden', theme: ThemeData( primarySwatch: Colors.green, useMaterial3: true, ), home: const SmartGardenScreen(), debugShowCheckedModeBanner: false, ); } } class ChatMessage { final String text; final bool isUser; final DateTime timestamp; ChatMessage({ required this.text, required this.isUser, required this.timestamp, }); } class SmartGardenScreen extends StatefulWidget { const SmartGardenScreen({Key? key}) : super(key: key); @override State createState() => _SmartGardenScreenState(); } class _SmartGardenScreenState extends State { final TextEditingController _textController = TextEditingController(); final List _messages = []; final ScrollController _scrollController = ScrollController(); late VideoPlayerController _videoController; bool _isVideoInitialized = false; @override void initState() { super.initState(); _initializeVideo(); _addInitialMessage(); } void _initializeVideo() { _videoController = VideoPlayerController.asset('assets/videos/basic_img.mp4') ..initialize().then((_) { _videoController.setLooping(true); _videoController.setVolume(0.0); _videoController.play(); setState(() { _isVideoInitialized = true; }); }).catchError((error) { print('비디오 로드 오류: $error'); }); } void _addInitialMessage() { setState(() { _messages.add( ChatMessage( text: '상태 분석이 완료되었습니다.\n무엇을 도와드릴까요?', isUser: false, timestamp: DateTime.now(), ), ); }); } void _sendMessage(String text) { if (text.isEmpty) return; // 사용자 메시지 추가 setState(() { _messages.add( ChatMessage( text: text, isUser: true, timestamp: DateTime.now(), ), ); }); _textController.clear(); _scrollToBottom(); // 봇 응답 (0.5초 딜레이) Future.delayed(const Duration(milliseconds: 500), () { setState(() { _messages.add( ChatMessage( text: '안녕하세요 스마트가든 AI 가이드 푸미입니다', isUser: false, timestamp: DateTime.now(), ), ); }); _scrollToBottom(); }); } void _scrollToBottom() { Future.delayed(const Duration(milliseconds: 100), () { _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); }); } void _sendQuickQuestion(String question) { _textController.text = question; _sendMessage(question); } @override void dispose() { _textController.dispose(); _scrollController.dispose(); _videoController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.transparent, body: Container( decoration: BoxDecoration( image: DecorationImage( image: AssetImage('assets/images/background.png'), fit: BoxFit.cover, ), ), child: Row( children: [ // 좌측 캐릭터 영역 (60%) Expanded( flex: 6, child: Container( margin: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: Color(0xFFE0E0E0), width: 1), ), child: Column( children: [ // 헤더 Padding( padding: const EdgeInsets.all(30), child: Row( children: [ Container( width: 44, height: 44, decoration: BoxDecoration( color: Color(0xFFC8E6C9), borderRadius: BorderRadius.circular(12), ), child: const Center( child: Text('🌱', style: TextStyle(fontSize: 28)), ), ), const SizedBox(width: 16), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Smart Garden', style: TextStyle( fontSize: 20, fontWeight: FontWeight.w600, color: Color(0xFF212121), ), ), Text( '스마트가든 챗봇', style: TextStyle( fontSize: 16, color: Color(0xFF757575), ), ), ], ), ], ), ), // 캐릭터 영역 Expanded( child: Container( margin: const EdgeInsets.symmetric(horizontal: 20), decoration: BoxDecoration( color: Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(12), ), child: _isVideoInitialized ? ClipRRect( borderRadius: BorderRadius.circular(12), child: AspectRatio( aspectRatio: _videoController.value.aspectRatio, child: VideoPlayer(_videoController), ), ) : Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 180, height: 180, decoration: BoxDecoration( color: Color(0xFFF1F8E9), borderRadius: BorderRadius.circular(12), border: Border.all( color: Color(0xFFC8E6C9), width: 1, ), ), child: const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator( color: Color(0xFF81C784), ), SizedBox(height: 20), Text( '비디오 로딩 중...', style: TextStyle( fontSize: 12, color: Color(0xFF757575), ), ), ], ), ), ), ], ), ), ), const SizedBox(height: 40), // 캐릭터 정보 + 설명 문구 Padding( padding: const EdgeInsets.only(left: 30, bottom: 30, right: 30), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 푸미 정보 Row( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), ), child: ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.asset( 'assets/images/profile.png', fit: BoxFit.cover, ), ), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '푸미', style: TextStyle( fontSize: 20, // 16 → 18 fontWeight: FontWeight.w600, color: Color(0xFF212121), ), ), Text( '스마트가든 AI 가이드', style: TextStyle( fontSize: 18, // 12 → 14 color: Color(0xFF757575), ), ), ], ), ], ), const SizedBox(height: 16), // 설명 문구 (반응형) Container( width: double.infinity, // ← 추가: 양쪽 꽉차게 padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Color(0xFFF1F8E9), borderRadius: BorderRadius.circular(8), border: Border.all( color: Color(0xFFC8E6C9), width: 1, ), ), child: const Text( '안녕하세요!\n스마트가든의 식물들이 건강하게 자랄 수 있도록 도와드릴게요.\n궁금한 점을 언제든 물어보세요!', style: TextStyle( fontSize: 13, color: Color(0xFF558B2F), height: 1.6, ), ), ), ], ), ), ], ), ), ), // 우측 채팅 영역 (40%) Expanded( flex: 4, child: Container( margin: const EdgeInsets.all(20), decoration: BoxDecoration( color: Color(0xFFFAFAFA), borderRadius: BorderRadius.circular(16), border: Border.all(color: Color(0xFFE0E0E0), width: 1), ), child: Column( children: [ // 헤더 (왼쪽정렬) Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), ), border: Border( bottom: BorderSide( color: Color(0xFFE0E0E0), width: 1, ), ), ), child: const Text( '푸미와 대화하기', style: TextStyle( fontSize: 22, fontWeight: FontWeight.w600, color: Color(0xFF212121), ), textAlign: TextAlign.left, // ← 왼쪽정렬 ), ), // 채팅 메시지 영역 Expanded( child: Container( margin: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all( color: Color(0xFFE0E0E0), width: 0.5, ), ), child: ListView.builder( controller: _scrollController, padding: const EdgeInsets.all(16), itemCount: _messages.length, itemBuilder: (context, index) { final message = _messages[index]; return _ChatBubble(message: message); }, ), ), ), // 자주하는 질문 버튼들 (채팅 아래, 입력창 위) Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), child: Row( children: [ // 온도 정보 Expanded( child: Container( decoration: BoxDecoration( color: Color(0xFFBBDEFB), borderRadius: BorderRadius.circular(8), border: Border.all( color: Color(0xFF1976D2).withOpacity(0.2), width: 0.5, ), ), child: Material( color: Colors.transparent, child: InkWell( onTap: () => _sendQuickQuestion('현재 온도는?'), borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 12, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( '🌡️', style: TextStyle(fontSize: 20), ), const SizedBox(width: 8), Text( '온도 정보', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1976D2), ), ), ], ), ), ), ), ), ), const SizedBox(width: 8), // 습도 정보 Expanded( child: Container( decoration: BoxDecoration( color: Color(0xFFC8E6C9), borderRadius: BorderRadius.circular(8), border: Border.all( color: Color(0xFF388E3C).withOpacity(0.2), width: 0.5, ), ), child: Material( color: Colors.transparent, child: InkWell( onTap: () => _sendQuickQuestion('현재 습도는?'), borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 12, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( '💧', style: TextStyle(fontSize: 20), ), const SizedBox(width: 8), Text( '습도 정보', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF388E3C), ), ), ], ), ), ), ), ), ), const SizedBox(width: 8), // 물 주기 Expanded( child: Container( decoration: BoxDecoration( color: Color(0xFFFFE0B2), borderRadius: BorderRadius.circular(8), border: Border.all( color: Color(0xFFFFA000).withOpacity(0.2), width: 0.5, ), ), child: Material( color: Colors.transparent, child: InkWell( onTap: () => _sendQuickQuestion('물을 주세요'), borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 12, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( '💦', style: TextStyle(fontSize: 20), ), const SizedBox(width: 8), Text( '물 주기', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFFFFA000), ), ), ], ), ), ), ), ), ), ], ), ), // 입력 필드 + 전송 버튼 (같은 줄) Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), child: Row( children: [ // 입력 필드 Expanded( child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all( color: Color(0xFFE0E0E0), width: 0.5, ), ), child: TextField( controller: _textController, onSubmitted: (value) { _sendMessage(_textController.text); // 엔터로 전송 }, decoration: InputDecoration( hintText: '메시지를 입력하세요...', hintStyle: TextStyle( fontSize: 20, color: Color(0xFF9E9E9E), ), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), style: const TextStyle(fontSize: 20), maxLines: 1, ), ), ), const SizedBox(width: 12), // 전송 버튼 SizedBox( height: 48, width: 60, child: ElevatedButton( onPressed: () { _sendMessage(_textController.text); }, style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF81C784), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: EdgeInsets.zero, ), child: const Text( '전송', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white, ), ), ), ), ], ), ), ], ), ), ), ], ), )); } } class _ChatBubble extends StatelessWidget { final ChatMessage message; const _ChatBubble({required this.message}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( mainAxisAlignment: message.isUser ? MainAxisAlignment.end : MainAxisAlignment.start, children: [ if (!message.isUser) ...[ Container( width: 40, // 32 → 40 height: 40, // 32 → 40 decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), ), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.asset( 'assets/images/profile.png', fit: BoxFit.cover, ), ), ), const SizedBox(width: 10), // 8 → 10 ], Flexible( child: Container( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 10, ), decoration: BoxDecoration( color: message.isUser ? Color(0xFFBBDEFB) : Color(0xFFEEEEEE), borderRadius: BorderRadius.circular(8), ), child: SelectableText( message.text, style: TextStyle( fontSize: 20, color: message.isUser ? Color(0xFF1976D2) : Color(0xFF424242), height: 1.4, ), ), ), ), if (message.isUser) const SizedBox(width: 8), ], ), ); } } class _QuickButton extends StatelessWidget { final String emoji; final String title; final String subtitle; final Color bgColor; final Color textColor; final VoidCallback onPressed; const _QuickButton({ required this.emoji, required this.title, required this.subtitle, required this.bgColor, required this.textColor, required this.onPressed, }); @override Widget build(BuildContext context) { return Material( color: Colors.transparent, child: InkWell( onTap: onPressed, borderRadius: BorderRadius.circular(8), child: Container( decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(8), border: Border.all( color: textColor.withOpacity(0.2), width: 0.5, ), ), padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 10, ), child: Column( children: [ Text( emoji, style: const TextStyle(fontSize: 18), ), const SizedBox(height: 4), Text( title, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: textColor, ), textAlign: TextAlign.center, ), if (subtitle.isNotEmpty) ...[ const SizedBox(height: 2), Text( subtitle, style: TextStyle( fontSize: 10, color: textColor.withOpacity(0.8), ), textAlign: TextAlign.center, ), ], ], ), ), ), ); } }