allscore_app/lib/survey/survey_page.dart

506 lines
16 KiB
Dart
Raw Normal View History

2025-01-18 09:28:24 +00:00
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // /* 숫자만 입력 위해 */
2025-01-18 09:28:24 +00:00
import 'package:http/http.dart' as http;
import 'dart:convert';
// Server API (서버 API)
2025-01-18 09:28:24 +00:00
import '../plugins/api.dart';
// Main Page (메인 페이지)
2025-01-18 09:28:24 +00:00
import '../views/room/main_page.dart';
// Alert modal dialog (알람 모달창)
2025-01-18 09:28:24 +00:00
import '../dialogs/response_dialog.dart';
class SurveyPage extends StatefulWidget {
final String nickname;
const SurveyPage({
Key? key,
required this.nickname,
}) : super(key: key);
@override
State<SurveyPage> createState() => _SurveyPageState();
}
class _SurveyPageState extends State<SurveyPage> {
int _currentIndex = 0;
/* 현재 페이지 인덱스 (0~4) */
2025-01-18 09:28:24 +00:00
List<String> _questions = [];
/* 전체 질문 목록 (5개 예시) */
2025-01-18 09:28:24 +00:00
List<String> _questionsOriginal = [];
/* 질문의 한글 원본 목록 */
2025-01-18 09:28:24 +00:00
final List<String?> _answers = List.filled(5, null, growable: false);
/* 사용자가 입력한 답변(5개) */
2025-01-18 09:28:24 +00:00
final Map<int, String> _selectedRadioValue = {};
/* 각 페이지별 라디오 선택 값 */
2025-01-18 09:28:24 +00:00
final Map<int, TextEditingController> _textControllers = {};
/* 각 페이지별 텍스트필드 컨트롤러 */
2025-01-18 09:28:24 +00:00
@override
void initState() {
super.initState();
// Set questions (질문 설정)
2025-01-18 09:28:24 +00:00
_questions = [
"Q1. How old are you, ${widget.nickname}?",
/* "Q1. ${widget.nickname}님의 나이는 어떻게 되나요?" */
"Q2. What is your occupation, ${widget.nickname}?",
/* "Q2. ${widget.nickname}님의 직업이 무엇인가요?" */
"Q3. How did you hear about ALLSCORE, ${widget.nickname}?",
/* "Q3. ${widget.nickname}님은 올스코어 앱을 어떻게 알게 됐나요?" */
"Q4. Where have you experienced ALLSCORE, ${widget.nickname}?",
/* "Q4. ${widget.nickname}님은 올스코어 앱을 어디서 경험하셨나요?" */
"Q5. Do you plan to keep using ALLSCORE?",
/* "Q5. 올스코어를 계속 사용할 의사가 있나요?" */
2025-01-18 09:28:24 +00:00
];
// Original Korean questions (원래 한글 질문 목록)
2025-01-18 09:28:24 +00:00
_questionsOriginal = [
"나이는 어떻게 되나요?",
"직업이 무엇인가요?",
"올스코어 앱을 어떻게 알게 됐나요?",
"올스코어 앱을 어디서 경험하셨나요?",
"올스코어를 계속 사용할 의사가 있나요?",
];
// Initialize TextEditingController for each page
// (페이지마다 TextEditingController 초기화)
2025-01-18 09:28:24 +00:00
for (int i = 0; i < _questions.length; i++) {
_textControllers[i] = TextEditingController();
}
}
@override
void dispose() {
// Dispose text controllers (텍스트 컨트롤러 정리)
2025-01-18 09:28:24 +00:00
for (var ctrl in _textControllers.values) {
ctrl.dispose();
}
super.dispose();
}
/// Exit the survey (설문 그만하기)
2025-01-18 09:28:24 +00:00
Future<void> _onExitSurvey() async {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const MainPage()),
(route) => false,
);
}
/// Next button (다음 버튼)
2025-01-18 09:28:24 +00:00
void _onNext() {
if (!_validateCurrentPage()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Please fill in all required fields.'
/* '값을 모두 입력해 주세요.' */)),
2025-01-18 09:28:24 +00:00
);
return;
}
if (_currentIndex < _questions.length - 1) {
setState(() {
_currentIndex++;
});
}
}
/// Previous button (이전 버튼)
2025-01-18 09:28:24 +00:00
void _onPrev() {
if (_currentIndex > 0) {
setState(() {
_currentIndex--;
});
}
}
/// Submit (제출하기)
2025-01-18 09:28:24 +00:00
Future<void> _onSubmit() async {
if (!_validateCurrentPage()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Please fill in all required fields.'
/* '값을 모두 입력해 주세요.' */)),
2025-01-18 09:28:24 +00:00
);
return;
}
// body: {"QNA": ["질문1","답변1","질문2","답변2", ... ]}
final List<String> qnaList = [];
for (int i = 0; i < _questionsOriginal.length; i++) {
qnaList.add(_questionsOriginal[i]);
/* 한글 질문 넣기 */
2025-01-18 09:28:24 +00:00
qnaList.add(_answers[i] ?? '');
/* 사용자 답변 */
2025-01-18 09:28:24 +00:00
}
final requestBody = {
2025-01-22 10:51:36 +00:00
"QNA": qnaList,
2025-01-18 09:28:24 +00:00
};
try {
final response = await Api.serverRequest(uri: '/survey/collect', body: requestBody);
if (response['result'] == 'OK') {
final resp = response['response'] ?? {};
if (resp['result'] == 'OK') {
// Survey submitted (설문 제출 성공)
2025-01-18 09:28:24 +00:00
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Your survey has been submitted. Thank you!'
/* '설문이 제출되었습니다. 감사합니다!' */)),
);
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const MainPage()),
(route) => false,
2025-01-18 09:28:24 +00:00
);
} else {
showResponseDialog(context, 'Error' /* 오류 */, 'Failed to submit the survey.'
/* '설문 제출 실패' */);
2025-01-18 09:28:24 +00:00
}
} else {
showResponseDialog(context, 'Error' /* 오류 */, 'Failed to submit the survey.'
/* '설문 제출 실패' */);
2025-01-18 09:28:24 +00:00
}
} catch (e) {
showResponseDialog(context, 'Error' /* 오류 */, 'Failed to submit the survey.'
/* '설문 제출 실패' */);
2025-01-18 09:28:24 +00:00
}
}
/// Validate current page input (현재 페이지 입력값이 유효한지 체크)
2025-01-18 09:28:24 +00:00
bool _validateCurrentPage() {
final index = _currentIndex;
String? answer;
switch (index) {
case 0:
// Age (나이)
2025-01-18 09:28:24 +00:00
final txt = _textControllers[index]?.text.trim() ?? '';
if (txt.isEmpty) {
return false;
}
answer = '$txt yrs old'
/* '$txt 세' */;
2025-01-18 09:28:24 +00:00
break;
case 1:
// Occupation (직업)
2025-01-18 09:28:24 +00:00
final selected = _selectedRadioValue[index];
if (selected == null || selected.isEmpty) {
return false;
}
answer = selected;
break;
case 2:
// How did you hear about ALLSCORE? (앱 알게된 경로)
2025-01-18 09:28:24 +00:00
final selected2 = _selectedRadioValue[index];
if (selected2 == null || selected2.isEmpty) {
return false;
}
if (selected2 == 'Others' /* '기타' */) {
2025-01-18 09:28:24 +00:00
final etc = _textControllers[index]?.text.trim() ?? '';
if (etc.isEmpty) {
return false;
}
answer = "Others($etc)"
/* "기타($etc)" */;
2025-01-18 09:28:24 +00:00
} else {
answer = selected2;
}
break;
case 3:
// Where have you experienced? (어디서 경험?)
2025-01-18 09:28:24 +00:00
final sel3 = _selectedRadioValue[index];
if (sel3 == null || sel3.isEmpty) {
return false;
}
answer = sel3;
break;
case 4:
// Will you continue using ALLSCORE? (계속 사용할 의사?)
2025-01-18 09:28:24 +00:00
final sel4 = _selectedRadioValue[index];
if (sel4 == null || sel4.isEmpty) {
return false;
}
final comment = _textControllers[index]?.text.trim() ?? '';
answer = sel4 + (comment.isNotEmpty ? " / comment: $comment"
/* "/ 의견: $comment" */ : "");
2025-01-18 09:28:24 +00:00
break;
default:
return false;
}
_answers[index] = answer;
return true;
}
@override
Widget build(BuildContext context) {
final questionText = _questions[_currentIndex];
/* 현재 페이지 질문 */
2025-01-18 09:28:24 +00:00
final pageNumber = _currentIndex + 1;
final totalPage = _questions.length;
return WillPopScope(
onWillPop: () async {
_onExitSurvey();
return false;
},
child: Scaffold(
appBar: AppBar(
leadingWidth: 120,
2025-01-18 09:28:24 +00:00
leading: TextButton(
onPressed: _onExitSurvey,
child: const Text(
'Stop Survey'
/* '설문 그만하기' */,
2025-01-18 09:28:24 +00:00
style: TextStyle(color: Colors.white),
),
),
title: Text('Survey ($pageNumber/$totalPage)'
/* '설문조사 ($pageNumber/$totalPage)' */),
2025-01-18 09:28:24 +00:00
backgroundColor: Colors.black,
),
body: SingleChildScrollView(
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Question text (질문)
2025-01-18 09:28:24 +00:00
Text(
questionText,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
// Page-specific UI (페이지별 UI)
2025-01-18 09:28:24 +00:00
_buildSurveyPage(_currentIndex),
],
),
),
),
bottomNavigationBar: Container(
color: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
children: [
if (_currentIndex > 0)
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.grey),
onPressed: _onPrev,
child: const Text('Previous'
/* '이전' */),
2025-01-18 09:28:24 +00:00
),
),
if (_currentIndex > 0) const SizedBox(width: 8),
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.black),
onPressed: (_currentIndex < totalPage - 1) ? _onNext : _onSubmit,
child: Text(
(_currentIndex < totalPage - 1) ? 'Next' /* 다음 */ : 'Submit' /* 제출하기 */,
2025-01-18 09:28:24 +00:00
style: const TextStyle(color: Colors.white),
),
),
),
],
),
),
),
);
}
/// Build UI for each page (페이지별 UI)
2025-01-18 09:28:24 +00:00
Widget _buildSurveyPage(int index) {
switch (index) {
case 0:
// Age input (나이 입력)
2025-01-18 09:28:24 +00:00
return Column(
children: [
const Text(
'(e.g. Please enter your age in digits.)'
/* '(예: 나이를 숫자로 입력해 주세요.)' */,
textAlign: TextAlign.center,
),
2025-01-18 09:28:24 +00:00
const SizedBox(height: 16),
TextField(
controller: _textControllers[index],
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
textAlign: TextAlign.center,
2025-01-18 09:28:24 +00:00
decoration: const InputDecoration(
labelText: 'Age'
/* '나이' */,
2025-01-18 09:28:24 +00:00
border: OutlineInputBorder(),
),
),
],
);
case 1:
// Occupation (직업)
final jobs = [
'Student' /* '학생' */,
'Office Worker' /* '회사원' */,
'Professional' /* '전문직' */,
'Professor/Teacher' /* '교수/교사' */,
'Technical' /* '기술직' */,
'Government Official' /* '공무원' */,
'Art/Sports' /* '예술/스포츠' */,
'Others' /* '기타' */,
];
2025-01-18 09:28:24 +00:00
return Column(
mainAxisSize: MainAxisSize.min,
children: jobs.map((job) {
return RadioListTile<String>(
title: Text(job, textAlign: TextAlign.center),
value: job,
groupValue: _selectedRadioValue[index],
onChanged: (val) {
setState(() {
_selectedRadioValue[index] = val!;
});
},
);
}).toList(),
);
case 2:
// How did you hear about ALLSCORE? (앱 알게된 경로)
final paths = [
'Friend/Acquaintance' /* '친구/지인 추천' */,
'Social Media' /* '소셜 미디어' */,
'Blog/Online Review' /* '블로그/온라인 리뷰' */,
'School/Workplace' /* '학교나 직장' */,
'Others' /* '기타' */,
];
2025-01-18 09:28:24 +00:00
return Column(
mainAxisSize: MainAxisSize.min,
children: [
...paths.map((p) {
return RadioListTile<String>(
title: Text(p, textAlign: TextAlign.center),
value: p,
groupValue: _selectedRadioValue[index],
onChanged: (val) {
setState(() {
_selectedRadioValue[index] = val!;
});
},
);
}).toList(),
if (_selectedRadioValue[index] == 'Others'
/* '기타' */)
2025-01-18 09:28:24 +00:00
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: TextField(
controller: _textControllers[index],
textAlign: TextAlign.center,
decoration: const InputDecoration(
labelText: 'Please specify.'
/* '기타 내용을 입력해 주세요.' */,
2025-01-18 09:28:24 +00:00
border: OutlineInputBorder(),
),
),
),
],
);
case 3:
// Where have you experienced ALLSCORE? (어디서 경험?)
final places = [
'With Family' /* '가족과 함께' */,
'With Friends' /* '친구들과 모임' */,
'School (for education)' /* '학교 교육 목적' */,
'Work Club' /* '직장 동호회' */,
'Cafe or Public Space' /* '카페나 공공장소' */,
'Travel' /* '여행 중' */,
'Others' /* '기타' */,
];
2025-01-18 09:28:24 +00:00
return Column(
mainAxisSize: MainAxisSize.min,
children: places.map((pl) {
return RadioListTile<String>(
title: Text(pl, textAlign: TextAlign.center),
value: pl,
groupValue: _selectedRadioValue[index],
onChanged: (val) {
setState(() {
_selectedRadioValue[index] = val!;
});
},
);
}).toList(),
);
case 4:
// Will you continue using ALLSCORE? (계속 사용할 의사?)
2025-01-18 09:28:24 +00:00
return Column(
mainAxisSize: MainAxisSize.min,
children: [
RadioListTile<String>(
title: const Text('Yes' /* '네' */, textAlign: TextAlign.center),
value: 'Yes' /* '네' */,
2025-01-18 09:28:24 +00:00
groupValue: _selectedRadioValue[index],
onChanged: (val) {
setState(() {
_selectedRadioValue[index] = val!;
});
},
),
RadioListTile<String>(
title: const Text('No' /* '아니오' */, textAlign: TextAlign.center),
value: 'No' /* '아니오' */,
2025-01-18 09:28:24 +00:00
groupValue: _selectedRadioValue[index],
onChanged: (val) {
setState(() {
_selectedRadioValue[index] = val!;
});
},
),
const SizedBox(height: 16),
const Text(
'If you have any additional comments, feel free to write them here.'
/* '추가 의견이 있다면 자유롭게 작성해 주세요.' */,
textAlign: TextAlign.center,
),
2025-01-18 09:28:24 +00:00
const SizedBox(height: 8),
TextField(
controller: _textControllers[index],
maxLines: 3,
textAlign: TextAlign.center,
decoration: const InputDecoration(
hintText: 'e.g. inconveniences, improvement ideas, etc.'
/* 'ex) 불편사항, 개선 아이디어 등' */,
2025-01-18 09:28:24 +00:00
border: OutlineInputBorder(),
),
),
],
);
default:
return const Text(
'Survey question error'
/* '설문 문항 오류' */,
textAlign: TextAlign.center
);
2025-01-18 09:28:24 +00:00
}
}
}