418 lines
13 KiB
Dart
418 lines
13 KiB
Dart
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,
|
||
};
|
||
}
|
||
}
|
||
}
|