...
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:service_api/service_api.dart';
|
||||
import 'package:feature_common/feature_common.dart';
|
||||
|
||||
// --- [Import] 각 게임별 패키지 ---
|
||||
|
||||
// 1. 스도쿠
|
||||
import 'package:feature_game_sudoku/feature_game_sudoku.dart' as sudoku_pkg;
|
||||
import 'package:feature_game_sudoku/models/game_level.dart' as sudoku_model;
|
||||
import 'package:feature_game_sudoku/screens/game_screen.dart' as sudoku_screen;
|
||||
|
||||
// 2. 수학 퀴즈
|
||||
import 'package:feature_game_mathquiz/feature_game_mathquiz.dart' as math_pkg;
|
||||
import 'package:feature_game_mathquiz/models/math_quiz_difficulty.dart' as math_model;
|
||||
import 'package:feature_game_mathquiz/screens/math_quiz_screen.dart' as math_screen;
|
||||
import 'package:feature_game_mathquiz/controllers/math_quiz_controller.dart';
|
||||
|
||||
// 3. 순서 기억
|
||||
import 'package:feature_game_sequence/feature_game_sequence.dart' as sequence_pkg;
|
||||
import 'package:feature_game_sequence/models/sequence_models.dart' as sequence_model;
|
||||
import 'package:feature_game_sequence/screens/sequence_game_screen.dart' as sequence_screen;
|
||||
import 'package:feature_game_sequence/controllers/sequence_game_controller.dart';
|
||||
|
||||
// 4. 카드 뒤집기
|
||||
import 'package:feature_game_cardflip/feature_game_cardflip.dart' as cardflip_pkg;
|
||||
import 'package:feature_game_cardflip/models/cardflip_models.dart' as cardflip_model;
|
||||
import 'package:feature_game_cardflip/screens/cardflip_game_screen.dart' as cardflip_screen;
|
||||
import 'package:feature_game_cardflip/controllers/cardflip_controller.dart';
|
||||
|
||||
// 5. 색상 매칭
|
||||
import 'package:feature_game_colormatch/feature_game_colormatch.dart' as colormatch_pkg;
|
||||
import 'package:feature_game_colormatch/models/colormatch_models.dart' as colormatch_model;
|
||||
import 'package:feature_game_colormatch/screens/colormatch_game_screen.dart' as colormatch_screen;
|
||||
import 'package:feature_game_colormatch/controllers/colormatch_controller.dart';
|
||||
|
||||
// 6. 다른 그림 찾기
|
||||
import 'package:feature_game_finddiff/feature_game_finddiff.dart' as finddiff_pkg;
|
||||
import 'package:feature_game_finddiff/models/finddiff_models.dart' as finddiff_model;
|
||||
import 'package:feature_game_finddiff/screens/finddiff_game_screen.dart' as finddiff_screen;
|
||||
import 'package:feature_game_finddiff/controllers/finddiff_controller.dart';
|
||||
|
||||
import 'package:feature_game_schulte/feature_game_schulte.dart' as schulte_pkg; // ✅ 추가
|
||||
|
||||
// 8. [신규] 따라 그리기
|
||||
import 'package:feature_game_tracing/feature_game_tracing.dart' as tracing_pkg;
|
||||
|
||||
import 'package:feature_game_read_aloud/feature_game_read_aloud.dart' as readAloud;
|
||||
import 'package:feature_game_dictation/feature_game_dictation.dart' as dictation_pkg;
|
||||
|
||||
class DailyCourseController {
|
||||
final BuildContext context;
|
||||
final List<BrainGameType> course;
|
||||
int _currentIndex = 0;
|
||||
|
||||
DailyCourseController(this.context, this.course);
|
||||
|
||||
// 1. 코스 시작
|
||||
void start() {
|
||||
if (course.isEmpty) return;
|
||||
_currentIndex = 0;
|
||||
_launchCurrentGame();
|
||||
}
|
||||
|
||||
// 2. 다음 게임으로 이동
|
||||
void _next() {
|
||||
_currentIndex++;
|
||||
if (_currentIndex < course.length) {
|
||||
Future.delayed(const Duration(milliseconds: 300), () {
|
||||
_launchCurrentGame();
|
||||
});
|
||||
} else {
|
||||
_showCourseCompletedDialog();
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 게임 실행 로직
|
||||
void _launchCurrentGame() async {
|
||||
final gameType = course[_currentIndex];
|
||||
|
||||
final session = context.read<SessionNotifier>().session;
|
||||
final String userId = session?.userId ?? 'guest';
|
||||
final String? userName = session?.userName;
|
||||
|
||||
Widget? gameScreen;
|
||||
|
||||
const int defaultLevelIndex = 1;
|
||||
|
||||
try {
|
||||
switch (gameType) {
|
||||
// --- 1. 스도쿠 ---
|
||||
case BrainGameType.sudoku:
|
||||
final level = sudoku_model.AppLevels.getLevel(defaultLevelIndex);
|
||||
final puzzleService = PuzzleService();
|
||||
final gameData = await puzzleService.startGame(level.levelIndex.toString());
|
||||
|
||||
gameScreen = sudoku_screen.GameScreen(
|
||||
gameData: gameData,
|
||||
themeName: "랜덤",
|
||||
userId: userId,
|
||||
userName: userName,
|
||||
levelIndex: level.levelIndex,
|
||||
onNextGame: _next,
|
||||
);
|
||||
break;
|
||||
|
||||
// --- 2. 수학 퀴즈 ---
|
||||
case BrainGameType.mathQuiz:
|
||||
final level = math_model.MathQuizDifficulties.getLevel(defaultLevelIndex);
|
||||
final ctrl = MathQuizController();
|
||||
ctrl.setUserInfo(userId, userName);
|
||||
ctrl.startNewGame(level);
|
||||
|
||||
gameScreen = ChangeNotifierProvider<MathQuizController>.value(
|
||||
value: ctrl,
|
||||
child: math_screen.MathQuizScreen(
|
||||
// onNextGame: _next, // MathQuizScreen에 onNextGame 추가 필요
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
// --- 3. 순서 기억 ---
|
||||
case BrainGameType.sequence:
|
||||
final level = sequence_model.SequenceDifficulties.getLevel(defaultLevelIndex);
|
||||
final ctrl = SequenceGameController();
|
||||
ctrl.setUserInfo(userId, userName);
|
||||
ctrl.startNewGame(level);
|
||||
|
||||
gameScreen = ChangeNotifierProvider<SequenceGameController>.value(
|
||||
value: ctrl,
|
||||
child: sequence_screen.SequenceGameScreen(
|
||||
// onNextGame: _next,
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
// --- 4. 카드 뒤집기 ---
|
||||
case BrainGameType.cardFlip:
|
||||
final level = cardflip_model.CardFlipDifficulties.getLevel(defaultLevelIndex);
|
||||
final ctrl = CardFlipController();
|
||||
ctrl.setUserInfo(userId, userName);
|
||||
ctrl.startNewGame(level);
|
||||
|
||||
gameScreen = ChangeNotifierProvider<CardFlipController>.value(
|
||||
value: ctrl,
|
||||
child: cardflip_screen.CardFlipGameScreen(
|
||||
// onNextGame: _next,
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
// --- 5. 색상 매칭 ---
|
||||
case BrainGameType.colorMatch:
|
||||
final level = colormatch_model.ColorMatchDifficulties.getLevel(defaultLevelIndex);
|
||||
final ctrl = ColorMatchController();
|
||||
ctrl.setUserInfo(userId, userName);
|
||||
ctrl.startNewGame(level);
|
||||
|
||||
gameScreen = ChangeNotifierProvider<ColorMatchController>.value(
|
||||
value: ctrl,
|
||||
child: colormatch_screen.ColorMatchGameScreen(
|
||||
// onNextGame: _next,
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
// --- 6. 다른 그림 찾기 ---
|
||||
case BrainGameType.findDiff:
|
||||
final level = finddiff_model.FindDiffDifficulties.getLevel(defaultLevelIndex);
|
||||
final ctrl = FindDiffController();
|
||||
ctrl.setUserInfo(userId, userName);
|
||||
ctrl.startNewGame(level);
|
||||
|
||||
gameScreen = ChangeNotifierProvider<FindDiffController>.value(
|
||||
value: ctrl,
|
||||
child: finddiff_screen.FindDiffGameScreen(
|
||||
// onNextGame: _next,
|
||||
),
|
||||
);
|
||||
break;
|
||||
|
||||
// --- 7. 스파이더 ---
|
||||
case BrainGameType.schulte:
|
||||
// Lv1~3: 3x3, Lv4~6: 4x4 ... (화면 내부에서 처리)
|
||||
gameScreen = schulte_pkg.SchulteGameScreen(
|
||||
levelIndex: defaultLevelIndex,
|
||||
onNextGame: _next,
|
||||
);
|
||||
break;
|
||||
|
||||
// --- 8. [신규] 따라 그리기 ---
|
||||
case BrainGameType.tracing:
|
||||
// TracingGame은 별도 컨트롤러 없이 로컬 상태로 구현됨
|
||||
gameScreen = tracing_pkg.TracingGameScreen(
|
||||
levelIndex: defaultLevelIndex, // 필요한 레벨 전달 (예: 1~6)
|
||||
onNextGame: _next,
|
||||
);
|
||||
break;
|
||||
|
||||
// --- 9. [신규] 소리 내어 읽기 (추후 구현 시 추가) ---
|
||||
case BrainGameType.readAloud:
|
||||
gameScreen = readAloud.ReadAloudGameScreen(
|
||||
levelIndex: defaultLevelIndex, // 필요한 레벨 전달 (예: 1~6)
|
||||
onNextGame: _next,
|
||||
);
|
||||
break;
|
||||
case BrainGameType.dictation:
|
||||
gameScreen = dictation_pkg.DictationGameScreen(
|
||||
levelIndex: 1, // 또는 사용자 레벨
|
||||
onNextGame: _next,
|
||||
);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (gameScreen != null) {
|
||||
final route = MaterialPageRoute(builder: (_) => gameScreen!);
|
||||
await Navigator.push(context, route);
|
||||
} else {
|
||||
debugPrint("게임 화면 생성 실패: $gameType");
|
||||
_next();
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
debugPrint("게임 실행 중 오류 발생 ($gameType): $e");
|
||||
_next();
|
||||
}
|
||||
}
|
||||
|
||||
void _showCourseCompletedDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text("🎉 훈련 완료!"),
|
||||
content: const Text("오늘의 추천 코스를 모두 완수했습니다.\n두뇌가 한층 더 건강해졌습니다!"),
|
||||
actions: [
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
child: const Text("확인"),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
/// A Calculator.
|
||||
class Calculator {
|
||||
/// Returns [value] plus 1.
|
||||
int addOne(int value) => value + 1;
|
||||
}
|
||||
library feature_brain_trainer;
|
||||
|
||||
export 'screens/brain_training_home.dart';
|
||||
export 'screens/assessment_screen.dart';
|
||||
export 'screens/assessment_history_screen.dart';
|
||||
export 'controllers/daily_course_controller.dart';
|
||||
@@ -1,21 +0,0 @@
|
||||
enum CognitiveArea {
|
||||
memory, // 기억력
|
||||
calculation, // 계산/논리력
|
||||
attention, // 주의집중력
|
||||
perception, // 시지각/공간지각력
|
||||
}
|
||||
|
||||
// 게임별 훈련 영역 매핑
|
||||
enum BrainGameType {
|
||||
sequence(CognitiveArea.memory, '순서 기억'), // feature_game_sequence
|
||||
cardFlip(CognitiveArea.memory, '카드 뒤집기'), // feature_game_cardflip
|
||||
mathQuiz(CognitiveArea.calculation, '암산 퀴즈'), // feature_game_mathquiz
|
||||
sudoku(CognitiveArea.calculation, '스도쿠'), // feature_game_sudoku
|
||||
colorMatch(CognitiveArea.attention, '색상 매칭'), // feature_game_colormatch
|
||||
findDiff(CognitiveArea.perception, '다른 그림 찾기'), // feature_game_finddiff
|
||||
spider(CognitiveArea.perception, '스파이더 카드'); // feature_game_spider
|
||||
|
||||
final CognitiveArea area;
|
||||
final String label;
|
||||
const BrainGameType(this.area, this.label);
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:service_api/service_api.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'assessment_screen.dart';
|
||||
|
||||
class AssessmentHistoryScreen extends StatefulWidget {
|
||||
const AssessmentHistoryScreen({super.key});
|
||||
|
||||
@override
|
||||
State<AssessmentHistoryScreen> createState() => _AssessmentHistoryScreenState();
|
||||
}
|
||||
|
||||
class _AssessmentHistoryScreenState extends State<AssessmentHistoryScreen> {
|
||||
List<AssessmentRecord> _history = [];
|
||||
bool _isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadHistory();
|
||||
}
|
||||
|
||||
Future<void> _loadHistory() async {
|
||||
final identityService = IdentityService();
|
||||
final list = await identityService.getAssessmentHistory();
|
||||
list.sort((a, b) => a.date.compareTo(b.date));
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_history = list;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _startNewAssessment() async {
|
||||
final result = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const AssessmentScreen()),
|
||||
);
|
||||
|
||||
if (result == true) {
|
||||
_loadHistory();
|
||||
}
|
||||
}
|
||||
|
||||
(String, Color) _getAreaStatus(int score) {
|
||||
// 8점 만점 기준 (8문항)
|
||||
// 6점 이상: 위험, 4~5점: 주의, 3점 이하: 양호
|
||||
if (score >= 6) return ("위험", Colors.redAccent);
|
||||
if (score >= 4) return ("주의", Colors.orange);
|
||||
return ("양호", Colors.green);
|
||||
}
|
||||
|
||||
String _getAreaName(CognitiveArea area) {
|
||||
switch (area) {
|
||||
case CognitiveArea.memory: return "기억력";
|
||||
case CognitiveArea.perception: return "시지각/지남력";
|
||||
case CognitiveArea.calculation: return "계산력/판단력";
|
||||
case CognitiveArea.attention: return "주의력/집행기능";
|
||||
case CognitiveArea.language: return "언어/구성 능력";
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final reversedList = _history.reversed.toList();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('나의 두뇌 건강 변화'),
|
||||
// 🔽 [수정] 컬러 충돌 방지를 위해 FilledButton.tonal 또는 IconButton 사용
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: FilledButton.tonalIcon(
|
||||
onPressed: _startNewAssessment,
|
||||
icon: const Icon(Icons.add_task),
|
||||
label: const Text('새 진단'),
|
||||
style: FilledButton.styleFrom(
|
||||
// 테마의 Secondary 컬러를 사용하여 자연스럽게 어우러짐
|
||||
// 글자색/아이콘색은 자동으로 맞춰짐
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _history.isEmpty
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.note_alt_outlined, size: 64, color: Colors.grey),
|
||||
const SizedBox(height: 16),
|
||||
const Text("아직 진단 기록이 없습니다.", style: TextStyle(fontSize: 18, color: Colors.grey)),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: _startNewAssessment,
|
||||
child: const Text("첫 진단 시작하기"),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// --- 1. 그래프 영역 ---
|
||||
Card(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"종합 위험도 변화 추이",
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text(
|
||||
"그래프가 낮을수록(점수가 낮을수록) 건강한 상태입니다.",
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(
|
||||
height: 200,
|
||||
width: double.infinity,
|
||||
child: CustomPaint(
|
||||
painter: AssessmentGraphPainter(_history),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
const Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text("상세 기록 (터치하여 상세 보기)", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// --- 2. 리스트 영역 ---
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: reversedList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final record = reversedList[index];
|
||||
final totalScore = record.scores.values.fold(0, (sum, val) => sum + val);
|
||||
|
||||
// 종합 판정 (32점 만점) -> 12점 이상 주의
|
||||
final bool isRisky = totalScore >= 12;
|
||||
final Color statusColor = isRisky ? Colors.redAccent : Colors.green;
|
||||
final String statusText = isRisky ? "종합 주의" : "종합 양호";
|
||||
|
||||
return Card(
|
||||
elevation: 2,
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: ExpansionTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: statusColor.withOpacity(0.1),
|
||||
child: Icon(
|
||||
isRisky ? Icons.priority_high : Icons.check,
|
||||
color: statusColor
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
DateFormat('yyyy년 MM월 dd일 (E) HH:mm', 'ko_KR').format(record.date),
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
subtitle: Row(
|
||||
children: [
|
||||
Text(statusText, style: TextStyle(color: statusColor, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(width: 8),
|
||||
Text("(위험점수: $totalScore점)", style: const TextStyle(color: Colors.grey)),
|
||||
],
|
||||
),
|
||||
children: [
|
||||
const Divider(height: 1),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
child: Column(
|
||||
children: CognitiveArea.values.map((area) {
|
||||
final int score = record.scores[area] ?? 0;
|
||||
final (String label, Color color) = _getAreaStatus(score);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text(_getAreaName(area), style: const TextStyle(fontSize: 15)),
|
||||
),
|
||||
Expanded(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: LinearProgressIndicator(
|
||||
value: score / 8.0, // 8점 만점 기준
|
||||
backgroundColor: Colors.grey[200],
|
||||
valueColor: AlwaysStoppedAnimation<Color>(color),
|
||||
minHeight: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
SizedBox(
|
||||
width: 50,
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14
|
||||
),
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AssessmentGraphPainter extends CustomPainter {
|
||||
final List<AssessmentRecord> history;
|
||||
AssessmentGraphPainter(this.history);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (history.isEmpty) return;
|
||||
|
||||
const double padding = 10.0;
|
||||
final double graphWidth = size.width - padding * 2;
|
||||
final double graphHeight = size.height - padding * 2;
|
||||
|
||||
final data = history.length > 7 ? history.sublist(history.length - 7) : history;
|
||||
|
||||
final paintLine = Paint()
|
||||
..color = Colors.blueAccent
|
||||
..strokeWidth = 3
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
||||
final paintDot = Paint()
|
||||
..color = Colors.redAccent
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
final paintGrid = Paint()
|
||||
..color = Colors.grey.withOpacity(0.2)
|
||||
..strokeWidth = 1;
|
||||
|
||||
// Y축 가이드라인 (최대 32점 기준)
|
||||
const double maxY = 32.0;
|
||||
for (int i = 0; i <= 4; i++) {
|
||||
double y = padding + graphHeight - (i * 8 / maxY) * graphHeight; // 8점 단위
|
||||
canvas.drawLine(Offset(padding, y), Offset(padding + graphWidth, y), paintGrid);
|
||||
}
|
||||
|
||||
final double stepX = data.length > 1 ? graphWidth / (data.length - 1) : graphWidth / 2;
|
||||
|
||||
Path path = Path();
|
||||
List<Offset> points = [];
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
int totalScore = data[i].scores.values.fold(0, (sum, val) => sum + val);
|
||||
|
||||
double x = padding + (i * stepX);
|
||||
double y = padding + graphHeight - (totalScore / maxY) * graphHeight;
|
||||
|
||||
if (data.length == 1) x = size.width / 2;
|
||||
|
||||
points.add(Offset(x, y));
|
||||
|
||||
if (i == 0) {
|
||||
path.moveTo(x, y);
|
||||
} else {
|
||||
path.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.length > 1) canvas.drawPath(path, paintLine);
|
||||
|
||||
for (var point in points) {
|
||||
canvas.drawCircle(point, 5, paintDot);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
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('결과 제출'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,120 @@ import 'package:provider/provider.dart';
|
||||
import 'package:feature_common/feature_common.dart';
|
||||
import 'package:service_api/service_api.dart';
|
||||
|
||||
// 각 게임 패키지 import
|
||||
import 'assessment_screen.dart';
|
||||
import 'assessment_history_screen.dart';
|
||||
import '../controllers/daily_course_controller.dart';
|
||||
|
||||
// 각 게임 패키지 로비 import
|
||||
import 'package:feature_game_sudoku/feature_game_sudoku.dart';
|
||||
import 'package:feature_game_mathquiz/feature_game_mathquiz.dart';
|
||||
// ... (나머지 게임들)
|
||||
import 'package:feature_game_sequence/feature_game_sequence.dart';
|
||||
import 'package:feature_game_cardflip/feature_game_cardflip.dart';
|
||||
import 'package:feature_game_colormatch/feature_game_colormatch.dart';
|
||||
import 'package:feature_game_finddiff/feature_game_finddiff.dart';
|
||||
import 'package:feature_game_schulte/feature_game_schulte.dart';
|
||||
import 'package:feature_game_read_aloud/feature_game_read_aloud.dart';
|
||||
import 'package:feature_game_dictation/feature_game_dictation.dart';
|
||||
|
||||
class BrainTrainingHome extends StatelessWidget {
|
||||
// 🔽 [신규] 따라 그리기 패키지 import
|
||||
import 'package:feature_game_tracing/feature_game_tracing.dart';
|
||||
|
||||
class BrainTrainingHome extends StatefulWidget {
|
||||
const BrainTrainingHome({super.key});
|
||||
|
||||
@override
|
||||
State<BrainTrainingHome> createState() => _BrainTrainingHomeState();
|
||||
}
|
||||
|
||||
class _BrainTrainingHomeState extends State<BrainTrainingHome> {
|
||||
bool _hasAssessment = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_checkAssessmentStatus();
|
||||
}
|
||||
|
||||
Future<void> _checkAssessmentStatus() async {
|
||||
final identityService = context.read<IdentityService>();
|
||||
final scores = await identityService.getCognitiveScores();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_hasAssessment = (scores != null && scores.isNotEmpty);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _handleCheckupTap() {
|
||||
if (_hasAssessment) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const AssessmentHistoryScreen()),
|
||||
).then((_) => _checkAssessmentStatus());
|
||||
} else {
|
||||
_openNewAssessment();
|
||||
}
|
||||
}
|
||||
|
||||
void _openNewAssessment() async {
|
||||
final result = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const AssessmentScreen()),
|
||||
);
|
||||
|
||||
if (result == true) {
|
||||
_checkAssessmentStatus();
|
||||
}
|
||||
}
|
||||
|
||||
void _startDailyCourse() async {
|
||||
final identityService = context.read<IdentityService>();
|
||||
final scores = await identityService.getCognitiveScores();
|
||||
|
||||
if (scores == null || scores.isEmpty) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('먼저 두뇌 건강 체크를 진행해 주세요!')),
|
||||
);
|
||||
_openNewAssessment();
|
||||
return;
|
||||
}
|
||||
|
||||
final trainer = BrainTrainingService();
|
||||
final recommendedCourse = trainer.recommendGames(scores);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
final controller = DailyCourseController(context, recommendedCourse);
|
||||
controller.start();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('두뇌 건강 지킴이')),
|
||||
appBar: AppBar(
|
||||
title: const Text('두뇌 건강 지킴이'),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.show_chart_rounded),
|
||||
tooltip: '진단 기록 보기',
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const AssessmentHistoryScreen())
|
||||
).then((_) => _checkAssessmentStatus());
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
onPressed: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const SettingsScreen())
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
@@ -25,14 +127,14 @@ class BrainTrainingHome extends StatelessWidget {
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 2. 오늘의 추천 코스 (자동 믹스 게임)
|
||||
// 2. 오늘의 추천 코스
|
||||
const Text('오늘의 추천 훈련', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 10),
|
||||
_buildDailyTrainingButton(context),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 3. 게임 아케이드 (자유 선택)
|
||||
// 3. 게임 아케이드
|
||||
const Text('전체 게임', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 10),
|
||||
_buildGameGrid(context),
|
||||
@@ -45,32 +147,54 @@ class BrainTrainingHome extends StatelessWidget {
|
||||
Widget _buildCheckupCard(BuildContext context) {
|
||||
return Card(
|
||||
color: Colors.indigo.shade50,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: BorderSide(color: Colors.indigo.shade100),
|
||||
),
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.health_and_safety, size: 40, color: Colors.indigo),
|
||||
title: const Text('나의 두뇌 건강 체크'),
|
||||
subtitle: const Text('간단한 질문으로 현재 상태를 확인하고\n맞춤형 훈련을 추천받으세요.'),
|
||||
trailing: const Icon(Icons.arrow_forward_ios),
|
||||
onTap: () {
|
||||
// TODO: 체크리스트 화면으로 이동
|
||||
// Navigator.push(context, MaterialPageRoute(builder: (_) => AssessmentScreen()));
|
||||
},
|
||||
contentPadding: const EdgeInsets.all(16),
|
||||
leading: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(Icons.health_and_safety, size: 32, color: Colors.indigo),
|
||||
),
|
||||
title: Text(_hasAssessment ? '나의 두뇌 건강 리포트' : '나의 두뇌 건강 체크', style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
subtitle: Text(_hasAssessment
|
||||
? '최근 진단 결과를 확인하고 변화를 추적하세요.'
|
||||
: '간단한 질문으로 상태를 확인하고\n맞춤형 훈련을 추천받으세요.'
|
||||
),
|
||||
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
|
||||
onTap: _handleCheckupTap,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDailyTrainingButton(BuildContext context) {
|
||||
return ElevatedButton.icon(
|
||||
return ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.all(20),
|
||||
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||
backgroundColor: Colors.orange,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
elevation: 4,
|
||||
),
|
||||
onPressed: _startDailyCourse,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const [
|
||||
Icon(Icons.play_circle_fill, size: 40),
|
||||
SizedBox(width: 16),
|
||||
Text(
|
||||
'오늘의 코스 시작하기\n(맞춤형 3종 세트)',
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)
|
||||
),
|
||||
],
|
||||
),
|
||||
icon: const Icon(Icons.play_circle_fill, size: 32),
|
||||
label: const Text('오늘의 코스 시작하기\n(맞춤형 3종 세트)', textAlign: TextAlign.center, style: TextStyle(fontSize: 18)),
|
||||
onPressed: () {
|
||||
// TODO: Assessment 결과에 따라 선정된 게임들을 순차적으로 실행하는 로직
|
||||
// 예: Sequence -> Math -> ColorMatch 순서로 실행되는 별도 Flow 화면으로 이동
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -80,7 +204,13 @@ class BrainTrainingHome extends StatelessWidget {
|
||||
{'name': '스도쿠', 'icon': Icons.grid_3x3, 'dest': const SudokuLobbyScreen()},
|
||||
{'name': '계산 퀴즈', 'icon': Icons.calculate, 'dest': const MathQuizLobbyScreen()},
|
||||
{'name': '순서 기억', 'icon': Icons.onetwothree, 'dest': const SequenceLobbyScreen()},
|
||||
// ... 나머지 게임 추가
|
||||
{'name': '카드 뒤집기', 'icon': Icons.flip, 'dest': const CardFlipLobbyScreen()},
|
||||
{'name': '색상 매칭', 'icon': Icons.palette, 'dest': const ColorMatchLobbyScreen()},
|
||||
{'name': '다른 그림', 'icon': Icons.image_search, 'dest': const FindDiffLobbyScreen()},
|
||||
{'name': '숫자 찾기', 'icon': Icons.looks_one, 'dest': const SchulteLobbyScreen()},
|
||||
{'name': '따라 그리기', 'icon': Icons.gesture, 'dest': const TracingLobbyScreen()},
|
||||
{'name': '읽고 말하기', 'icon': Icons.record_voice_over, 'dest': const ReadAloudLobbyScreen()},
|
||||
{'name': '받아쓰기', 'icon': Icons.keyboard, 'dest': const DictationLobbyScreen()},
|
||||
];
|
||||
|
||||
return GridView.builder(
|
||||
@@ -88,9 +218,9 @@ class BrainTrainingHome extends StatelessWidget {
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
childAspectRatio: 1.5,
|
||||
crossAxisSpacing: 10,
|
||||
mainAxisSpacing: 10,
|
||||
childAspectRatio: 1.3,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
itemCount: games.length,
|
||||
itemBuilder: (context, index) {
|
||||
@@ -98,14 +228,21 @@ class BrainTrainingHome extends StatelessWidget {
|
||||
onTap: () {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (_) => games[index]['dest'] as Widget));
|
||||
},
|
||||
child: Card(
|
||||
elevation: 2,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.grey.shade100, blurRadius: 4, offset: const Offset(0, 2))
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(games[index]['icon'] as IconData, size: 36, color: Colors.blueGrey),
|
||||
const SizedBox(height: 8),
|
||||
Text(games[index]['name'] as String, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
Icon(games[index]['icon'] as IconData, size: 40, color: Colors.blueGrey),
|
||||
const SizedBox(height: 12),
|
||||
Text(games[index]['name'] as String, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
name: feature_brain_trainer
|
||||
description: "A new Flutter package project."
|
||||
description: Brain training features including assessment, recommendation logic, and daily course management.
|
||||
version: 0.0.1
|
||||
homepage:
|
||||
publish_to: 'none'
|
||||
resolution: workspace
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.2
|
||||
flutter: ">=1.17.0"
|
||||
sdk: '^3.9.2'
|
||||
flutter: '>=3.10.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
# 1. 상태 관리
|
||||
provider: ^6.0.0
|
||||
|
||||
# 2. 공통 서비스 (IdentityService, BrainTrainingService, 모델 등)
|
||||
service_api:
|
||||
path: ../service_api
|
||||
|
||||
# 3. 공통 UI (CommonGameShell, 버튼 스타일 등)
|
||||
feature_common:
|
||||
path: ../feature_common
|
||||
|
||||
feature_game_sudoku:
|
||||
path: ../feature_game_sudoku
|
||||
feature_game_cardflip:
|
||||
path: ../feature_game_cardflip
|
||||
feature_game_colormatch:
|
||||
path: ../feature_game_colormatch
|
||||
feature_game_schulte:
|
||||
path: ../feature_game_schulte
|
||||
feature_game_sequence:
|
||||
path: ../feature_game_sequence
|
||||
feature_game_finddiff:
|
||||
path: ../feature_game_finddiff
|
||||
feature_game_mathquiz:
|
||||
path: ../feature_game_mathquiz
|
||||
# ... 나머지 게임 패키지들도 추가
|
||||
feature_game_tracing:
|
||||
path: ../feature_game_tracing
|
||||
feature_game_read_aloud:
|
||||
path: ../feature_game_read_aloud
|
||||
feature_game_dictation:
|
||||
path: ../feature_game_dictation
|
||||
|
||||
# (선택) 날짜 포맷팅 등이 필요하면 추가
|
||||
intl: ^0.18.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^5.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
||||
# To add assets to your package, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
#
|
||||
# For details regarding assets in packages, see
|
||||
# https://flutter.dev/to/asset-from-package
|
||||
#
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
||||
# To add custom fonts to your package, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts in packages, see
|
||||
# https://flutter.dev/to/font-from-package
|
||||
lints: ^3.0.0
|
||||
Reference in New Issue
Block a user