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,462 @@
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:initial_folder/helper/validator.dart';
import 'package:initial_folder/size_config.dart';
import 'package:initial_folder/theme.dart';
import 'package:initial_folder/widgets/custom_navigator_pop.dart';
import 'package:initial_folder/widgets/search_and_filter_course.dart';
import 'package:provider/provider.dart';
import 'package:initial_folder/providers/filters_course_provider.dart'
as filterCourseProv;
import 'package:initial_folder/providers/search_provider.dart'
as searchProvider;
class Filter extends StatelessWidget {
const Filter({
Key? key,
this.isNotSearch,
this.onApplyFilter,
}) : super(key: key);
final bool? isNotSearch;
final VoidCallback? onApplyFilter;
@override
Widget build(BuildContext context) {
filterCourseProv.FilterCourseProvider filterCourseProvider =
Provider.of<filterCourseProv.FilterCourseProvider>(context);
final listChoices = <ItemChoiceFilter>[
ItemChoiceFilter('', '0', 'Semua '),
ItemChoiceFilter('5', '1', '5'),
ItemChoiceFilter('4', '1', '4'),
ItemChoiceFilter('3', '1', '3'),
ItemChoiceFilter('2', '1', '2'),
ItemChoiceFilter('1', '1', '1'),
];
Widget categoriCheckBox(String id, String title, [Function? onChange]) {
return Container(
margin: EdgeInsets.only(bottom: getProportionateScreenWidth(8)),
child: Row(
children: [
SizedBox(
width: 24,
height: 24,
child: Checkbox(
value:
filterCourseProvider.categories.contains(id) ? true : false,
onChanged: id == ''
? (c) {
filterCourseProvider.allCategories();
filterCourseProvider.addCategory(id);
}
: (c) {
if (filterCourseProvider.categories.contains(id)) {
filterCourseProvider.removeCategory(id);
} else {
filterCourseProvider.removeCategory('');
filterCourseProvider.addCategory(id);
}
},
activeColor: Theme.of(context).brightness == Brightness.dark
? thirteenColor
: twelveColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
side:
BorderSide(color: Theme.of(context).colorScheme.onPrimary),
),
),
SizedBox(width: getProportionateScreenWidth(12)),
Expanded(
child: GestureDetector(
onTap: id == ''
? () {
filterCourseProvider.allCategories();
filterCourseProvider.addCategory(id);
}
: () {
if (filterCourseProvider.categories.contains(id)) {
filterCourseProvider.removeCategory(id);
} else {
filterCourseProvider.removeCategory('');
filterCourseProvider.addCategory(id);
}
},
child: Text(
title,
style: primaryTextStyle.copyWith(
color: secondaryColor,
fontSize: getProportionateScreenWidth(12),
letterSpacing: 0.5),
),
),
),
],
),
);
}
Widget levelCheckBox(String name, String title) {
return Container(
margin: EdgeInsets.only(bottom: getProportionateScreenWidth(8)),
child: Row(
children: [
SizedBox(
width: 24,
height: 24,
child: Checkbox(
checkColor: Theme.of(context).colorScheme.background,
value:
filterCourseProvider.levels.contains(name) ? true : false,
onChanged: name == ''
? (c) {
filterCourseProvider.allLevel();
filterCourseProvider.addLevel(name);
}
: (c) {
if (filterCourseProvider.levels.contains(name)) {
filterCourseProvider.removeLevel(name);
} else {
filterCourseProvider.removeLevel('');
filterCourseProvider.addLevel(name);
}
},
activeColor: primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
side:
BorderSide(color: Theme.of(context).colorScheme.onPrimary),
),
),
SizedBox(width: getProportionateScreenWidth(12)),
Expanded(
child: GestureDetector(
onTap: name == ''
? () {
filterCourseProvider.allLevel();
filterCourseProvider.addLevel(name);
}
: () {
// filterCourseProvider.currentIndex = id;
if (filterCourseProvider.levels.contains(name)) {
filterCourseProvider.removeLevel(name);
} else {
filterCourseProvider.removeLevel('');
filterCourseProvider.addLevel(name);
}
},
child: Text(
title,
style: thirdTextStyle.copyWith(
fontSize: getProportionateScreenWidth(12),
letterSpacing: 0.5),
),
),
),
],
),
);
}
Widget radioPrice(
String val,
String title,
) {
return Container(
margin: EdgeInsets.only(bottom: getProportionateScreenWidth(8)),
child: Row(
children: [
SizedBox(
width: 24,
height: 24,
child: Theme(
data: ThemeData(
unselectedWidgetColor: fourthColor,
),
child: Radio(
activeColor: primaryColor,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
value: val,
groupValue: filterCourseProvider.currentIndexPrice,
onChanged: (c) {
filterCourseProvider.currentIndexPrice = val;
}),
),
),
SizedBox(
width: getProportionateScreenWidth(12),
),
Expanded(
child: GestureDetector(
onTap: () {
filterCourseProvider.currentIndexPrice = val;
},
child: Text(
title,
style: thirdTextStyle.copyWith(
fontSize: getProportionateScreenWidth(12),
letterSpacing: 0.5,
),
),
),
),
],
),
);
}
final provSearch =
Provider.of<searchProvider.SearchProvider>(context, listen: false);
Widget bottomNav() {
return Container(
width: double.infinity,
height: getProportionateScreenHeight(60),
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
offset: Offset(0, -2),
color: Theme.of(context).colorScheme.primaryContainer,
)
],
),
child: Center(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size(getProportionateScreenWidth(110),
getProportionateScreenHeight(33)),
backgroundColor: primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
),
onPressed: () {
provSearch.searchText = "";
Provider.of<filterCourseProv.FilterCourseProvider>(context,
listen: false)
.isSearchsTrue();
filterCourseProvider.filter(
category: filterCourseProvider.categories.join(','),
price: filterCourseProvider.currentIndexPrice,
level: filterCourseProvider.levels.join(','),
rating: filterCourseProvider.currentIndexRating,
keyword: Provider.of<searchProvider.SearchProvider>(context,
listen: false)
.searchTextFilter,
);
onApplyFilter!();
isNotSearch == null
? Navigator.pop(context)
: Navigator.pushReplacement(
context,
CustomNavigatorPop(
child: SearchAndFilterCourse(),
),
);
},
child: Text(
'Filter',
style: thirdTextStyle.copyWith(color: baruTextutih),
),
),
),
);
}
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.background,
appBar: AppBar(
backgroundColor: Theme.of(context).brightness == Brightness.dark
? twelveColor
: baruTextutih,
leading: IconButton(
icon: Icon(Icons.close),
onPressed: () {
Navigator.pop(context);
},
),
actions: [
TextButton(
onPressed: () {
filterCourseProvider.resetFilter();
},
child: Text(
'Reset',
style: primaryTextStyle.copyWith(
letterSpacing: 0.5,
color: primaryColor,
),
),
)
],
),
body: Container(
margin:
EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)),
child: ListView(
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Theme.of(context).brightness == Brightness.dark
? seventeenColor
: secondaryColor.withOpacity(0.3),
),
height: 40,
child: Consumer<searchProvider.SearchProvider>(
builder: (context, state, _) => TextField(
autofocus: false,
onSubmitted: (value) {
filterCourseProvider.isSearchsFalse();
state.searchTextFilter = validatorSearchFilter(value);
state.initSearchCourse(
price: filterCourseProvider.currentIndexPrice,
level: filterCourseProvider.levels.join(','),
rating: filterCourseProvider.currentIndexRating,
);
},
style: primaryTextStyle.copyWith(
fontSize: getProportionateScreenWidth(14),
letterSpacing: 0.5,
),
onChanged: (value) {
state.searchTextFilter = validatorSearchFilter(value);
},
cursorColor: secondaryColor,
decoration: InputDecoration(
border: InputBorder.none,
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: sevenColor),
borderRadius: BorderRadius.circular(10)),
contentPadding:
EdgeInsets.only(top: getProportionateScreenHeight(2)),
prefixIcon: Icon(
FeatherIcons.search,
size: 20,
color: primaryColor,
),
hintText: 'Cari Kursus',
hintStyle: primaryTextStyle.copyWith(
fontSize: getProportionateScreenWidth(12),
color: secondaryColor,
letterSpacing: 0.5,
),
),
),
),
),
SizedBox(height: getProportionateScreenHeight(20)),
Text(
'Harga',
style:
thirdTextStyle.copyWith(letterSpacing: 1, fontWeight: bold),
),
SizedBox(height: getProportionateScreenWidth(8)),
radioPrice('', 'Semua'),
radioPrice('1', 'Gratis'),
radioPrice('0', 'Berbayar'),
Text(
'Tingkat',
style:
thirdTextStyle.copyWith(letterSpacing: 1, fontWeight: bold),
),
SizedBox(height: getProportionateScreenWidth(8)),
levelCheckBox('', 'Semua'),
levelCheckBox('beginner', 'Pemula'),
levelCheckBox('intermediate', 'Menengah'),
levelCheckBox('high', 'Ahli'),
Text(
'Rating',
style:
thirdTextStyle.copyWith(letterSpacing: 1, fontWeight: bold),
),
SizedBox(height: getProportionateScreenWidth(8)),
Wrap(
spacing: 5,
runSpacing: 5,
children: listChoices
.map(
(e) => ChoiceChip(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30)),
showCheckmark: false,
materialTapTargetSize: MaterialTapTargetSize.padded,
backgroundColor:
Theme.of(context).colorScheme.primaryContainer,
side: BorderSide(
color: Theme.of(context).brightness == Brightness.dark
? baruTextutih
: primaryColor,
),
labelPadding: const EdgeInsets.symmetric(horizontal: 6),
label: Row(
mainAxisSize: MainAxisSize.min,
children: [
(e.icon == '0')
? const Text('')
: FaIcon(
FontAwesomeIcons.solidStar,
color: thirteenColor,
size: getProportionateScreenWidth(11),
),
const SizedBox(
width: 4,
),
Text(
e.label,
style: primaryTextStyle.copyWith(
color: filterCourseProvider.currentIndexRating ==
e.id.toString()
? Theme.of(context).colorScheme.background
: Theme.of(context).colorScheme.onPrimary,
letterSpacing: 0.5,
fontSize: getProportionateScreenWidth(12),
fontWeight: reguler,
),
),
],
),
selected: filterCourseProvider.currentIndexRating ==
e.id.toString(),
selectedColor:
Theme.of(context).brightness == Brightness.dark
? baruTextutih
: primaryColor,
onSelected: (_) {
filterCourseProvider.currentIndexRating =
e.id.toString();
},
elevation: 1,
),
)
.toList(),
),
SizedBox(
height: getProportionateScreenWidth(16),
),
],
),
),
bottomNavigationBar: bottomNav(),
);
}
}
class ItemChoiceFilter {
final String id;
final String icon;
final String label;
ItemChoiceFilter(this.id, this.icon, this.label);
}

View File

@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import '../../../size_config.dart';
import '../../../theme.dart';
class ListCategoryIcon extends StatelessWidget {
const ListCategoryIcon({
Key? key,
required this.title,
required this.iconFa,
required this.onTap,
}) : super(key: key);
final String title;
final Icon iconFa;
final Function()? onTap;
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(
left: getProportionateScreenWidth(21),
right: getProportionateScreenWidth(17),
bottom: getProportionateScreenWidth(11)),
child: InkWell(
onTap: onTap,
child: Row(
children: [
iconFa,
SizedBox(
width: getProportionateScreenWidth(10),
),
Expanded(
child: Html(
data: title,
style: {
"body": Style(
margin: Margins.zero,
padding:
HtmlPaddings.only(left: getProportionateScreenWidth(6)),
fontSize: FontSize(getProportionateScreenWidth(13)),
fontWeight: reguler,
letterSpacing: 0.5,
fontFamily: 'Poppins',
),
},
),
),
Icon(
Icons.keyboard_arrow_right,
size: 22,
),
SizedBox(height: getProportionateScreenHeight(25)),
],
),
),
);
}
}

View File

@ -0,0 +1,335 @@
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:initial_folder/helper/validator.dart';
import 'package:initial_folder/providers/categories_provider.dart';
import 'package:initial_folder/providers/theme_provider.dart';
import 'package:initial_folder/screens/home/components/body_comp/course_by_category.dart';
import 'package:initial_folder/size_config.dart';
import 'package:initial_folder/theme.dart';
import 'package:initial_folder/widgets/custom_navigator.dart';
import 'package:initial_folder/widgets/login_regist/custom_font_awesome.dart';
import 'package:initial_folder/widgets/search_and_filter_course.dart';
import 'package:provider/provider.dart';
import 'component/list_category_icon.dart';
import 'package:shimmer/shimmer.dart';
import 'package:initial_folder/providers/search_provider.dart' as searchProv;
import 'package:initial_folder/providers/filters_course_provider.dart'
as filterProv;
class SearchPage extends StatefulWidget {
SearchPage({Key? key}) : super(key: key);
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
final GlobalKey<NavigatorState> searchKey = GlobalKey<NavigatorState>();
bool _showShimmer = false;
@override
Widget build(BuildContext context) {
final themeProvider = Provider.of<ThemeProvider>(context);
CategoriesProvider categoriesProvider =
Provider.of<CategoriesProvider>(context);
return WillPopScope(
onWillPop: () async {
if (searchKey.currentState!.canPop()) {
searchKey.currentState!.pop();
return false;
}
return true;
},
child: Navigator(
key: searchKey,
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(
builder: (BuildContext context) {
if (settings.name == '/courseByCategory') {
return CourseByCategory(
name: categoriesProvider.nameCategories[0] ?? '',
categoryId: categoriesProvider.ids[0] ?? '',
subId: '',
);
}
return SafeArea(
child: Scaffold(
backgroundColor:
Theme.of(context).brightness == Brightness.dark
? twelveColor
: baruTextutih,
body: RefreshIndicator(
onRefresh: () async {
await categoriesProvider.getAllCategories();
},
child: SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Container(
margin: EdgeInsets.symmetric(vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: Padding(
padding: EdgeInsets.only(
left: getProportionateScreenWidth(16),
right: getProportionateScreenWidth(16),
),
child: GestureDetector(
onTap: () {
Provider.of<searchProv.SearchProvider>(
context,
listen: false)
.resetState();
Provider.of<
filterProv
.FilterCourseProvider>(
context,
listen: false)
.resetState();
Provider.of<
filterProv
.FilterCourseProvider>(
context,
listen: false)
.resetFilter();
Navigator.push(
context,
CustomNavigator(
child: SearchAndFilterCourse(),
),
);
},
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(10),
color: Theme.of(context).brightness ==
Brightness.dark
? seventeenColor
: secondaryColor.withOpacity(0.3),
),
height: 40,
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(
left: 13),
child: Icon(
FeatherIcons.search,
size: 20,
color: themeProvider.themeData == ThemeClass.darkmode
?primaryColor : primaryColorligtmode,
),
),
SizedBox(
width:
getProportionateScreenWidth(
10)),
Text(
'Cari kursus..',
style:
secondaryTextStyle.copyWith(
fontSize: 12,
color: Theme.of(context)
.brightness ==
Brightness.dark
? baruTextutih
.withOpacity(0.5)
: Colors.black
.withOpacity(0.5),
),
)
],
),
),
),
),
),
],
),
SizedBox(height: getProportionateScreenWidth(16)),
Container(
margin: EdgeInsets.only(
left: getProportionateScreenWidth(16)),
child: Text(
'Cari Berdasarkan Kategori',
style: thirdTextStyle.copyWith(
fontSize: getProportionateScreenWidth(16),
),
),
),
SizedBox(height: getProportionateScreenWidth(12)),
Container(
child: Consumer<CategoriesProvider>(
builder: (context, state, _) {
if (state.state == ResultState.Loading) {
return Shimmer.fromColors(
baseColor: Colors.white,
highlightColor: Colors.grey,
child: Column(
mainAxisAlignment:
MainAxisAlignment.start,
children: List.generate(
9,
(index) => Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0, horizontal: 25.0),
child: Row(
children: [
Container(
width: 23,
height: 23,
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.circular(20),
),
),
SizedBox(width: 15),
Expanded(
child: Container(
height: 10,
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.circular(
5),
),
),
),
],
),
),
),
),
);
} else if (state.state == ResultState.HasData) {
var categori = state.result;
return Column(
children: categori
.map(
(e) => ListCategoryIcon(
title: e.nameCategory ?? '',
iconFa: Icon(
fontAwesomeIconsFromString(
'FontAwesomeIcons.${iconCategory(e.fontAwesomeClass ?? '')}'),
size: getProportionateScreenWidth(
20),
),
onTap: () {
Navigator.push(
context,
CustomNavigator(
child: CourseByCategory(
name: e.nameCategory ?? '',
categoryId: e.id ?? '',
subId: '',
),
),
);
},
),
)
.toList(),
);
} else if (state.state == ResultState.NoData) {
return Center(child: Text(state.message));
} else if (state.state == ResultState.Error) {
if (!_showShimmer) {
Future.delayed(Duration(seconds: 5), () {
if (mounted) {
setState(() {
_showShimmer = true;
});
}
});
return Shimmer.fromColors(
baseColor: Colors.white,
highlightColor: Colors.grey,
child: Column(
mainAxisAlignment:
MainAxisAlignment.start,
children: List.generate(
9,
(index) => Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 25.0),
child: Row(
children: [
Container(
width: 23,
height: 23,
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.circular(
20),
),
),
SizedBox(width: 15),
Expanded(
child: Container(
height: 10,
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.circular(
5),
),
),
),
],
),
),
),
),
);
} else {
return Center(
child: Padding(
padding: EdgeInsets.only(
top: getProportionateScreenHeight(
70)),
child: Text(
"Internet lemah, mohon muat ulang",
),
),
);
}
} else {
return Center(
child: Text(
'Terjadi Kesalahan ',
style: primaryTextStyle,
),
);
}
}),
),
],
),
),
),
),
),
);
},
);
},
),
);
}
}
class Ic {
String name;
String tile;
Ic({required this.name, this.tile = 'MaterialIcons'});
}