Initial commit: Penyerahan final Source code Tugas Akhir
This commit is contained in:
430
lib/screens/detail_course/components/ulasan.dart
Normal file
430
lib/screens/detail_course/components/ulasan.dart
Normal file
@ -0,0 +1,430 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:initial_folder/helper/validator.dart';
|
||||
import 'package:initial_folder/models/detail_rating_course_model.dart';
|
||||
import 'package:initial_folder/providers/detail_rating_course_provider.dart';
|
||||
import 'package:initial_folder/screens/detail_course/components/detail_list_ulasan.dart';
|
||||
import 'package:initial_folder/size_config.dart';
|
||||
import 'package:initial_folder/theme.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class Ulasan extends StatefulWidget {
|
||||
const Ulasan({
|
||||
Key? key,
|
||||
required this.id,
|
||||
}) : super(key: key);
|
||||
final String id;
|
||||
|
||||
@override
|
||||
_UlasanState createState() => _UlasanState();
|
||||
}
|
||||
|
||||
class _UlasanState extends State<Ulasan> {
|
||||
int _currentPage = 1;
|
||||
int _totalPages = 1;
|
||||
List<DataReview> _filteredReviews = []; // Menyimpan review yang sudah difilter
|
||||
|
||||
// Fungsi untuk membatasi ulasan per halaman
|
||||
List<DataReview> _getPaginatedReviews(List<DataReview> reviews) {
|
||||
int startIndex = (_currentPage - 1) * 5;
|
||||
int endIndex = startIndex + 5;
|
||||
return reviews.sublist(startIndex, endIndex.clamp(0, reviews.length));
|
||||
}
|
||||
|
||||
void _filterReviews(int rating, List<DataReview> allReviews) {
|
||||
setState(() {
|
||||
_filteredReviews = allReviews.where((review) => double.parse(review.rating ?? '0') == rating).toList();
|
||||
_totalPages = (_filteredReviews.length / 5).ceil();
|
||||
_currentPage = 1; // Reset ke halaman pertama saat filter diubah
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final listChoices = <ItemChoice>[
|
||||
ItemChoice(0, 0, 'Semua '),
|
||||
ItemChoice(5, 1, '5'),
|
||||
ItemChoice(4, 1, '4'),
|
||||
ItemChoice(3, 1, '3'),
|
||||
ItemChoice(2, 1, '2'),
|
||||
ItemChoice(1, 1, '1'),
|
||||
];
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
left: getProportionateScreenWidth(10),
|
||||
right: getProportionateScreenWidth(10),
|
||||
),
|
||||
child: Consumer<DetailRatingCourseProvider>(
|
||||
builder: (context, state, _) {
|
||||
if (state.state == ResultState.Loading) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: primaryColor,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (state.state == ResultState.HasData) {
|
||||
var ulasan = state.result;
|
||||
|
||||
// Hitung total halaman berdasarkan jumlah ulasan jika semua ulasan ditampilkan
|
||||
if (state.currentIndex == 0) {
|
||||
_filteredReviews = ulasan!.dataReview;
|
||||
_totalPages = (_filteredReviews.length / 5).ceil();
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: getProportionateScreenHeight(20)),
|
||||
Text(
|
||||
'Ulasan Kursus',
|
||||
style: thirdTextStyle.copyWith(
|
||||
fontWeight: semiBold,
|
||||
fontSize: getProportionateScreenWidth(15),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
ulasan!.data.avgRating is List<dynamic>
|
||||
? '0'
|
||||
: double.parse(ulasan.data.avgRating)
|
||||
.toStringAsFixed(1)
|
||||
.replaceAll('.', ','),
|
||||
style: thirdTextStyle.copyWith(
|
||||
fontSize: getProportionateScreenWidth(29),
|
||||
),
|
||||
),
|
||||
SizedBox(width: getProportionateScreenWidth(8)),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: getProportionateScreenHeight(2)),
|
||||
RatingBarIndicator(
|
||||
itemSize: getProportionateScreenWidth(10),
|
||||
rating: ulasan.data.avgRating is List<dynamic>
|
||||
? 5.0
|
||||
: double.parse(ulasan.data.avgRating),
|
||||
direction: Axis.horizontal,
|
||||
itemCount: 5,
|
||||
itemBuilder: (context, _) => FaIcon(
|
||||
FontAwesomeIcons.solidStar,
|
||||
color: thirteenColor,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: getProportionateScreenHeight(2)),
|
||||
Text(
|
||||
'(${ulasan.dataReview.length} Ulasan)',
|
||||
style: thirdTextStyle.copyWith(
|
||||
fontSize: getProportionateScreenWidth(12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: getProportionateScreenHeight(11)),
|
||||
VerticalRatingBar(
|
||||
lebar: ulasan.data.precentageRating.rating5.toString(),
|
||||
text: '5.0',
|
||||
total: (ulasan.data.precentageRating.rating5.runtimeType == String)
|
||||
? '${persentaseUlasan(ulasan.data.precentageRating.rating5)}'
|
||||
: '${ulasan.data.precentageRating.rating5}'),
|
||||
VerticalRatingBar(
|
||||
lebar: ulasan.data.precentageRating.rating4.toString(),
|
||||
text: '4.0',
|
||||
total: (ulasan.data.precentageRating.rating4.runtimeType == String)
|
||||
? '${persentaseUlasan(ulasan.data.precentageRating.rating4)}'
|
||||
: '${ulasan.data.precentageRating.rating4}'),
|
||||
VerticalRatingBar(
|
||||
lebar: ulasan.data.precentageRating.rating3.toString(),
|
||||
text: '3.0',
|
||||
total: (ulasan.data.precentageRating.rating3.runtimeType == String)
|
||||
? '${persentaseUlasan(ulasan.data.precentageRating.rating3)}'
|
||||
: '${ulasan.data.precentageRating.rating3}'),
|
||||
VerticalRatingBar(
|
||||
lebar: ulasan.data.precentageRating.rating2.toString(),
|
||||
text: '2.0',
|
||||
total: (ulasan.data.precentageRating.rating2.runtimeType == String)
|
||||
? '${persentaseUlasan(ulasan.data.precentageRating.rating2)}'
|
||||
: '${ulasan.data.precentageRating.rating2}'),
|
||||
VerticalRatingBar(
|
||||
lebar: ulasan.data.precentageRating.rating1.toString(),
|
||||
text: '1.0',
|
||||
total: (ulasan.data.precentageRating.rating1.runtimeType == String)
|
||||
? '${persentaseUlasan(ulasan.data.precentageRating.rating1)}'
|
||||
: '${ulasan.data.precentageRating.rating1}'),
|
||||
],
|
||||
);
|
||||
}
|
||||
return Text(
|
||||
'Terjadi Kesalan ',
|
||||
style: primaryTextStyle,
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: getProportionateScreenHeight(11)),
|
||||
Consumer<DetailRatingCourseProvider>(
|
||||
builder: (context, state, _) {
|
||||
if (state.state == ResultState.HasData) {
|
||||
var ulasan = state.result;
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(height: getProportionateScreenHeight(8)),
|
||||
Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 3,
|
||||
children: listChoices
|
||||
.map(
|
||||
(e) => InkWell(
|
||||
onTap: () {
|
||||
state.currentIndex = e.id;
|
||||
if (state.currentIndex == 0) {
|
||||
// Reset ke semua review jika memilih filter "Semua"
|
||||
Provider.of<DetailRatingCourseProvider>(context, listen: false)
|
||||
.getDetailCourse();
|
||||
_filteredReviews = ulasan!.dataReview;
|
||||
_totalPages = (_filteredReviews.length / 5).ceil();
|
||||
_currentPage = 1;
|
||||
} else {
|
||||
// Filter sesuai dengan rating yang dipilih
|
||||
_filterReviews(e.id, ulasan!.dataReview);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: state.currentIndex == e.id
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.primaryContainer,
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.brightness ==
|
||||
Brightness.dark
|
||||
? seventeenColor
|
||||
: secondaryColor,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: baruTexthitam.withOpacity(0.2),
|
||||
blurRadius: 2,
|
||||
spreadRadius: 1,
|
||||
offset: Offset(0, 2),
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: getProportionateScreenWidth(5),
|
||||
vertical: getProportionateScreenHeight(5),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
(e.icon == 0)
|
||||
? Text('')
|
||||
: FaIcon(
|
||||
FontAwesomeIcons.solidStar,
|
||||
color: thirteenColor,
|
||||
size: getProportionateScreenWidth(11),
|
||||
),
|
||||
SizedBox(width: getProportionateScreenWidth(3)),
|
||||
Text(
|
||||
e.label,
|
||||
style: thirdTextStyle.copyWith(
|
||||
color: state.currentIndex == e.id
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.background
|
||||
: e.label == "Semua "
|
||||
? primaryColor
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground,
|
||||
fontSize: getProportionateScreenWidth(12),
|
||||
fontWeight: reguler,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
SizedBox(height: getProportionateScreenHeight(17)),
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
left: getProportionateScreenWidth(15),
|
||||
right: getProportionateScreenWidth(10),
|
||||
),
|
||||
child: (_filteredReviews.isEmpty)
|
||||
? SizedBox(height: getProportionateScreenHeight(15))
|
||||
: Column(
|
||||
children: _getPaginatedReviews(_filteredReviews)
|
||||
.map(
|
||||
(e) => DetailListUlasan(
|
||||
review: e.review ?? '',
|
||||
date: e.date ?? '-',
|
||||
name: e.name ?? '',
|
||||
starRating: double.parse(e.rating ?? '5'),
|
||||
),
|
||||
)
|
||||
.toList()),
|
||||
),
|
||||
// Navigasi Pagination
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (_filteredReviews.isEmpty)
|
||||
Text(
|
||||
'Belum ada rating untuk kategori bintang ini',
|
||||
style: thirdTextStyle.copyWith(
|
||||
fontSize: getProportionateScreenWidth(12),
|
||||
),
|
||||
)
|
||||
else ...[
|
||||
if (_currentPage > 1)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_currentPage--;
|
||||
});
|
||||
},
|
||||
icon: Icon(Icons.arrow_back),
|
||||
),
|
||||
Text('$_currentPage of $_totalPages'),
|
||||
if (_currentPage < _totalPages)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_currentPage++;
|
||||
});
|
||||
},
|
||||
icon: Icon(Icons.arrow_forward),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return Text('');
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
height: 30,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VerticalRatingBar extends StatelessWidget {
|
||||
const VerticalRatingBar({
|
||||
Key? key,
|
||||
required this.text,
|
||||
this.lebar = '0',
|
||||
required this.total,
|
||||
}) : super(key: key);
|
||||
final String text;
|
||||
final String total;
|
||||
final String lebar;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(bottom: getProportionateScreenWidth(8)),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
FaIcon(
|
||||
FontAwesomeIcons.solidStar,
|
||||
color: thirteenColor,
|
||||
size: getProportionateScreenWidth(11),
|
||||
),
|
||||
SizedBox(width: getProportionateScreenWidth(4)),
|
||||
Text(
|
||||
text,
|
||||
style: thirdTextStyle.copyWith(
|
||||
fontSize: getProportionateScreenWidth(10),
|
||||
),
|
||||
),
|
||||
SizedBox(width: getProportionateScreenWidth(6)),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
height: getProportionateScreenWidth(4),
|
||||
decoration: BoxDecoration(
|
||||
color: fourthColor,
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
Container(
|
||||
width: (SizeConfig.screenWidth -
|
||||
getProportionateScreenWidth(110)) *
|
||||
double.parse(persentaseUlasan(lebar)
|
||||
.replaceAll(',', '.')) /
|
||||
100,
|
||||
height: getProportionateScreenWidth(4),
|
||||
decoration: BoxDecoration(
|
||||
color: thirteenColor,
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(width: getProportionateScreenWidth(8)),
|
||||
Container(
|
||||
width: getProportionateScreenWidth(30),
|
||||
child: Text(
|
||||
total,
|
||||
style: thirdTextStyle.copyWith(
|
||||
fontSize: getProportionateScreenWidth(10),
|
||||
),
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ItemChoice {
|
||||
final int id;
|
||||
final int icon;
|
||||
final String label;
|
||||
|
||||
ItemChoice(this.id, this.icon, this.label);
|
||||
}
|
Reference in New Issue
Block a user