develop #1

Merged
akhdanre merged 104 commits from develop into main 2025-07-10 12:38:53 +07:00
9 changed files with 111 additions and 29 deletions
Showing only changes of commit 93ab86e833 - Show all commits

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 {
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<List<QuizListingModel>>.fromJson(

View File

@ -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},
);
}

View File

@ -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<SubjectModel> 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),
),
);
},
),

View File

@ -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<HomeController> {
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<HomeController> {
title: "Quiz Rekomendasi",
datas: controller.data.toList(),
itemOnTap: controller.onRecommendationTap,
allOnTap: controller.goToListingsQuizPage,
allOnTap: () => controller.goToListingsQuizPage(ListingType.recomendation),
),
),
],

View File

@ -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<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() {
if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 200) {
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;
currentPage = 1;
BaseResponseModel? response = await _quizService.recomendationQuiz(amount: amountQuiz);
if (response != null && response.data != null) {
final data = response.data as List<QuizListingModel>;
quizzes.assignAll(data);
hasMore = data.length == amountQuiz;
}
final response = await _quizService.recomendationQuiz(page: currentPage, amount: amountQuiz);
_handleResponse(response, resetPage: resetPage);
isLoading.value = false;
}
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;
}
Future<void> 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<QuizListingModel>;
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);

View File

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

View File

@ -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<String>(
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<List<SubjectModel>>? respnse = await _subjectService.getSubject();
@ -65,8 +69,6 @@ class SearchQuizController extends GetxController {
}
}
void goToDetailQuizListing() {}
@override
void onClose() {
searchController.dispose();

View File

@ -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<SearchQuizController> {
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<SearchQuizController> {
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<SearchQuizController> {
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),