BCS 102 - Lab Final Assignment (Guidelines and Rubric)
BCS 102 - Lab Final Assignment (Guidelines and Rubric)
Lab
Course Learning Outcomes
Page 1 of 65
Assignment Description and Requirements
You are asked to implement the below tasks, attach the code, and the screen shots that prove that
your solution cover all test cases.
Rubric
Criterion Needs Improvement Good Excellent
[0-7] [8-11] [12-15]
Q&A The student provided The student answered The student answered
poor answers and some of the questions all questions correctly
insufficient evidence of correctly and showed and showed genuine
contribution to the some contribution to contribution to the
assignment. the assignment. assignment.
Submission Instructions
1. This is a group-based assignment (5 members).
2. The assignment consists of two parts: Coding & Reporting part and Q&A session.
3. Coding Part:
a. Your code should work.
b. It should contain embedded comments.
c. You should cover all test cases.
d. You should provide screen shots of the mobile app screen showing the fields and
the app functionality.
e. You must submit two bugs and how you did locate and fix them using the IDE
debug functionality.
f. You should show understanding of your code during the Q&A to deserve the
report marks.
g. You must submit both Part 1 (Report) and Part 2 (Source code .dart file).
4. Q&A Part (Individual grade):
Page 2 of 65
a. Each student will be asked individually.
b. The questions cover theory and practice of the assignment.
5. Use this template for your final report submission. Fill out the names of students on the
front page (double-click the Excel table to edit).
6. Attach your report to the end of this document (copy and paste. Note: the code should be
submitted as text not image. In case of submitting the code as an image, the group will
get zero mark for this assignment).
Upload one submission per group including the report and the source code. In case of having
more than one submission per group, the group will lose the assignment mark.
Page 3 of 65
Introduction:
As universities grow and their programs expand, managing class schedules can become a
challenge. While printed schedules work, they are not always accessible or convenient for
students. This project aims to solve this problem by creating a mobile app that reads from a file
containing university timetable data and displays it in a user-friendly format.
The file containing the timetable data should be in a specific format that can be easily parsed
by the app. The data should contain information about the course name, course code,
instructor, classroom, and time of the class.
The app should have a clean and intuitive user interface that allows students to quickly view
their schedules without any confusion. The schedule should be displayed in a visual format,
such as a calendar or a grid, that clearly shows the time and location of each class.
The app should also allow students to filter their schedule by course, instructor, or classroom.
(Bonus Feature: 3 marks) In addition to the basic schedule display functionality, the app
should also include features such as reminders for upcoming classes, and the ability to add
notes or assignments related to specific classes.
The project should follow good programming practices and principles, including writing
clean, well-documented code and using proper coding standards. The app should also be
tested thoroughly to ensure it is reliable and error-free.
Sample UI App
Page 4 of 65
Page 5 of 65
Code:(for the code we split it to multiple files so it would be organized)
1. main.screen:
import 'package:app_cud/db/db_helper.dart';
import 'package:app_cud/services/constants.dart';
import 'package:app_cud/services/routes.dart';
import 'package:app_cud/UI/splash_screen.dart';
import 'package:app_cud/services/theme_services.dart';
import 'package:flutter/material.dart';
import 'package:get/get_navigation/src/root/get_material_app.dart';
import 'package:get_storage/get_storage.dart';
import 'package:google_fonts/google_fonts.dart';
Page 6 of 65
fontWeight: FontWeight.w300),
),
Page 7 of 65
.copyWith(
bodyLarge: const TextStyle(
color: kTextWhiteColor,
fontSize: 35.0,
fontWeight: FontWeight.bold),
headlineSmall: const TextStyle(
color: kTextWhiteColor,
fontSize: 22.0,
fontWeight: FontWeight.w500),
titleSmall: const TextStyle(
color: kTextWhiteColor,
fontSize: 18.0,
fontWeight: FontWeight.w300),
),
//input decoration theme for all our app
inputDecorationTheme: InputDecorationTheme(
//label style
labelStyle:
TextStyle(fontSize: 15.0, color: kTextLightColor, height: 0.5),
//hint style
hintStyle:
TextStyle(fontSize: 16.0, color: kTextBlackColor, height: 0.5),
//we are using underline input border
//not outline
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: kTextLightColor, width: 0.7),
),
border: UnderlineInputBorder(
borderSide: BorderSide(color: kTextLightColor),
),
disabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: kTextLightColor),
),
//on focus change color
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: kPrimaryColor,
),
),
//color change when user enters wrong information,
//we use validation for this process
errorBorder: UnderlineInputBorder(
borderSide: BorderSide(color: kErrorBorderColor, width: 1.2),
),
//same on focus error color
focusedErrorBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: kErrorBorderColor,
width: 1.2,
),
),
),
),
themeMode: ThemeServices().theme,
//initial route is splash screen
Page 8 of 65
// as in the first screen
initialRoute: SplashScreen.routeName,
//define the routes file here to access the routes any where
routes: routes,
);
}
}
2. UI:
a. splash_screen.dart:
import 'package:app_cud/UI/login_screen.dart';
import 'package:app_cud/services/constants.dart';
import 'package:flutter/material.dart';
const SplashScreen({super.key});
@override
Widget build(BuildContext context) {
//we use future to go from one screen to other via duration time
Future.delayed(const Duration(seconds: 5), () {
//no return when user is on login screen and press back, it will not
return the user to the splash screen
Navigator.pushNamedAndRemoveUntil(
context, LoginScreen.routeName, (route) => false);
});
Page 9 of 65
children: [
Text(
'Canadian University',
style: Theme.of(context)
.textTheme
.bodyLarge!
.copyWith(color: kTextWhiteColor),
),
],
),
SizedBox(
height: kDefaultPadding / 6,
),
Text(
'of Dubai',
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
color: kTextWhiteColor,
),
),
],
),
),
)
],
),
);
}
}
b. login_screen:
import 'package:app_cud/UI/home_screen.dart';
import 'package:app_cud/controllers/course_controller.dart';
import 'package:app_cud/services/constants.dart';
import 'package:app_cud/services/theme_services.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'widgets/custom_buttons.dart';
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
Page 10 of 65
//charges current state
@override
void initState() {
super.initState();
_passwordVisible = true;
}
@override
Widget build(BuildContext context) {
return GestureDetector(
//when user taps anywhere on the screen, keyboard hides
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
child: Scaffold(
appBar: AppBar(
actions: [
GestureDetector(
onTap: () {
ThemeServices().switchTheme();
},
child: Icon(Get.isDarkMode ?
Icons.wb_sunny:Icons.nightlight_round,
size: 20,
),
),
SizedBox(
width: 20,
),
],
),
body: Container(
child: ListView(
children: [
//divide the body into two half
Container(
decoration: BoxDecoration(
color: kPrimaryColor,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(kDefaultPadding * 1.5),
bottomLeft: Radius.circular(kDefaultPadding * 1.5),
),
),
child: SizedBox(
//use media query in order to fit all sizes in same manner
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height / 2.3,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/splash.png',
height: 150.0,
width: 150.0,
),
Page 11 of 65
const SizedBox(
height: kDefaultPadding / 2,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Hi ',
style: Theme.of(context)
.textTheme
.bodyLarge!
.copyWith(
fontWeight: FontWeight.w200,
color: kTextWhiteColor,
),
),
Text(
'Student',
style: Theme.of(context)
.textTheme
.bodyLarge!
.copyWith(
color: kTextWhiteColor,
),
),
],
),
const SizedBox(
height: kDefaultPadding / 6,
),
Text(
'Sign in to continue',
style:
Theme.of(context).textTheme.titleSmall!.copyWith(
color: kTextWhiteColor,
),
),
],
),
),
),
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(kDefaultPadding * 1.5),
topRight: Radius.circular(kDefaultPadding * 1.5),
),
),
child: Padding(
padding: const EdgeInsets.all(kDefaultPadding),
child: Column(
children: [
Page 12 of 65
Form(
key: _formKey,
child: Column(
children: [
sizedBox,
buildEmailField(),
sizedBox,
buildPasswordField(),
sizedBox,
DefaultButton(
onPress: () async {
if (_formKey.currentState!.validate()) {
_courseController.getCourses();
await
Navigator.pushNamedAndRemoveUntil(context,
HomeScreen.routeName, (route) =>
false);
}
},
title: 'SIGN IN',
iconData: Icons.arrow_forward_outlined,
),
sizedBox,
Align(
alignment: Alignment.bottomRight,
child: Text(
'Forgot Password',
textAlign: TextAlign.end,
style: TextStyle(
color: kPrimaryColor, fontSize: 15.0),
),
)
],
),
),
],
),
),
)
],
),
)),
);
}
TextFormField buildEmailField() {
return TextFormField(
textAlign: TextAlign.start,
keyboardType: TextInputType.emailAddress,
style: TextStyle(
color: Get.isDarkMode?Colors.white:Colors.black,
fontSize: 17.0,
fontWeight: FontWeight.w300,
),
decoration: const InputDecoration(
Page 13 of 65
labelText: 'Email Address',
floatingLabelBehavior: FloatingLabelBehavior.always,
isDense: true,
),
validator: (value) {
//for validation
RegExp regExp = new RegExp(emailPattern);
if (value == null || value.isEmpty) {
return 'please enter some text';
//if it does not match the pattern, like
//it does not contain @
} else if (!regExp.hasMatch(value)) {
'Please enter a valid email address';
}
},
);
}
TextFormField buildPasswordField() {
return TextFormField(
obscureText: _passwordVisible,
textAlign: TextAlign.start,
keyboardType: TextInputType.visiblePassword,
style: TextStyle(
color: Get.isDarkMode?Colors.white:Colors.black,
fontSize: 17.0,
fontWeight: FontWeight.w300,
),
decoration: InputDecoration(
labelText: 'Password',
floatingLabelBehavior: FloatingLabelBehavior.always,
isDense: true,
suffixIcon: IconButton(
onPressed: () {
setState(() {
_passwordVisible = !_passwordVisible;
});
},
icon: Icon(
_passwordVisible
? Icons.visibility
: Icons.visibility_off_outlined,
),
iconSize: kDefaultPadding,
),
),
validator: (value) {
if (value!.length < 5) {
return 'Must be more than 5 characters';
}
},
);
}
}
Page 14 of 65
c. home_screen.dart:
import 'package:app_cud/UI/assignment_screen.dart';
import 'package:app_cud/UI/calendar_screen.dart';
import 'package:app_cud/UI/my_profile.dart';
import 'package:app_cud/controllers/task_controller.dart';
import 'package:app_cud/services/constants.dart';
import 'package:app_cud/services/theme_services.dart';
import 'package:app_cud/UI/widgets/course_tile.dart';
import 'package:flutter/material.dart';
import
'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:app_cud/controllers/course_controller.dart';
import '../models/course.dart';
import 'widgets/student_data.dart';
@override
State<HomeScreen> createState() => _HomeScreenState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
GestureDetector(
onTap: () {
ThemeServices().switchTheme();
},
child: Icon(
Get.isDarkMode ? Icons.wb_sunny : Icons.nightlight_round,
size: 20,
),
),
Page 15 of 65
SizedBox(
width: 20,
),
],
),
body: Column(
children: [
//we divide the screen into two
//fixed height for first half
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height / 4.5,
decoration: BoxDecoration(
color: Get.isDarkMode? kPrimaryColorDark:kPrimaryColor,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(kDefaultPadding * 1.6),
bottomLeft: Radius.circular(kDefaultPadding * 1.6),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
StudentName(
studentName: 'Someone',
),
kHalfSisedBox,
StudentID(studentID: 'ID: 20220002456'),
kHalfSisedBox,
StudentYear(studentYear: 'SP 2022-2023'),
],
),
kHalfSisedBox,
StudentPicture(
picAddress: 'assets/images/splash.png',
onPress: () {
// go to profile detail screen
Navigator.pushNamed(
context, MyProfileScreen.routeName);
})
],
),
Expanded(
child: Align(
alignment: FractionalOffset.bottomCenter,
child: Padding(
padding: EdgeInsets.only(bottom: 10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Page 16 of 65
//Time Table Button
InkWell(
onTap: () {},
child: Container(
width: MediaQuery.of(context).size.width / 3.33,
height: MediaQuery.of(context).size.height / 15,
decoration: BoxDecoration(
color: Get.isDarkMode?
kDarkGreyColor:kOtherColor,
borderRadius: BorderRadius.circular(200),
),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
Text(
'Time Table',
style: Theme.of(context)
.textTheme
.titleSmall!
.copyWith(
fontWeight: FontWeight.w500,
color: Get.isDarkMode?
kOtherColor:kPrimaryColor),
),
],
),
),
),
//Calender Button
InkWell(
onTap: () {
Navigator.pushNamed(
context, CalendarScreen.routeName);
},
child: Container(
width: MediaQuery.of(context).size.width / 3.33,
height: MediaQuery.of(context).size.height / 15,
decoration: BoxDecoration(
color: Get.isDarkMode?
kSecondaryColorDark:kSecondaryColor,
borderRadius: BorderRadius.circular(200),
),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
Text(
'Calender',
style: Theme.of(context)
.textTheme
.titleSmall!
.copyWith(color: kOtherColor),
),
],
Page 17 of 65
),
),
),
//Assignment Button
InkWell(
onTap: () async {
_taskController.getTasks();
await Navigator.pushNamed(
context, AssignmentScreen.routeName);
},
child: Container(
width: MediaQuery.of(context).size.width / 3.33,
height: MediaQuery.of(context).size.height / 15,
decoration: BoxDecoration(
color: Get.isDarkMode?
kSecondaryColorDark:kSecondaryColor,
borderRadius: BorderRadius.circular(200),
),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
Text(
'Assignments',
style: Theme.of(context)
.textTheme
.titleSmall!
.copyWith(color: kOtherColor),
),
],
),
),
),
],
),
),
),
)
],
),
),
Page 18 of 65
onTap: () {
// Update the state to show the tasks for the selected
day.
setState(() {
_selectedDay = _daysOfWeek[index];
});
},
child: Container(
width: 80,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: _daysOfWeek[index] == _selectedDay
? Get.isDarkMode?
kPrimaryColorDark:kPrimaryColor
: Colors.transparent,
),
child: Text(
_daysOfWeek[index],
style: GoogleFonts.lato(
textStyle: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: _daysOfWeek[index] == _selectedDay
? Colors.white
: kTextLightColor,
),
),
),
),
);
},
),
),
SizedBox(
height: 20,
),
Expanded(
child: Obx(() {
final List<Course> courses = coursesController.courses;
return ListView.builder(
itemCount: courses.length,
itemBuilder: (context, index) {
final Course course = courses[index];
if(course.day == _selectedDay){
return AnimationConfiguration.staggeredList(
position: index,
child: SlideAnimation(
child: FadeInAnimation(
child: Row(
children: [
GestureDetector(
child: CourseTile(
course),
)
Page 19 of 65
],
),
),
),
);
}else{
return Container();
}
});
}),
)
],
)),
],
),
);
}
}
d. calender_screen:
import 'package:app_cud/UI/assignment_screen.dart';
import 'package:app_cud/UI/home_screen.dart';
import 'package:app_cud/UI/my_profile.dart';
import 'package:app_cud/controllers/course_controller.dart';
import 'package:app_cud/controllers/task_controller.dart';
import 'package:app_cud/services/constants.dart';
import 'package:app_cud/services/theme_services.dart';
import 'package:app_cud/UI/widgets/calendar_widget.dart';
import 'package:app_cud/UI/widgets/student_data.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart';
import 'package:table_calendar/table_calendar.dart';
@override
State<CalendarScreen> createState() => _CalendarScreenState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
GestureDetector(
onTap: () {
ThemeServices().switchTheme();
Page 20 of 65
},
child: Icon(
Get.isDarkMode ? Icons.wb_sunny : Icons.nightlight_round,
size: 20,
),
),
SizedBox(
width: 20,
),
],
),
body: Column(
children: [
//we divide the screen into two
//fixed height for first half
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height / 4.5,
decoration: BoxDecoration(
color: Get.isDarkMode? kPrimaryColorDark:kPrimaryColor,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(kDefaultPadding * 1.6),
bottomLeft: Radius.circular(kDefaultPadding * 1.6),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
StudentName(
studentName: 'Someone',
),
kHalfSisedBox,
StudentID(studentID: 'ID: 20220002456'),
kHalfSisedBox,
StudentYear(studentYear: 'SP 2022-2023'),
],
),
kHalfSisedBox,
StudentPicture(
picAddress: 'assets/images/splash.png',
onPress: () {
// go to profile detail screen
Navigator.pushNamed(
context, MyProfileScreen.routeName);
})
],
),
Expanded(
child: Align(
Page 21 of 65
alignment: FractionalOffset.bottomCenter,
child: Padding(
padding: EdgeInsets.only(bottom: 10.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
//Time Table Button
InkWell(
onTap: () async {
_courseController.getCourses();
await Navigator.pushNamed(
context, HomeScreen.routeName);
},
child: Container(
width: MediaQuery.of(context).size.width /
3.33,
height:
MediaQuery.of(context).size.height /
15,
decoration: BoxDecoration(
color: Get.isDarkMode?
kSecondaryColorDark:kSecondaryColor,
borderRadius:
BorderRadius.circular(200),
),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
Text(
'Time Table',
style: Theme.of(context)
.textTheme
.titleSmall!
.copyWith(color: kOtherColor),
),
],
),
),
),
//Calender Button
InkWell(
onTap: () {},
child: Container(
width: MediaQuery.of(context).size.width /
3.33,
height:
MediaQuery.of(context).size.height /
15,
decoration: BoxDecoration(
color: Get.isDarkMode?
kDarkGreyColor:kOtherColor,
borderRadius:
BorderRadius.circular(200),
Page 22 of 65
),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
Text(
'Calender',
style: Theme.of(context)
.textTheme
.titleSmall!
.copyWith(fontWeight:FontWeight.
w500,color: Get.isDarkMode? kOtherColor:kPrimaryColor),
),
],
),
),
),
//Assignment Button
InkWell(
onTap: () async {
_taskController.getTasks();
await Navigator.pushNamed(
context, AssignmentScreen.routeName);
},
child: Container(
width: MediaQuery.of(context).size.width /
3.33,
height:
MediaQuery.of(context).size.height /
15,
decoration: BoxDecoration(
color: Get.isDarkMode?
kSecondaryColorDark:kSecondaryColor,
borderRadius:
BorderRadius.circular(200),
),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
Text(
'Assignments',
style: Theme.of(context)
.textTheme
.titleSmall!
.copyWith(color: kOtherColor),
),
],
),
),
),
],
))),
)
Page 23 of 65
],
),
),
Expanded(child: CalendarPage()),
],
));
}
}
e. assignment_screen.dart:
import 'package:app_cud/UI/add_task_bar.dart';
import 'package:app_cud/UI/calendar_screen.dart';
import 'package:app_cud/UI/home_screen.dart';
import 'package:app_cud/UI/my_profile.dart';
import 'package:app_cud/models/task.dart';
import 'package:app_cud/services/constants.dart';
import 'package:app_cud/services/theme_services.dart';
import 'package:app_cud/UI/widgets/add_button.dart';
import 'package:app_cud/UI/widgets/student_data.dart';
import 'package:app_cud/UI/widgets/task_tile.dart';
import 'package:date_picker_timeline/date_picker_timeline.dart';
import 'package:flutter/material.dart';
import
'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_navigation/get_navigation.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import '../controllers/task_controller.dart';
@override
State<AssignmentScreen> createState() => _AssignmentScreenState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(
color: kOtherColor,
),
actions: [
GestureDetector(
Page 24 of 65
onTap: () {
ThemeServices().switchTheme();
},
child: Icon(
Get.isDarkMode ? Icons.wb_sunny : Icons.nightlight_round,
size: 20,
color: kOtherColor,
),
),
SizedBox(
width: 20,
),
],
),
body: Column(
children: [
//we divide the screen into two
//fixed height for first half
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height / 4.5,
decoration: BoxDecoration(
color: Get.isDarkMode?kPrimaryColorDark:kPrimaryColor,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(kDefaultPadding * 1.6),
bottomLeft: Radius.circular(kDefaultPadding * 1.6),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
StudentName(
studentName: 'Someone',
),
kHalfSisedBox,
StudentID(studentID: 'ID: 20220002456'),
kHalfSisedBox,
StudentYear(studentYear: 'SP 2022-2023'),
],
),
kHalfSisedBox,
StudentPicture(
picAddress: 'assets/images/splash.png',
onPress: () {
Navigator.pushNamed(
context, MyProfileScreen.routeName);
})
],
),
Page 25 of 65
Expanded(
child: Align(
alignment: FractionalOffset.bottomCenter,
child: Padding(
padding: EdgeInsets.only(bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
//Time Table Button
InkWell(
onTap: () {
Navigator.pushNamed(
context, HomeScreen.routeName);
},
child: Container(
width:
MediaQuery.of(context).size.width / 3.33,
height:
MediaQuery.of(context).size.height / 15,
decoration: BoxDecoration(
color: Get.isDarkMode?
kSecondaryColorDark:kSecondaryColor,
borderRadius: BorderRadius.circular(200),
),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
Text(
'Time Table',
style: Theme.of(context)
.textTheme
.titleSmall!
.copyWith(color: kOtherColor),
),
],
),
),
),
//Calender Button
InkWell(
onTap: () {
Navigator.pushNamed(
context, CalendarScreen.routeName);
},
child: Container(
width:
MediaQuery.of(context).size.width / 3.33,
height:
MediaQuery.of(context).size.height / 15,
decoration: BoxDecoration(
color: Get.isDarkMode?
kSecondaryColorDark:kSecondaryColor,
borderRadius: BorderRadius.circular(200),
),
Page 26 of 65
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
Text(
'Calender',
style: Theme.of(context)
.textTheme
.titleSmall!
.copyWith(color: kOtherColor),
),
],
),
),
),
//Assignment Button
InkWell(
onTap: () {},
child: Container(
width:
MediaQuery.of(context).size.width / 3.33,
height:
MediaQuery.of(context).size.height / 15,
decoration: BoxDecoration(
color: Get.isDarkMode?
kDarkGreyColor:kOtherColor,
borderRadius: BorderRadius.circular(200),
),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
Text(
'Assignments',
style: Theme.of(context)
.textTheme
.titleSmall!
.copyWith(
fontWeight: FontWeight.w500,
color: Get.isDarkMode?
kOtherColor:kPrimaryColor),
),
],
),
),
),
],
),),),
)
],
),
),
Expanded(
child: Column(
Page 27 of 65
children: [
Container(
margin: const EdgeInsets.only(left: 20, right: 20, top: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
DateFormat.yMMMMd().format(DateTime.now()),
style: Theme.of(context)
.textTheme
.bodyLarge!
.copyWith(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Get.isDarkMode
? Colors.grey[400]
: Colors.grey,
),
),
Text(
"Today",
style: Theme.of(context)
.textTheme
.bodyLarge!
.copyWith(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Get.isDarkMode
? Colors.white
: Colors.black),
),
],
),
),
AddButton(
label: '+ Add Task',
onPress: () async {
await Navigator.pushNamed(
context, AddTaskPage.routeName);
_taskController.getTasks();
})
],
),
),
Container(
margin: const EdgeInsets.only(top: 20, left: 20),
child: DatePicker(
DateTime.now(),
height: 100,
width: 80,
initialSelectedDate: DateTime.now(),
Page 28 of 65
selectionColor: Get.isDarkMode?
kPrimaryColorDark:kPrimaryColor,
selectedTextColor: kTextWhiteColor,
dateTextStyle: GoogleFonts.lato(
textStyle: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: kTextLightColor,
)),
dayTextStyle: GoogleFonts.lato(
textStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: kTextLightColor,
)),
monthTextStyle: GoogleFonts.lato(
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: kTextLightColor,
)),
onDateChange: (date) {
setState(() {
_selectedDate=date;
});
},
),
),
SizedBox(
height: 15,
),
Expanded(
child: Obx(() {
return ListView.builder(
itemCount: _taskController.taskList.length,
itemBuilder: (_, index) {
Task task = _taskController.taskList[index];
DateTime startDate =
DateFormat.yMd().parse(task.startDate!);
DateTime endDate =
DateFormat.yMd().parse(task.endDate!);
if
(_selectedDate.isAfter(startDate.subtract(Duration(days: 1))) &&
_selectedDate.isBefore(endDate.add(Duration(days: 1)))) {
return AnimationConfiguration.staggeredList(
position: index,
child: SlideAnimation(
child: FadeInAnimation(
child: Row(
children: [
GestureDetector(
onTap: () {
_showBottomSheet(context, task);
},
Page 29 of 65
child: TaskTile(task),
)
],
),
),
),
);
} else {
return Container();
}
});
}),
)
],
),
),
],
),
);
}
_bottomSheetButton(
label: 'Delete Task',
onTap: () {
Page 30 of 65
_taskController.delete(task);
Get.back();
},
clr:Colors.red[300]!,
context: context),
SizedBox(height: 20,),
_bottomSheetButton(
label: 'Close',
onTap: () {
Get.back();
},
clr:Colors.red[300]!,
isClose: true,
context: context),
SizedBox(height: 10,)
],
),
),
);
}
_bottomSheetButton(
{required String label,
required Function()? onTap,
required Color clr,
bool isClose = false,
required BuildContext context}) {
return GestureDetector(
onTap: onTap,
child: Container(
margin: const EdgeInsets.symmetric(vertical: 4),
height: 55,
width: MediaQuery.of(context).size.width * 0.9,
decoration: BoxDecoration(
border:
Border.all(width: 2, color: isClose == true ? Get.isDarkMode?
Colors.grey[600]!:Colors.grey[300]! : clr),
borderRadius: BorderRadius.circular(20),
color: isClose == true ? Colors.transparent : clr,
),
child: Center(
child: Text(
label,
style: isClose==true?GoogleFonts.lato(
textStyle: TextStyle(fontSize: 16,fontWeight:
FontWeight.w400,color: Get.isDarkMode?
Colors.white:Colors.black)):GoogleFonts.lato(
textStyle: TextStyle(fontSize: 16,fontWeight:
FontWeight.w400,color: Colors.white)),
),
),
),
);
}
}
Page 31 of 65
f. add_task_bar.dart:
import 'package:app_cud/controllers/task_controller.dart';
import 'package:app_cud/models/task.dart';
import 'package:app_cud/services/constants.dart';
import 'package:app_cud/services/theme_services.dart';
import 'package:app_cud/UI/widgets/add_button.dart';
import 'package:app_cud/UI/widgets/input_field.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_navigation/get_navigation.dart';
import 'package:intl/intl.dart';
@override
State<AddTaskPage> createState() => _AddTaskPageState();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
hideKeyboard(context);
},
child: Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(
color: Get.isDarkMode ? kOtherColor : kDarkGreyColor,
),
automaticallyImplyLeading: true,
backgroundColor: Colors.transparent,
actions: [
Page 32 of 65
GestureDetector(
onTap: () {
ThemeServices().switchTheme();
},
child: Icon(
Get.isDarkMode ? Icons.wb_sunny : Icons.nightlight_round,
size: 20,
color: Get.isDarkMode ? kOtherColor : kDarkGreyColor,
),
),
SizedBox(
width: 20,
),
],
),
body: Container(
padding: const EdgeInsets.only(left: 20, right: 20, top: 0),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 20,
),
Text(
'Add Task',
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Get.isDarkMode ? Colors.white : Colors.black),
),
MyInputField(
title: 'Title',
hint: 'Enter title here',
controller: _titleController,
),
MyInputField(
title: 'Note',
hint: 'Enter your note',
controller: _noteController,
),
Row(
children: [
Expanded(
child: MyInputField(
title: "Start Date",
hint: DateFormat.yMd().format(_selectedStartDate),
widget: IconButton(
onPressed: () {
_getDateFromUser(isStartDate: true);
},
icon: Icon(
Icons.calendar_today_outlined,
color: kTextLightColor,
),
Page 33 of 65
),
),
),
SizedBox(
width: 12,
),
Expanded(
child: MyInputField(
title: "start Time",
hint: _startTime,
widget: IconButton(
onPressed: () {
_getTimeFromUser(isStartTime: false);
},
icon: Icon(Icons.access_time_rounded),
color: kTextLightColor,
),
),
),
],
),
Row(
children: [
Expanded(
child: MyInputField(
title: "End Date",
hint: DateFormat.yMd().format(_selectedEndDate),
widget: IconButton(
onPressed: () {
_getDateFromUser(isStartDate: false);
},
icon: Icon(
Icons.calendar_today_outlined,
color: kTextLightColor,
),
),
),
),
SizedBox(
width: 12,
),
Expanded(
child: MyInputField(
title: "End Time",
hint: _endTime,
widget: IconButton(
onPressed: () {
_getTimeFromUser(isStartTime: false);
},
icon: Icon(Icons.access_time_rounded),
color: kTextLightColor,
),
),
),
],
Page 34 of 65
),
MyInputField(
title: 'Remind',
hint: "$_selectedReminder minutes early",
widget: DropdownButton(
icon: Icon(
Icons.keyboard_arrow_down,
color: kTextLightColor,
),
iconSize: 32,
elevation: 4,
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Get.isDarkMode
? Colors.grey[100]
: Colors.grey[700],
),
underline: Container(
height: 0,
),
onChanged: (String? newValue) {
setState(() {
_selectedReminder = int.parse(newValue!);
});
},
items:
reminderList.map<DropdownMenuItem<String>>((int value)
{
return DropdownMenuItem<String>(
value: value.toString(),
child: Text(value.toString()),
);
}).toList(),
),
),
SizedBox(
height: 18,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Color",
style:
Theme.of(context).textTheme.bodyLarge!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w400,
color: Get.isDarkMode
? Colors.white
: Colors.black,
Page 35 of 65
),
),
SizedBox(
height: 8.0,
),
Wrap(
children: List<Widget>.generate(6, (int index) {
return GestureDetector(
onTap: () {
setState(() {
_selectedColor = index;
});
},
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: CircleAvatar(
radius: 14,
backgroundColor: index == 0 ? kOrangeColor :
index == 1 ? Colors.amber[700] :index == 2 ? Colors.green[900] : index == 3 ?
kBlueColor : index == 4 ? Colors.purple[600] : kPinkColor,
child: _selectedColor == index
? Icon(
Icons.done,
color: Colors.white,
size: 16,
)
: Container(),
),
),
);
}),
),
],
),
AddButton(
label: 'Create Task',
onPress: () {
_validateData();
_taskController.getTasks();
},
)
],
)
],
),
),
),
),
);
}
_validateData() {
if (_titleController.text.isNotEmpty && _noteController.text.isNotEmpty &&
_selectedStartDate.isBefore(_selectedEndDate)) {
_addTaskToDb();
Page 36 of 65
Get.back();
} else if (_titleController.text.isEmpty || _noteController.text.isEmpty)
{
Get.snackbar(
"Required",
'All fields are required !',
margin: EdgeInsets.only(left: 20, right: 20, bottom: 20),
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Get.isDarkMode?Colors.grey[900]:Colors.grey[200],
colorText: kPrimaryColor,
icon: Icon(
Icons.warning_amber_rounded,
color: kPrimaryColor,
),
);
} else if(_selectedStartDate.isAfter(_selectedEndDate)){
Get.snackbar(
"Required",
'End date to be after Start date',
margin: EdgeInsets.only(left: 20, right: 20, bottom: 20),
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Get.isDarkMode?Colors.grey[900]:Colors.grey[200],
colorText: kPrimaryColor,
icon: Icon(
Icons.warning_amber_rounded,
color: kPrimaryColor,
),
);
}
}
_addTaskToDb() async {
var value = await _taskController.addTask(
task: Task(
note: _noteController.text,
title: _titleController.text,
startDate: DateFormat.yMd().format(_selectedStartDate),
endDate: DateFormat.yMd().format(_selectedEndDate),
startTime: _startTime,
endTime: _endTime,
remind: _selectedReminder,
color: _selectedColor,
isCompleted: 0,
));
print("My id is " + "$value");
}
Page 37 of 65
} else if (isStartDate == true) {
setState(() {
_selectedStartDate = _pickedDate;
});
} else if (isStartDate == false) {
setState(() {
_selectedEndDate = _pickedDate;
});
}
}
_showTimePicker() {
return showTimePicker(
initialEntryMode: TimePickerEntryMode.input,
context: context,
initialTime: TimeOfDay(
hour: int.parse(_startTime.split(":")[0]),
minute: int.parse(_startTime.split(":")[1].split(' ')[0]),
),
);
}
g. Widgets:
i. task_tile.dart:
import 'package:app_cud/models/task.dart';
import 'package:app_cud/services/constants.dart';
import 'package:flutter/cupertino.dart';
Page 38 of 65
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
@override
Widget build(BuildContext context) {
return Container(
padding:
EdgeInsets.symmetric(horizontal: 20),
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.only(bottom: 12),
child: Container(
padding: EdgeInsets.all(16),
// width: SizeConfig.screenWidth * 0.78,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: _getBGClr(task?.color??0),
),
child: Row(children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
task?.title??"",
style: GoogleFonts.lato(
textStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
SizedBox(
height: 12,
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
children: [
Text(
"Start:",
style: GoogleFonts.lato(
textStyle:
TextStyle(fontSize: 13, color: Colors.grey[100]),
),
),
SizedBox(width: 8),
Icon(
Icons.date_range_rounded,
color: Colors.grey[200],
size: 18,
Page 39 of 65
),
SizedBox(width: 4),
Text(
"${task!.startDate}",
style: GoogleFonts.lato(
textStyle:
TextStyle(fontSize: 13, color: Colors.grey[100]),
),
),
],
),
SizedBox(width: 20,),
Row(
children: [
Icon(
Icons.access_time_rounded,
color: Colors.grey[200],
size: 18,
),
SizedBox(width: 4),
Text(
"${task!.startTime}",
style: GoogleFonts.lato(
textStyle:
TextStyle(fontSize: 13, color: Colors.grey[100]),
),
),
],
),
],
),
SizedBox(
height: 12,
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"End: ",
style: GoogleFonts.lato(
textStyle:
TextStyle(fontSize: 13, color: Colors.grey[100]),
),
),
SizedBox(width: 8),
Row(
children: [
Icon(
Icons.date_range_rounded,
color: Colors.grey[200],
size: 18,
),
SizedBox(width: 4),
Text(
"${task!.endDate}",
Page 40 of 65
style: GoogleFonts.lato(
textStyle:
TextStyle(fontSize: 13, color: Colors.grey[100]),
),
),
],
),
SizedBox(width: 20,),
Row(
children: [
Icon(
Icons.access_time_rounded,
color: Colors.grey[200],
size: 18,
),
SizedBox(width: 4),
Text(
"${task!.endTime}",
style: GoogleFonts.lato(
textStyle:
TextStyle(fontSize: 13, color: Colors.grey[100]),
),
),
],
),
],
),
SizedBox(height: 12),
Text(
task?.note??"",
style: GoogleFonts.lato(
textStyle: TextStyle(fontSize: 15, color:
Colors.grey[100]),
),
),
],
),
),
Container(
margin: EdgeInsets.symmetric(horizontal: 10),
height: 60,
width: 0.5,
color: Colors.grey[200]!.withOpacity(0.7),
),
RotatedBox(
quarterTurns: 3,
child: Text(
task!.isCompleted == 1 ? "COMPLETED" : "TODO",
style: GoogleFonts.lato(
textStyle: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
Page 41 of 65
),
]),
),
);
}
_getBGClr(int no) {
switch (no) {
case 0:
return kOrangeColor;
case 1:
return Colors.amber[700];
case 2:
return Colors.green[900];
case 3:
return kBlueColor;
case 4:
return Colors.purple[600];
case 5:
return kPinkColor;
default:
return kOrangeColor;
}
}
}
ii. courses_tile.dart:
import 'package:app_cud/models/course.dart';
import 'package:app_cud/services/constants.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
@override
Widget build(BuildContext context) {
return Container(
padding:
EdgeInsets.symmetric(horizontal: 20),
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.only(bottom: 12),
child: Container(
padding: EdgeInsets.all(16),
// width: SizeConfig.screenWidth * 0.78,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: Get.isDarkMode? kPrimaryColorDark:kPrimaryColor,
),
child: Row(children: [
Expanded(
child: Column(
Page 42 of 65
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${course!.name}",
style: GoogleFonts.lato(
textStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
SizedBox(
height: 12,
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.access_time_rounded,
color: Colors.grey[200],
size: 18,
),
SizedBox(width: 4),
Text(
"${course!.start_time} - ${course!.end_time}",
style: GoogleFonts.lato(
textStyle:
TextStyle(fontSize: 13, color: Colors.grey[100]),
),
),
],
),
SizedBox(height: 12),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: MediaQuery.of(context).size.width/4,
child: Row(
children: [
Icon(
Icons.meeting_room_rounded,
color: Colors.grey[200],
size: 18,
),
SizedBox(width: 4),
Text(
"${course!.class_loc}",
style: GoogleFonts.lato(
textStyle:
TextStyle(fontSize: 13, color:
Colors.grey[100]),
),
),
],
Page 43 of 65
),
),
SizedBox(width: 20,),
Container(
width: MediaQuery.of(context).size.width/2.5,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
Icons.person_rounded,
color: Colors.grey[200],
size: 18,
),
SizedBox(width: 4),
Expanded(
child: Text(
"${course!.instructor}",
style: GoogleFonts.lato(
textStyle: TextStyle(fontSize: 13, color:
Colors.grey[100]),
),
),
),
],
),
),
],
),
],
),
),
Column(
children: [
Container(
margin: EdgeInsets.symmetric(horizontal: 10),
height: 60,
width: 0.5,
color: Colors.grey[200]!.withOpacity(0.7),
),
],
),
Column(
children: [
RotatedBox(
quarterTurns: 3,
child: Text(
'${course!.course_id}',
style: GoogleFonts.lato(
textStyle: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
),
Page 44 of 65
],
),
]),
),
);
}
}
iii. student_data.dart:
import 'package:app_cud/services/constants.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@override
Widget build(BuildContext context) {
return Row(
children: [
Text(
'Hi ',
style: Theme.of(context).textTheme.headlineSmall!.copyWith(
fontWeight: FontWeight.w200,
color: kTextWhiteColor,
),
),
Text(
studentName,
style: Theme.of(context).textTheme.headlineSmall!.copyWith(
fontWeight: FontWeight.w500,
color: kTextWhiteColor,
),
),
],
);
}
}
@override
Widget build(BuildContext context) {
return Text(
studentID,
style: Theme.of(context).textTheme.titleSmall!.copyWith(
fontSize: 14.0,
color: kTextWhiteColor,
),
);
}
Page 45 of 65
}
@override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 20,
decoration: BoxDecoration(
color: Get.isDarkMode?kDarkGreyColor:kOtherColor,
borderRadius: BorderRadius.circular(kDefaultPadding),
),
child: Center(
child: Text(
studentYear,
style: TextStyle(
fontSize: 12.0,
color: Get.isDarkMode?kTextWhiteColor:kTextBlackColor,
fontWeight: FontWeight.w200),
),
),
);
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onPress,
child: CircleAvatar(
minRadius: 50.0,
maxRadius: 50.0,
backgroundColor: Get.isDarkMode?kSecondaryColorDark:kSecondaryColor,
backgroundImage:
AssetImage(picAddress),
),
);
}
}
iv. input_field:
import 'package:app_cud/services/constants.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get_core/src/get_main.dart';
Page 46 of 65
import 'package:get/get_navigation/get_navigation.dart';
import 'package:get/get_utils/get_utils.dart';
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w400,
color: Get.isDarkMode?Colors.white:Colors.black,
),
),
Container(
height: 60,
margin: EdgeInsets.only(top: 8.0),
padding: EdgeInsets.only(left: 14),
decoration: BoxDecoration(
border: Border.all(
color: kContainerColor,
width: 1.0,
),
borderRadius: BorderRadius.circular(12)
),
child: Row(
children:[
Expanded(
child: TextFormField(
readOnly: widget==null?false:true,
autofocus: false,
cursorColor: Get.isDarkMode?
Colors.grey[100]:Colors.grey[700],
controller: controller,
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontSize: 16,
fontWeight: FontWeight.w400,
color: Get.isDarkMode?
Colors.grey[100]:Colors.grey[700],
),
decoration: InputDecoration(
hintText: hint,
Page 47 of 65
hintStyle:
Theme.of(context).textTheme.bodyLarge!.copyWith(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Get.isDarkMode?
Colors.grey[100]:Colors.grey[400],
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: context.theme.backgroundColor,
width: 0,
)
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: context.theme.backgroundColor,
width: 0,
)
),
),
),
),
widget==null?Container():Container(child: widget,)
]
),
)
],
),
);
}
}
v. course_button:
import 'package:app_cud/services/constants.dart';
import 'package:flutter/material.dart';
const DefaultButton(
{super.key,
required this.onPress,
required this.title,
required this.iconData});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onPress,
child: Container(
margin: EdgeInsets.only(
left: kDefaultPadding,
right: kDefaultPadding,
Page 48 of 65
),
padding: EdgeInsets.only(right: kDefaultPadding),
width: double.infinity,
height: 60.0,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [kSecondaryColor, kPrimaryColor],
begin: FractionalOffset(0.0, 0.0),
end: FractionalOffset(0.5, 0.0),
stops: [0.0, 1.0],
tileMode: TileMode.clamp,
),
borderRadius: BorderRadius.circular(kDefaultPadding)),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Spacer(),
Text(
title,
style: Theme.of(context).textTheme.titleSmall!.copyWith(
fontWeight: FontWeight.w500,
fontSize: 16.0,
color: kOtherColor,
),
),
Spacer(),
Icon(
iconData,
size: 30.0,
color: kOtherColor,
)
],
)),
);
}
}
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onPress,
child: Container(
margin: EdgeInsets.only(top: 1),
width: MediaQuery.of(context).size.width / 2.5,
Page 49 of 65
height: MediaQuery.of(context).size.height / 14,
decoration: BoxDecoration(
color: kPrimaryColor,
borderRadius: BorderRadius.circular(kDefaultPadding / 2)),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
title,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleSmall,
),
],
),
),
);
}
}
vi. add_button.dart:
import 'package:app_cud/services/constants.dart';
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onPress,
child: Container(
width: 120,
height: 60,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Get.isDarkMode?kPrimaryColorDark:kPrimaryColor
),
child:Center(
child: Text(
label,
style: TextStyle(color: kTextWhiteColor),
),
)
),
);
}
}
Page 50 of 65
vii. calendar_widget.dart:
import 'package:app_cud/db/db_helper_course.dart';
import 'package:app_cud/services/constants.dart';
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart';
import 'package:app_cud/models/course.dart';
@override
Widget build(BuildContext context) {
return Scaffold(
body: SfCalendar(
view: _calendarView.value,
allowedViews: [
CalendarView.month,
CalendarView.week,
CalendarView.day,
CalendarView.workWeek,
CalendarView.timelineDay,
CalendarView.timelineWeek,
CalendarView.timelineWorkWeek,
CalendarView.timelineMonth,
CalendarView.schedule],
showDatePickerButton: true,
dataSource: CoursesDataSource(_getDataSource()),
//onTap: _onCalendarTap,
cellBorderColor: Colors.transparent,
selectionDecoration: BoxDecoration(
color: Colors.transparent,
border:
Border.all(color: kPrimaryColor,
width: 2),
borderRadius: const BorderRadius.all(Radius.circular(15)),
shape: BoxShape.rectangle,
),
monthViewSettings: MonthViewSettings(showAgenda: true),
timeSlotViewSettings:
TimeSlotViewSettings(timelineAppointmentHeight: 100),
todayHighlightColor: kPrimaryColor,
),
);
}
}
Page 51 of 65
3. Services:
a. theme_services.dart:
import 'package:flutter/material.dart';
import 'package:get_storage/get_storage.dart';
import 'package:get/get.dart';
class ThemeServices{
final _box = GetStorage();
final _key = 'isDarkMode';
};
c. constants.dart:
import 'package:flutter/material.dart';
//Colors
const Color kPrimaryColor = Color(0xFFD50000);
const Color kPrimaryColorDark = Color(0xFFB60000);
const Color kSecondaryColor = Color(0xFFDB5B5B);
const Color kSecondaryColorDark = Color(0xFFBB3A3A);
const Color kTextBlackColor = Color(0xFF000000);
const Color kTextWhiteColor = Color(0xFFFFFFFF);
const Color kContainerColor = Color(0xFF777777);
const Color kOtherColor = Color(0xFFF4F6F7);
Page 52 of 65
const Color kBlackColor = Color(0xFF050505);
const Color kTextLightColor = Color(0xFFA5A5A5);
const Color kErrorBorderColor = Color(0xFFE74C3C);
const Color kDarkGreyColor = Color(0xFF303030);
const Color kBlueColor = Color(0xFF0066FF);
const Color kPinkColor = Color(0xFFFF076E);
const Color kOrangeColor = Color(0xFFFF4D00);
//default value
const kDefaultPadding = 20.0;
Course({
this.id,
this.name,
this.course_id,
this.day,
this.start_time,
this.end_time,
this.class_loc,
Page 53 of 65
this.instructor,
});
Map<String,dynamic> toJson(){
final Map<String,dynamic> data = new Map<String,dynamic>();
data['id'] = this.id;
data['name'] = this.name;
data['course_id'] = this.course_id;
data['day'] = this.day;
data['start_time'] = this.start_time;
data['end_time'] = this.end_time;
data['class_loc'] = this.class_loc;
data['instructor'] = this.instructor;
return data;
}
}
b. task.dart:
class Task {
int? id;
String? title;
String? note;
int? isCompleted;
String? startDate;
String? endDate;
String? startTime;
String? endTime;
int? color;
int? remind;
Task({
this.id,
this.title,
this.note,
this.isCompleted,
this.startDate,
this.endDate,
this.startTime,
this.endTime,
this.color,
this.remind,
});
Task.fromJson(Map<String, dynamic> json){
id = json["id"];
title = json["title"];
Page 54 of 65
note = json["note"];
isCompleted = json["isCompleted"];
startDate = json["startDate"];
endDate = json["endDate"];
startTime = json["startTime"];
endTime = json["endTime"];
color = json["color"];
remind = json["remind"];
}
Map<String,dynamic> toJson(){
final Map<String,dynamic> data = new Map<String,dynamic>();
data['id'] = this.id;
data['title'] = this.title;
data['startDate'] = this.startDate;
data['endDate'] = this.endDate;
data['note'] = this.note;
data['isCompleted'] = this.isCompleted;
data['startTime'] = this.startTime;
data['endTime'] = this.endTime;
data['color'] = this.color;
data['remind'] = this.remind;
return data;
}
}
5. db:
a. db_helper_course.dart:
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
class DBHelperCourses {
static const _databaseName = 'courses.db';
static const _databaseVersion = 1;
DBHelperCourses._();
static final DBHelperCourses instance = DBHelperCourses._();
Page 55 of 65
if (!await databaseExists(path)) {
// Copy the database from assets to the device's local filesystem
ByteData data = await rootBundle.load(join("assets", _databaseName));
List<int> bytes = data.buffer.asUint8List(data.offsetInBytes,
data.lengthInBytes);
await File(path).writeAsBytes(bytes);
}
class DBHelper{
static Database? _db;
static final int _version = 1;
static final String _tableName = "task";
Page 56 of 65
}
}
6. Controllers:
a. course_controllers.dart:
import 'package:app_cud/db/db_helper_course.dart';
import 'package:app_cud/models/course.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@override
void onInit() {
super.onInit();
getCourses();
}
Page 57 of 65
class TaskController extends GetxController{
@override
void onReady(){
super.onReady();
}
7. dependencies:
cupertino_icons: ^1.0.2
google_fonts: ^4.0.3
flutter_svg: ^2.0.5
get_storage: ^2.1.1
get: ^4.6.5
flutter_local_notifications: ^13.0.0
flutter_native_timezone: ^2.0.0
sqflite: ^2.2.7
intl:
date_picker_timeline:
flutter_staggered_animations:
table_calendar:
syncfusion_flutter_calendar:
provider:
Technical details:
The student app was built using the Flutter framework, which allowed for cross-platform
development on both iOS and Android devices. The app was developed using Dart, a statically
typed programming language that is optimized for building user interfaces.
To manage user authentication and data storage, the app uses the SQLite package, a lightweight,
embedded database that runs locally on the user's device. SQLite was used to store user data such
Page 58 of 65
as assignments, notes, and task lists. The app also utilizes
the get_storage package for storing user preferences and
settings.
To implement the user interface, the app uses the
cupertino_icons package for icons, the google_fonts
package for custom fonts, and the flutter_svg package for
rendering scalable vector graphics.
To implement the calendar view, the app uses the
Syncfusion Flutter Calendar (SfCalendar) package, a third-
party package for Flutter that provides a customizable
calendar widget with support for multiple view modes.
The date_picker_timeline package was used for displaying
dates in a horizontal list.
The app also utilizes the flutter_staggered_animations
package for animating task and course tiles on screen, and
the provider package for state management.
In terms of testing, the app was tested using a combination
of unit tests and manual testing. Unit tests were used to test
individual functions and components of the app, while
manual testing was used to ensure overall functionality and
usability.
User interface:
The user interface of our university app is designed to be simple and
user-friendly, with a clear and intuitive layout that makes it easy for
students to access the information they need.
The app features a splash screen that displays the university logo and
it name.it stays visible for 5 seconds.
Once the splash screen fades, users are presented with a login screen
that in the top part displays the university logo and a brief message
welcoming students to the app, and the bottom part where they can
enter their credentials to access the app's features. The login screen
includes fields for the user's email and password.
Once the user logs in, they are taken to the app's homepage, which
displays the students name, ID, current semester, and the student’s
picture under these information’s there is three buttons that navigate
to different pages. the first button labeled timetable is set for the
current page, the second button is set to navigate to the calendar page
Page 59 of 65
and the third button is for the assignments page, this part of the page is constant through the
other pages.
Under this, you can find timetable of the courses the student is enrolled in. The timetable is
presented in a list format, with each course displayed as a separate tile in the list each tile shows
the course name and code, start and end time, classroom location and the instructor for the
course. The list is split into dates, allowing users to easily navigate to the date they are interested
in.
The calendar screen in our student app is designed to help students keep track of their
coursework and schedule. The main view of the calendar is a monthly view, and it also includes
a drop-down button that shows a list of views including a day view and a week view, this allows
users to switch between different views. This calendar displays all the student's courses,
assignments, and notes based on the selected day.
The assignments screen in our student app is designed to help students keep track of their
coursework and assignments. The screen displays a list of all upcoming assignments, with each
item in the list including the assignment title, note, due date, and any additional details.
The list of assignments is split by day, making it easy for students to see what they have due on a
specific day. Users can navigate between different days using a horizontal list of days, which
allows them to quickly jump to a specific day or scroll through the list of assignments.
Tapping on a specific assignment tile in the list brings up two choices, “task complete” which
will change the task from to-do to complete and “delete task” that will remove unwanted
assignments. For assignments, this includes information such as the assignment description, due
date, and any additional instructions or requirements. For notes, this includes the note title and
the full text of the note.
Page 60 of 65
In addition to displaying upcoming assignments, the screen also allows users to add their own
notes or assignments. Tapping on a "Add Task" button which opens a new screen called add
Task screen.
The add task page in our student app is designed to allow students to create new tasks or
assignments quickly and easily. The page includes input fields for the task title, task note, start
date and time, end date and time, and task color.
The task title and task note input fields include a validator that ensures the user does not leave
them empty. If the user tries to submit the form with empty title or note, an error message will be
displayed, prompting the user to enter the missing information.
Page 61 of 65
The start and end date and time input fields allow the user to set the date and time range for the
task. The user can select the start and end date from a calendar widget, and the start and end time
from a time picker widget. If the user sets the end date and time to be before the start date and
time, an error message will be displayed to remind the user to adjust the input.
Page 62 of 65
The task color input field allows the user to choose a color for the task tile, making it easy to
visually distinguish between different tasks or assignments.
Once the user has entered all the required information and selected a task color, they can submit
the form by tapping on a "Create Task" button at the bottom of the
page. If the user has left any required fields empty or set an invalid
date range, an error message will be displayed, prompting the user to
correct their input before submitting the form.
And finally, if you noticed throughout the three pages, they have a
similar top part that displays students name, ID, current semester,
and student’s picture. Clicking on the picture will send the user to
the profile page where the students info shows up.
Page 63 of 65
Two bugs:
first bug:
During the development of the splash screen, I encountered a bug where the university logo
would not load, and instead an error message would appear. To troubleshoot this issue, I decided
to run a debugging session and placed a breakpoint at the line of code where the image was being
loaded:
Image.asset('assets/images/splash.png',height: 200.0, width: 200.0,),
Upon further investigation, I discovered that the issue was becaused the assets path was not
included in the pubspec.yaml file. Once I updated the file to include the proper asset path, the
image loaded successfully.
Resolution:
To fix this bug, I updated the pubspec.yaml file to include the proper asset path for the university
logo. This allowed the image to load correctly on the splash screen, resolving the error message
that was previously being displayed.
Page 64 of 65
Second bug:
While working on the assignment screen in the university app, I noticed that the app was
displaying an error message saying "Null check operator was used on a null value" when I tried
to view the list of tasks.
To troubleshoot the issue, I used the debugging feature in the IDE to set a breakpoint at the
ListView.builder method and examine the behavior of the app. I discovered that the TaskTile
widget was set to null instead of the task list.
Resolution:
To fix the bug, I modified the code to ensure that the taskTile widget was set to the task list
instead of null. This involved checking the code responsible for initializing the task list and
making sure that it was properly passed to the TaskTile widget.
After implementing these changes, I retested the app using the debugging feature in your IDE
and confirmed that the issue had been resolved. The task list was now properly displayed on the
assignment screen, and the error message was no longer appearing.
Page 65 of 65