Skip to content

Commit

Permalink
Merge pull request #78 from IamMuuo/jess
Browse files Browse the repository at this point in the history
Exam Timetable Feature
  • Loading branch information
IamMuuo authored Aug 23, 2024
2 parents 759e43e + 5bb81e0 commit c79c924
Show file tree
Hide file tree
Showing 15 changed files with 765 additions and 35 deletions.
Binary file added assets/images/star_trek.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 11 additions & 11 deletions lib/constants/tools.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import 'package:get/get.dart';
import 'package:academia/exports/barrel.dart';

final List<Map<String, dynamic>> allTools = [
// {
// "id": 8,
// "name": "Exam Timetable",
// "action": "Show exam timetable",
// "image": "assets/images/exam_timetable.png",
// "ontap": () {
// // Get.to(const ExamTimeTablePage());
// },
// "description":
// "Exams around the corner? Don't panic we've got you covered with the timetable",
// },
{
"id": 8,
"name": "Exam Timetable",
"action": "Show exam timetable",
"image": "assets/images/star_trek.png",
"ontap": () {
Get.to(const ExamTimeTablePage());
},
"description":
"Exams around the corner? Don't panic we've got you covered with the timetable",
},
{
"id": 1,
"name": "GPA Calculator",
Expand Down
2 changes: 1 addition & 1 deletion lib/exports/barrel.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export 'package:academia/tools/exam_timetable/exams_timetable_page.dart';
export 'package:academia/tools/exam_timetable/exam_timetable.dart';
export 'package:flutter_pdfview/flutter_pdfview.dart';
export 'package:academia/widgets/info_card.dart';
export 'package:cached_network_image/cached_network_image.dart';
Expand Down
14 changes: 14 additions & 0 deletions lib/storage/schemas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ const schemas = <String, String>{
);
""",

// Exams
"exams": """
CREATE TABLE IF NOT EXISTS exams (
course_code TEXT PRIMARY KEY,
day TEXT NOT NULL,
time TEXT NOT NULL,
venue TEXT NOT NULL,
hrs TEXT NOT NULL,
invigilator TEXT,
coordinator TEXT,
campus TEXT
);
""",

// Rewards table
"rewards": """
CREATE TABLE IF NOT EXISTS rewards (
Expand Down
172 changes: 172 additions & 0 deletions lib/tools/exam_timetable/controllers/exam_timetable_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import 'package:academia/exports/barrel.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';

import '../../../models/models.dart';

class ExamsTimeTableController extends GetxController {
var index = (-1).obs;
var hasExams = false.obs;
late List<Map<String, dynamic>> quotes = [];
List<Exam> exams = [];
final ExamModelHelper _examDbHelper = ExamModelHelper();
final CourseModelHelper _courseDbHelper = CourseModelHelper();

Future<void> fetchRandomQuote() async {
const String apiUrl = "https://zenquotes.io/api/quotes/";

try {
final response = await http.get(Uri.parse(apiUrl));

if (response.statusCode == 200) {
// Parse the JSON data
List<dynamic> data = json.decode(response.body);

// Convert the JSON data into a list of quotes
quotes = data.map((quote) {
return {
'q': quote['q'],
'a': quote['a'],
};
}).toList();

index.value = 0;
} else {
throw Exception("Failed to load quotes");
}
} catch (e) {
Get.snackbar(
"Error",
e.toString(),
colorText: Colors.red,
backgroundColor: Colors.grey,
);
}
}


void nextQuote() {
if (quotes.isNotEmpty && index.value < 49) {
index.value++;
} else if (index.value == 49) {
fetchRandomQuote().then((value) => value); // Do nothing
}
}

void previousQuote() {
if (quotes.isNotEmpty && index.value > 0) {
index.value--;
} else if (index.value == 0) {
fetchRandomQuote().then((value) => value); // Do nothing
}
}

Future<List<Exam>> fetchExams(List<String> units) async {
const String apiUrl = "http://academia.erick.serv00.net/timetables/exams/";

try {
// Prepare the request body
final body = json.encode({'course_codes': units});

// Send the POST request to the server
final response = await http.post(
Uri.parse(apiUrl),
headers: {'Content-Type': 'application/json'},
body: body,
);

if (response.statusCode == 200) {
// Parse the JSON data
final List<dynamic> data = json.decode(response.body);

// Convert the JSON data into a list of Exam objects
List<Exam> examData = data.map((e) => Exam.fromJson(e)).toList();

// Sort the exams by date and time
examData.sort((a, b) {
final formatter = DateFormat('EEEE dd/MM/yy');
final aDate = formatter.parse(a.day.title());
final bDate = formatter.parse(b.day.title());

// Compare the dates first
final dateComparison = aDate.compareTo(bDate);
if (dateComparison != 0) return dateComparison;

// If the dates are the same, compare the times
final aTimeRange = a.time.split('-');
final bTimeRange = b.time.split('-');
final aStartTime = DateFormat('h:mma').parse(aTimeRange[0]);
final bStartTime = DateFormat('h:mma').parse(bTimeRange[0]);

return aStartTime.compareTo(bStartTime);
});

return examData;
} else {
throw Exception("Failed to load exams. Status code: ${response.statusCode}");
}
} catch (e) {
throw Exception("Error fetching exams: $e");
}
}

Future<void> addExamToStorage(Exam exam) async {
// Insert exam into the database using the SQLite helper
await _examDbHelper.create(exam.toMap());

// Fetch all exams to refresh the list
exams = await _examDbHelper.queryAll().then(
(data) => data.map((e) => Exam.fromJson(e)).toList(),
);

// Update the hasExams observable
hasExams.value = exams.isNotEmpty;
}

Future<void> removeExamFromStorage(Exam exam) async {
// Remove exam from the database using the SQLite helper
await _examDbHelper.delete({'course_code': exam.courseCode});

// Fetch all exams to refresh the list
exams = await _examDbHelper.queryAll().then(
(data) => data.map((e) => Exam.fromJson(e)).toList(),
);

// Update the hasExams observable
hasExams.value = exams.isNotEmpty;
}

@override
Future<void> onInit() async {
await fetchRandomQuote();
hasExams.value = false;

// Check if the local database has exams
exams = await _examDbHelper.queryAll().then(
(data) => data.map((e) => Exam.fromJson(e)).toList(),
);

if (exams.isNotEmpty) {
hasExams.value = true;
} else {
// Load the units
final List<Map<String, dynamic>> courseData = await _courseDbHelper
.queryAll(); // Assuming _courseDbHelper is your SQLite helper for courses
List<Course> courses = courseData.map((e) => Course.fromJson(e)).toList();

List<String> courseTitles = courses
.map((e) => "${e.unit.replaceAll('-', '')}${e.section.split('-')[0]}")
.toList();
print(courseTitles);

// Fetch from server and store in the database
exams = await fetchExams(courseTitles);
for (var exam in exams) {
await _examDbHelper.create(exam.toMap());
}
hasExams.value = exams.isNotEmpty;
}
super.onInit();
}
}
4 changes: 4 additions & 0 deletions lib/tools/exam_timetable/exam_timetable.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export 'widgets/widgets.dart';
export 'models/exam.dart';
export 'controllers/exam_timetable_controller.dart';
export 'pages/exam_timetable_page.dart';
23 changes: 0 additions & 23 deletions lib/tools/exam_timetable/exams_timetable_page.dart

This file was deleted.

2 changes: 2 additions & 0 deletions lib/tools/exam_timetable/models/exam.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export 'exam_helper.dart';
export 'exam_model.dart';
57 changes: 57 additions & 0 deletions lib/tools/exam_timetable/models/exam_helper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:academia/exports/barrel.dart';
import 'package:academia/storage/storage.dart';
import 'package:sqflite/sqlite_api.dart';

class ExamModelHelper implements DatabaseOperations {
static final ExamModelHelper _instance = ExamModelHelper._internal();

factory ExamModelHelper() {
return _instance;
}

ExamModelHelper._internal();

@override
Future<int> create(Map<String, dynamic> data) async {
final db = await DatabaseHelper().database;
final id = await db.insert(
'exams',
data,
conflictAlgorithm: ConflictAlgorithm.replace,
);

debugPrint("[+] Exam written successfully");
return id;
}

@override
Future<List<Map<String, dynamic>>> queryAll() async {
final db = await DatabaseHelper().database;
final exams = await db.query('exams');
return exams;
}

@override
Future<int> delete(Map<String, dynamic> data) async {
final db = await DatabaseHelper().database;
return await db.delete('exams',
where: 'course_code = ?', whereArgs: [data['course_code']]);
}

@override
Future<int> update(Map<String, dynamic> data) async {
final db = await DatabaseHelper().database;
return await db.update(
'exams',
data,
where: 'course_code = ?',
whereArgs: [data['course_code']],
);
}

@override
Future<void> truncate() async {
final db = await DatabaseHelper().database;
await db.execute('DELETE FROM exams');
}
}
49 changes: 49 additions & 0 deletions lib/tools/exam_timetable/models/exam_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
class Exam {
String courseCode;
String day;
String time;
String venue;
String hrs;
String? invigilator;
String? coordinator;
String? campus;

Exam({
required this.courseCode,
required this.day,
required this.time,
required this.venue,
required this.hrs,
this.invigilator,
this.coordinator,
this.campus,
});

// Convert an Exam into a Map.
Map<String, dynamic> toMap() {
return {
'course_code': courseCode,
'day': day,
'time': time,
'venue': venue,
'hrs': hrs,
'invigilator': invigilator,
'coordinator': coordinator,
'campus': campus,
};
}

// Create an Exam from a Map.
factory Exam.fromJson(Map<String, dynamic> json) {
return Exam(
courseCode: json['course_code'],
day: json['day'],
time: json['time'],
venue: json['venue'],
hrs: json['hrs'],
invigilator: json['invigilator'],
coordinator: json['coordinator'],
campus: json['campus'],
);
}
}
Loading

0 comments on commit c79c924

Please sign in to comment.