develop #1

Merged
akhdanre merged 104 commits from develop into main 2025-07-10 12:38:53 +07:00
7 changed files with 441 additions and 1 deletions
Showing only changes of commit 7a90e7ea16 - Show all commits

View File

@ -3,6 +3,7 @@ import 'package:quiz_app/app/middleware/auth_middleware.dart';
import 'package:quiz_app/feature/history/binding/history_binding.dart';
import 'package:quiz_app/feature/home/binding/home_binding.dart';
import 'package:quiz_app/feature/home/view/home_page.dart';
import 'package:quiz_app/feature/library/binding/library_binding.dart';
import 'package:quiz_app/feature/login/bindings/login_binding.dart';
import 'package:quiz_app/feature/login/view/login_page.dart';
import 'package:quiz_app/feature/navigation/bindings/navigation_binding.dart';
@ -48,6 +49,7 @@ class AppPages {
NavbarBinding(),
HomeBinding(),
SearchBinding(),
LibraryBinding(),
HistoryBinding(),
ProfileBinding(),
],

View File

@ -0,0 +1,9 @@
import 'package:get/get.dart';
import 'package:quiz_app/feature/library/controller/library_controller.dart';
class LibraryBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<LibraryController>(() => LibraryController());
}
}

View File

@ -0,0 +1,72 @@
import 'package:get/get.dart';
class DetailQuizController extends GetxController {
Rx<QuizDetailData> quizData = QuizDetailData(
title: "Sejarah Indonesia - Kerajaan Hindu Budha",
description: "Kuis ini membahas kerajaan-kerajaan Hindu Budha di Indonesia seperti Kutai, Sriwijaya, dan Majapahit.",
date: DateTime.parse("2025-04-25 10:00:00"),
isPublic: true,
totalQuiz: 3,
limitDuration: 900, // dalam detik (900 = 15 menit)
questionListings: [
QuestionListing(
question: "Kerajaan Hindu tertua di Indonesia adalah?",
targetAnswer: "Kutai",
duration: 30,
type: "fill_the_blank",
),
QuestionListing(
question: "Apakah benar Majapahit mencapai puncak kejayaan pada masa Hayam Wuruk?",
targetAnswer: "Ya",
duration: 30,
type: "true_false",
),
QuestionListing(
question: "Kerajaan maritim terbesar di Asia Tenggara pada abad ke-7 adalah?",
targetAnswer: "Sriwijaya",
duration: 30,
type: "fill_the_blank",
),
],
).obs;
@override
void onInit() {
super.onInit();
// Dummy data sudah di-inject saat controller init
}
}
class QuizDetailData {
String title;
String description;
DateTime date;
bool isPublic;
int totalQuiz;
int limitDuration;
List<QuestionListing> questionListings;
QuizDetailData({
this.title = '',
this.description = '',
required this.date,
this.isPublic = true,
this.totalQuiz = 0,
this.limitDuration = 0,
this.questionListings = const [],
});
}
class QuestionListing {
String question;
String targetAnswer;
int duration;
String type;
QuestionListing({
required this.question,
required this.targetAnswer,
required this.duration,
required this.type,
});
}

View File

@ -0,0 +1,40 @@
import 'package:get/get.dart';
class LibraryController extends GetxController {
RxList<Map<String, dynamic>> quizList = <Map<String, dynamic>>[].obs;
@override
void onInit() {
super.onInit();
loadDummyQuiz();
}
void loadDummyQuiz() {
quizList.assignAll([
{
"author_id": "user_12345",
"title": "Sejarah Indonesia - Kerajaan Hindu Budha",
"description": "Kuis ini membahas kerajaan-kerajaan Hindu Budha di Indonesia seperti Kutai, Sriwijaya, dan Majapahit.",
"is_public": true,
"date": "2025-04-25 10:00:00",
"total_quiz": 3,
"limit_duration": 900,
},
// Tambahkan data dummy lain kalau mau
]);
}
String formatDuration(int seconds) {
int minutes = seconds ~/ 60;
return '$minutes menit';
}
String formatDate(String dateString) {
try {
// DateTime date = DateTime.parse(dateString);
return "19-04-2025";
} catch (e) {
return '-';
}
}
}

View File

@ -0,0 +1,174 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:quiz_app/app/const/colors/app_colors.dart';
import 'package:quiz_app/feature/library/controller/detail_quiz_controller.dart';
class DetailQuizView extends GetView<DetailQuizController> {
const DetailQuizView({super.key});
@override
Widget build(BuildContext context) {
final data = controller.quizData.value;
return Scaffold(
backgroundColor: AppColors.background,
appBar: AppBar(
backgroundColor: AppColors.background,
elevation: 0,
title: const Text(
'Detail Quiz',
style: TextStyle(
color: AppColors.darkText,
fontWeight: FontWeight.bold,
),
),
centerTitle: true,
iconTheme: const IconThemeData(color: AppColors.darkText),
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header Section
Text(
data.title,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: AppColors.darkText,
),
),
const SizedBox(height: 8),
Text(
data.description,
style: const TextStyle(
fontSize: 14,
color: AppColors.softGrayText,
),
),
const SizedBox(height: 16),
Row(
children: [
const Icon(Icons.calendar_today_rounded, size: 16, color: AppColors.softGrayText),
const SizedBox(width: 6),
Text(
_formatDate(data.date),
style: const TextStyle(fontSize: 12, color: AppColors.softGrayText),
),
const SizedBox(width: 12),
const Icon(Icons.timer_rounded, size: 16, color: AppColors.softGrayText),
const SizedBox(width: 6),
Text(
'${data.limitDuration ~/ 60} menit',
style: const TextStyle(fontSize: 12, color: AppColors.softGrayText),
),
],
),
const SizedBox(height: 20),
const Divider(thickness: 1.2, color: AppColors.borderLight),
const SizedBox(height: 20),
// Soal Section
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: data.questionListings.length,
itemBuilder: (context, index) {
final question = data.questionListings[index];
return _buildQuestionItem(question, index + 1);
},
),
],
),
),
),
),
);
}
Widget _buildQuestionItem(QuestionListing question, int index) {
return Container(
width: double.infinity,
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.borderLight),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 6,
offset: const Offset(2, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Soal $index',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: AppColors.darkText,
),
),
const SizedBox(height: 8),
Text(
_mapQuestionTypeToText(question.type),
style: const TextStyle(
fontSize: 12,
fontStyle: FontStyle.italic,
color: AppColors.softGrayText,
),
),
const SizedBox(height: 12),
Text(
question.question,
style: const TextStyle(
fontSize: 14,
color: AppColors.darkText,
),
),
const SizedBox(height: 12),
Text(
'Jawaban: ${question.targetAnswer}',
style: const TextStyle(
fontSize: 14,
color: AppColors.softGrayText,
),
),
const SizedBox(height: 8),
Text(
'Durasi: ${question.duration} detik',
style: const TextStyle(
fontSize: 12,
color: AppColors.softGrayText,
),
),
],
),
);
}
String _mapQuestionTypeToText(String? type) {
switch (type) {
case 'option':
return 'Pilihan Ganda';
case 'fill_the_blank':
return 'Isian Kosong';
case 'true_false':
return 'Benar / Salah';
default:
return 'Tipe Tidak Diketahui';
}
}
String _formatDate(DateTime date) {
return '${date.day}/${date.month}/${date.year}';
}
}

View File

@ -0,0 +1,136 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:quiz_app/feature/library/controller/library_controller.dart';
class LibraryView extends GetView<LibraryController> {
const LibraryView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF9FAFB),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Library Soal',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 24,
),
),
const SizedBox(height: 8),
const Text(
"Kumpulan soal-soal kuis yang sudah dibuat untuk dipelajari.",
style: TextStyle(
color: Colors.grey,
fontSize: 14,
),
),
const SizedBox(height: 20),
Expanded(
child: Obx(
() => ListView.builder(
itemCount: controller.quizList.length,
itemBuilder: (context, index) {
final quiz = controller.quizList[index];
return _buildQuizCard(quiz);
},
),
),
),
],
),
),
),
);
}
Widget _buildQuizCard(Map<String, dynamic> quiz) {
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 6,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: const Color(0xFF2563EB),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(Icons.menu_book_rounded, color: Colors.white),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
quiz['title'] ?? '-',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: Colors.black,
overflow: TextOverflow.ellipsis,
),
maxLines: 1,
),
const SizedBox(height: 4),
Text(
quiz['description'] ?? '-',
style: const TextStyle(
color: Colors.grey,
fontSize: 12,
overflow: TextOverflow.ellipsis,
),
maxLines: 2,
),
const SizedBox(height: 8),
Row(
children: [
const Icon(Icons.calendar_today_rounded, size: 14, color: Colors.grey),
const SizedBox(width: 4),
Text(
controller.formatDate(quiz['date']),
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
const SizedBox(width: 12),
const Icon(Icons.list, size: 14, color: Colors.grey),
const SizedBox(width: 4),
Text(
'${quiz['total_quiz']} Quizzes',
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
const SizedBox(width: 12),
const Icon(Icons.access_time, size: 14, color: Colors.grey),
const SizedBox(width: 4),
Text(
controller.formatDuration(quiz['limit_duration'] ?? 0),
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
],
),
],
),
),
],
),
);
}
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:quiz_app/feature/history/view/history_view.dart';
import 'package:quiz_app/feature/home/view/home_page.dart';
import 'package:quiz_app/feature/library/view/library_view.dart';
import 'package:quiz_app/feature/navigation/controllers/navigation_controller.dart';
import 'package:quiz_app/feature/profile/view/profile_view.dart';
import 'package:quiz_app/feature/search/view/search_view.dart';
@ -19,8 +20,10 @@ class NavbarView extends GetView<NavigationController> {
case 1:
return const SearchView();
case 2:
return const HistoryView();
return const LibraryView();
case 3:
return const HistoryView();
case 4:
return const ProfileView();
default:
return const HomeView();
@ -40,6 +43,10 @@ class NavbarView extends GetView<NavigationController> {
icon: Icon(Icons.search),
label: 'Search',
),
BottomNavigationBarItem(
icon: Icon(Icons.menu_book),
label: 'Library',
),
BottomNavigationBarItem(
icon: Icon(Icons.history),
label: 'History',