-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #78 from IamMuuo/jess
Exam Timetable Feature
- Loading branch information
Showing
15 changed files
with
765 additions
and
35 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
172 changes: 172 additions & 0 deletions
172
lib/tools/exam_timetable/controllers/exam_timetable_controller.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export 'exam_helper.dart'; | ||
export 'exam_model.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'], | ||
); | ||
} | ||
} |
Oops, something went wrong.