diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 13d9a90..70bbb1c 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -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(), ], diff --git a/lib/feature/library/binding/library_binding.dart b/lib/feature/library/binding/library_binding.dart new file mode 100644 index 0000000..2ff7624 --- /dev/null +++ b/lib/feature/library/binding/library_binding.dart @@ -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()); + } +} diff --git a/lib/feature/library/controller/detail_quiz_controller.dart b/lib/feature/library/controller/detail_quiz_controller.dart new file mode 100644 index 0000000..25856c3 --- /dev/null +++ b/lib/feature/library/controller/detail_quiz_controller.dart @@ -0,0 +1,72 @@ +import 'package:get/get.dart'; + +class DetailQuizController extends GetxController { + Rx 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 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, + }); +} diff --git a/lib/feature/library/controller/library_controller.dart b/lib/feature/library/controller/library_controller.dart new file mode 100644 index 0000000..eb45563 --- /dev/null +++ b/lib/feature/library/controller/library_controller.dart @@ -0,0 +1,40 @@ +import 'package:get/get.dart'; + +class LibraryController extends GetxController { + RxList> quizList = >[].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 '-'; + } + } +} diff --git a/lib/feature/library/view/detail_quix_view.dart b/lib/feature/library/view/detail_quix_view.dart new file mode 100644 index 0000000..57f8578 --- /dev/null +++ b/lib/feature/library/view/detail_quix_view.dart @@ -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 { + 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}'; + } +} diff --git a/lib/feature/library/view/library_view.dart b/lib/feature/library/view/library_view.dart new file mode 100644 index 0000000..98b60ff --- /dev/null +++ b/lib/feature/library/view/library_view.dart @@ -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 { + 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 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), + ), + ], + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/feature/navigation/views/navbar_view.dart b/lib/feature/navigation/views/navbar_view.dart index b85ccc7..2f52203 100644 --- a/lib/feature/navigation/views/navbar_view.dart +++ b/lib/feature/navigation/views/navbar_view.dart @@ -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 { 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 { icon: Icon(Icons.search), label: 'Search', ), + BottomNavigationBarItem( + icon: Icon(Icons.menu_book), + label: 'Library', + ), BottomNavigationBarItem( icon: Icon(Icons.history), label: 'History',