서버 연결 ã 코드 작성ì중
This commit is contained in:
892
lib/main.dart
892
lib/main.dart
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'screens/chat_screen.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const SmartGardenApp());
|
||||
@@ -12,890 +12,12 @@ class SmartGardenApp extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Smart Garden',
|
||||
theme: ThemeData(primarySwatch: Colors.green, useMaterial3: true),
|
||||
home: const SmartGardenScreen(),
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.green,
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: const ChatScreen(),
|
||||
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<SmartGardenScreen> createState() => _SmartGardenScreenState();
|
||||
}
|
||||
|
||||
class _SmartGardenScreenState extends State<SmartGardenScreen> {
|
||||
final TextEditingController _textController = TextEditingController();
|
||||
final List<ChatMessage> _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: Stack(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage('assets/images/background1.png'),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(40), // 메인 컨테이너 여백 조정
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.8), // 투명도 넣기
|
||||
borderRadius: BorderRadius.circular(16), // 모서리 둥글게
|
||||
border: Border.all(color: Color(0xFFE0E0E0), width: 1),
|
||||
),
|
||||
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(
|
||||
color: Color(0xFFC8E6C9),
|
||||
// ← 연한 초록 배경 추가
|
||||
borderRadius: BorderRadius.circular(
|
||||
24,
|
||||
),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
24,
|
||||
),
|
||||
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,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF212121),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'스마트가든 AI 가이드',
|
||||
style: TextStyle(
|
||||
fontSize: 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(),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.eco,
|
||||
color: Color(0xFF81C784),
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
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(
|
||||
image: DecorationImage(
|
||||
image: AssetImage(
|
||||
'assets/images/chat_img.png',
|
||||
),
|
||||
// ← 배경 이미지 추가
|
||||
fit: BoxFit.cover,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Colors.white.withOpacity(0.9),
|
||||
// 채팅 배경 이미지 투명도 조정
|
||||
BlendMode.lighten,
|
||||
),
|
||||
),
|
||||
color: Colors.white.withOpacity(0.5),
|
||||
// 채팅창 투명도
|
||||
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);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
// 자주하는 질문 버튼들을 감싸는 컨테이너
|
||||
Container(
|
||||
margin: const EdgeInsets.fromLTRB(16, 12, 16, 12),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xFFC8E6C9).withOpacity(0.3), // 배경
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// 온도 정보
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white, // ← 흰색
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
border: Border.all(
|
||||
color: Color(
|
||||
0xFF1976D2,
|
||||
).withOpacity(0.2),
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () =>
|
||||
_sendQuickQuestion('현재 온도는?'),
|
||||
borderRadius: BorderRadius.circular(
|
||||
25,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 12,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(
|
||||
6,
|
||||
),
|
||||
),
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.thermostat,
|
||||
color: Color(0xFF4CAF50),
|
||||
size: 25,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'온도 정보',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF000000),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// 습도 정보
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white, // ← 흰색
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
border: Border.all(
|
||||
color: Color(
|
||||
0xFF388E3C,
|
||||
).withOpacity(0.2),
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () =>
|
||||
_sendQuickQuestion('현재 습도는?'),
|
||||
borderRadius: BorderRadius.circular(
|
||||
25,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 12,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(
|
||||
6,
|
||||
),
|
||||
),
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.water_drop,
|
||||
color: Color(0xFF4CAF50),
|
||||
size: 25,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'습도 정보',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF000000),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// 물 주기
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white, // ← 흰색
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
border: Border.all(
|
||||
color: Color(
|
||||
0xFFFFA000,
|
||||
).withOpacity(0.2),
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () =>
|
||||
_sendQuickQuestion('물을 주세요'),
|
||||
borderRadius: BorderRadius.circular(
|
||||
25,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 12,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(
|
||||
6,
|
||||
),
|
||||
),
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
Icons.waves,
|
||||
color: Color(0xFF4CAF50),
|
||||
size: 25,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'물 주기',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF000000),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 입력 필드 + 전송 버튼 (같은 줄)
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Layer 이미지 (화면 전체, 클릭 불가)
|
||||
IgnorePointer(
|
||||
child: Image.asset(
|
||||
'assets/images/layer_img1.png',
|
||||
fit: BoxFit.cover,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ChatBubble extends StatelessWidget {
|
||||
final ChatMessage message;
|
||||
|
||||
const _ChatBubble({required this.message});
|
||||
|
||||
String _formatTime(DateTime dateTime) {
|
||||
final hour = dateTime.hour;
|
||||
final minute = dateTime.minute;
|
||||
final period = hour >= 12 ? '오후' : '오전';
|
||||
final displayHour = hour > 12 ? hour - 12 : (hour == 0 ? 12 : hour);
|
||||
|
||||
return '$period ${displayHour.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 30), // 채팅창 사이 간격 여백 주기
|
||||
child: Row(
|
||||
mainAxisAlignment: message.isUser
|
||||
? MainAxisAlignment.end
|
||||
: MainAxisAlignment.start,
|
||||
children: [
|
||||
if (!message.isUser) ...[
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xFFC8E6C9), // ← 연한 초록 배경
|
||||
borderRadius: BorderRadius.circular(20), // ← 원형 (8 → 20)
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20), // ← 원형 (8 → 20)
|
||||
child: Image.asset(
|
||||
'assets/images/profile.png',
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
Flexible(
|
||||
child: Row(
|
||||
mainAxisAlignment: message.isUser
|
||||
? MainAxisAlignment.end
|
||||
: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (message.isUser)
|
||||
Text(
|
||||
_formatTime(message.timestamp),
|
||||
style: TextStyle(fontSize: 12, color: Color(0xFF9E9E9E)),
|
||||
),
|
||||
if (message.isUser) const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 10,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: message.isUser ? Color(0xFFE8F5E9) : Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: SelectableText(
|
||||
message.text,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: message.isUser
|
||||
? Color(0xFF000000)
|
||||
: Color(0xFF424242),
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!message.isUser) const SizedBox(width: 8),
|
||||
if (!message.isUser)
|
||||
Text(
|
||||
_formatTime(message.timestamp),
|
||||
style: TextStyle(fontSize: 12, color: Color(0xFF9E9E9E)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user