feat: search endpoint

This commit is contained in:
akhdanre 2025-05-02 00:16:45 +07:00
parent 14cd51c65b
commit cf9483834e
13 changed files with 245 additions and 79 deletions

View File

@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
import 'package:quiz_app/app/const/colors/app_colors.dart';
import 'package:quiz_app/data/models/quiz/quiz_listing_model.dart';
class QuizContainerComponent extends StatelessWidget {
const QuizContainerComponent({super.key});
final QuizListingModel data;
const QuizContainerComponent({required this.data, super.key});
@override
Widget build(BuildContext context) {
@ -40,8 +42,8 @@ class QuizContainerComponent extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Physics",
Text(
data.title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
@ -49,8 +51,8 @@ class QuizContainerComponent extends StatelessWidget {
),
),
const SizedBox(height: 4),
const Text(
"created by Akhdan Rabbani",
Text(
"created by ${data.authorName}",
style: TextStyle(
fontSize: 12,
color: Color(0xFF6B778C),

View File

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:quiz_app/component/quiz_container_component.dart';
import 'package:quiz_app/data/models/quiz/quiz_listing_model.dart';
class RecomendationComponent extends StatelessWidget {
final String title;
final List<QuizListingModel> datas;
const RecomendationComponent({
required this.title,
required this.datas,
super.key,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle(title),
const SizedBox(height: 10),
datas.isNotEmpty
// ? Text("yeay ${datas.length}")
? ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: datas.length,
itemBuilder: (context, index) => QuizContainerComponent(data: datas[index]),
)
: SizedBox.shrink()
],
);
}
// Widget _label() {
// return const Padding(
// padding: EdgeInsets.symmetric(horizontal: 16),
// child: Text(
// "Quiz Recommendation",
// style: TextStyle(
// fontSize: 18,
// fontWeight: FontWeight.bold,
// color: Color(0xFF172B4D), // dark text
// ),
// ),
// );
// }
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Text(
"Lihat semua",
style: TextStyle(fontSize: 14, color: Colors.blue.shade700),
),
],
),
);
}
}

View File

@ -8,6 +8,8 @@ class APIEndpoint {
static const String quiz = "/quiz";
static const String userQuiz = "/quiz/user";
static const String quizRecomendation = "/quiz/recomendation";
static const String quizSearch = "/quiz/search";
static const String historyQuiz = "/history";
}

View File

@ -0,0 +1,39 @@
class QuizListingModel {
final String quizId;
final String authorId;
final String authorName;
final String title;
final String description;
final String date;
QuizListingModel({
required this.quizId,
required this.authorId,
required this.authorName,
required this.title,
required this.description,
required this.date,
});
factory QuizListingModel.fromJson(Map<String, dynamic> json) {
return QuizListingModel(
quizId: json['quiz_id'] as String,
authorId: json['author_id'] as String,
authorName: json['author_name'] as String,
title: json['title'] as String,
description: json['description'] as String,
date: json['date'] as String,
);
}
Map<String, dynamic> toJson() {
return {
'quiz_id': quizId,
'author_id': authorId,
'author_name': authorName,
'title': title,
'description': description,
'date': date,
};
}
}

View File

@ -26,6 +26,7 @@ class HistoryService extends GetxService {
return parsedResponse.data;
} catch (e, stacktrace) {
logC.e(e, stackTrace: stacktrace);
return null;
}
}
}

View File

@ -5,6 +5,8 @@ import 'package:quiz_app/core/utils/logger.dart';
import 'package:quiz_app/data/models/base/base_model.dart';
import 'package:quiz_app/data/models/quiz/library_quiz_model.dart';
import 'package:quiz_app/data/models/quiz/question_create_request.dart';
import 'package:quiz_app/data/models/quiz/question_listings_model.dart';
import 'package:quiz_app/data/models/quiz/quiz_listing_model.dart';
import 'package:quiz_app/data/providers/dio_client.dart';
class QuizService extends GetxService {
@ -49,4 +51,44 @@ class QuizService extends GetxService {
return [];
}
}
Future<BaseResponseModel<List<QuizListingModel>>?> recomendationQuiz() async {
try {
final response = await _dio.get(APIEndpoint.quizRecomendation);
if (response.statusCode == 200) {
final parsedResponse = BaseResponseModel<List<QuizListingModel>>.fromJson(
response.data,
(data) => (data as List).map((e) => QuizListingModel.fromJson(e as Map<String, dynamic>)).toList(),
);
return parsedResponse;
} else {
logC.e("Failed to fetch recommendation quizzes. Status: ${response.statusCode}");
return null;
}
} catch (e) {
logC.e("Error fetching recommendation quizzes: $e");
return null;
}
}
Future<BaseResponseModel<List<QuizListingModel>>?> searchQuiz(String keyword) async {
try {
final response = await _dio.get("${APIEndpoint.quizSearch}?keyword=$keyword&page=1&limit=10");
if (response.statusCode == 200) {
final parsedResponse = BaseResponseModel<List<QuizListingModel>>.fromJson(
response.data,
(data) => (data as List).map((e) => QuizListingModel.fromJson(e as Map<String, dynamic>)).toList(),
);
return parsedResponse;
} else {
logC.e("Failed to fetch search quizzes. Status: ${response.statusCode}");
return null;
}
} catch (e) {
logC.e("Error fetching search quizzes: $e");
return null;
}
}
}

View File

@ -1,9 +1,11 @@
import 'package:get/get.dart';
import 'package:quiz_app/data/services/quiz_service.dart';
import 'package:quiz_app/feature/home/controller/home_controller.dart';
class HomeBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<HomeController>(() => HomeController());
Get.lazyPut<QuizService>(() => QuizService());
Get.lazyPut<HomeController>(() => HomeController(Get.find<QuizService>()));
}
}

View File

@ -1,12 +1,32 @@
import 'package:get/get.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';
import 'package:quiz_app/data/models/quiz/quiz_listing_model.dart';
import 'package:quiz_app/data/services/quiz_service.dart';
class HomeController extends GetxController {
final UserController _userController = Get.find<UserController>();
QuizService _quizService;
HomeController(this._quizService);
Rx<String> get userName => _userController.userName;
Rx<String?> get userImage => _userController.userImage;
RxList<QuizListingModel> data = <QuizListingModel>[].obs;
void goToQuizCreation() => Get.toNamed(AppRoutes.quizCreatePage);
@override
void onInit() {
_getRecomendationQuiz();
super.onInit();
}
void _getRecomendationQuiz() async {
BaseResponseModel? response = await _quizService.recomendationQuiz();
if (response != null) {
data.assignAll(response.data as List<QuizListingModel>);
}
}
}

View File

@ -1,36 +0,0 @@
import 'package:flutter/material.dart';
import 'package:quiz_app/component/quiz_container_component.dart';
class RecomendationComponent extends StatelessWidget {
const RecomendationComponent({super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_label(),
const SizedBox(height: 10),
QuizContainerComponent(),
const SizedBox(height: 10),
QuizContainerComponent(),
const SizedBox(height: 10),
QuizContainerComponent()
],
);
}
Widget _label() {
return const Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text(
"Quiz Recommendation",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF172B4D), // dark text
),
),
);
}
}

View File

@ -3,7 +3,7 @@ import 'package:get/get.dart';
import 'package:quiz_app/app/const/colors/app_colors.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/recomendation_component.dart';
import 'package:quiz_app/component/widget/recomendation_component.dart';
import 'package:quiz_app/feature/home/view/component/search_component.dart';
import 'package:quiz_app/feature/home/view/component/user_gretings.dart';
@ -43,7 +43,12 @@ class HomeView extends GetView<HomeController> {
children: [
SearchComponent(),
const SizedBox(height: 20),
RecomendationComponent(),
Obx(
() => RecomendationComponent(
title: "Quiz Rekomendasi",
datas: controller.data.toList(),
),
),
],
),
),

View File

@ -1,9 +1,13 @@
import 'package:get/get.dart';
import 'package:quiz_app/data/services/quiz_service.dart';
import 'package:quiz_app/feature/search/controller/search_controller.dart';
class SearchBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<SearchQuizController>(() => SearchQuizController());
if (!Get.isRegistered<QuizService>()) {
Get.lazyPut<QuizService>(() => QuizService());
}
Get.lazyPut<SearchQuizController>(() => SearchQuizController(Get.find<QuizService>()));
}
}

View File

@ -1,16 +1,47 @@
import 'package:flutter/material.dart';
import 'package:get/get.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 SearchQuizController extends GetxController {
final QuizService _quizService;
SearchQuizController(this._quizService);
final searchController = TextEditingController();
final searchText = ''.obs;
RxList<QuizListingModel> recommendationQData = <QuizListingModel>[].obs;
RxList<QuizListingModel> searchQData = <QuizListingModel>[].obs;
@override
void onInit() {
getRecomendation();
super.onInit();
searchController.addListener(() {
searchText.value = searchController.text;
});
debounce<String>(
searchText,
(value) => getSearchData(value),
time: Duration(seconds: 2),
);
}
void getRecomendation() async {
BaseResponseModel? response = await _quizService.recomendationQuiz();
if (response != null) {
recommendationQData.assignAll(response.data as List<QuizListingModel>);
}
}
void getSearchData(String keyword) async {
searchQData.clear();
BaseResponseModel? response = await _quizService.searchQuiz(keyword);
if (response != null) {
searchQData.assignAll(response.data);
}
}
@override

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:quiz_app/component/quiz_container_component.dart';
import 'package:quiz_app/component/widget/recomendation_component.dart';
import 'package:quiz_app/feature/search/controller/search_controller.dart';
class SearchView extends GetView<SearchQuizController> {
@ -15,7 +16,6 @@ class SearchView extends GetView<SearchQuizController> {
padding: const EdgeInsets.all(16),
child: Obx(() {
final isSearching = controller.searchText.isNotEmpty;
return ListView(
children: [
_buildSearchBar(),
@ -23,20 +23,24 @@ class SearchView extends GetView<SearchQuizController> {
if (isSearching) ...[
_buildCategoryFilter(),
const SizedBox(height: 20),
const Text(
"Result",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
const SizedBox(height: 10),
_buildQuizList(count: 5),
...controller.searchQData.map(
(e) => QuizContainerComponent(data: e),
)
] else ...[
_buildSectionTitle("Rekomendasi Quiz"),
const SizedBox(height: 10),
_buildQuizList(),
Obx(
() => RecomendationComponent(
title: "Quiz Rekomendasi",
datas: controller.recommendationQData.toList(),
),
),
const SizedBox(height: 30),
_buildSectionTitle("Quiz Populer"),
const SizedBox(height: 10),
_buildQuizList(),
Obx(
() => RecomendationComponent(
title: "Quiz Populer",
datas: controller.recommendationQData.toList(),
),
),
],
],
);
@ -87,25 +91,9 @@ class SearchView extends GetView<SearchQuizController> {
);
}
Widget _buildSectionTitle(String title) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
Text(
"Lihat semua",
style: TextStyle(fontSize: 14, color: Colors.blue.shade700),
),
],
);
}
Widget _buildQuizList({int count = 3}) {
return Column(
children: List.generate(count, (_) => const QuizContainerComponent()),
);
}
// Widget _buildQuizList({int count = 3}) {
// return Column(
// children: List.generate(count, (_) => const QuizContainerComponent()),
// );
// }
}