감정표현, 채팅응답대기 추가
This commit is contained in:
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import '../models/chat_message.dart';
|
||||
import '../services/chat_service.dart';
|
||||
import 'dart:math'; //26.06.24 추가
|
||||
|
||||
|
||||
class ChatScreen extends StatefulWidget {
|
||||
const ChatScreen({Key? key}) : super(key: key);
|
||||
@@ -18,31 +20,73 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
late VideoPlayerController _videoController;
|
||||
bool _isVideoInitialized = false;
|
||||
bool _isLoading = false;
|
||||
int _currentEmotion = 1; // ← 추가 (현재 감정, 기본값: 1 = DEFAULT)
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeVideo();
|
||||
// 더미 컨트롤러로 먼저 초기화
|
||||
_videoController = VideoPlayerController.asset('assets/videos/default.mp4');
|
||||
// 실제 감정에 따른 비디오 초기화
|
||||
_initializeVideo(_currentEmotion);
|
||||
_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');
|
||||
//26.06.24 추가 시작
|
||||
String _getVideoFileName(int emotionCategory) {
|
||||
switch(emotionCategory) {
|
||||
case 1:
|
||||
return 'default.mp4'; // DEFAULT
|
||||
case 2:
|
||||
return 'joy.mp4'; // JOY
|
||||
case 3:
|
||||
return 'thinking.mp4'; // THINKING
|
||||
case 4:
|
||||
return 'realization.mp4'; // REALIZATION
|
||||
case 5:
|
||||
return 'angry.mp4'; // ANGER
|
||||
case 6:
|
||||
return 'thirst.mp4'; // THIRST
|
||||
case 7:
|
||||
return 'cold.mp4'; // COLD
|
||||
default:
|
||||
return 'default.mp4'; // 기본값
|
||||
}
|
||||
}
|
||||
|
||||
void _initializeVideo(int emotionCategory) {
|
||||
final videoFileName = _getVideoFileName(emotionCategory);
|
||||
|
||||
print('비디오 변경: emotion=$emotionCategory → $videoFileName');
|
||||
|
||||
// 새 비디오 컨트롤러 생성
|
||||
final newController = VideoPlayerController.asset('assets/videos/$videoFileName');
|
||||
|
||||
// 새 비디오를 먼저 로드
|
||||
newController.initialize().then((_) {
|
||||
// 로드 완료 후에 기존 컨트롤러 교체
|
||||
try {
|
||||
if (_videoController.hasListeners) {
|
||||
_videoController.dispose();
|
||||
}
|
||||
} catch (e) {
|
||||
print('기존 컨트롤러 dispose 스킵: $e');
|
||||
}
|
||||
|
||||
// 새 컨트롤러로 교체
|
||||
_videoController = newController;
|
||||
_videoController.setLooping(true);
|
||||
_videoController.setVolume(0.0);
|
||||
_videoController.play();
|
||||
|
||||
setState(() {
|
||||
_isVideoInitialized = true;
|
||||
});
|
||||
}).catchError((error) {
|
||||
print('비디오 로드 오류: $error');
|
||||
});
|
||||
}
|
||||
//26.06.24 추가 끝
|
||||
|
||||
void _addInitialMessage() {
|
||||
setState(() {
|
||||
@@ -51,35 +95,68 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
text: '상태 분석이 완료되었습니다.\n무엇을 도와드릴까요?',
|
||||
isUser: false,
|
||||
timestamp: DateTime.now(),
|
||||
emotionCategory: _currentEmotion, // 감정 추가
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
//26.06.24 추가 시작
|
||||
void _sendMessage(String text) async {
|
||||
if (text.isEmpty) return;
|
||||
|
||||
// 사용자 메시지 추가
|
||||
setState(() {
|
||||
_messages.add(
|
||||
ChatMessage(text: text, isUser: true, timestamp: DateTime.now()),
|
||||
ChatMessage(
|
||||
text: text,
|
||||
isUser: true,
|
||||
timestamp: DateTime.now(),
|
||||
emotionCategory: null, // 사용자 메시지는 감정값 없음
|
||||
),
|
||||
);
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
_textController.clear();
|
||||
_scrollToBottom();
|
||||
|
||||
// 로딩 메시지 추가 (... 표시)
|
||||
setState(() {
|
||||
_messages.add(
|
||||
ChatMessage(
|
||||
text: '로딩 중...',
|
||||
isUser: false,
|
||||
timestamp: DateTime.now(),
|
||||
emotionCategory: _currentEmotion, // 현재 감정값 유지
|
||||
),
|
||||
);
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
_scrollToBottom();
|
||||
|
||||
// 서버로 메시지 전송
|
||||
try {
|
||||
final response = await ChatService.sendMessage(text);
|
||||
final (responseMessage, emotionCategory) = await ChatService.sendMessage(text);
|
||||
|
||||
print('받은 감정값: $emotionCategory (기존: $_currentEmotion)');
|
||||
|
||||
setState(() {
|
||||
// 마지막 로딩 메시지를 실제 응답으로 교체
|
||||
_messages.removeLast(); // 로딩 메시지 제거
|
||||
// 감정값이 변경되었으면 비디오 변경
|
||||
if (emotionCategory != _currentEmotion) {
|
||||
print('감정 변경! $emotionCategory로 비디오 교체');
|
||||
_currentEmotion = emotionCategory;
|
||||
// 백그라운드에서 비디오 로드 (화면 깜빡임 없음)
|
||||
_initializeVideo(_currentEmotion);
|
||||
}
|
||||
_messages.add(
|
||||
ChatMessage(
|
||||
text: response,
|
||||
text: responseMessage,
|
||||
isUser: false,
|
||||
timestamp: DateTime.now(),
|
||||
emotionCategory: emotionCategory, // ← 추가
|
||||
),
|
||||
);
|
||||
_isLoading = false;
|
||||
@@ -88,11 +165,13 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
} catch (e) {
|
||||
print('메시지 전송 오류: $e');
|
||||
setState(() {
|
||||
_messages.removeLast(); // ← 로딩 메시지 제거
|
||||
_messages.add(
|
||||
ChatMessage(
|
||||
text: '오류가 발생했습니다. 다시 시도해주세요.',
|
||||
isUser: false,
|
||||
timestamp: DateTime.now(),
|
||||
emotionCategory: _currentEmotion, // ← 추가
|
||||
),
|
||||
);
|
||||
_isLoading = false;
|
||||
@@ -100,6 +179,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
_scrollToBottom();
|
||||
}
|
||||
}
|
||||
//26.06.24 추가 끝
|
||||
|
||||
void _scrollToBottom() {
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
@@ -453,10 +533,18 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: _messages.length,
|
||||
//26.06.24 추가 시작
|
||||
itemBuilder: (context, index) {
|
||||
final message = _messages[index];
|
||||
return _ChatBubble(message: message);
|
||||
final isLoadingMessage = _isLoading &&
|
||||
index == _messages.length - 1 &&
|
||||
!message.isUser;
|
||||
return _ChatBubble(
|
||||
message: message,
|
||||
isLoading: isLoadingMessage,
|
||||
);
|
||||
},
|
||||
//26.06.24 추가 끝
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -748,8 +836,12 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
|
||||
class _ChatBubble extends StatelessWidget {
|
||||
final ChatMessage message;
|
||||
final bool isLoading; //26.06.24 추가
|
||||
|
||||
const _ChatBubble({required this.message});
|
||||
const _ChatBubble({
|
||||
required this.message,
|
||||
this.isLoading = false //26.06.24 추가
|
||||
});
|
||||
|
||||
String _formatTime(DateTime dateTime) {
|
||||
final hour = dateTime.hour;
|
||||
@@ -819,16 +911,20 @@ class _ChatBubble extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
child: SelectableText(
|
||||
// 26.06.24 추가 시작
|
||||
child: isLoading
|
||||
? _LoadingAnimation() // 로딩 중
|
||||
: SelectableText(
|
||||
message.text,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: message.isUser
|
||||
? Colors.black
|
||||
: Colors.black,
|
||||
? Color(0xFF000000)
|
||||
: Color(0xFF424242),
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
// 26.06.24 추가 끝
|
||||
),
|
||||
),
|
||||
if (!message.isUser) const SizedBox(width: 8),
|
||||
@@ -844,4 +940,128 @@ class _ChatBubble extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//26.06.24 추가 시작
|
||||
class _LoadingAnimation extends StatefulWidget {
|
||||
const _LoadingAnimation();
|
||||
|
||||
@override
|
||||
State<_LoadingAnimation> createState() => _LoadingAnimationState();
|
||||
}
|
||||
|
||||
class _LoadingAnimationState extends State<_LoadingAnimation>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 900), //움직이는 속도
|
||||
vsync: this,
|
||||
)..repeat();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
double _getOffset(double progress, int index) {
|
||||
final delay = index * 0.15;
|
||||
final adjustedProgress = (progress + delay) % 1.0;
|
||||
return sin(adjustedProgress * pi * 2) * 2;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
final progress = _controller.value;
|
||||
|
||||
return SizedBox(
|
||||
height: 20, // 일반 텍스트 높이와 동일
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
_AnimatedDot(
|
||||
progress: progress,
|
||||
index: 0,
|
||||
getOffset: _getOffset,
|
||||
),
|
||||
const SizedBox(width: 3),
|
||||
_AnimatedDot(
|
||||
progress: progress,
|
||||
index: 1,
|
||||
getOffset: _getOffset,
|
||||
),
|
||||
const SizedBox(width: 3),
|
||||
_AnimatedDot(
|
||||
progress: progress,
|
||||
index: 2,
|
||||
getOffset: _getOffset,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 개별 원형 점 위젯
|
||||
class _AnimatedDot extends StatelessWidget {
|
||||
final double progress;
|
||||
final int index;
|
||||
final double Function(double, int) getOffset;
|
||||
|
||||
const _AnimatedDot({
|
||||
required this.progress,
|
||||
required this.index,
|
||||
required this.getOffset,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final offset = getOffset(progress, index);
|
||||
|
||||
return Transform.translate(
|
||||
offset: Offset(0, offset),
|
||||
child: SizedBox( //원형 사이즈
|
||||
width: 5,
|
||||
height: 5,
|
||||
child: CustomPaint(
|
||||
painter: _DotPainter(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// CustomPaint로 원 그리기
|
||||
class _DotPainter extends CustomPainter {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = const Color(0xFF424242)
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
// 원의 중심과 반지름
|
||||
final center = Offset(size.width / 2, size.height / 2);
|
||||
final radius = size.width / 2;
|
||||
|
||||
canvas.drawCircle(center, radius, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_DotPainter oldDelegate) => false;
|
||||
}
|
||||
//26.06.24 추가 끝
|
||||
|
||||
//26.06.24 추가 시작
|
||||
//26.06.24 추가 끝
|
||||
Reference in New Issue
Block a user