228 lines
8.0 KiB
Dart
228 lines
8.0 KiB
Dart
import 'dart:math';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:service_api/service_api.dart';
|
|
import 'package:service_api/models/assessment_data.dart';
|
|
|
|
class AssessmentScreen extends StatefulWidget {
|
|
const AssessmentScreen({super.key});
|
|
|
|
@override
|
|
State<AssessmentScreen> createState() => _AssessmentScreenState();
|
|
}
|
|
|
|
class _AssessmentScreenState extends State<AssessmentScreen> {
|
|
final Map<String, bool> _answers = {};
|
|
final PageController _pageController = PageController();
|
|
int _currentPage = 0;
|
|
|
|
late final Map<CognitiveArea, List<AssessmentQuestion>> _dailyQuestions;
|
|
late final List<CognitiveArea> _areaKeys;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_dailyQuestions = _generateDailyQuestions();
|
|
_areaKeys = _dailyQuestions.keys.toList();
|
|
}
|
|
|
|
/// [핵심 로직] 전체 문제 풀에서 영역별로 8개씩 랜덤 추출 (총 32문항)
|
|
Map<CognitiveArea, List<AssessmentQuestion>> _generateDailyQuestions() {
|
|
final random = Random();
|
|
Map<CognitiveArea, List<AssessmentQuestion>> grouped = {};
|
|
|
|
// 1. 전체 질문 분류
|
|
Map<CognitiveArea, List<AssessmentQuestion>> pool = {};
|
|
for (var q in rawAssessmentQuestions) {
|
|
if (!pool.containsKey(q.area)) pool[q.area] = [];
|
|
pool[q.area]!.add(q);
|
|
}
|
|
|
|
// 2. 각 영역에서 랜덤하게 8문제씩 추출 (총 32문제)
|
|
// (데이터가 부족할 경우를 대비해 take 사용)
|
|
pool.forEach((area, questions) {
|
|
var shuffled = List<AssessmentQuestion>.from(questions)..shuffle(random);
|
|
grouped[area] = shuffled.take(8).toList(); // 👈 8개로 설정
|
|
});
|
|
|
|
return grouped;
|
|
}
|
|
|
|
void _submitResult() async {
|
|
// 1. 점수 계산
|
|
Map<CognitiveArea, int> scores = {};
|
|
int totalYesCount = 0;
|
|
|
|
_dailyQuestions.forEach((area, questions) {
|
|
int areaScore = 0;
|
|
for (var q in questions) {
|
|
if (_answers[q.id] == true) {
|
|
areaScore++;
|
|
totalYesCount++;
|
|
}
|
|
}
|
|
scores[area] = areaScore;
|
|
});
|
|
|
|
// 2. 히스토리 저장
|
|
final identityService = IdentityService();
|
|
await identityService.saveAssessmentResult(scores);
|
|
|
|
if (!mounted) return;
|
|
|
|
// 3. 결과 알림 (기준: 32문항 중 12개 이상이면 주의)
|
|
String message = "진단 결과가 기록되었습니다.";
|
|
if (totalYesCount >= 12) {
|
|
message += "\n\n주의가 필요한 항목이 다수 확인되었습니다.\n($totalYesCount/32개 해당)\n꾸준한 훈련을 권장합니다.";
|
|
} else {
|
|
message += "\n\n현재 양호한 상태입니다.\n이 상태를 유지하기 위해 매일 훈련하세요!";
|
|
}
|
|
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (ctx) => AlertDialog(
|
|
title: const Text('진단 완료'),
|
|
content: Text(message),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.pop(ctx);
|
|
Navigator.pop(context, true);
|
|
},
|
|
child: const Text('확인'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
String _getAreaTitle(CognitiveArea area) {
|
|
switch (area) {
|
|
case CognitiveArea.memory: return "1. 기억력 체크";
|
|
case CognitiveArea.perception: return "2. 시공간/지남력 체크";
|
|
case CognitiveArea.calculation: return "3. 계산력/판단력 체크";
|
|
case CognitiveArea.attention: return "4. 주의력/집행기능 체크";
|
|
case CognitiveArea.language: return "5. 언어/구성 능력";
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('자가 진단 체크리스트'),
|
|
centerTitle: true,
|
|
bottom: PreferredSize(
|
|
preferredSize: const Size.fromHeight(4.0),
|
|
child: LinearProgressIndicator(
|
|
value: (_currentPage + 1) / _areaKeys.length,
|
|
backgroundColor: Colors.grey[200],
|
|
valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).primaryColor),
|
|
),
|
|
),
|
|
),
|
|
body: Column(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Text(
|
|
_getAreaTitle(_areaKeys[_currentPage]),
|
|
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: PageView.builder(
|
|
controller: _pageController,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
itemCount: _areaKeys.length,
|
|
onPageChanged: (idx) {
|
|
setState(() { _currentPage = idx; });
|
|
},
|
|
itemBuilder: (context, index) {
|
|
final area = _areaKeys[index];
|
|
final questions = _dailyQuestions[area]!;
|
|
|
|
return ListView.separated(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
itemCount: questions.length,
|
|
separatorBuilder: (c, i) => const Divider(height: 1),
|
|
itemBuilder: (context, qIndex) {
|
|
final q = questions[qIndex];
|
|
final bool isChecked = _answers[q.id] ?? false;
|
|
|
|
return CheckboxListTile(
|
|
title: Text(
|
|
q.text,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: isChecked ? Colors.black : Colors.grey[800],
|
|
fontWeight: isChecked ? FontWeight.w600 : FontWeight.normal,
|
|
),
|
|
),
|
|
value: isChecked,
|
|
activeColor: Colors.redAccent,
|
|
onChanged: (val) {
|
|
setState(() { _answers[q.id] = val!; });
|
|
},
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
if (_currentPage > 0)
|
|
ElevatedButton.icon(
|
|
onPressed: () => _pageController.previousPage(
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeInOut
|
|
),
|
|
icon: const Icon(Icons.arrow_back),
|
|
label: const Text('이전'),
|
|
style: ElevatedButton.styleFrom(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
),
|
|
)
|
|
else
|
|
const SizedBox(width: 90),
|
|
|
|
Text(
|
|
"${_currentPage + 1} / ${_areaKeys.length}",
|
|
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
|
),
|
|
|
|
if (_currentPage < _areaKeys.length - 1)
|
|
ElevatedButton.icon(
|
|
onPressed: () => _pageController.nextPage(
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeInOut
|
|
),
|
|
icon: const Icon(Icons.arrow_forward),
|
|
label: const Text('다음'),
|
|
style: ElevatedButton.styleFrom(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
),
|
|
)
|
|
else
|
|
ElevatedButton.icon(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blueAccent,
|
|
foregroundColor: Colors.white,
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
|
),
|
|
onPressed: _submitResult,
|
|
icon: const Icon(Icons.check),
|
|
label: const Text('결과 제출'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |