feat: search endpoint
This commit is contained in:
parent
14cd51c65b
commit
cf9483834e
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ class HistoryService extends GetxService {
|
|||
return parsedResponse.data;
|
||||
} catch (e, stacktrace) {
|
||||
logC.e(e, stackTrace: stacktrace);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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>()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()),
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue