feat: done working on subject quiz listing

This commit is contained in:
akhdanre 2025-05-05 11:38:40 +07:00
parent cd38b79bef
commit 93ab86e833
9 changed files with 111 additions and 29 deletions

View File

@ -0,0 +1 @@
enum ListingType { recomendation, populer, subject }

View File

@ -74,9 +74,17 @@ class QuizService extends GetxService {
} }
} }
Future<BaseResponseModel<List<QuizListingModel>>?> searchQuiz(String keyword) async { Future<BaseResponseModel<List<QuizListingModel>>?> searchQuiz(String keyword, int page, {int limit = 10, String? subjectId}) async {
try { 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) { if (response.statusCode == 200) {
final parsedResponse = BaseResponseModel<List<QuizListingModel>>.fromJson( final parsedResponse = BaseResponseModel<List<QuizListingModel>>.fromJson(

View File

@ -1,4 +1,5 @@
import 'package:get/get.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/app/routes/app_pages.dart';
import 'package:quiz_app/data/controllers/user_controller.dart'; import 'package:quiz_app/data/controllers/user_controller.dart';
import 'package:quiz_app/data/models/base/base_model.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 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},
);
} }

View File

@ -4,10 +4,12 @@ import 'package:quiz_app/data/models/subject/subject_model.dart';
class SearchComponent extends StatelessWidget { class SearchComponent extends StatelessWidget {
final Function() onSearchTap; final Function() onSearchTap;
final Function(String, String) onSubjectTap;
final List<SubjectModel> subject; final List<SubjectModel> subject;
const SearchComponent({ const SearchComponent({
super.key, super.key,
required this.onSubjectTap,
required this.onSearchTap, required this.onSearchTap,
required this.subject, required this.subject,
}); });
@ -66,9 +68,12 @@ class SearchComponent extends StatelessWidget {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: subject.length, itemCount: subject.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return Padding( return GestureDetector(
onTap: () => onSubjectTap(subject[index].id, subject[index].alias),
child: Padding(
padding: EdgeInsets.only(right: index != subject.length - 1 ? 8.0 : 0), padding: EdgeInsets.only(right: index != subject.length - 1 ? 8.0 : 0),
child: _buildCategoryComponent(subject[index].alias), child: _buildCategoryComponent(subject[index].alias),
),
); );
}, },
), ),

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:quiz_app/app/const/colors/app_colors.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/controller/home_controller.dart';
import 'package:quiz_app/feature/home/view/component/button_option.dart'; import 'package:quiz_app/feature/home/view/component/button_option.dart';
import 'package:quiz_app/component/widget/recomendation_component.dart'; import 'package:quiz_app/component/widget/recomendation_component.dart';
@ -44,6 +45,7 @@ class HomeView extends GetView<HomeController> {
Obx(() => SearchComponent( Obx(() => SearchComponent(
onSearchTap: controller.goToSearch, onSearchTap: controller.goToSearch,
subject: controller.subjects.toList(), subject: controller.subjects.toList(),
onSubjectTap: (p0, p1) => controller.goToListingsQuizPage(ListingType.subject, subjectId: p0, subjecName: p1),
)), )),
const SizedBox(height: 20), const SizedBox(height: 20),
Obx( Obx(
@ -51,7 +53,7 @@ class HomeView extends GetView<HomeController> {
title: "Quiz Rekomendasi", title: "Quiz Rekomendasi",
datas: controller.data.toList(), datas: controller.data.toList(),
itemOnTap: controller.onRecommendationTap, itemOnTap: controller.onRecommendationTap,
allOnTap: controller.goToListingsQuizPage, allOnTap: () => controller.goToListingsQuizPage(ListingType.recomendation),
), ),
), ),
], ],

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.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/app/routes/app_pages.dart';
import 'package:quiz_app/data/models/base/base_model.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/models/quiz/quiz_listing_model.dart';
@ -20,13 +21,37 @@ class ListingQuizController extends GetxController {
int currentPage = 1; int currentPage = 1;
bool hasMore = true; bool hasMore = true;
RxString appBarTitle = "Quiz Recommendation".obs;
bool isSearchMode = false;
String? currentSubjectId;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
_getRecomendationQuiz(); loadData();
scrollController.addListener(_onScroll); scrollController.addListener(_onScroll);
} }
void loadData() {
final Map<String, dynamic> data = Get.arguments as Map<String, dynamic>;
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() { void _onScroll() {
if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 200) { if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 200) {
if (!isLoadingMore.value && hasMore) { if (!isLoadingMore.value && hasMore) {
@ -35,28 +60,60 @@ class ListingQuizController extends GetxController {
} }
} }
Future<void> _getRecomendationQuiz() async { Future<void> _loadRecommendation({bool resetPage = false}) async {
isSearchMode = false;
currentSubjectId = null;
if (resetPage) currentPage = 1;
isLoading.value = true; isLoading.value = true;
currentPage = 1;
BaseResponseModel? response = await _quizService.recomendationQuiz(amount: amountQuiz); final response = await _quizService.recomendationQuiz(page: currentPage, amount: amountQuiz);
if (response != null && response.data != null) { _handleResponse(response, resetPage: resetPage);
final data = response.data as List<QuizListingModel>;
quizzes.assignAll(data); isLoading.value = false;
hasMore = data.length == amountQuiz;
} }
Future<void> _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; isLoading.value = false;
} }
Future<void> loadMoreQuiz() async { Future<void> loadMoreQuiz() async {
if (!hasMore) return;
isLoadingMore.value = true; isLoadingMore.value = true;
currentPage++; 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) { if (response != null && response.data != null) {
final data = response.data as List<QuizListingModel>; final data = response.data as List<QuizListingModel>;
if (resetPage) {
quizzes.assignAll(data);
} else {
quizzes.addAll(data); quizzes.addAll(data);
}
hasMore = data.length == amountQuiz; hasMore = data.length == amountQuiz;
} }
isLoadingMore.value = false;
} }
void goToDetailQuiz(String quizId) => Get.toNamed(AppRoutes.detailQuizPage, arguments: quizId); void goToDetailQuiz(String quizId) => Get.toNamed(AppRoutes.detailQuizPage, arguments: quizId);

View File

@ -13,8 +13,10 @@ class ListingsQuizView extends GetView<ListingQuizController> {
backgroundColor: AppColors.background, backgroundColor: AppColors.background,
appBar: AppBar( appBar: AppBar(
centerTitle: true, centerTitle: true,
title: const Text( title: Obx(
'Daftar Kuis', () => Text(
controller.appBarTitle.value,
),
), ),
), ),
body: Padding( body: Padding(

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.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/app/routes/app_pages.dart';
import 'package:quiz_app/data/models/base/base_model.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/models/quiz/quiz_listing_model.dart';
@ -35,7 +36,7 @@ class SearchQuizController extends GetxController {
debounce<String>( debounce<String>(
searchText, searchText,
(value) => getSearchData(value), (value) => getSearchData(value),
time: Duration(seconds: 2), time: Duration(seconds: 1),
); );
} }
@ -48,7 +49,7 @@ class SearchQuizController extends GetxController {
void getSearchData(String keyword) async { void getSearchData(String keyword) async {
searchQData.clear(); searchQData.clear();
BaseResponseModel? response = await _quizService.searchQuiz(keyword); BaseResponseModel? response = await _quizService.searchQuiz(keyword, 1);
if (response != null) { if (response != null) {
searchQData.assignAll(response.data); searchQData.assignAll(response.data);
} }
@ -56,7 +57,10 @@ class SearchQuizController extends GetxController {
void goToDetailPage(String quizId) => Get.toNamed(AppRoutes.detailQuizPage, arguments: quizId); 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 { void loadSubjectData() async {
BaseResponseModel<List<SubjectModel>>? respnse = await _subjectService.getSubject(); BaseResponseModel<List<SubjectModel>>? respnse = await _subjectService.getSubject();
@ -65,8 +69,6 @@ class SearchQuizController extends GetxController {
} }
} }
void goToDetailQuizListing() {}
@override @override
void onClose() { void onClose() {
searchController.dispose(); searchController.dispose();

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.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/quiz_container_component.dart';
import 'package:quiz_app/component/widget/recomendation_component.dart'; import 'package:quiz_app/component/widget/recomendation_component.dart';
import 'package:quiz_app/data/models/subject/subject_model.dart'; import 'package:quiz_app/data/models/subject/subject_model.dart';
@ -33,7 +34,7 @@ class SearchView extends GetView<SearchQuizController> {
title: "Quiz Rekomendasi", title: "Quiz Rekomendasi",
datas: controller.recommendationQData.toList(), datas: controller.recommendationQData.toList(),
itemOnTap: controller.goToDetailPage, itemOnTap: controller.goToDetailPage,
allOnTap: controller.goToListingsQuizPage, allOnTap: () => controller.goToListingsQuizPage(ListingType.recomendation),
), ),
), ),
const SizedBox(height: 30), const SizedBox(height: 30),
@ -42,7 +43,7 @@ class SearchView extends GetView<SearchQuizController> {
title: "Quiz Populer", title: "Quiz Populer",
datas: controller.recommendationQData.toList(), datas: controller.recommendationQData.toList(),
itemOnTap: controller.goToDetailPage, itemOnTap: controller.goToDetailPage,
allOnTap: controller.goToListingsQuizPage, allOnTap: () => controller.goToListingsQuizPage(ListingType.populer),
), ),
), ),
], ],
@ -85,7 +86,7 @@ class SearchView extends GetView<SearchQuizController> {
runSpacing: 1, runSpacing: 1,
children: data.map((cat) { children: data.map((cat) {
return InkWell( return InkWell(
onTap: () => controller.goToDetailQuizListing, onTap: () => controller.goToListingsQuizPage(ListingType.subject, subjectId: cat.id, subjecName: cat.alias),
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
margin: const EdgeInsets.symmetric(vertical: 2), margin: const EdgeInsets.symmetric(vertical: 2),