diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 7d3ada7..bb57760 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -6,6 +6,8 @@ import 'package:quiz_app/feature/home/view/home_page.dart'; import 'package:quiz_app/feature/detail_quiz/binding/detail_quiz_binding.dart'; import 'package:quiz_app/feature/library/binding/library_binding.dart'; import 'package:quiz_app/feature/detail_quiz/view/detail_quix_view.dart'; +import 'package:quiz_app/feature/listing_quiz/binding/listing_quiz_binding.dart'; +import 'package:quiz_app/feature/listing_quiz/view/listing_quiz_view.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'; @@ -85,6 +87,11 @@ class AppPages { name: AppRoutes.resultQuizPage, page: () => QuizResultView(), binding: QuizResultBinding(), + ), + GetPage( + name: AppRoutes.listingQuizPage, + page: () => ListingsQuizView(), + binding: ListingQuizBinding(), ) ]; } diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart index 8d4553a..12ae8a0 100644 --- a/lib/app/routes/app_routes.dart +++ b/lib/app/routes/app_routes.dart @@ -10,7 +10,7 @@ abstract class AppRoutes { static const quizCreatePage = "/quiz/creation"; static const quizPreviewPage = "/quiz/preview"; - + static const listingQuizPage = "/quiz/listing"; static const detailQuizPage = "/quiz/detail"; static const playQuizPage = "/quiz/play"; diff --git a/lib/component/widget/recomendation_component.dart b/lib/component/widget/recomendation_component.dart index 2cd43af..8d73c25 100644 --- a/lib/component/widget/recomendation_component.dart +++ b/lib/component/widget/recomendation_component.dart @@ -6,10 +6,13 @@ class RecomendationComponent extends StatelessWidget { final String title; final List datas; final Function(String) itemOnTap; + final Function() allOnTap; + const RecomendationComponent({ required this.title, required this.datas, required this.itemOnTap, + required this.allOnTap, super.key, }); @@ -60,9 +63,12 @@ class RecomendationComponent extends StatelessWidget { title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), - Text( - "Lihat semua", - style: TextStyle(fontSize: 14, color: Colors.blue.shade700), + GestureDetector( + onTap: allOnTap, + child: Text( + "Lihat semua", + style: TextStyle(fontSize: 14, color: Colors.blue.shade700), + ), ), ], ), diff --git a/lib/data/services/quiz_service.dart b/lib/data/services/quiz_service.dart index 94a1948..d41392b 100644 --- a/lib/data/services/quiz_service.dart +++ b/lib/data/services/quiz_service.dart @@ -51,9 +51,9 @@ class QuizService extends GetxService { } } - Future>?> recomendationQuiz() async { + Future>?> recomendationQuiz({int page = 1, int amount = 3}) async { try { - final response = await _dio.get(APIEndpoint.quizRecomendation); + final response = await _dio.get("${APIEndpoint.quizRecomendation}?page=$page&limit=$amount"); if (response.statusCode == 200) { final parsedResponse = BaseResponseModel>.fromJson( diff --git a/lib/feature/home/controller/home_controller.dart b/lib/feature/home/controller/home_controller.dart index bc8b970..c0b2d84 100644 --- a/lib/feature/home/controller/home_controller.dart +++ b/lib/feature/home/controller/home_controller.dart @@ -7,7 +7,7 @@ import 'package:quiz_app/data/services/quiz_service.dart'; class HomeController extends GetxController { final UserController _userController = Get.find(); - QuizService _quizService; + final QuizService _quizService; HomeController(this._quizService); Rx get userName => _userController.userName; @@ -31,4 +31,6 @@ class HomeController extends GetxController { } void onRecommendationTap(String quizId) => Get.toNamed(AppRoutes.detailQuizPage, arguments: quizId); + + void goToListingsQuizPage() => Get.toNamed(AppRoutes.listingQuizPage); } diff --git a/lib/feature/home/view/home_page.dart b/lib/feature/home/view/home_page.dart index 932453a..7f8b84a 100644 --- a/lib/feature/home/view/home_page.dart +++ b/lib/feature/home/view/home_page.dart @@ -48,6 +48,7 @@ class HomeView extends GetView { title: "Quiz Rekomendasi", datas: controller.data.toList(), itemOnTap: controller.onRecommendationTap, + allOnTap: controller.goToListingsQuizPage, ), ), ], diff --git a/lib/feature/listing_quiz/binding/listing_quiz_binding.dart b/lib/feature/listing_quiz/binding/listing_quiz_binding.dart new file mode 100644 index 0000000..ae61395 --- /dev/null +++ b/lib/feature/listing_quiz/binding/listing_quiz_binding.dart @@ -0,0 +1,13 @@ +import 'package:get/get.dart'; +import 'package:quiz_app/data/services/quiz_service.dart'; +import 'package:quiz_app/feature/listing_quiz/controller/listing_quiz_controller.dart'; + +class ListingQuizBinding extends Bindings { + @override + void dependencies() { + if (!Get.isRegistered()) { + Get.lazyPut(() => QuizService()); + } + Get.lazyPut(() => ListingQuizController(Get.find())); + } +} diff --git a/lib/feature/listing_quiz/controller/listing_quiz_controller.dart b/lib/feature/listing_quiz/controller/listing_quiz_controller.dart new file mode 100644 index 0000000..6cf2b93 --- /dev/null +++ b/lib/feature/listing_quiz/controller/listing_quiz_controller.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:quiz_app/app/routes/app_pages.dart'; +import 'package:quiz_app/data/models/base/base_model.dart'; +import 'package:quiz_app/data/models/quiz/quiz_listing_model.dart'; +import 'package:quiz_app/data/services/quiz_service.dart'; + +class ListingQuizController extends GetxController { + final QuizService _quizService; + + ListingQuizController(this._quizService); + + RxBool isLoading = false.obs; + RxBool isLoadingMore = false.obs; + RxList quizzes = [].obs; + + final ScrollController scrollController = ScrollController(); + + final int amountQuiz = 8; + int currentPage = 1; + bool hasMore = true; + + @override + void onInit() { + super.onInit(); + _getRecomendationQuiz(); + scrollController.addListener(_onScroll); + } + + void _onScroll() { + if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 200) { + if (!isLoadingMore.value && hasMore) { + loadMoreQuiz(); + } + } + } + + Future _getRecomendationQuiz() async { + isLoading.value = true; + currentPage = 1; + BaseResponseModel? response = await _quizService.recomendationQuiz(amount: amountQuiz); + if (response != null && response.data != null) { + final data = response.data as List; + quizzes.assignAll(data); + hasMore = data.length == amountQuiz; + } + isLoading.value = false; + } + + Future loadMoreQuiz() async { + isLoadingMore.value = true; + currentPage++; + BaseResponseModel? response = await _quizService.recomendationQuiz(page: currentPage, amount: amountQuiz); + if (response != null && response.data != null) { + final data = response.data as List; + quizzes.addAll(data); + hasMore = data.length == amountQuiz; + } + isLoadingMore.value = false; + } + + void goToDetailQuiz(String quizId) => Get.toNamed(AppRoutes.detailQuizPage, arguments: quizId); +} diff --git a/lib/feature/listing_quiz/view/listing_quiz_view.dart b/lib/feature/listing_quiz/view/listing_quiz_view.dart new file mode 100644 index 0000000..af386f3 --- /dev/null +++ b/lib/feature/listing_quiz/view/listing_quiz_view.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:quiz_app/app/const/colors/app_colors.dart'; +import 'package:quiz_app/component/quiz_container_component.dart'; +import 'package:quiz_app/feature/listing_quiz/controller/listing_quiz_controller.dart'; + +class ListingsQuizView extends GetView { + const ListingsQuizView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar( + centerTitle: true, + title: const Text( + 'Daftar Kuis', + ), + ), + body: Padding( + padding: const EdgeInsets.all(20.0), + child: Obx(() { + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } + + if (controller.quizzes.isEmpty) { + return const Center(child: Text('Tidak ada kuis tersedia.')); + } + + return ListView.builder( + controller: controller.scrollController, + itemCount: controller.quizzes.length + 1, // +1 untuk indikator loading + itemBuilder: (context, index) { + if (index == controller.quizzes.length) { + return Obx(() => controller.isLoadingMore.value + ? const Padding( + padding: EdgeInsets.all(16), + child: Center(child: CircularProgressIndicator()), + ) + : const SizedBox()); + } + + final quiz = controller.quizzes[index]; + return QuizContainerComponent( + data: quiz, + onTap: controller.goToDetailQuiz, + ); + }, + ); + })), + ); + } +} diff --git a/lib/feature/search/controller/search_controller.dart b/lib/feature/search/controller/search_controller.dart index 2a59484..f8c932c 100644 --- a/lib/feature/search/controller/search_controller.dart +++ b/lib/feature/search/controller/search_controller.dart @@ -47,6 +47,8 @@ class SearchQuizController extends GetxController { void goToDetailPage(String quizId) => Get.toNamed(AppRoutes.detailQuizPage, arguments: quizId); + void goToListingsQuizPage() => Get.toNamed(AppRoutes.listingQuizPage); + @override void onClose() { searchController.dispose(); diff --git a/lib/feature/search/view/search_view.dart b/lib/feature/search/view/search_view.dart index 14c5ee5..a188618 100644 --- a/lib/feature/search/view/search_view.dart +++ b/lib/feature/search/view/search_view.dart @@ -32,6 +32,7 @@ class SearchView extends GetView { title: "Quiz Rekomendasi", datas: controller.recommendationQData.toList(), itemOnTap: controller.goToDetailPage, + allOnTap: controller.goToListingsQuizPage, ), ), const SizedBox(height: 30), @@ -40,6 +41,7 @@ class SearchView extends GetView { title: "Quiz Populer", datas: controller.recommendationQData.toList(), itemOnTap: controller.goToDetailPage, + allOnTap: controller.goToListingsQuizPage, ), ), ],