feat: listing quiz done

This commit is contained in:
akhdanre 2025-05-02 03:56:05 +07:00
parent c026a53d6f
commit 668c7eac27
11 changed files with 157 additions and 7 deletions

View File

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

View File

@ -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";

View File

@ -6,10 +6,13 @@ class RecomendationComponent extends StatelessWidget {
final String title;
final List<QuizListingModel> 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,10 +63,13 @@ class RecomendationComponent extends StatelessWidget {
title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Text(
GestureDetector(
onTap: allOnTap,
child: Text(
"Lihat semua",
style: TextStyle(fontSize: 14, color: Colors.blue.shade700),
),
),
],
),
);

View File

@ -51,9 +51,9 @@ class QuizService extends GetxService {
}
}
Future<BaseResponseModel<List<QuizListingModel>>?> recomendationQuiz() async {
Future<BaseResponseModel<List<QuizListingModel>>?> 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<List<QuizListingModel>>.fromJson(

View File

@ -7,7 +7,7 @@ import 'package:quiz_app/data/services/quiz_service.dart';
class HomeController extends GetxController {
final UserController _userController = Get.find<UserController>();
QuizService _quizService;
final QuizService _quizService;
HomeController(this._quizService);
Rx<String> 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);
}

View File

@ -48,6 +48,7 @@ class HomeView extends GetView<HomeController> {
title: "Quiz Rekomendasi",
datas: controller.data.toList(),
itemOnTap: controller.onRecommendationTap,
allOnTap: controller.goToListingsQuizPage,
),
),
],

View File

@ -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<QuizService>()) {
Get.lazyPut<QuizService>(() => QuizService());
}
Get.lazyPut<ListingQuizController>(() => ListingQuizController(Get.find<QuizService>()));
}
}

View File

@ -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<QuizListingModel> quizzes = <QuizListingModel>[].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<void> _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<QuizListingModel>;
quizzes.assignAll(data);
hasMore = data.length == amountQuiz;
}
isLoading.value = false;
}
Future<void> 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<QuizListingModel>;
quizzes.addAll(data);
hasMore = data.length == amountQuiz;
}
isLoadingMore.value = false;
}
void goToDetailQuiz(String quizId) => Get.toNamed(AppRoutes.detailQuizPage, arguments: quizId);
}

View File

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

View File

@ -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();

View File

@ -32,6 +32,7 @@ class SearchView extends GetView<SearchQuizController> {
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<SearchQuizController> {
title: "Quiz Populer",
datas: controller.recommendationQData.toList(),
itemOnTap: controller.goToDetailPage,
allOnTap: controller.goToListingsQuizPage,
),
),
],