diff --git a/lib/app/const/enums/listing_type.dart b/lib/app/const/enums/listing_type.dart new file mode 100644 index 0000000..ac76db8 --- /dev/null +++ b/lib/app/const/enums/listing_type.dart @@ -0,0 +1 @@ +enum ListingType { recomendation, populer, subject } diff --git a/lib/data/services/quiz_service.dart b/lib/data/services/quiz_service.dart index 66e590e..b92358d 100644 --- a/lib/data/services/quiz_service.dart +++ b/lib/data/services/quiz_service.dart @@ -74,9 +74,17 @@ class QuizService extends GetxService { } } - Future>?> searchQuiz(String keyword) async { + Future>?> searchQuiz(String keyword, int page, {int limit = 10, String? subjectId}) async { try { - final response = await _dio.get("${APIEndpoint.quizSearch}?keyword=$keyword&page=1&limit=10"); + final queryParams = { + "keyword": keyword, + "page": page.toString(), + "limit": limit.toString(), + if (subjectId != null && subjectId.isNotEmpty) "subject_id": subjectId, + }; + + final uri = Uri.parse(APIEndpoint.quizSearch).replace(queryParameters: queryParams); + final response = await _dio.getUri(uri); 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 78afb4e..8312079 100644 --- a/lib/feature/home/controller/home_controller.dart +++ b/lib/feature/home/controller/home_controller.dart @@ -1,4 +1,5 @@ import 'package:get/get.dart'; +import 'package:quiz_app/app/const/enums/listing_type.dart'; import 'package:quiz_app/app/routes/app_pages.dart'; import 'package:quiz_app/data/controllers/user_controller.dart'; import 'package:quiz_app/data/models/base/base_model.dart'; @@ -56,5 +57,8 @@ class HomeController extends GetxController { void onRecommendationTap(String quizId) => Get.toNamed(AppRoutes.detailQuizPage, arguments: quizId); - void goToListingsQuizPage() => Get.toNamed(AppRoutes.listingQuizPage); + void goToListingsQuizPage(ListingType page, {String? subjectId, String? subjecName}) => Get.toNamed( + AppRoutes.listingQuizPage, + arguments: {"page": page, "id": subjectId, "subject_name": subjecName}, + ); } diff --git a/lib/feature/home/view/component/search_component.dart b/lib/feature/home/view/component/search_component.dart index 129d733..c02235e 100644 --- a/lib/feature/home/view/component/search_component.dart +++ b/lib/feature/home/view/component/search_component.dart @@ -4,10 +4,12 @@ import 'package:quiz_app/data/models/subject/subject_model.dart'; class SearchComponent extends StatelessWidget { final Function() onSearchTap; + final Function(String, String) onSubjectTap; final List subject; const SearchComponent({ super.key, + required this.onSubjectTap, required this.onSearchTap, required this.subject, }); @@ -66,9 +68,12 @@ class SearchComponent extends StatelessWidget { scrollDirection: Axis.horizontal, itemCount: subject.length, itemBuilder: (context, index) { - return Padding( - padding: EdgeInsets.only(right: index != subject.length - 1 ? 8.0 : 0), - child: _buildCategoryComponent(subject[index].alias), + return GestureDetector( + onTap: () => onSubjectTap(subject[index].id, subject[index].alias), + child: Padding( + padding: EdgeInsets.only(right: index != subject.length - 1 ? 8.0 : 0), + child: _buildCategoryComponent(subject[index].alias), + ), ); }, ), diff --git a/lib/feature/home/view/home_page.dart b/lib/feature/home/view/home_page.dart index f220ed5..7cc6bd7 100644 --- a/lib/feature/home/view/home_page.dart +++ b/lib/feature/home/view/home_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:quiz_app/app/const/colors/app_colors.dart'; +import 'package:quiz_app/app/const/enums/listing_type.dart'; import 'package:quiz_app/feature/home/controller/home_controller.dart'; import 'package:quiz_app/feature/home/view/component/button_option.dart'; import 'package:quiz_app/component/widget/recomendation_component.dart'; @@ -44,6 +45,7 @@ class HomeView extends GetView { Obx(() => SearchComponent( onSearchTap: controller.goToSearch, subject: controller.subjects.toList(), + onSubjectTap: (p0, p1) => controller.goToListingsQuizPage(ListingType.subject, subjectId: p0, subjecName: p1), )), const SizedBox(height: 20), Obx( @@ -51,7 +53,7 @@ class HomeView extends GetView { title: "Quiz Rekomendasi", datas: controller.data.toList(), itemOnTap: controller.onRecommendationTap, - allOnTap: controller.goToListingsQuizPage, + allOnTap: () => controller.goToListingsQuizPage(ListingType.recomendation), ), ), ], diff --git a/lib/feature/listing_quiz/controller/listing_quiz_controller.dart b/lib/feature/listing_quiz/controller/listing_quiz_controller.dart index 6cf2b93..8a5db66 100644 --- a/lib/feature/listing_quiz/controller/listing_quiz_controller.dart +++ b/lib/feature/listing_quiz/controller/listing_quiz_controller.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:quiz_app/app/const/enums/listing_type.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'; @@ -20,13 +21,37 @@ class ListingQuizController extends GetxController { int currentPage = 1; bool hasMore = true; + RxString appBarTitle = "Quiz Recommendation".obs; + bool isSearchMode = false; + String? currentSubjectId; + @override void onInit() { super.onInit(); - _getRecomendationQuiz(); + loadData(); scrollController.addListener(_onScroll); } + void loadData() { + final Map data = Get.arguments as Map; + final pageType = data['page'] as ListingType; + + switch (pageType) { + case ListingType.populer: + appBarTitle.value = "Quiz Populer"; + _loadRecommendation(resetPage: true); + break; + case ListingType.recomendation: + appBarTitle.value = "Quiz Recommendation"; + _loadRecommendation(resetPage: true); + break; + case ListingType.subject: + appBarTitle.value = "Quiz ${data["subject_name"]}"; + _loadBySubject(subjectId: data["id"], resetPage: true); + break; + } + } + void _onScroll() { if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 200) { if (!isLoadingMore.value && hasMore) { @@ -35,28 +60,60 @@ class ListingQuizController extends GetxController { } } - Future _getRecomendationQuiz() async { + Future _loadRecommendation({bool resetPage = false}) async { + isSearchMode = false; + currentSubjectId = null; + if (resetPage) currentPage = 1; + 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; - } + + final response = await _quizService.recomendationQuiz(page: currentPage, amount: amountQuiz); + _handleResponse(response, resetPage: resetPage); + + isLoading.value = false; + } + + Future _loadBySubject({required String subjectId, bool resetPage = false}) async { + isSearchMode = true; + currentSubjectId = subjectId; + if (resetPage) currentPage = 1; + + isLoading.value = true; + + final response = await _quizService.searchQuiz("", currentPage, subjectId: subjectId); + _handleResponse(response, resetPage: resetPage); + isLoading.value = false; } Future loadMoreQuiz() async { + if (!hasMore) return; + isLoadingMore.value = true; currentPage++; - BaseResponseModel? response = await _quizService.recomendationQuiz(page: currentPage, amount: amountQuiz); + + BaseResponseModel? response; + if (isSearchMode && currentSubjectId != null) { + response = await _quizService.searchQuiz("", currentPage, subjectId: currentSubjectId!); + } else { + response = await _quizService.recomendationQuiz(page: currentPage, amount: amountQuiz); + } + + _handleResponse(response, resetPage: false); + + isLoadingMore.value = false; + } + + void _handleResponse(BaseResponseModel? response, {required bool resetPage}) { if (response != null && response.data != null) { final data = response.data as List; - quizzes.addAll(data); + if (resetPage) { + quizzes.assignAll(data); + } else { + 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 index af386f3..270e402 100644 --- a/lib/feature/listing_quiz/view/listing_quiz_view.dart +++ b/lib/feature/listing_quiz/view/listing_quiz_view.dart @@ -13,8 +13,10 @@ class ListingsQuizView extends GetView { backgroundColor: AppColors.background, appBar: AppBar( centerTitle: true, - title: const Text( - 'Daftar Kuis', + title: Obx( + () => Text( + controller.appBarTitle.value, + ), ), ), body: Padding( diff --git a/lib/feature/search/controller/search_controller.dart b/lib/feature/search/controller/search_controller.dart index f03fef4..d71062a 100644 --- a/lib/feature/search/controller/search_controller.dart +++ b/lib/feature/search/controller/search_controller.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:quiz_app/app/const/enums/listing_type.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'; @@ -35,7 +36,7 @@ class SearchQuizController extends GetxController { debounce( searchText, (value) => getSearchData(value), - time: Duration(seconds: 2), + time: Duration(seconds: 1), ); } @@ -48,7 +49,7 @@ class SearchQuizController extends GetxController { void getSearchData(String keyword) async { searchQData.clear(); - BaseResponseModel? response = await _quizService.searchQuiz(keyword); + BaseResponseModel? response = await _quizService.searchQuiz(keyword, 1); if (response != null) { searchQData.assignAll(response.data); } @@ -56,7 +57,10 @@ class SearchQuizController extends GetxController { void goToDetailPage(String quizId) => Get.toNamed(AppRoutes.detailQuizPage, arguments: quizId); - void goToListingsQuizPage() => Get.toNamed(AppRoutes.listingQuizPage); + void goToListingsQuizPage(ListingType page, {String? subjectId, String? subjecName}) => Get.toNamed( + AppRoutes.listingQuizPage, + arguments: {"page": page, "id": subjectId, "subject_name": subjecName}, + ); void loadSubjectData() async { BaseResponseModel>? respnse = await _subjectService.getSubject(); @@ -65,8 +69,6 @@ class SearchQuizController extends GetxController { } } - void goToDetailQuizListing() {} - @override void onClose() { searchController.dispose(); diff --git a/lib/feature/search/view/search_view.dart b/lib/feature/search/view/search_view.dart index 51db20c..43a9521 100644 --- a/lib/feature/search/view/search_view.dart +++ b/lib/feature/search/view/search_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:quiz_app/app/const/enums/listing_type.dart'; import 'package:quiz_app/component/quiz_container_component.dart'; import 'package:quiz_app/component/widget/recomendation_component.dart'; import 'package:quiz_app/data/models/subject/subject_model.dart'; @@ -33,7 +34,7 @@ class SearchView extends GetView { title: "Quiz Rekomendasi", datas: controller.recommendationQData.toList(), itemOnTap: controller.goToDetailPage, - allOnTap: controller.goToListingsQuizPage, + allOnTap: () => controller.goToListingsQuizPage(ListingType.recomendation), ), ), const SizedBox(height: 30), @@ -42,7 +43,7 @@ class SearchView extends GetView { title: "Quiz Populer", datas: controller.recommendationQData.toList(), itemOnTap: controller.goToDetailPage, - allOnTap: controller.goToListingsQuizPage, + allOnTap: () => controller.goToListingsQuizPage(ListingType.populer), ), ), ], @@ -85,7 +86,7 @@ class SearchView extends GetView { runSpacing: 1, children: data.map((cat) { return InkWell( - onTap: () => controller.goToDetailQuizListing, + onTap: () => controller.goToListingsQuizPage(ListingType.subject, subjectId: cat.id, subjecName: cat.alias), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), margin: const EdgeInsets.symmetric(vertical: 2),