amoriai/lib/screens/testing/sus_questionnaire_screen.dart

418 lines
13 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/theme/app_theme.dart';
import '../../core/constants/app_constants.dart';
class SUSQuestionnaireScreen extends ConsumerStatefulWidget {
const SUSQuestionnaireScreen({super.key});
@override
ConsumerState<SUSQuestionnaireScreen> createState() =>
_SUSQuestionnaireScreenState();
}
class _SUSQuestionnaireScreenState
extends ConsumerState<SUSQuestionnaireScreen> {
final List<int> _responses = List.filled(10, 0);
bool _isCompleted = false;
final List<String> _questions = [
"Saya pikir saya akan sering menggunakan aplikasi ini",
"Saya merasa aplikasi ini terlalu rumit untuk digunakan",
"Saya pikir aplikasi ini mudah digunakan",
"Saya pikir saya membutuhkan bantuan teknis untuk menggunakan aplikasi ini",
"Saya merasa berbagai fitur dalam aplikasi ini terintegrasi dengan baik",
"Saya pikir ada terlalu banyak ketidakkonsistenan dalam aplikasi ini",
"Saya pikir kebanyakan orang akan belajar menggunakan aplikasi ini dengan cepat",
"Saya merasa aplikasi ini sangat merepotkan untuk digunakan",
"Saya merasa sangat percaya diri menggunakan aplikasi ini",
"Saya perlu belajar banyak hal sebelum bisa menggunakan aplikasi ini dengan baik",
];
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppTheme.warmWhite,
appBar: AppBar(
title: const Text('System Usability Scale (SUS)'),
backgroundColor: Colors.transparent,
elevation: 0,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(AppConstants.defaultPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
_buildHeader(),
const SizedBox(height: 24),
// Questions
..._buildQuestions(),
const SizedBox(height: 32),
// Submit Button
_buildSubmitButton(),
if (_isCompleted) ...[const SizedBox(height: 24), _buildResults()],
],
),
),
);
}
Widget _buildHeader() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppTheme.info.withOpacity(0.1),
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
border: Border.all(color: AppTheme.info.withOpacity(0.2)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.quiz_outlined, color: AppTheme.info, size: 24),
const SizedBox(width: 8),
Text(
'Evaluasi Kegunaan Sistem',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: AppTheme.info,
),
),
],
),
const SizedBox(height: 12),
Text(
'Mohon berikan penilaian Anda terhadap aplikasi AMORI Menu Recommender berdasarkan pengalaman penggunaan. Skala penilaian dari 1 (Sangat Tidak Setuju) hingga 5 (Sangat Setuju).',
style: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(color: AppTheme.grey700),
),
],
),
);
}
List<Widget> _buildQuestions() {
return _questions.asMap().entries.map((entry) {
final index = entry.key;
final question = entry.value;
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Pertanyaan ${index + 1}',
style: Theme.of(context).textTheme.labelLarge?.copyWith(
color: AppTheme.primaryBrown,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
question,
style: Theme.of(
context,
).textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500),
),
const SizedBox(height: 16),
// Rating Scale
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Sangat\nTidak Setuju',
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: AppTheme.grey600),
textAlign: TextAlign.center,
),
Text(
'Sangat\nSetuju',
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: AppTheme.grey600),
textAlign: TextAlign.center,
),
],
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(5, (rating) {
final value = rating + 1;
final isSelected = _responses[index] == value;
return GestureDetector(
onTap: () {
setState(() {
_responses[index] = value;
});
},
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isSelected
? AppTheme.primaryBrown
: Colors.transparent,
border: Border.all(
color: isSelected
? AppTheme.primaryBrown
: AppTheme.grey300,
width: 2,
),
),
child: Center(
child: Text(
value.toString(),
style: TextStyle(
color: isSelected ? Colors.white : AppTheme.grey600,
fontWeight: FontWeight.bold,
),
),
),
),
);
}),
),
],
),
);
}).toList();
}
Widget _buildSubmitButton() {
final allAnswered = !_responses.contains(0);
return SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: allAnswered ? _calculateSUSScore : null,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: allAnswered
? AppTheme.primaryBrown
: AppTheme.grey300,
),
child: Text(
_isCompleted ? 'Selesai' : 'Hitung Skor SUS',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
);
}
Widget _buildResults() {
final score = _calculateSUSScore();
final interpretation = _interpretSUSScore(score);
return Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
AppTheme.success.withOpacity(0.1),
AppTheme.info.withOpacity(0.1),
],
),
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
border: Border.all(color: AppTheme.success.withOpacity(0.3)),
),
child: Column(
children: [
Icon(Icons.analytics_outlined, size: 48, color: AppTheme.success),
const SizedBox(height: 16),
Text(
'Hasil SUS Score',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: AppTheme.success,
),
),
const SizedBox(height: 8),
Text(
score.toStringAsFixed(1),
style: Theme.of(context).textTheme.displayMedium?.copyWith(
fontWeight: FontWeight.bold,
color: AppTheme.primaryBrown,
),
),
const SizedBox(height: 8),
Text(
'dari 100',
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(color: AppTheme.grey600),
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.7),
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
Text(
'Interpretasi:',
style: Theme.of(
context,
).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(
interpretation['category']!,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: interpretation['color'] as Color,
),
),
const SizedBox(height: 4),
Text(
interpretation['description']!,
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.center,
),
],
),
),
const SizedBox(height: 16),
_buildScoreBreakdown(),
],
),
);
}
Widget _buildScoreBreakdown() {
return ExpansionTile(
title: const Text('Detail Perhitungan'),
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Rumus SUS Score:',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text('• Pertanyaan ganjil (1,3,5,7,9): (nilai - 1) × 2.5'),
const Text('• Pertanyaan genap (2,4,6,8,10): (5 - nilai) × 2.5'),
const Text('• Total: Jumlah semua skor'),
const SizedBox(height: 12),
const Text(
'Interpretasi Skor:',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
_buildScoreRange('90-100', 'Excellent', AppTheme.success),
_buildScoreRange('80-89', 'Good', Colors.lightGreen),
_buildScoreRange('70-79', 'OK', AppTheme.warning),
_buildScoreRange('60-69', 'Poor', Colors.orange),
_buildScoreRange('0-59', 'Awful', AppTheme.error),
],
),
),
],
);
}
Widget _buildScoreRange(String range, String category, Color color) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
children: [
Container(
width: 12,
height: 12,
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
),
const SizedBox(width: 8),
Text('$range: $category'),
],
),
);
}
double _calculateSUSScore() {
double totalScore = 0;
for (int i = 0; i < _responses.length; i++) {
if (i % 2 == 0) {
// Odd questions (1,3,5,7,9) - positive statements
totalScore += (_responses[i] - 1) * 2.5;
} else {
// Even questions (2,4,6,8,10) - negative statements
totalScore += (5 - _responses[i]) * 2.5;
}
}
if (!_isCompleted) {
setState(() => _isCompleted = true);
}
return totalScore;
}
Map<String, dynamic> _interpretSUSScore(double score) {
if (score >= 90) {
return {
'category': 'Excellent',
'description': 'Aplikasi memiliki kegunaan yang sangat baik',
'color': AppTheme.success,
};
} else if (score >= 80) {
return {
'category': 'Good',
'description': 'Aplikasi memiliki kegunaan yang baik',
'color': Colors.lightGreen,
};
} else if (score >= 70) {
return {
'category': 'OK',
'description': 'Aplikasi memiliki kegunaan yang cukup',
'color': AppTheme.warning,
};
} else if (score >= 60) {
return {
'category': 'Poor',
'description': 'Aplikasi memiliki kegunaan yang kurang',
'color': Colors.orange,
};
} else {
return {
'category': 'Awful',
'description': 'Aplikasi memiliki kegunaan yang sangat kurang',
'color': AppTheme.error,
};
}
}
}