Initial commit: Penyerahan final Source code Tugas Akhir
This commit is contained in:
134
lib/screens/course/component/announcement.dart
Normal file
134
lib/screens/course/component/announcement.dart
Normal 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();
|
||||
}
|
||||
}
|
710
lib/screens/course/component/detail_play_course.dart
Normal file
710
lib/screens/course/component/detail_play_course.dart
Normal 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'));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
400
lib/screens/course/component/detail_quest_and_answer.dart
Normal file
400
lib/screens/course/component/detail_quest_and_answer.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
32
lib/screens/course/component/download_certificate.dart
Normal file
32
lib/screens/course/component/download_certificate.dart
Normal 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'))
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
487
lib/screens/course/component/expansion_tile_copy.dart
Normal file
487
lib/screens/course/component/expansion_tile_copy.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
308
lib/screens/course/component/inside_announcement.dart
Normal file
308
lib/screens/course/component/inside_announcement.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
53
lib/screens/course/component/pdfReader.dart
Normal file
53
lib/screens/course/component/pdfReader.dart
Normal 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,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
338
lib/screens/course/component/quest_and_answer.dart
Normal file
338
lib/screens/course/component/quest_and_answer.dart
Normal 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();
|
||||
}
|
||||
}
|
46
lib/screens/course/component/txtReader.dart
Normal file
46
lib/screens/course/component/txtReader.dart
Normal 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,
|
||||
)));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user