Initial commit: Penyerahan final Source code Tugas Akhir

This commit is contained in:
ferdiakhh
2025-07-10 19:15:14 +07:00
commit e1f2206b8a
687 changed files with 80132 additions and 0 deletions

View File

@ -0,0 +1,134 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:initial_folder/models/section_model.dart';
import 'package:initial_folder/providers/selected_title_provider.dart';
import 'package:initial_folder/services/announcement_service.dart';
import 'package:initial_folder/size_config.dart';
import 'package:initial_folder/theme.dart';
import 'package:initial_folder/widgets/announcement_user_page.dart';
import 'package:provider/provider.dart';
class Announcement extends StatefulWidget {
const Announcement({
Key? key,
required this.id,
required this.lesonOper,
required this.sectionOper,
required this.lessonMapIdoper,
required this.dataLessonOper,
}) : super(key: key);
final id;
final lesonOper;
final sectionOper;
final lessonMapIdoper;
final List<DataLesson> dataLessonOper;
@override
State<Announcement> createState() => _AnnouncementState();
String? showSummary(String? selectedTitle) {
String? selectedSummary = dataLessonOper
.firstWhere((data) => data.title == selectedTitle,
orElse: () => DataLesson(
summary: ' Tidak ada ringkasan'))
.summary;
return selectedSummary;
}
}
class _AnnouncementState extends State<Announcement> {
double value = 0;
final _controller = TextEditingController();
@override
void initState() {
super.initState();
AnnouncementService().getAnnouncement(widget.id);
}
@override
Widget build(BuildContext context) {
final selectedTitle =
Provider.of<SelectedTitleProvider>(context).selectedTitle;
return SingleChildScrollView(
child: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(
left: getProportionateScreenWidth(16),
right: getProportionateScreenWidth(16),
top: getProportionateScreenWidth(16),
),
child: Text(
'Ringkasan Kursus',
style: thirdTextStyle.copyWith(
fontWeight: semiBold,
letterSpacing: 1,
fontSize: getProportionateScreenWidth(14),
),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(
left: getProportionateScreenWidth(16),
right: getProportionateScreenWidth(16),
top: getProportionateScreenWidth(10),
),
child: Column(
children: [
if (widget.showSummary(selectedTitle) != "")
Text(
widget.showSummary(selectedTitle)!,
style: thirdTextStyle,
)
else
Center(
child: Text(
'Tidak Ada Ringkasan',
style: thirdTextStyle,
),
),
],
),
),
],
),
Container(
margin: EdgeInsets.only(
left: getProportionateScreenWidth(16),
right: getProportionateScreenWidth(16),
top: getProportionateScreenWidth(16),
),
child: Text(
'Pengumuman',
style: thirdTextStyle.copyWith(
fontWeight: semiBold,
letterSpacing: 1,
fontSize: getProportionateScreenWidth(14),
),
),
),
SizedBox(height: getProportionateScreenHeight(14)),
AnnouncementUserPage(idCourse: widget.id),
],
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

View File

@ -0,0 +1,710 @@
import 'dart:convert';
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:initial_folder/models/detail_course_model.dart';
import 'package:initial_folder/providers/detail_course_provider.dart';
import 'package:initial_folder/providers/section_lesson_provider.dart';
import 'package:initial_folder/providers/theme_provider.dart';
import 'package:initial_folder/screens/detail_course/components/instruktur.dart';
import 'package:initial_folder/screens/detail_course/components/kursus_include_item.dart';
import 'package:provider/provider.dart';
import '../../../size_config.dart';
import '../../../theme.dart';
class DetailPlayCourse extends StatefulWidget {
const DetailPlayCourse({super.key});
@override
State<DetailPlayCourse> createState() => _DetailPlayCourseState();
}
class _DetailPlayCourseState extends State<DetailPlayCourse> {
bool isExpanded = false;
bool isExpanded2 = false;
@override
Widget build(BuildContext context) {
final Brightness brightnessValue =
MediaQuery.of(context).platformBrightness;
bool isDarkMode = brightnessValue == Brightness.dark;
SectionLessonProvider sectionLessonProvider =
Provider.of<SectionLessonProvider>(context);
final themeProvider = Provider.of<ThemeProvider>(context);
Widget kemampuanDiraih(String title) {
return Container(
margin: EdgeInsets.only(bottom: 10),
child: Row(
children: [
Icon(Icons.check,
size: getProportionateScreenHeight(13), color: themeProvider.themeData == ThemeClass.darkmode
?primaryColor : primaryColorligtmode,),
SizedBox(
width: 10,
),
Expanded(
child: Align(
alignment: Alignment.topLeft,
child: Text(
title,
style: thirdTextStyle.copyWith(
fontWeight: light,
color: Theme.of(context).colorScheme.onBackground,
fontSize: getProportionateScreenWidth(12),
),
),
),
),
],
),
);
}
Widget persyaratan(String title) {
return Container(
margin: EdgeInsets.only(bottom: 10),
child: Row(
children: [
title.isNotEmpty
? Icon(
Icons.brightness_1,
size: getProportionateScreenHeight(13),
color: themeProvider.themeData == ThemeClass.darkmode
?primaryColor : primaryColorligtmode,
)
: SizedBox.shrink(),
SizedBox(
width: 10,
),
Expanded(
child: Align(
alignment: Alignment.topLeft,
child: Text(
title,
style: thirdTextStyle.copyWith(
fontWeight: light,
color: Theme.of(context).colorScheme.onBackground,
fontSize: getProportionateScreenWidth(12),
),
),
),
),
],
),
);
}
return Consumer<DetailCourseProvider>(builder: (context, state, _) {
if (state.state == ResultState.Loading) {
return Center(
child: CircularProgressIndicator(
color: primaryColor,
strokeWidth: 2,
),
);
} else if (state.state == ResultState.HasData) {
var detailCourse = state.result!.data[0][0];
print(detailCourse.outcome);
print(detailCourse.requirement);
List requirement;
List outcomes = [];
if (detailCourse.outcome != '') {
outcomes = jsonDecode(detailCourse.outcome ?? 'gagal');
}
try {
requirement = jsonDecode(detailCourse.requirement ?? '[]');
} catch (e) {
requirement = [];
}
final bool hasDescription = detailCourse.description!.isNotEmpty;
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(left: 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: getProportionateScreenHeight(9)),
Container(
margin: EdgeInsets.only(
left: getProportionateScreenWidth(10),
top: getProportionateScreenHeight(10),
),
child: Text(
'Kursus Ini Sudah Termasuk',
style: thirdTextStyle.copyWith(
fontWeight: semiBold,
fontSize: getProportionateScreenWidth(14),
),
),
),
SizedBox(height: 10),
KursusIncludeItems(
svg: 'assets/icons/clock.svg',
text:
'${detailCourse.totalDuration} video pembelajaran'),
KursusIncludeItems(
svg: 'assets/icons/lesson.svg',
text: '${detailCourse.totalLesson} pelajaran'),
KursusIncludeItems(
svg: 'assets/icons/calendar.svg',
text: 'Akses full seumur hidup'),
KursusIncludeItems(
svg: 'assets/icons/phone.svg',
text: ' Akses di ponsel dan TV '),
],
),
),
SizedBox(
height: 5,
),
ExpandableNotifier(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 12,
),
Container(
margin: EdgeInsets.only(
left: getProportionateScreenWidth(10),
right: getProportionateScreenWidth(106),
bottom: getProportionateScreenWidth(8),
),
child: Text('Kemampuan Yang Akan Diraih',
style: primaryTextStyle.copyWith(
fontWeight: semiBold,
letterSpacing: 1,
fontSize: getProportionateScreenWidth(14))),
),
Expandable(
collapsed: ExpandableButton(
child: Container(
margin: EdgeInsets.only(
left: getProportionateScreenWidth(10)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
outcomes.isEmpty
? Row(
children: [
Expanded(
child: Align(
alignment: Alignment.topLeft,
child: Text(
'',
),
),
),
],
)
: Column(children: [
...outcomes
.map((e) => kemampuanDiraih(e))
.take(2)
]),
if (outcomes.isNotEmpty && outcomes.length >= 3)
SizedBox(height: 8),
if (outcomes.isNotEmpty && outcomes.length >= 3)
Container(
child: Text(
"Tampilkan Lebih Banyak",
style: primaryTextStyle.copyWith(
fontWeight: semiBold,
color: Theme.of(context).brightness ==
Brightness.dark
? baruTextutih
: fourthColor,
fontSize:
getProportionateScreenWidth(12),
),
),
),
],
),
),
),
expanded: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ExpandableButton(
child: Container(
margin: EdgeInsets.only(
left: getProportionateScreenWidth(10)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
outcomes.isEmpty
? Row(
children: [
Expanded(
child: Align(
alignment: Alignment.topLeft,
child: Text(
'',
),
),
),
],
)
: Column(
children: outcomes
.map((e) => kemampuanDiraih(e))
.toList(),
),
if (outcomes.isNotEmpty &&
outcomes.length >= 3)
SizedBox(
height: 16,
),
if (outcomes.isNotEmpty &&
outcomes.length >= 3)
Container(
child: Text(
"Tampilkan Lebih Sedikit",
style: primaryTextStyle.copyWith(
fontWeight: semiBold,
color:
Theme.of(context).brightness ==
Brightness.dark
? baruTextutih
: fourthColor,
fontSize:
getProportionateScreenWidth(12),
),
),
)
],
),
),
)
],
),
),
],
),
),
ExpandableNotifier(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 12,
),
Container(
margin: EdgeInsets.only(
left: getProportionateScreenWidth(10),
right: getProportionateScreenWidth(106),
bottom: getProportionateScreenWidth(8),
),
child: Text('Persyaratan',
style: primaryTextStyle.copyWith(
fontWeight: semiBold,
letterSpacing: 1,
fontSize: getProportionateScreenWidth(14))),
),
Expandable(
collapsed: ExpandableButton(
child: Container(
margin: EdgeInsets.only(
left: getProportionateScreenWidth(10)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
requirement.isEmpty
? Row(
children: [
Expanded(
child: Align(
alignment: Alignment.topLeft,
child: Text(
'',
),
),
),
],
)
: Column(children: [
...requirement
.map((e) => persyaratan(e))
.take(2)
]),
if (requirement.isNotEmpty &&
requirement.length >= 3)
SizedBox(height: 8),
if (requirement.isNotEmpty &&
requirement.length >= 3)
Container(
child: Text(
"Tampilkan Lebih Banyak",
style: primaryTextStyle.copyWith(
fontWeight: semiBold,
color: Theme.of(context).brightness ==
Brightness.dark
? baruTextutih
: fourthColor,
fontSize:
getProportionateScreenWidth(12),
),
),
),
],
),
),
),
expanded: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ExpandableButton(
child: Container(
margin: EdgeInsets.only(
left: getProportionateScreenWidth(10)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
requirement.isEmpty
? Row(
children: [
Expanded(
child: Align(
alignment: Alignment.topLeft,
child: Text(
'',
),
),
),
],
)
: Column(
children: requirement
.map((e) => persyaratan(e))
.toList(),
),
if (requirement.isNotEmpty &&
requirement.length >= 3)
SizedBox(
height: 16,
),
if (requirement.isNotEmpty &&
requirement.length >= 3)
Container(
child: Text(
"Tampilkan Lebih Sedikit",
style: primaryTextStyle.copyWith(
fontWeight: semiBold,
color:
Theme.of(context).brightness ==
Brightness.dark
? baruTextutih
: fourthColor,
fontSize:
getProportionateScreenWidth(12),
),
),
)
],
),
),
)
],
),
),
],
),
),
SizedBox(height: getProportionateScreenHeight(5)),
SizedBox(
height: 14,
),
Align(
alignment: Alignment.topLeft,
child: Container(
margin:
EdgeInsets.only(left: getProportionateScreenWidth(10)),
child: Text(
'Deskripsi',
style: primaryTextStyle.copyWith(
fontWeight: semiBold,
letterSpacing: 1,
fontSize: getProportionateScreenWidth(14),
),
),
),
),
AnimatedSize(
curve: Curves.fastOutSlowIn,
duration: const Duration(milliseconds: 300),
child: detailCourse.description!.isNotEmpty
? Container(
height: sectionLessonProvider.isExpanded ? 55 : null,
margin: EdgeInsets.symmetric(
horizontal: getProportionateScreenWidth(5)),
child: Html(
data: detailCourse.description!
.split('\n')
.take(2)
.join('\n'),
style: {
"body": Style(
fontSize:
FontSize(getProportionateScreenWidth(12)),
fontWeight: FontWeight.bold,
fontFamily: 'Poppins',
textAlign: TextAlign.justify,
color: Theme.of(context).brightness ==
Brightness.dark
? Colors.grey[100]
: Colors.grey[600],
),
},
),
)
: SizedBox(),
),
Padding(
padding:
EdgeInsets.only(left: getProportionateScreenWidth(3)),
child: detailCourse.description!.length > 140
? TextButton(
style: TextButton.styleFrom(
foregroundColor: primaryColor),
onPressed: () => sectionLessonProvider.expanded(),
child: sectionLessonProvider.isExpanded
? Text(
'Tampilkan Lebih Banyak',
style: primaryTextStyle.copyWith(
fontWeight: semiBold,
color: Theme.of(context).brightness ==
Brightness.dark
? baruTextutih
: fourthColor,
fontSize: getProportionateScreenWidth(12),
),
textAlign: TextAlign.left,
)
: Text(
'Tampilkan Lebih Sedikit',
style: primaryTextStyle.copyWith(
fontWeight: semiBold,
color: Theme.of(context).brightness ==
Brightness.dark
? baruTextutih
: fourthColor,
fontSize: getProportionateScreenWidth(12),
),
),
)
: SizedBox(),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (hasDescription &&
detailCourse.description!.length > 120)
SizedBox(height: getProportionateScreenHeight(10)),
Padding(
padding: EdgeInsets.symmetric(
horizontal: getProportionateScreenWidth(10)),
child: Row(
children: [
CircleAvatar(
radius: 20,
backgroundColor: primaryColor,
backgroundImage: detailCourse.fotoProfile == null
? AssetImage("assets/images/Profile Image.png")
: NetworkImage(detailCourse.fotoProfile!)
as ImageProvider,
),
SizedBox(width: getProportionateScreenWidth(10)),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
detailCourse.instructor ?? '',
style: thirdTextStyle.copyWith(
fontSize: getProportionateScreenWidth(12),
fontWeight: bold,
),
),
Row(
children: [
Text(
'Instructor, ',
style: thirdTextStyle.copyWith(
fontSize: getProportionateScreenWidth(10),
fontWeight: light,
),
),
Text(
'${detailCourse.totalStudents ?? '0'} Murid, ',
style: thirdTextStyle.copyWith(
fontSize: getProportionateScreenWidth(10),
fontWeight: light,
),
),
Text(
'${detailCourse.totalLesson ?? ''} Kursus',
style: thirdTextStyle.copyWith(
fontSize: getProportionateScreenWidth(10),
fontWeight: light,
),
)
],
),
],
),
],
),
),
// SizedBox(height: 2),
// widget.headline != null
// ? Container(
// margin: EdgeInsets.only(left: 10),
// child: Text(
// widget.headline!,
// style: TextStyle(fontSize: 13),
// ),
// )
// : const SizedBox.shrink(),
// Container(
// margin: EdgeInsets.only(left: 10),
// child: Row(
// children: [
// RatingBarIndicator(
// itemSize: 11,
// rating: double.parse(widget.rating ?? '0'),
// direction: Axis.horizontal,
// itemCount: 5,
// itemBuilder: (context, _) => const FaIcon(
// FontAwesomeIcons.solidStar,
// color: Colors.amber,
// ),
// ),
// SizedBox(width: 4),
// Text(
// double.parse(widget.rating ?? '0').toString(),
// style: TextStyle(fontSize: 10),
// ),
// SizedBox(width: 4),
// Text(
// '(${widget.review ?? '0'})',
// style: TextStyle(fontSize: 10),
// ),
// ],
// ),
// ),
if (detailCourse.bio == null || detailCourse.bio!.isEmpty)
Padding(
padding: EdgeInsets.only(
left: getProportionateScreenHeight(10),
right: getProportionateScreenHeight(10),
bottom: getProportionateScreenHeight(10),
),
child: const SizedBox(height: 10)
)
else
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(
left: getProportionateScreenWidth(5),
right: getProportionateScreenWidth(10),
),
child: isExpanded2
? Html(
data: detailCourse.bio,
style: {
"body": Style(
fontSize: FontSize(
getProportionateScreenWidth(12)),
fontWeight: light,
fontFamily: 'Poppins',
),
},
)
: Html(
data: detailCourse.bio != null &&
detailCourse.bio!.length > 100
? detailCourse.bio!.substring(0, 200)
: detailCourse.bio!,
style: {
"body": Style(
fontSize: FontSize(
getProportionateScreenWidth(12)),
fontWeight: reguler,
fontFamily: 'Poppins',
),
},
),
),
if (detailCourse.bio!.isNotEmpty &&
detailCourse.bio!.length > 100)
Padding(
padding: EdgeInsets.only(
left: getProportionateScreenWidth(12),
bottom: getProportionateScreenHeight(10),
),
child: GestureDetector(
onTap: () {
setState(() {
isExpanded2 = !isExpanded2;
print('asdasd');
});
},
child: Text(
isExpanded2
? 'Tampilkan Lebih Sedikit'
: 'Tampilkan Lebih Banyak',
style: thirdTextStyle.copyWith(
fontWeight: semiBold,
color: Theme.of(context).brightness ==
Brightness.dark
? baruTextutih
: fourthColor,
fontSize: getProportionateScreenWidth(12),
),
),
),
),
],
),
],
),
// Instruktur(
// id: detailCourse.instructorId ?? '1',
// bio: detailCourse.bio,
// instructor: detailCourse.instructor,
// rating: detailCourse.rating[0].avgRating.toString(),
// review: detailCourse.rating[0].totalReview ?? '',
// fotoProfile: detailCourse.fotoProfile,
// totalLesson: detailCourse.totalLesson,
// totalStudent: detailCourse.totalStudents,
// headline: detailCourse.headlineInstructor,
// ),
],
),
),
);
} else if (state.state == ResultState.Error) {
return Center(
child: Text('Terjadi Kesalahan'),
);
} else {
return Center(child: Text('error'));
}
});
}
}

View File

@ -0,0 +1,400 @@
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:initial_folder/models/qna_model.dart';
import 'package:initial_folder/providers/like_or_unlike_provider.dart';
import 'package:initial_folder/providers/posting_qna_reply_provider.dart';
import 'package:initial_folder/providers/qna_provider.dart';
import 'package:initial_folder/size_config.dart';
import 'package:initial_folder/theme.dart';
import 'package:initial_folder/widgets/reply_qna_user_page.dart';
import 'package:provider/provider.dart';
import '../../../get_it.dart';
import '../../../models/comment_qna_model.dart';
// import '../../../widgets/qna_user.dart';
final scaffoldKey = GlobalKey<ScaffoldState>();
class DetailQuestAndAnswer extends StatefulWidget {
const DetailQuestAndAnswer({
Key? key,
required this.id,
required this.qnaDataModel,
required this.index,
required this.userId,
}) : super(key: key);
final QnaDataModel qnaDataModel;
final id;
final int index;
final int userId;
@override
State<DetailQuestAndAnswer> createState() => _DetailQuestAndAnswerState();
}
class _DetailQuestAndAnswerState extends State<DetailQuestAndAnswer> {
final _controller = TextEditingController();
final provider = qnaGetIt<QnaProvider>();
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
void onReplyDeleted(String idRep) {
setState(() {
// Temukan indeks balasan yang akan dihapus
final int indexToRemove = widget.qnaDataModel.comment.indexWhere((comment) => comment.idRep == idRep);
// Jika balasan ditemukan, hapus dan perbarui jumlah komentar
if (indexToRemove != -1) {
widget.qnaDataModel.comment.removeAt(indexToRemove);
widget.qnaDataModel.countComment = (widget.qnaDataModel.countComment ?? 1) - 1;
}
});
}
@override
Widget build(BuildContext context) {
PostingQnaReplyProvider postingQnaReplyProvider = Provider.of<PostingQnaReplyProvider>(context);
LikeOrUnlikeProvider _likeOrUnlikeProvider = Provider.of<LikeOrUnlikeProvider>(context);
likeOrUnlikes(int idQna) async {
final provider = qnaGetIt<QnaProvider>();
if (await _likeOrUnlikeProvider.likeOrUnlike(idQna)) {
provider.getQna(widget.id);
print("Respon Baik");
}
}
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text(
'Pertanyaan',
style: primaryTextStyle.copyWith(
fontWeight: semiBold,
fontSize: getProportionateScreenWidth(16),
letterSpacing: 0.2,
),
),
),
body: GestureDetector(
child: Stack(
children: [
ListView(
children: [
// Tampilan pertanyaan utama
Padding(
padding: EdgeInsets.all(getProportionateScreenWidth(0)),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15),
),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Padding(
padding: EdgeInsets.all(getProportionateScreenWidth(16)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(
backgroundColor: primaryColor,
backgroundImage: widget.qnaDataModel.fotoProfile == null
? AssetImage("assets/images/Profile Image.png")
: NetworkImage(widget.qnaDataModel.fotoProfile ?? '') as ImageProvider,
),
SizedBox(width: getProportionateScreenWidth(8)),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.qnaDataModel.username ?? '',
style: thirdTextStyle.copyWith(
fontSize: getProportionateScreenWidth(13),
color: Theme.of(context).colorScheme.onBackground,
),
),
Text(
widget.qnaDataModel.date ?? '',
style: primaryTextStyle.copyWith(
fontSize: getProportionateScreenWidth(12),
color: Theme.of(context).colorScheme.onBackground,
),
),
],
),
],
),
SizedBox(height: getProportionateScreenHeight(10)),
if (widget.qnaDataModel.title != '')
Text(
widget.qnaDataModel.title!,
style: secondaryTextStyle.copyWith(
fontSize: getProportionateScreenWidth(16),
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onBackground,
),
),
SizedBox(height: getProportionateScreenHeight(10)),
Html(
data: widget.qnaDataModel.quest ?? 'Pertanyaan tidak tersedia',
style: {
"*": Style(margin: Margins.zero),
},
),
SizedBox(height: getProportionateScreenHeight(16)),
Row(
children: [
kLike(
widget.qnaDataModel.selfLiked ?? false,
int.parse(widget.qnaDataModel.countLike ?? '0'),
() async {
await likeOrUnlikes(int.parse(widget.qnaDataModel.idQna.toString()));
setState(() {
widget.qnaDataModel.selfLiked = !(widget.qnaDataModel.selfLiked ?? false);
widget.qnaDataModel.countLike = widget.qnaDataModel.selfLiked!
? (int.parse(widget.qnaDataModel.countLike ?? '0') + 1).toString()
: (int.parse(widget.qnaDataModel.countLike ?? '0') - 1).toString();
});
},
),
SizedBox(width: getProportionateScreenWidth(13)),
kComment(widget.qnaDataModel.comment.length),
],
),
],
),
),
),
),
SizedBox(height: getProportionateScreenHeight(10)),
// Divider(),
Padding(
padding: EdgeInsets.symmetric(
horizontal: getProportionateScreenWidth(16),
vertical: getProportionateScreenWidth(8),
),
child: Text(
'Balasan',
style: thirdTextStyle.copyWith(
color: Theme.of(context).colorScheme.onBackground,
fontWeight: semiBold,
fontSize: getProportionateScreenWidth(16),
letterSpacing: 1,
),
),
),
SizedBox(height: getProportionateScreenHeight(13)),
ReplyQnaUserPage(
idCourse: widget.id,
idQna: widget.qnaDataModel.idQna ?? '',
userId: widget.userId,
onReplyDeleted: onReplyDeleted,
),
SizedBox(height: getProportionateScreenHeight(65)),
],
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).brightness == Brightness.dark
? Color.fromARGB(255, 54, 61, 96)
: Color.fromARGB(255, 221, 221, 221),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
height: getProportionateScreenWidth(72),
width: double.infinity,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: getProportionateScreenWidth(16),
vertical: getProportionateScreenWidth(16),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: SizeConfig.screenWidth * 0.65,
child: TextFormField(
scrollPhysics: AlwaysScrollableScrollPhysics(),
textAlignVertical: TextAlignVertical.center,
controller: _controller,
scrollPadding: EdgeInsets.zero,
cursorColor: secondaryColor,
maxLines: 1,
minLines: 1,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
filled: true,
fillColor: Theme.of(context).brightness == Brightness.dark
? seventeenColor
: Colors.grey[200],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
hintStyle: secondaryTextStyle.copyWith(
color: secondaryColor,
letterSpacing: 0.5,
fontSize: getProportionateScreenWidth(12),
),
hintText: "Balas Pertanyaan",
),
),
),
ElevatedButton(
onPressed: () async {
if (_controller.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
duration: Duration(seconds: 2),
backgroundColor: Colors.orange,
content: Text(
'Ups, balasan masih kosong. isi dulu yuk!',
style: primaryTextStyle.copyWith(color: Colors.white),
),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
} else {
bool isSuccessful = await postingQnaReplyProvider.postQnaReply(
_controller.text,
widget.qnaDataModel.idQna.toString(),
);
if (isSuccessful) {
setState(() {
// Tambahkan balasan baru ke dalam komentar
widget.qnaDataModel.comment.add(
Comment(
textRep: _controller.text,
username: '',
createAt: DateTime.now().toString(),
),
);
});
provider.getQna(widget.id);
_controller.clear();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
duration: Duration(seconds: 2),
backgroundColor: Colors.green,
content: Text(
'Balasan berhasil dikirim',
style: primaryTextStyle.copyWith(color: Colors.white),
),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
duration: Duration(seconds: 2),
backgroundColor: Colors.red,
content: Text(
'Gagal mengirim balasan, silakan coba lagi',
style: primaryTextStyle.copyWith(color: Colors.white),
),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
}
}
},
child: Text(
'Kirim',
style: thirdTextStyle.copyWith(
color: Colors.white,
letterSpacing: 0.3,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: primaryColor,
minimumSize: Size(
getProportionateScreenWidth(35),
getProportionateScreenWidth(35),
),
),
)
],
),
),
),
),
],
),
),
);
}
}
Widget kLike(bool isLiked, int likeCount, Function onTap) {
return GestureDetector(
onTap: () => onTap(),
child: Row(
children: [
Icon(
isLiked ? Icons.favorite : Icons.favorite_border_rounded,
color: isLiked ? Colors.red : secondaryColor,
size: 25,
),
SizedBox(width: 5),
Text(
"$likeCount",
style: secondaryTextStyle.copyWith(
fontSize: getProportionateScreenWidth(12),
letterSpacing: 0.3,
),
),
],
),
);
}
Widget kComment(int? commentCount) {
return Row(
children: [
Icon(
FontAwesomeIcons.comment,
color: secondaryColor,
size: 25,
),
SizedBox(width: 5),
Text(
"${commentCount ?? 0}",
style: secondaryTextStyle.copyWith(
fontSize: getProportionateScreenWidth(12),
letterSpacing: 0.3,
),
),
],
);
}

View File

@ -0,0 +1,32 @@
import 'dart:js';
import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:initial_folder/screens/course/sertif.dart';
import 'package:initial_folder/providers/user_info_provider.dart';
import 'package:initial_folder/size_config.dart';
import 'package:initial_folder/theme.dart';
import 'package:provider/provider.dart';
import 'package:initial_folder/providers/certificate_provider.dart';
class DownloadCertificate extends StatelessWidget {
final int? idCourse;
const DownloadCertificate({Key? key, this.idCourse}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
Row(
children: [
ElevatedButton(onPressed: () {}, child: Text('PNG')),
ElevatedButton(onPressed: () {}, child: Text('PDF'))
],
)
],
),
);
}
}

View File

@ -0,0 +1,487 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:initial_folder/theme.dart';
const Duration _kExpand = Duration(milliseconds: 200);
/// A single-line [ListTile] with an expansion arrow icon that expands or collapses
/// the tile to reveal or hide the [children].
///
/// This widget is typically used with [ListView] to create an
/// "expand / collapse" list entry. When used with scrolling widgets like
/// [ListView], a unique [PageStorageKey] must be specified to enable the
/// [ExpansionTile] to save and restore its expanded state when it is scrolled
/// in and out of view.
///
/// This class overrides the [ListTileThemeData.iconColor] and [ListTileThemeData.textColor]
/// theme properties for its [ListTile]. These colors animate between values when
/// the tile is expanded and collapsed: between [iconColor], [collapsedIconColor] and
/// between [textColor] and [collapsedTextColor].
///
/// The expansion arrow icon is shown on the right by default in left-to-right languages
/// (i.e. the trailing edge). This can be changed using [controlAffinity]. This maps
/// to the [leading] and [trailing] properties of [ExpansionTile].
///
/// {@tool dartpad}
/// This example demonstrates different configurations of ExpansionTile.
///
/// ** See code in examples/api/lib/material/expansion_tile/expansion_tile.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [ListTile], useful for creating expansion tile [children] when the
/// expansion tile represents a sublist.
/// * The "Expand and collapse" section of
/// <https://material.io/components/lists#types>
class ExpansionTileCopy extends StatefulWidget {
/// Creates a single-line [ListTile] with an expansion arrow icon that expands or collapses
/// the tile to reveal or hide the [children]. The [initiallyExpanded] property must
/// be non-null.
const ExpansionTileCopy({
Key? key,
this.leading,
required this.title,
this.subtitle,
this.onExpansionChanged,
this.children = const <Widget>[],
this.trailing,
this.initiallyExpanded = false,
this.maintainState = false,
this.tilePadding,
this.expandedCrossAxisAlignment,
this.expandedAlignment,
this.childrenPadding,
this.backgroundColor,
this.collapsedBackgroundColor,
this.textColor,
this.collapsedTextColor,
this.iconColor,
this.collapsedIconColor,
this.controlAffinity,
}) : assert(
expandedCrossAxisAlignment != CrossAxisAlignment.baseline,
'CrossAxisAlignment.baseline is not supported since the expanded children '
'are aligned in a column, not a row. Try to use another constant.',
),
super(key: key);
/// A widget to display before the title.
///
/// Typically a [CircleAvatar] widget.
///
/// Note that depending on the value of [controlAffinity], the [leading] widget
/// may replace the rotating expansion arrow icon.
final Widget? leading;
/// The primary content of the list item.
///
/// Typically a [Text] widget.
final Widget title;
/// Additional content displayed below the title.
///
/// Typically a [Text] widget.
final Widget? subtitle;
/// Called when the tile expands or collapses.
///
/// When the tile starts expanding, this function is called with the value
/// true. When the tile starts collapsing, this function is called with
/// the value false.
final ValueChanged<bool>? onExpansionChanged;
/// The widgets that are displayed when the tile expands.
///
/// Typically [ListTile] widgets.
final List<Widget> children;
/// The color to display behind the sublist when expanded.
///
/// If this property is null then [ExpansionTileThemeData.backgroundColor] is used. If that
/// is also null then Colors.transparent is used.
///
/// See also:
///
/// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s
/// [ExpansionTileThemeData].
final Color? backgroundColor;
/// When not null, defines the background color of tile when the sublist is collapsed.
///
/// If this property is null then [ExpansionTileThemeData.collapsedBackgroundColor] is used.
/// If that is also null then Colors.transparent is used.
///
/// See also:
///
/// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s
/// [ExpansionTileThemeData].
final Color? collapsedBackgroundColor;
/// A widget to display after the title.
///
/// Note that depending on the value of [controlAffinity], the [trailing] widget
/// may replace the rotating expansion arrow icon.
final Widget? trailing;
/// Specifies if the list tile is initially expanded (true) or collapsed (false, the default).
final bool initiallyExpanded;
/// Specifies whether the state of the children is maintained when the tile expands and collapses.
///
/// When true, the children are kept in the tree while the tile is collapsed.
/// When false (default), the children are removed from the tree when the tile is
/// collapsed and recreated upon expansion.
final bool maintainState;
/// Specifies padding for the [ListTile].
///
/// Analogous to [ListTile.contentPadding], this property defines the insets for
/// the [leading], [title], [subtitle] and [trailing] widgets. It does not inset
/// the expanded [children] widgets.
///
/// If this property is null then [ExpansionTileThemeData.tilePadding] is used. If that
/// is also null then the tile's padding is `EdgeInsets.symmetric(horizontal: 16.0)`.
///
/// See also:
///
/// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s
/// [ExpansionTileThemeData].
final EdgeInsetsGeometry? tilePadding;
/// Specifies the alignment of [children], which are arranged in a column when
/// the tile is expanded.
///
/// The internals of the expanded tile make use of a [Column] widget for
/// [children], and [Align] widget to align the column. The `expandedAlignment`
/// parameter is passed directly into the [Align].
///
/// Modifying this property controls the alignment of the column within the
/// expanded tile, not the alignment of [children] widgets within the column.
/// To align each child within [children], see [expandedCrossAxisAlignment].
///
/// The width of the column is the width of the widest child widget in [children].
///
/// If this property is null then [ExpansionTileThemeData.expandedAlignment]is used. If that
/// is also null then the value of `expandedAlignment` is [Alignment.center].
///
/// See also:
///
/// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s
/// [ExpansionTileThemeData].
final Alignment? expandedAlignment;
/// Specifies the alignment of each child within [children] when the tile is expanded.
///
/// The internals of the expanded tile make use of a [Column] widget for
/// [children], and the `crossAxisAlignment` parameter is passed directly into the [Column].
///
/// Modifying this property controls the cross axis alignment of each child
/// within its [Column]. Note that the width of the [Column] that houses
/// [children] will be the same as the widest child widget in [children]. It is
/// not necessarily the width of [Column] is equal to the width of expanded tile.
///
/// To align the [Column] along the expanded tile, use the [expandedAlignment] property
/// instead.
///
/// When the value is null, the value of `expandedCrossAxisAlignment` is [CrossAxisAlignment.center].
final CrossAxisAlignment? expandedCrossAxisAlignment;
/// Specifies padding for [children].
///
/// If this property is null then [ExpansionTileThemeData.childrenPadding] is used. If that
/// is also null then the value of `childrenPadding` is [EdgeInsets.zero].
///
/// See also:
///
/// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s
/// [ExpansionTileThemeData].
final EdgeInsetsGeometry? childrenPadding;
/// The icon color of tile's expansion arrow icon when the sublist is expanded.
///
/// Used to override to the [ListTileThemeData.iconColor].
///
/// If this property is null then [ExpansionTileThemeData.iconColor] is used. If that
/// is also null then the value of [ListTileThemeData.iconColor] is used.
///
/// See also:
///
/// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s
/// [ExpansionTileThemeData].
final Color? iconColor;
/// The icon color of tile's expansion arrow icon when the sublist is collapsed.
///
/// Used to override to the [ListTileThemeData.iconColor].
final Color? collapsedIconColor;
/// The color of the tile's titles when the sublist is expanded.
///
/// Used to override to the [ListTileThemeData.textColor].
///
/// If this property is null then [ExpansionTileThemeData.textColor] is used. If that
/// is also null then the value of [ListTileThemeData.textColor] is used.
///
/// See also:
///
/// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s
/// [ExpansionTileThemeData].
final Color? textColor;
/// The color of the tile's titles when the sublist is collapsed.
///
/// Used to override to the [ListTileThemeData.textColor].
///
/// If this property is null then [ExpansionTileThemeData.collapsedTextColor] is used. If that
/// is also null then the value of [ListTileThemeData.textColor] is used.
///
/// See also:
///
/// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s
/// [ExpansionTileThemeData].
final Color? collapsedTextColor;
/// Typically used to force the expansion arrow icon to the tile's leading or trailing edge.
///
/// By default, the value of `controlAffinity` is [ListTileControlAffinity.platform],
/// which means that the expansion arrow icon will appear on the tile's trailing edge.
final ListTileControlAffinity? controlAffinity;
@override
State<ExpansionTileCopy> createState() => _ExpansionTileCopyState();
}
class _ExpansionTileCopyState extends State<ExpansionTileCopy>
with SingleTickerProviderStateMixin {
static final Animatable<double> _easeOutTween =
CurveTween(curve: Curves.easeOut);
static final Animatable<double> _easeInTween =
CurveTween(curve: Curves.easeIn);
static final Animatable<double> _halfTween =
Tween<double>(begin: 0.0, end: 0.5);
final ColorTween _borderColorTween = ColorTween();
final ColorTween _headerColorTween = ColorTween();
final ColorTween _iconColorTween = ColorTween();
final ColorTween _backgroundColorTween = ColorTween();
late AnimationController _controller;
late Animation<double> _iconTurns;
late Animation<double> _heightFactor;
late Animation<Color?> _borderColor;
late Animation<Color?> _headerColor;
late Animation<Color?> _iconColor;
late Animation<Color?> _backgroundColor;
bool _isExpanded = false;
@override
void initState() {
super.initState();
_controller = AnimationController(duration: _kExpand, vsync: this);
_heightFactor = _controller.drive(_easeInTween);
_iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
_borderColor = _controller.drive(_borderColorTween.chain(_easeOutTween));
_headerColor = _controller.drive(_headerColorTween.chain(_easeInTween));
_iconColor = _controller.drive(_iconColorTween.chain(_easeInTween));
_backgroundColor =
_controller.drive(_backgroundColorTween.chain(_easeOutTween));
_isExpanded = PageStorage.of(context)?.readState(context) as bool? ??
widget.initiallyExpanded;
if (_isExpanded) {
_controller.value = 1.0;
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _handleTap() {
setState(() {
_isExpanded = !_isExpanded;
if (_isExpanded) {
_controller.forward();
} else {
_controller.reverse().then<void>((void value) {
if (!mounted) {
return;
}
setState(() {
// Rebuild without widget.children.
});
});
}
PageStorage.of(context)?.writeState(context, _isExpanded);
});
widget.onExpansionChanged?.call(_isExpanded);
}
// Added to class
void closeExpansion() {
if (_isExpanded) _handleTap();
}
// Added to class
void openExpansion() {
if (!_isExpanded) _handleTap();
}
// Platform or null affinity defaults to trailing.
ListTileControlAffinity _effectiveAffinity(
ListTileControlAffinity? affinity) {
switch (affinity ?? ListTileControlAffinity.trailing) {
case ListTileControlAffinity.leading:
return ListTileControlAffinity.leading;
case ListTileControlAffinity.trailing:
case ListTileControlAffinity.platform:
return ListTileControlAffinity.trailing;
}
}
Widget? _buildIcon(BuildContext context) {
return RotationTransition(
turns: _iconTurns,
child: const Icon(Icons.expand_more),
);
}
Widget? _buildLeadingIcon(BuildContext context) {
if (_effectiveAffinity(widget.controlAffinity) !=
ListTileControlAffinity.leading) {
return null;
}
return _buildIcon(context);
}
// Ubah trailing menjadi "+" dan "-" saat ExpansionTileCopy dibuka
Widget? _buildTrailingIcon(BuildContext context) {
if (_effectiveAffinity(widget.controlAffinity) !=
ListTileControlAffinity.trailing) {
return null;
}
final Color trailingIconColor = Theme.of(context).brightness == Brightness.light
? primaryColor
: primaryColorligtmode;
return _isExpanded
? Icon(
Icons.remove,
color: trailingIconColor,
)
: Icon(
Icons.add,
color: trailingIconColor,
);
}
Widget _buildChildren(BuildContext context, Widget? child) {
final ExpansionTileThemeData expansionTileTheme =
ExpansionTileTheme.of(context);
final Color borderSideColor = _borderColor.value ?? Colors.transparent;
return Container(
decoration: BoxDecoration(
color: _backgroundColor.value ??
expansionTileTheme.backgroundColor ??
Colors.transparent,
border: Border(
top: BorderSide(color: borderSideColor),
bottom: BorderSide(color: borderSideColor),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTileTheme.merge(
iconColor: _iconColor.value ?? expansionTileTheme.iconColor,
textColor: _headerColor.value,
child: ListTile(
onTap: _handleTap,
contentPadding:
widget.tilePadding ?? expansionTileTheme.tilePadding,
leading: widget.leading ?? _buildLeadingIcon(context),
title: widget.title,
subtitle: widget.subtitle,
trailing: widget.trailing ?? _buildTrailingIcon(context),
),
),
ClipRect(
child: Align(
alignment: widget.expandedAlignment ??
expansionTileTheme.expandedAlignment ??
Alignment.center,
heightFactor: _heightFactor.value,
child: child,
),
),
],
),
);
}
@override
void didChangeDependencies() {
final ThemeData theme = Theme.of(context);
final ExpansionTileThemeData expansionTileTheme =
ExpansionTileTheme.of(context);
final ColorScheme colorScheme = theme.colorScheme;
_borderColorTween.end = theme.dividerColor;
_headerColorTween
..begin = widget.collapsedTextColor ??
expansionTileTheme.collapsedTextColor ??
theme.textTheme.subtitle1!.color
..end = widget.textColor ??
expansionTileTheme.textColor ??
colorScheme.primary;
_iconColorTween
..begin = widget.collapsedIconColor ??
expansionTileTheme.collapsedIconColor ??
theme.unselectedWidgetColor
..end = widget.iconColor ??
expansionTileTheme.iconColor ??
colorScheme.primary;
_backgroundColorTween
..begin = widget.collapsedBackgroundColor ??
expansionTileTheme.collapsedBackgroundColor
..end = widget.backgroundColor ?? expansionTileTheme.backgroundColor;
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
final ExpansionTileThemeData expansionTileTheme =
ExpansionTileTheme.of(context);
final bool closed = !_isExpanded && _controller.isDismissed;
final bool shouldRemoveChildren = closed && !widget.maintainState;
final Widget result = Offstage(
offstage: closed,
child: TickerMode(
enabled: !closed,
child: Padding(
padding: widget.childrenPadding ??
expansionTileTheme.childrenPadding ??
EdgeInsets.zero,
child: Column(
crossAxisAlignment:
widget.expandedCrossAxisAlignment ?? CrossAxisAlignment.center,
children: widget.children,
),
),
),
);
return AnimatedBuilder(
animation: _controller.view,
builder: _buildChildren,
child: shouldRemoveChildren ? null : result,
);
}
}

View File

@ -0,0 +1,308 @@
import 'package:flutter/material.dart';
import 'package:initial_folder/models/announcement_model.dart';
import 'package:initial_folder/providers/announcement_provider.dart';
import 'package:initial_folder/providers/posting_announcement_reply_provider.dart';
import 'package:initial_folder/size_config.dart';
import 'package:initial_folder/theme.dart';
import 'package:initial_folder/widgets/announcement_user.dart';
import 'package:initial_folder/widgets/reply_announcement_user_page.dart';
import 'package:provider/provider.dart';
import '../../../get_it.dart';
final scaffoldKey = GlobalKey<ScaffoldState>();
class InsideAnnouncement extends StatefulWidget {
const InsideAnnouncement({
Key? key,
required this.id,
required this.announcementDataModel,
required this.index,
required this.userId,
}) : super(key: key);
final AnnouncementDataModel announcementDataModel;
final id;
final int index;
final int userId;
@override
State<InsideAnnouncement> createState() => _InsideAnnouncementState();
}
class _InsideAnnouncementState extends State<InsideAnnouncement> {
final _controller = TextEditingController();
final provider = announcementGetIt<AnnouncementProvider>();
late Widget announcement;
@override
Widget build(BuildContext context) {
PostingAnnouncementReplyProvider postingAnnouncementReplyProvider =
Provider.of<PostingAnnouncementReplyProvider>(context);
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
title: Text(
'Pengumuman',
style: primaryTextStyle.copyWith(
fontWeight: semiBold,
fontSize: getProportionateScreenWidth(16),
letterSpacing: 0.2,
),
),
),
body: GestureDetector(
// onTap: () => FocusScope.of(context).unfocus(),
child: Stack(
children: [
ListView(
children: [
// QnaUser(
// qnaDataModel: widget.qnaDataModel,
// id: widget.id,
// index: widget.index,
// userId: widget.userId,
// ),
StreamBuilder<AnnouncementModel>(
stream: provider.announcementStream,
builder:
(context, AsyncSnapshot<AnnouncementModel> snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(
'Terjadi Kesalahan',
style: thirdTextStyle,
),
);
} else {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
announcement = Center(
child: CircularProgressIndicator(
color: primaryColor,
strokeWidth: 2,
),
);
break;
case ConnectionState.none:
announcement = Center(
child: Text(
'Tidak ada koneksi',
style: thirdTextStyle,
),
);
break;
case ConnectionState.active:
print('masuk siniiiiiiiiiii active' +
snapshot.data!.error.toString());
announcement = snapshot.data!.data[0].length > 0
? Container(
padding: EdgeInsets.only(top: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10),
bottomLeft: Radius.circular(10)),
color: Theme.of(context)
.colorScheme
.primaryContainer,
boxShadow: [
BoxShadow(
color: Theme.of(context)
.brightness ==
Brightness.dark
? Color(0xff212643)
: Colors.grey,
blurRadius: 0.5,
offset: Offset(0, 2),
spreadRadius: 0.001)
]),
child: AnnouncementUser(
announcementDataModel:
snapshot.data!.data[0][widget.index],
id: widget.id,
index: widget.index,
userId: widget.userId,
))
: Center(
child: Text(
'Belum ada pengumuman',
style: thirdTextStyle,
),
);
break;
case ConnectionState.done:
announcement = snapshot.data!.data[0].length > 0
? AnnouncementUser(
announcementDataModel:
snapshot.data!.data[0][widget.index],
id: widget.id,
index: widget.index,
userId: widget.userId,
)
: Center(
child: Text(
'Belum ada pertanyaan',
style: thirdTextStyle,
),
);
break;
}
}
return announcement;
}),
Padding(
padding: EdgeInsets.symmetric(
horizontal: getProportionateScreenWidth(16),
vertical: getProportionateScreenWidth(8)),
child: InkWell(
onTap: () {},
child: Text(
'Balasan',
style: thirdTextStyle.copyWith(
color: Theme.of(context).colorScheme.onBackground,
fontWeight: semiBold,
fontSize: getProportionateScreenWidth(16),
letterSpacing: 1),
),
),
),
SizedBox(height: getProportionateScreenHeight(13)),
ReplyAnnouncementUserPage(
idCourse: widget.id,
index: widget.index,
userId: widget.userId,
),
SizedBox(height: 50)
],
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).brightness == Brightness.dark
? Color.fromARGB(255, 54, 61, 96)
: Color.fromARGB(255, 221, 221, 221),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20))),
height: getProportionateScreenWidth(72),
width: double.infinity,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: getProportionateScreenWidth(16),
vertical: getProportionateScreenWidth(16),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: SizeConfig.screenWidth * 0.65,
child: TextFormField(
controller: _controller,
scrollPadding: EdgeInsets.zero,
cursorColor: secondaryColor,
decoration: InputDecoration(
filled: true,
fillColor:
Theme.of(context).brightness == Brightness.dark
? seventeenColor
: Colors.grey[200],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(
10,
),
borderSide: BorderSide.none),
hintStyle: secondaryTextStyle.copyWith(
color: secondaryColor,
letterSpacing: 0.5,
fontSize: getProportionateScreenWidth(12),
),
hintText: "Balas",
),
),
),
ElevatedButton(
onPressed: () async {
if (await postingAnnouncementReplyProvider
.postAnnouncementReply(
_controller.text,
widget.announcementDataModel.idAnnouncement
.toString(),
widget.announcementDataModel.tokenAnnouncement
.toString(),
widget.announcementDataModel.idAnnouncement
.toString(),
)) {
provider.getAnnouncement(widget.id);
_controller.clear();
ScaffoldMessenger.of(scaffoldKey.currentContext!)
.showSnackBar(
SnackBar(
duration: Duration(seconds: 2),
backgroundColor: primaryColor,
content: Text(
'Balasan Terkirim',
style: primaryTextStyle.copyWith(
color: backgroundColor),
),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
action: SnackBarAction(
label: 'Lihat',
onPressed: () {
ScaffoldMessenger.of(
scaffoldKey.currentContext!)
.hideCurrentSnackBar();
},
),
),
);
} else {
ScaffoldMessenger.of(scaffoldKey.currentContext!)
.showSnackBar(
SnackBar(
duration: Duration(seconds: 2),
backgroundColor: primaryColor,
content: Text(
'Terjadi kesalahan',
style: primaryTextStyle.copyWith(
color: backgroundColor,
),
),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
),
);
}
},
child: Text(
'Kirim',
style: thirdTextStyle.copyWith(
color: Colors.white,
letterSpacing: 0.3,
),
),
style: ElevatedButton.styleFrom(
backgroundColor: primaryColor,
minimumSize: Size(
getProportionateScreenWidth(35),
getProportionateScreenWidth(35),
),
),
)
],
),
),
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:easy_pdf_viewer/easy_pdf_viewer.dart';
import 'package:initial_folder/size_config.dart';
import 'package:initial_folder/theme.dart';
class pdfReader extends StatefulWidget {
final String link;
final String title;
const pdfReader({super.key, required this.link, required this.title});
@override
State<pdfReader> createState() => _pdfReaderState();
}
class _pdfReaderState extends State<pdfReader> {
late PDFDocument document;
bool _isLoading = true;
@override
void initState() {
super.initState();
// Load from URL
loadPDF();
}
void loadPDF() async {
document = await PDFDocument.fromURL(widget.link);
setState(() {
_isLoading = false;
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(
widget.title,
style: secondaryTextStyle.copyWith(
letterSpacing: 1,
fontWeight: semiBold,
fontSize: getProportionateScreenWidth(16)),
),
),
body: Center(
child: _isLoading
? Center(child: CircularProgressIndicator())
: PDFViewer(
document: document,
pickerButtonColor: primaryColor,
)),
);
}
}

View File

@ -0,0 +1,338 @@
import 'package:flutter/material.dart';
import 'package:initial_folder/main.dart';
import 'package:initial_folder/providers/qna_provider.dart';
import 'package:initial_folder/services/qna_service.dart';
import 'package:initial_folder/size_config.dart';
import 'package:initial_folder/theme.dart';
import 'package:initial_folder/providers/posting_qna_provider.dart';
import 'package:initial_folder/widgets/qna_user_page.dart';
import 'package:provider/provider.dart';
import 'package:quill_html_editor/quill_html_editor.dart';
class QuestAndAnswer extends StatefulWidget {
const QuestAndAnswer({
Key? key,
required this.id,
required this.idLesson,
}) : super(key: key);
final id;
final idLesson;
@override
State<QuestAndAnswer> createState() => _QuestAndAnswerState();
}
class _QuestAndAnswerState extends State<QuestAndAnswer> {
double value = 0;
final _controllerTitle = TextEditingController();
final _controller = TextEditingController();
final QuillEditorController _controllerQuest = QuillEditorController();
final customToolbar = [
ToolBarStyle.headerOne,
ToolBarStyle.headerTwo,
ToolBarStyle.bold,
ToolBarStyle.italic,
ToolBarStyle.underline,
ToolBarStyle.color,
ToolBarStyle.listBullet,
ToolBarStyle.listOrdered,
];
@override
void initState() {
// TODO: implement initState
QnaService().getMyQna(widget.id);
super.initState();
}
@override
Widget build(BuildContext context) {
PostingQnaProvider postingQnaProvider =
Provider.of<PostingQnaProvider>(context);
return SingleChildScrollView(
child: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.symmetric(
horizontal: getProportionateScreenWidth(16),
),
child: Column(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: getProportionateScreenWidth(16),
),
Text(
'Ajukan Pertanyaan',
style: thirdTextStyle.copyWith(
fontWeight: semiBold,
letterSpacing: 0.1,
fontSize: getProportionateScreenWidth(16),
),
),
SizedBox(
height: getProportionateScreenWidth(8),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color:
Theme.of(context).colorScheme.primaryContainer,
boxShadow: [
BoxShadow(
color: Colors.grey,
blurRadius: 0.5,
offset: Offset(0, 2),
spreadRadius: 0.001)
]),
child: TextField(
controller: _controllerTitle,
cursorColor: secondaryColor,
scrollPadding: EdgeInsets.zero,
decoration: InputDecoration(
filled: true,
fillColor:
Theme.of(context).brightness == Brightness.dark
? seventeenColor
: Colors.grey[200],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(
10,
),
borderSide: BorderSide.none),
hintStyle: secondaryTextStyle.copyWith(
color: secondaryColor,
letterSpacing: 0.5,
fontSize: getProportionateScreenWidth(12),
),
hintText: "Masukan Judul Pertanyaan",
),
),
),
SizedBox(
height: getProportionateScreenWidth(18),
),
ToolBar(
toolBarColor:
Theme.of(context).brightness == Brightness.dark
? seventeenColor
: Colors.grey[200]!,
activeIconColor: primaryColor,
iconColor: secondaryColor,
padding: const EdgeInsets.all(8),
iconSize: 20,
controller: _controllerQuest,
toolBarConfig: customToolbar,
),
Container(
height: 180,
width: double.infinity,
padding: EdgeInsets.all(15),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10),
),
color:
Theme.of(context).colorScheme.primaryContainer,
boxShadow: [
BoxShadow(
color: Colors.grey,
blurRadius: 0.5,
offset: Offset(0, 2),
spreadRadius: 0.001)
]),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: QuillHtmlEditor(
hintText: 'Ketik pertanyaan mu',
controller: _controllerQuest,
isEnabled: true,
minHeight: 100,
backgroundColor: Theme.of(context).brightness ==
Brightness.dark
? seventeenColor
: Colors.grey[200]!,
textStyle: secondaryTextStyle.copyWith(
color: secondaryColor,
fontSize: getProportionateScreenWidth(16),
),
hintTextStyle: secondaryTextStyle.copyWith(
color: secondaryColor,
fontSize: getProportionateScreenWidth(16),
),
hintTextAlign: TextAlign.start,
padding: const EdgeInsets.all(3),
hintTextPadding: const EdgeInsets.all(0),
loadingBuilder: (context) {
return const Center(
child: CircularProgressIndicator(
strokeWidth: 0.4,
));
},
),
),
// TextField(
// controller: _controller,
// cursorColor: secondaryColor,
// scrollPadding: EdgeInsets.zero,
// minLines: 2,
// keyboardType: TextInputType.multiline,
// maxLines: 2,
// decoration: InputDecoration(
// filled: true,
// fillColor: Theme.of(context).brightness ==
// Brightness.dark
// ? seventeenColor
// : Colors.grey[200],
// border: OutlineInputBorder(
// borderRadius: BorderRadius.circular(
// 10,
// ),
// borderSide: BorderSide.none),
// hintStyle: secondaryTextStyle.copyWith(
// color: secondaryColor,
// letterSpacing: 0.5,
// fontSize: getProportionateScreenWidth(12),
// ),
// hintText: "Ketikkan Pertanyaanmu disini",
// ),
// ),
Align(
alignment: Alignment.topRight,
child: ElevatedButton(
onPressed: () async {
if (await postingQnaProvider.postingQna(
_controllerTitle.text,
await _controllerQuest.getText(),
widget.id,
widget.idLesson)) {
_controllerQuest.clear();
_controllerTitle.clear();
ScaffoldMessenger.of(
globalScaffoldKey.currentContext!)
.showSnackBar(
SnackBar(
duration: Duration(seconds: 2),
backgroundColor: primaryColor,
content: Text(
'Pertanyaan berhasil dikirimkan',
style: primaryTextStyle.copyWith(
color: backgroundColor,
),
),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(5),
),
action: SnackBarAction(
label: 'Lihat',
onPressed: () {
ScaffoldMessenger.of(
globalScaffoldKey
.currentContext!)
.hideCurrentSnackBar();
},
),
),
);
} else {
ScaffoldMessenger.of(
globalScaffoldKey.currentContext!)
.showSnackBar(
SnackBar(
duration: Duration(seconds: 2),
backgroundColor: primaryColor,
content: Text(
'Terjadi kesalahan',
style: primaryTextStyle.copyWith(
color: backgroundColor,
),
),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(5),
),
),
);
}
},
child: Text(
'Kirim',
style: thirdTextStyle.copyWith(
color: Colors.white,
fontSize: SizeConfig.blockHorizontal! * 4,
),
),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
backgroundColor: primaryColor,
),
),
),
],
)),
),
],
),
],
),
),
SizedBox(
height: 15,
),
Container(
margin: EdgeInsets.symmetric(
horizontal: getProportionateScreenWidth(16),
),
child: Text(
'Pertanyaan Dan Jawaban',
style: primaryTextStyle.copyWith(
fontWeight: semiBold,
letterSpacing: 1,
fontSize: getProportionateScreenWidth(16),
),
),
),
SizedBox(
height: getProportionateScreenHeight(14),
),
QnaUserPage(idCourse: widget.id),
SizedBox(
height: getProportionateScreenHeight(14),
),
// QandA(
// divider: Divider(),
// ),
// QandA(
// divider: Divider(),
// ),
],
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

View File

@ -0,0 +1,46 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:easy_pdf_viewer/easy_pdf_viewer.dart';
import 'package:flutter_text_viewer/model/text_viewer.dart';
import 'package:flutter_text_viewer/screen/text_viewer_page.dart';
import 'package:initial_folder/size_config.dart';
import 'package:initial_folder/theme.dart';
class txtReader extends StatefulWidget {
final String link;
// final String title;
const txtReader({Key? key, required this.link});
@override
State<txtReader> createState() => _txtReaderState();
}
class _txtReaderState extends State<txtReader> {
@override
void initState() {
super.initState();
print(widget.link);
}
@override
void dispose() {
super.dispose();
// Hapus file .txt saat widget di-dispose
File(widget.link).deleteSync(recursive: true);
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TextViewerPage(
textViewer: TextViewer.asset(
widget.link,
highLightColor: Colors.yellow,
focusColor: Colors.orange,
ignoreCase: true,
),
showSearchAppBar: true,
)));
}
}