diff --git a/assets/images/bot_hello.png b/assets/images/bot_hello.png deleted file mode 100644 index 07b05e4..0000000 Binary files a/assets/images/bot_hello.png and /dev/null differ diff --git a/assets/images/bot_hello.png.import b/assets/images/bot_hello.png.import deleted file mode 100644 index 58a94d2..0000000 --- a/assets/images/bot_hello.png.import +++ /dev/null @@ -1,34 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dk0s2l0u4i2h7" -path="res://.godot/imported/bot_hello.png-5a15f66f439d0634c0665d6d2217cc74.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://apps/academia/assets/images/bot_hello.png" -dest_files=["res://.godot/imported/bot_hello.png-5a15f66f439d0634c0665d6d2217cc74.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/assets/images/bot_love.png b/assets/images/bot_love.png deleted file mode 100644 index d5b6586..0000000 Binary files a/assets/images/bot_love.png and /dev/null differ diff --git a/assets/images/bot_love.png.import b/assets/images/bot_love.png.import deleted file mode 100644 index f56865b..0000000 --- a/assets/images/bot_love.png.import +++ /dev/null @@ -1,34 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cdy2c7sibhl12" -path="res://.godot/imported/bot_love.png-60acd0c951bdb969e6c9725bff7c63e1.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://apps/academia/assets/images/bot_love.png" -dest_files=["res://.godot/imported/bot_love.png-60acd0c951bdb969e6c9725bff7c63e1.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/assets/images/bot_sad.png b/assets/images/bot_sad.png deleted file mode 100644 index eb92003..0000000 Binary files a/assets/images/bot_sad.png and /dev/null differ diff --git a/assets/images/bot_sad.png.import b/assets/images/bot_sad.png.import deleted file mode 100644 index bb68534..0000000 --- a/assets/images/bot_sad.png.import +++ /dev/null @@ -1,34 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://ljlftmav5l2y" -path="res://.godot/imported/bot_sad.png-eedd2fb6064fd6224817fde458c09767.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://apps/academia/assets/images/bot_sad.png" -dest_files=["res://.godot/imported/bot_sad.png-eedd2fb6064fd6224817fde458c09767.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/assets/images/bot_search.png b/assets/images/bot_search.png deleted file mode 100644 index 608dff1..0000000 Binary files a/assets/images/bot_search.png and /dev/null differ diff --git a/assets/images/bot_search.png.import b/assets/images/bot_search.png.import deleted file mode 100644 index 8ef8354..0000000 --- a/assets/images/bot_search.png.import +++ /dev/null @@ -1,34 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://ox6r1l17f8s1" -path="res://.godot/imported/bot_search.png-57528de00257c4f19e5cc73806927f62.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://apps/academia/assets/images/bot_search.png" -dest_files=["res://.godot/imported/bot_search.png-57528de00257c4f19e5cc73806927f62.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/assets/images/bot_wave.png b/assets/images/bot_wave.png deleted file mode 100644 index d76d6e0..0000000 Binary files a/assets/images/bot_wave.png and /dev/null differ diff --git a/assets/images/bot_wave.png.import b/assets/images/bot_wave.png.import deleted file mode 100644 index 866c1ab..0000000 --- a/assets/images/bot_wave.png.import +++ /dev/null @@ -1,34 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dfgg2up81r5jy" -path="res://.godot/imported/bot_wave.png-4a3d9b4797018627216f1c21b96fe157.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://apps/academia/assets/images/bot_wave.png" -dest_files=["res://.godot/imported/bot_wave.png-4a3d9b4797018627216f1c21b96fe157.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/assets/images/cake.png b/assets/images/cake.png deleted file mode 100644 index 31b824a..0000000 Binary files a/assets/images/cake.png and /dev/null differ diff --git a/assets/images/coin.png b/assets/images/coin.png deleted file mode 100644 index 6fbd515..0000000 Binary files a/assets/images/coin.png and /dev/null differ diff --git a/assets/images/congratulations_askMe.jpeg b/assets/images/congratulations_askMe.jpeg deleted file mode 100644 index 7ebf99c..0000000 Binary files a/assets/images/congratulations_askMe.jpeg and /dev/null differ diff --git a/assets/images/female_student.png b/assets/images/female_student.png deleted file mode 100644 index 815cf4f..0000000 Binary files a/assets/images/female_student.png and /dev/null differ diff --git a/assets/images/female_student.png.import b/assets/images/female_student.png.import deleted file mode 100644 index 3ad40f1..0000000 --- a/assets/images/female_student.png.import +++ /dev/null @@ -1,34 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://qcr6rjjk8omh" -path="res://.godot/imported/female_student.png-2572ab7c592c22006e70f7a730b11bd6.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://apps/academia/assets/images/female_student.png" -dest_files=["res://.godot/imported/female_student.png-2572ab7c592c22006e70f7a730b11bd6.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/assets/images/gifts.png b/assets/images/gifts.png deleted file mode 100644 index aac4d0c..0000000 Binary files a/assets/images/gifts.png and /dev/null differ diff --git a/assets/images/girl_holding_phone.png b/assets/images/girl_holding_phone.png deleted file mode 100644 index 05f4d5b..0000000 Binary files a/assets/images/girl_holding_phone.png and /dev/null differ diff --git a/assets/images/girl_holding_phone.png.import b/assets/images/girl_holding_phone.png.import deleted file mode 100644 index 9ed1b37..0000000 --- a/assets/images/girl_holding_phone.png.import +++ /dev/null @@ -1,34 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://blfq33ob762rs" -path="res://.godot/imported/girl_holding_phone.png-753357cda8659433fcb6027e7b3f159c.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://apps/academia/assets/images/girl_holding_phone.png" -dest_files=["res://.godot/imported/girl_holding_phone.png-753357cda8659433fcb6027e7b3f159c.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/assets/images/girl_sitted.png b/assets/images/girl_sitted.png deleted file mode 100644 index e48fd35..0000000 Binary files a/assets/images/girl_sitted.png and /dev/null differ diff --git a/assets/images/girl_sitted.png.import b/assets/images/girl_sitted.png.import deleted file mode 100644 index 9baf139..0000000 --- a/assets/images/girl_sitted.png.import +++ /dev/null @@ -1,34 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://by5y4lcfssse4" -path="res://.godot/imported/girl_sitted.png-4f437a9052312c9414d00606722532c3.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://apps/academia/assets/images/girl_sitted.png" -dest_files=["res://.godot/imported/girl_sitted.png-4f437a9052312c9414d00606722532c3.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/assets/images/grade.png b/assets/images/grade.png deleted file mode 100644 index c993386..0000000 Binary files a/assets/images/grade.png and /dev/null differ diff --git a/assets/images/left_arrow.png b/assets/images/left_arrow.png deleted file mode 100644 index 481b22c..0000000 Binary files a/assets/images/left_arrow.png and /dev/null differ diff --git a/assets/images/male_student.png b/assets/images/male_student.png deleted file mode 100644 index 46c5d77..0000000 Binary files a/assets/images/male_student.png and /dev/null differ diff --git a/assets/images/male_student.png.import b/assets/images/male_student.png.import deleted file mode 100644 index 544daed..0000000 --- a/assets/images/male_student.png.import +++ /dev/null @@ -1,34 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cqcbam7eai358" -path="res://.godot/imported/male_student.png-a24e3b70452cfbf9590368568d894d77.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://apps/academia/assets/images/male_student.png" -dest_files=["res://.godot/imported/male_student.png-a24e3b70452cfbf9590368568d894d77.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/assets/images/organize.png b/assets/images/organize.png deleted file mode 100644 index a89e8aa..0000000 Binary files a/assets/images/organize.png and /dev/null differ diff --git a/assets/images/present.png b/assets/images/present.png deleted file mode 100644 index 4ab5058..0000000 Binary files a/assets/images/present.png and /dev/null differ diff --git a/assets/images/present2.png b/assets/images/present2.png deleted file mode 100644 index 4000a07..0000000 Binary files a/assets/images/present2.png and /dev/null differ diff --git a/assets/images/present3.png b/assets/images/present3.png deleted file mode 100644 index edd651b..0000000 Binary files a/assets/images/present3.png and /dev/null differ diff --git a/assets/images/right_arrow.png b/assets/images/right_arrow.png deleted file mode 100644 index a6e2c78..0000000 Binary files a/assets/images/right_arrow.png and /dev/null differ diff --git a/assets/images/schedule.png b/assets/images/schedule.png deleted file mode 100644 index 9697a39..0000000 Binary files a/assets/images/schedule.png and /dev/null differ diff --git a/assets/images/sketchbook-coworkers-discussing-something-1.png b/assets/images/sketchbook-coworkers-discussing-something-1.png deleted file mode 100644 index 64dee9b..0000000 Binary files a/assets/images/sketchbook-coworkers-discussing-something-1.png and /dev/null differ diff --git a/assets/images/sketchbook-man-analyzing-business-data.png b/assets/images/sketchbook-man-analyzing-business-data.png deleted file mode 100644 index 573a8af..0000000 Binary files a/assets/images/sketchbook-man-analyzing-business-data.png and /dev/null differ diff --git a/assets/images/sketchbook-office-worker-having-an-idea.png b/assets/images/sketchbook-office-worker-having-an-idea.png deleted file mode 100644 index 42986c4..0000000 Binary files a/assets/images/sketchbook-office-worker-having-an-idea.png and /dev/null differ diff --git a/assets/images/sketchbook-passersby-people-working-around-1.png b/assets/images/sketchbook-passersby-people-working-around-1.png deleted file mode 100644 index 07c8bce..0000000 Binary files a/assets/images/sketchbook-passersby-people-working-around-1.png and /dev/null differ diff --git a/assets/images/sketchbook-school-backpack-with-school-supplies-1.png b/assets/images/sketchbook-school-backpack-with-school-supplies-1.png deleted file mode 100644 index c3e1cd6..0000000 Binary files a/assets/images/sketchbook-school-backpack-with-school-supplies-1.png and /dev/null differ diff --git a/assets/images/sketchbook-woman-and-a-man-analyze-data-2.png b/assets/images/sketchbook-woman-and-a-man-analyze-data-2.png deleted file mode 100644 index c2786d2..0000000 Binary files a/assets/images/sketchbook-woman-and-a-man-analyze-data-2.png and /dev/null differ diff --git a/assets/images/sketchbook-woman-taking-pictures-of-packaged-goods.png b/assets/images/sketchbook-woman-taking-pictures-of-packaged-goods.png deleted file mode 100644 index 001e7c4..0000000 Binary files a/assets/images/sketchbook-woman-taking-pictures-of-packaged-goods.png and /dev/null differ diff --git a/assets/images/sketchbook-workspace-taking-notes-during-call-1.png b/assets/images/sketchbook-workspace-taking-notes-during-call-1.png deleted file mode 100644 index 6eccff8..0000000 Binary files a/assets/images/sketchbook-workspace-taking-notes-during-call-1.png and /dev/null differ diff --git a/assets/images/sketchbook-young-businesswoman-giving-a-presentation-1.png b/assets/images/sketchbook-young-businesswoman-giving-a-presentation-1.png deleted file mode 100644 index ddf618d..0000000 Binary files a/assets/images/sketchbook-young-businesswoman-giving-a-presentation-1.png and /dev/null differ diff --git a/assets/images/sketchbook-young-man-and-his-dog-are-having-a-picnic-in-the-park-1.png b/assets/images/sketchbook-young-man-and-his-dog-are-having-a-picnic-in-the-park-1.png deleted file mode 100644 index 0bbac96..0000000 Binary files a/assets/images/sketchbook-young-man-and-his-dog-are-having-a-picnic-in-the-park-1.png and /dev/null differ diff --git a/assets/images/sketchbook-young-man-studying-in-classroom-2.gif b/assets/images/sketchbook-young-man-studying-in-classroom-2.gif deleted file mode 100644 index 85cdc63..0000000 Binary files a/assets/images/sketchbook-young-man-studying-in-classroom-2.gif and /dev/null differ diff --git a/assets/images/sketchbook-young-man-studying.png b/assets/images/sketchbook-young-man-studying.png deleted file mode 100644 index 44f6c8c..0000000 Binary files a/assets/images/sketchbook-young-man-studying.png and /dev/null differ diff --git a/assets/images/sketchbook-young-woman-chooses-book-1.png b/assets/images/sketchbook-young-woman-chooses-book-1.png deleted file mode 100644 index cd75774..0000000 Binary files a/assets/images/sketchbook-young-woman-chooses-book-1.png and /dev/null differ diff --git a/assets/images/study.gif b/assets/images/study.gif deleted file mode 100644 index d4c0835..0000000 Binary files a/assets/images/study.gif and /dev/null differ diff --git a/assets/images/studying.svg b/assets/images/studying.svg new file mode 100644 index 0000000..3bd6513 --- /dev/null +++ b/assets/images/studying.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/tasks_manager.png b/assets/images/tasks_manager.png deleted file mode 100644 index 3a5b7df..0000000 Binary files a/assets/images/tasks_manager.png and /dev/null differ diff --git a/assets/rive/bunny_login.riv b/assets/rive/bunny_login.riv new file mode 100644 index 0000000..5396653 Binary files /dev/null and b/assets/rive/bunny_login.riv differ diff --git a/lib/app.dart b/lib/app.dart new file mode 100644 index 0000000..44edfef --- /dev/null +++ b/lib/app.dart @@ -0,0 +1,23 @@ +import 'package:academia/utils/router/router.dart'; +import 'package:flutter/material.dart'; +import 'package:dynamic_color/dynamic_color.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class Academia extends StatelessWidget { + const Academia({super.key}); + + @override + Widget build(BuildContext context) { + return DynamicColorBuilder( + builder: (lightscheme, darkscheme) => MaterialApp.router( + title: "Academia", + routerConfig: AcademiaRouter.router, + theme: ThemeData( + colorScheme: lightscheme, + useMaterial3: true, + fontFamily: GoogleFonts.inter().fontFamily, + ), + ), + ); + } +} diff --git a/lib/constants/common.dart b/lib/constants/common.dart deleted file mode 100644 index 9ead181..0000000 --- a/lib/constants/common.dart +++ /dev/null @@ -1,139 +0,0 @@ -// const String urlPrefix = "https://daystar-backend-atrocious-jpg.vercel.app"; -// For local development - -import 'package:academia/exports/barrel.dart'; - -late Magnet magnet; - -extension StringExtension on String { - String toCapitalized() => - length > 0 ? '${this[0].toUpperCase()}${substring(1).toLowerCase()}' : ''; - String title() { - return replaceAll(RegExp(' +'), ' ') - .split(' ') - .map((str) => str.toCapitalized()) - .join(' '); - - // return "${this[0].toUpperCase()}${substring(1).toLowerCase()}"; - } -} - -String utf8convert(String text) { - List bytes = text.toString().codeUnits; - return utf8.decode(bytes); -} - -String trimTo99Characters(String text) { - if (text.length <= 99) { - return text; - } else { - return '${text.substring(0, 96)}...'; - } -} - -// Returns the day percent gone -double dayPercentGone() { - DateTime now = DateTime.now(); - DateTime startOfDay = DateTime(now.year, now.month, now.day); - DateTime endOfDay = startOfDay.add( - const Duration( - days: 1, - ), - ); - - double elapsedSeconds = now.difference(startOfDay).inSeconds.toDouble(); - double totalSeconds = endOfDay.difference(startOfDay).inSeconds.toDouble(); - - double percent = (elapsedSeconds / totalSeconds) * 100; - return percent; -} - -double weekPercentGone() { - DateTime now = DateTime.now(); - - // Determine the start of the current week (assuming week starts on Monday) - DateTime startOfWeek = now.subtract(Duration(days: now.weekday - 1)); - startOfWeek = DateTime(startOfWeek.year, startOfWeek.month, startOfWeek.day); - - // Determine the end of the current week - DateTime endOfWeek = startOfWeek.add( - const Duration( - days: 7, - ), - ); - - // Calculate the number of seconds elapsed since the start of the week - double elapsedSeconds = now.difference(startOfWeek).inSeconds.toDouble(); - - // Calculate the total number of seconds in a week - double totalSeconds = endOfWeek.difference(startOfWeek).inSeconds.toDouble(); - - // Calculate the percentage of the week gone - double percent = (elapsedSeconds / totalSeconds) * 100; - - return percent; -} - -int getNumericDayOfWeek(String dayOfWeek) { - switch (dayOfWeek.toLowerCase()) { - case 'monday': - return 1; - case 'tuesday': - return 2; - case 'wednesday': - return 3; - case 'thursday': - return 4; - case 'friday': - return 5; - case 'saturday': - return 6; - case 'sunday': - return 7; - default: - return 0; // Handle unknown dayOfWeek - } -} - -/// Returns the pecentage of time gone -double calculateSemesterPercent(DateTime start, DateTime end) { - Duration totalDuration = end.difference(start); - - DateTime now = DateTime.now(); - Duration elapsedDuration = now.difference(start); - - double percentage = - (elapsedDuration.inMilliseconds / totalDuration.inMilliseconds) * 100; - - if (percentage < 0) { - return 0; - } else if (percentage > 100) { - return 100; - } else { - return percentage; - } -} - -// Extension method to convert weekday number to string -extension WeekdayToString on DateTime { - String weekdayToString() { - switch (weekday) { - case DateTime.monday: - return "MONDAY"; - case DateTime.tuesday: - return "TUESDAY"; - case DateTime.wednesday: - return "WEDNESDAY"; - case DateTime.thursday: - return "THURSDAY"; - case DateTime.friday: - return "FRIDAY"; - case DateTime.saturday: - return "SATURDAY"; - case DateTime.sunday: - return "SUNDAY"; - default: - return ""; - } - } -} diff --git a/lib/constants/tools.dart b/lib/constants/tools.dart deleted file mode 100644 index 7794cf6..0000000 --- a/lib/constants/tools.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'package:get/get.dart'; -import 'package:academia/exports/barrel.dart'; - -final List> allTools = [ - { - "id": 1, - "name": "GPA Calculator", - "action": "Calculate GPA", - "image": - "assets/images/sketchbook-young-businesswoman-giving-a-presentation-1.png", - "ontap": () { - Get.to(GpaCalculator()); - }, - "description": "Wanna calculate your GPA? try it here" - }, - { - "id": 2, - "name": "Fees statements", - "action": "Preview my fees statements", - "image": "assets/images/sketchbook-woman-and-a-man-analyze-data-2.png", - "ontap": () { - Get.to(const FeesPage()); - }, - "description": "Having trouble tracking your finaces? We're here for you" - }, - { - "id": 3, - "name": "Elearning", - "image": "assets/images/sketchbook-coworkers-discussing-something-1.png", - "action": "Visit Elearning", - "ontap": () { - Platform.isIOS || Platform.isAndroid - ? Get.to(const WebviewPage( - title: "Elearning", url: "https://elearning.daystar.ac.ke")) - : Get.rawSnackbar( - title: "Missing Feature", - message: - "Current platform does not support this feature please try it on android", - backgroundColor: Colors.red, - snackPosition: SnackPosition.BOTTOM, - ); - }, - "description": - "Psst! Elearning is here for you. Keep track of your assignments and notes!", - }, - { - "id": 4, - "name": "Exam Timetable", - "action": "Show exam timetable", - "image": "assets/images/sketchbook-man-analyzing-business-data.png", - "ontap": () { - Get.to(const ExamTimeTablePage()); - }, - "description": - "Exams around the corner? Don't panic we've got you covered with the timetable", - }, - { - "id": 5, - "name": "Task Manager", - "action": "Manage your tasks", - "image": "assets/images/organize.png", - "ontap": () { - Get.to(const TodoHomeScreen()); - }, - "description": - "Having trouble keeping track of your todos? We've got you covered", - }, - { - "id": 6, - "name": "Anki Flash Cards", - "action": "Lets Up That Grade", - "image": - "assets/images/sketchbook-young-man-and-his-dog-are-having-a-picnic-in-the-park-1.png", - "ontap": () { - Get.to(const AnkiHomePage()); - }, - "description": - "Ready to revolutionize your study habits?\nLet's help you master courses effortlessly", - }, - { - "id": 7, - "name": "Ask Me", - "action": "Study with Ai", - "image": - "assets/images/sketchbook-woman-taking-pictures-of-packaged-goods.png", - "ontap": () { - Get.to(const AskMeHome()); - }, - "description": - "Generate quiz questions from your notes and study materials with our AI tool." - }, - { - "id": 8, - "name": "Student Audit", - "action": "Get student Audit", - "image": - "assets/images/sketchbook-school-backpack-with-school-supplies-1.png", - "ontap": () { - Get.to( - const StudentPerformancePage( - parameter: StudentPerfomanceParameter.audit, - ), - ); - }, - "description": "Need your student audit? Get it here" - }, - { - "id": 9, - "name": "Transcript", - "action": "Show my transcript", - "image": - "assets/images/sketchbook-young-businesswoman-giving-a-presentation-1.png", - "ontap": () { - Get.to( - const StudentPerformancePage( - parameter: StudentPerfomanceParameter.transcript, - ), - ); - }, - "description": - "Worried of your entire student progression? Fetch your transcript here" - } -]; diff --git a/lib/controllers/controllers.dart b/lib/controllers/controllers.dart deleted file mode 100644 index 24dcc93..0000000 --- a/lib/controllers/controllers.dart +++ /dev/null @@ -1,6 +0,0 @@ -export 'user_controller.dart'; -export 'courses_controller.dart'; -export 'network_controller.dart'; -export 'reward_controller.dart'; -export 'settings_controller.dart'; -export 'notifications_controller.dart'; diff --git a/lib/controllers/courses_controller.dart b/lib/controllers/courses_controller.dart deleted file mode 100644 index 78775bf..0000000 --- a/lib/controllers/courses_controller.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:academia/constants/common.dart'; -import 'package:get/get.dart'; -import 'package:academia/models/models.dart'; -import 'package:dartz/dartz.dart'; - -class CoursesController extends GetxController { - RxBool hasCourses = false.obs; - RxList courses = [].obs; - RxList coursesTopics = [].obs; - - @override - void onInit() { - super.onInit(); - CourseModelHelper().queryAll().then((value) { - courses.value = value.map((e) => Course.fromJson(e)).toList(); - }); - - // Load the course topics - CourseTopicModelHelper().queryAll().then((value) { - coursesTopics.value = value.map((e) => CourseTopic.fromJson(e)).toList(); - }); - } - - /// Fetches the curent user's courses and saves them to local storage - Future>> fetchUserCourses() async { - final result = await magnet.fetchUserTimeTable(); - return result.fold((l) { - return left("Sorry we couldn't find your courses please try again later"); - }, (r) { - courses.value = r.map((e) => Course.fromJson(e)).toList(); - - /// Write the courses to local db - CourseModelHelper().truncate(); - for (final course in courses) { - CourseModelHelper().create(course.toJson()); - } - return right(courses); - }); - } - - int get numberOfCoursesToday { - int today = DateTime.now().weekday; - int count = 0; - - for (final course in courses) { - if (getNumericDayOfWeek(course.dayOfWeek) == today) { - count++; - } - } - return count; - } - - List get coursesToday { - int today = DateTime.now().weekday; - final coursestoday = []; - - for (final course in courses) { - if (getNumericDayOfWeek(course.dayOfWeek) == today) { - coursestoday.add(course); - } - } - return coursestoday; - } - - Future createCourseTopic(CourseTopic coursesTopic) async { - final data = await CourseTopicModelHelper().create(coursesTopic.toJson()); - coursesTopics.add(coursesTopic); - return data > 0 ? true : false; - } - - Future deleteCourseTopic(CourseTopic coursesTopic) async { - final data = await CourseTopicModelHelper().delete(coursesTopic.toJson()); - coursesTopics.remove(coursesTopic); - return data > 0 ? true : false; - } -} diff --git a/lib/controllers/network_controller.dart b/lib/controllers/network_controller.dart deleted file mode 100644 index ed7a104..0000000 --- a/lib/controllers/network_controller.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:get/get.dart'; -import 'package:connectivity_plus/connectivity_plus.dart'; -import 'package:flutter/material.dart'; -import 'package:ionicons/ionicons.dart'; - -class NetworkController extends GetxController { - final Connectivity _connectivity = Connectivity(); - - @override - void onInit() { - super.onInit(); - _connectivity.onConnectivityChanged.listen(_onConnectionChanged); - } - - void _onConnectionChanged(List result) { - if (result.contains(ConnectivityResult.none)) { - Get.rawSnackbar( - messageText: const Text( - "You are not connected to the internet some features might not work", - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - ), - ), - snackPosition: SnackPosition.TOP, - isDismissible: false, - duration: const Duration(seconds: 5), - backgroundColor: Colors.red[400]!, - icon: const Icon(Ionicons.cloud_offline_outline, color: Colors.white), - ); - } else { - if (Get.isSnackbarOpen) { - Get.closeCurrentSnackbar(); - Get.rawSnackbar( - messageText: const Text( - "Your connection is back", - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - ), - ), - duration: const Duration(seconds: 5), - isDismissible: false, - backgroundColor: Colors.green[400]!, - icon: const Icon(Ionicons.cloud_done_outline, color: Colors.white), - ); - } - } - } -} diff --git a/lib/controllers/notifications_controller.dart b/lib/controllers/notifications_controller.dart deleted file mode 100644 index 6d7c6d0..0000000 --- a/lib/controllers/notifications_controller.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'dart:math'; - -import "package:academia/exports/barrel.dart"; -import 'package:get/get.dart'; - -class NotificationsController extends GetxController { - int _generateRandomIntId({int min = 100000, int max = 999999}) { - final random = Random(); - return min + random.nextInt(max - min); - } - - Future createInstantNotification( - String title, - String content, { - NotificationLayout? layout, - String? bigPicture, - }) async { - await AwesomeNotifications().createNotification( - content: NotificationContent( - id: _generateRandomIntId(), - channelKey: 'social', - title: title, - body: content, - bigPicture: bigPicture, - notificationLayout: layout ?? NotificationLayout.Default, - ), - ); - } - - Future scheduleNotification( - DateTime date, - String title, - String body, { - String? channelKey, - NotificationLayout? notificationLayout, - String? bigPicture, - Color? color, - Color? backgroundColor, - NotificationCategory? category, - bool repeats = false, - }) async { - return await AwesomeNotifications().createNotification( - schedule: NotificationCalendar( - day: date.day, - month: date.month, - year: date.year, - preciseAlarm: true, - allowWhileIdle: true, - repeats: repeats, - ), - content: NotificationContent( - id: _generateRandomIntId(), - body: body, - title: title, - wakeUpScreen: channelKey == null ? false : true, - color: color, - backgroundColor: backgroundColor, - channelKey: channelKey ?? "social", - notificationLayout: notificationLayout ?? NotificationLayout.Default, - bigPicture: bigPicture, - category: category ?? NotificationCategory.Message, - ), - ); - } - - /// Cancel all scheduled schedules - /// - /// Waring beware of cancelling all schedules it might result - /// to deleting all schedules which might be unintended - Future cancelScheduledNotifications() async { - await AwesomeNotifications().cancelAllSchedules(); - } -} diff --git a/lib/controllers/reward_controller.dart b/lib/controllers/reward_controller.dart deleted file mode 100644 index 0406f00..0000000 --- a/lib/controllers/reward_controller.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:academia/controllers/user_controller.dart'; -import 'package:academia/models/models.dart'; -import 'package:academia/models/services/services.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:dartz/dartz.dart'; - -class RewardController extends GetxController { - RxInt vibePoints = 0.obs; - final RxList allPoints = [].obs; - final RewardsService service = RewardsService(); - - final UserController _userController = - Get.find(); // Allow data sync - - @override - void onInit() { - super.onInit(); - loadRewards(); - debugPrint("[+] Rewards Loaded!"); - } - - // Returns a list of the current leaderboard - Future>> fetchLeaderBoard() async { - return await service.fetchLeaderBoard(_userController.authHeaders); - } - - Future>> fetchCurrentUserRewards() async { - final result = await service.fetchUserRewards( - _userController.user.value!.id!, _userController.authHeaders); - - return result.fold((l) { - return left(l); - }, (r) async { - for (Reward reward in r) { - await RewardModelHelper().create(reward.toJson()); - } - return right(r); - }); - } - - Future>> loadRewards() async { - final data = await RewardModelHelper().queryAll(); - - if (data.isEmpty) { - final result = await fetchCurrentUserRewards(); - return result.fold((l) => left(l), (r) async { - r.map((e) async { - await UserModelHelper().create(e.toJson()); - }); - return right(r); - }); - } - final rewards = data.map((e) => Reward.fromJson(e)).toList(); - return right(rewards); - } -} diff --git a/lib/controllers/settings_controller.dart b/lib/controllers/settings_controller.dart deleted file mode 100644 index 3534fea..0000000 --- a/lib/controllers/settings_controller.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'dart:async'; - -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import 'package:academia/models/models.dart'; -import 'package:academia/storage/storage.dart'; -import 'package:local_auth/local_auth.dart'; - -class SettingsController extends GetxController with StateMixin { - Rx settings = Settings.empty().obs; - - final LocalAuthentication localAuthentication = LocalAuthentication(); - - @override - void onInit() async { - super.onInit(); - ever(settings, (value) { - saveSettings(value); - }); - - change(null, status: RxStatus.loading()); - await SettingsHelper().init(); - - settings.value = SettingsHelper().getSettings(); - // print(settings.value.toJson()); - debugPrint("[+] Settings Loaded!"); - - change(null, status: RxStatus.success()); - } - - Future performLocalAuthentication(String reason) async { - bool authenticated = false; - - if (Platform.isIOS || Platform.isAndroid) { - try { - authenticated = await localAuthentication.authenticate( - localizedReason: reason, - options: const AuthenticationOptions( - stickyAuth: true, - sensitiveTransaction: true, - ), - ); - } on PlatformException catch (e) { - authenticated = false; - debugPrint(e.toString()); - } catch (e) { - debugPrint(e.toString()); - } - - return authenticated; - } - return true; - } - - Future deviceSupportsLocalAuth() async { - return localAuthentication.isDeviceSupported(); - } - - /// Saves the current settings - void saveSettings(Settings settings) async { - this.settings.value = settings; - await SettingsHelper().saveSettings(settings); - } - - Future _truncateDatabase() async { - await DatabaseHelper().truncateDataBase(); - } - - Future logout() async { - await _truncateDatabase(); - final userController = Get.find(); - userController.isLoggedIn.value = false; - } -} diff --git a/lib/controllers/user_controller.dart b/lib/controllers/user_controller.dart deleted file mode 100644 index 47338e8..0000000 --- a/lib/controllers/user_controller.dart +++ /dev/null @@ -1,161 +0,0 @@ -/// # UserController -/// Author: Erick -/// File: Defines a controller that manages user info state across the application - -import 'package:academia/exports/barrel.dart'; -import 'package:academia/models/services/services.dart'; -import 'package:get/get.dart'; -import 'package:dartz/dartz.dart'; -import 'package:academia/models/models.dart'; - -class UserController extends GetxController { - Rx user = Rxn(); - Rx isLoggedIn = false.obs; - final UserService service = UserService(); - - @override - - /// Load the user from disk and initialize the controller - Future onInit() async { - super.onInit(); - final loadedUser = await loadUserFromDisk(); - - if (loadedUser != null) { - user.value = loadedUser; - final data = await login( - user.value!.admissionNumber, - user.value!.password, - ); - - data.fold((l) { - Get.rawSnackbar( - messageText: Text( - l, - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.white, - ), - ), - duration: const Duration(seconds: 5), - isDismissible: false, - backgroundColor: Colors.red[400]!, - icon: const Icon(Ionicons.magnet, color: Colors.white), - ); - }, (r) {}); - - isLoggedIn.value = true; - } - } - - Future loadUserFromDisk() async { - // load the user from local storage - final storedUsers = await UserModelHelper().queryAll(); - - if (storedUsers.isNotEmpty) { - user.value = User.fromJson(storedUsers[0]); - - // initialize magnet - magnet = Magnet( - user.value!.admissionNumber, - user.value!.password, - ); - return user.value; - } - return null; - } - - Future> register(Map data) async { - final newUser = User.fromMagnet(data); - - final result = await service.register(newUser.toJson()); - - return result.fold((l) => left(l), (r) async { - final res = await service.login(r.admissionNumber, data["password"]); - return res.fold((l) => left(l), (r) { - r.password = data["password"]; - user.value = r; - isLoggedIn.value = true; - UserModelHelper().create(user.value!.toJson()); - return right(true); - }); - }); - } - - Future>> login( - String admno, String password) async { - /// Authenticate with magnet - magnet = Magnet(admno, password); - final result = await magnet.login(); - if (result.isLeft()) { - return left( - "Please check your admission number and password and try again", - ); - } - - /// Check if student already exists on verisafe - final isRegistered = await service.isStudentRegistered(admno); - if (isRegistered.isRight()) { - /// Fetch student details from verisafe - final result = await service.login(admno, password); - return result.fold((l) { - return left(l); - }, (r) { - r.password = password; - user.value = r; - isLoggedIn.value = true; - UserModelHelper().create(user.value!.toMap()); - return right(r.toJson()); - }); - } - - /// Fetch student details via magnet - final res = await magnet.fetchUserDetails(); - if (res.isLeft()) { - return left( - "Please check your admission number and password and try again", - ); - } - - // Return the data for registration - return res.fold((l) => left(l.toString()), (r) => right(r)); - } - - Future> uploadProfilePicture(XFile file) async { - final result = await service.uploadProfilePicture( - user.value!.id!, - await file.readAsBytes(), - file.name, - ); - - return result.fold((l) { - return left(l); - }, (r) { - r.password = user.value!.password; - user.value = r; - UserModelHelper().update(user.value!.toMap()); - return right(user.value!); - }); - } - - Map get authHeaders { - return service.getTokenHeaders(); - } - - /// Logout a user - Future logout() async { - try { - // Close the Hive box - // await appDB.close(); - // Delete the Hive box directory to remove all data - // await Hive.deleteBoxFromDisk(dbName); - Get.reloadAll(); // Clear all the controllers - } catch (e) { - debugPrint("Error during logout: ${e.toString()}"); - } - } - - Future> deleteUser() async { - final result = await service.deleteStudent(authHeaders, user.value!.id!); - return result; - } -} diff --git a/lib/database/database.dart b/lib/database/database.dart new file mode 100644 index 0000000..2966f96 --- /dev/null +++ b/lib/database/database.dart @@ -0,0 +1,61 @@ +import 'dart:io'; + +import 'package:academia/features/auth/repository/user.dart'; +import 'package:academia/features/auth/repository/user_credentials.dart'; +import 'package:academia/features/auth/repository/user_profile.dart'; +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:drift_flutter/drift_flutter.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as p; +import 'package:sqlite3/sqlite3.dart'; +import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart'; + +part 'database.g.dart'; + +LazyDatabase _openConnection() { + return LazyDatabase(() async { + // Determine the correct location for the database based on the platform + final dbFolder = await _getDatabaseDirectory(); + final file = File(p.join(dbFolder.path, 'db.sqlite')); + + // Check for Android workaround (only if on Android) + if (Platform.isAndroid) { + await applyWorkaroundToOpenSqlite3OnOldAndroidVersions(); + } + + // Handle temporary directory setup for all platforms. + final cacheBase = (await getTemporaryDirectory()).path; + sqlite3.tempDirectory = cacheBase; + + return NativeDatabase(file); + }); +} + +Future _getDatabaseDirectory() async { + if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) { + // On desktop, store the database in the home directory + final homeDir = Directory.systemTemp; + return homeDir; + } else { + // On mobile platforms, use application documents directory + return await getApplicationDocumentsDirectory(); + } +} + +@DriftDatabase(tables: [User, UserProfile, UserCredential]) +class AppDatabase extends _$AppDatabase { + // After generating code, this class needs to define a schemaVersion getter + // and a constructor telling drift where the database should be stored. + // These are described in the getting started guide: https://drift.simonbinder.eu/getting-started/#open + AppDatabase() : super(_openConnection()); + + @override + int get schemaVersion => 1; + + static QueryExecutor _openConnection() { + // driftDatabase from package:drift_flutter stores the database in + // getApplicationDocumentsDirectory(). + return driftDatabase(name: 'academia'); + } +} diff --git a/lib/database/database.g.dart b/lib/database/database.g.dart new file mode 100644 index 0000000..80162db --- /dev/null +++ b/lib/database/database.g.dart @@ -0,0 +1,2710 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'database.dart'; + +// ignore_for_file: type=lint +class $UserTable extends User with TableInfo<$UserTable, UserData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $UserTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _usernameMeta = + const VerificationMeta('username'); + @override + late final GeneratedColumn username = GeneratedColumn( + 'username', aliasedName, false, + additionalChecks: + GeneratedColumn.checkTextLength(minTextLength: 1, maxTextLength: 50), + type: DriftSqlType.string, + requiredDuringInsert: true); + static const VerificationMeta _firstnameMeta = + const VerificationMeta('firstname'); + @override + late final GeneratedColumn firstname = GeneratedColumn( + 'firstname', aliasedName, false, + additionalChecks: + GeneratedColumn.checkTextLength(minTextLength: 1, maxTextLength: 100), + type: DriftSqlType.string, + requiredDuringInsert: true); + static const VerificationMeta _othernamesMeta = + const VerificationMeta('othernames'); + @override + late final GeneratedColumn othernames = GeneratedColumn( + 'othernames', aliasedName, true, + additionalChecks: + GeneratedColumn.checkTextLength(minTextLength: 1, maxTextLength: 100), + type: DriftSqlType.string, + requiredDuringInsert: false); + static const VerificationMeta _phoneMeta = const VerificationMeta('phone'); + @override + late final GeneratedColumn phone = GeneratedColumn( + 'phone', aliasedName, false, + additionalChecks: + GeneratedColumn.checkTextLength(minTextLength: 10, maxTextLength: 15), + type: DriftSqlType.string, + requiredDuringInsert: true); + static const VerificationMeta _emailMeta = const VerificationMeta('email'); + @override + late final GeneratedColumn email = GeneratedColumn( + 'email', aliasedName, true, + additionalChecks: + GeneratedColumn.checkTextLength(minTextLength: 5, maxTextLength: 100), + type: DriftSqlType.string, + requiredDuringInsert: false); + static const VerificationMeta _genderMeta = const VerificationMeta('gender'); + @override + late final GeneratedColumn gender = GeneratedColumn( + 'gender', aliasedName, false, + additionalChecks: + GeneratedColumn.checkTextLength(minTextLength: 1, maxTextLength: 10), + type: DriftSqlType.string, + requiredDuringInsert: true); + static const VerificationMeta _activeMeta = const VerificationMeta('active'); + @override + late final GeneratedColumn active = GeneratedColumn( + 'active', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("active" IN (0, 1))'), + defaultValue: const Constant(true)); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _modifiedAtMeta = + const VerificationMeta('modifiedAt'); + @override + late final GeneratedColumn modifiedAt = GeneratedColumn( + 'modified_at', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _dateOfBirthMeta = + const VerificationMeta('dateOfBirth'); + @override + late final GeneratedColumn dateOfBirth = GeneratedColumn( + 'date_of_birth', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _nationalIdMeta = + const VerificationMeta('nationalId'); + @override + late final GeneratedColumn nationalId = GeneratedColumn( + 'national_id', aliasedName, false, + additionalChecks: + GeneratedColumn.checkTextLength(minTextLength: 1, maxTextLength: 20), + type: DriftSqlType.string, + requiredDuringInsert: true); + @override + List get $columns => [ + id, + username, + firstname, + othernames, + phone, + email, + gender, + active, + createdAt, + modifiedAt, + dateOfBirth, + nationalId + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('username')) { + context.handle(_usernameMeta, + username.isAcceptableOrUnknown(data['username']!, _usernameMeta)); + } else if (isInserting) { + context.missing(_usernameMeta); + } + if (data.containsKey('firstname')) { + context.handle(_firstnameMeta, + firstname.isAcceptableOrUnknown(data['firstname']!, _firstnameMeta)); + } else if (isInserting) { + context.missing(_firstnameMeta); + } + if (data.containsKey('othernames')) { + context.handle( + _othernamesMeta, + othernames.isAcceptableOrUnknown( + data['othernames']!, _othernamesMeta)); + } + if (data.containsKey('phone')) { + context.handle( + _phoneMeta, phone.isAcceptableOrUnknown(data['phone']!, _phoneMeta)); + } else if (isInserting) { + context.missing(_phoneMeta); + } + if (data.containsKey('email')) { + context.handle( + _emailMeta, email.isAcceptableOrUnknown(data['email']!, _emailMeta)); + } + if (data.containsKey('gender')) { + context.handle(_genderMeta, + gender.isAcceptableOrUnknown(data['gender']!, _genderMeta)); + } else if (isInserting) { + context.missing(_genderMeta); + } + if (data.containsKey('active')) { + context.handle(_activeMeta, + active.isAcceptableOrUnknown(data['active']!, _activeMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } else if (isInserting) { + context.missing(_createdAtMeta); + } + if (data.containsKey('modified_at')) { + context.handle( + _modifiedAtMeta, + modifiedAt.isAcceptableOrUnknown( + data['modified_at']!, _modifiedAtMeta)); + } else if (isInserting) { + context.missing(_modifiedAtMeta); + } + if (data.containsKey('date_of_birth')) { + context.handle( + _dateOfBirthMeta, + dateOfBirth.isAcceptableOrUnknown( + data['date_of_birth']!, _dateOfBirthMeta)); + } else if (isInserting) { + context.missing(_dateOfBirthMeta); + } + if (data.containsKey('national_id')) { + context.handle( + _nationalIdMeta, + nationalId.isAcceptableOrUnknown( + data['national_id']!, _nationalIdMeta)); + } else if (isInserting) { + context.missing(_nationalIdMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + UserData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}id'])!, + username: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}username'])!, + firstname: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}firstname'])!, + othernames: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}othernames']), + phone: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}phone'])!, + email: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}email']), + gender: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}gender'])!, + active: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}active'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + modifiedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}modified_at'])!, + dateOfBirth: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}date_of_birth'])!, + nationalId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}national_id'])!, + ); + } + + @override + $UserTable createAlias(String alias) { + return $UserTable(attachedDatabase, alias); + } +} + +class UserData extends DataClass implements Insertable { + final String id; + final String username; + final String firstname; + final String? othernames; + final String phone; + final String? email; + final String gender; + final bool active; + final DateTime createdAt; + final DateTime modifiedAt; + final DateTime dateOfBirth; + final String nationalId; + const UserData( + {required this.id, + required this.username, + required this.firstname, + this.othernames, + required this.phone, + this.email, + required this.gender, + required this.active, + required this.createdAt, + required this.modifiedAt, + required this.dateOfBirth, + required this.nationalId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['username'] = Variable(username); + map['firstname'] = Variable(firstname); + if (!nullToAbsent || othernames != null) { + map['othernames'] = Variable(othernames); + } + map['phone'] = Variable(phone); + if (!nullToAbsent || email != null) { + map['email'] = Variable(email); + } + map['gender'] = Variable(gender); + map['active'] = Variable(active); + map['created_at'] = Variable(createdAt); + map['modified_at'] = Variable(modifiedAt); + map['date_of_birth'] = Variable(dateOfBirth); + map['national_id'] = Variable(nationalId); + return map; + } + + UserCompanion toCompanion(bool nullToAbsent) { + return UserCompanion( + id: Value(id), + username: Value(username), + firstname: Value(firstname), + othernames: othernames == null && nullToAbsent + ? const Value.absent() + : Value(othernames), + phone: Value(phone), + email: + email == null && nullToAbsent ? const Value.absent() : Value(email), + gender: Value(gender), + active: Value(active), + createdAt: Value(createdAt), + modifiedAt: Value(modifiedAt), + dateOfBirth: Value(dateOfBirth), + nationalId: Value(nationalId), + ); + } + + factory UserData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserData( + id: serializer.fromJson(json['id']), + username: serializer.fromJson(json['username']), + firstname: serializer.fromJson(json['firstname']), + othernames: serializer.fromJson(json['othernames']), + phone: serializer.fromJson(json['phone']), + email: serializer.fromJson(json['email']), + gender: serializer.fromJson(json['gender']), + active: serializer.fromJson(json['active']), + createdAt: serializer.fromJson(json['created_at']), + modifiedAt: serializer.fromJson(json['modified_at']), + dateOfBirth: serializer.fromJson(json['date_of_birth']), + nationalId: serializer.fromJson(json['national_id']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'username': serializer.toJson(username), + 'firstname': serializer.toJson(firstname), + 'othernames': serializer.toJson(othernames), + 'phone': serializer.toJson(phone), + 'email': serializer.toJson(email), + 'gender': serializer.toJson(gender), + 'active': serializer.toJson(active), + 'created_at': serializer.toJson(createdAt), + 'modified_at': serializer.toJson(modifiedAt), + 'date_of_birth': serializer.toJson(dateOfBirth), + 'national_id': serializer.toJson(nationalId), + }; + } + + UserData copyWith( + {String? id, + String? username, + String? firstname, + Value othernames = const Value.absent(), + String? phone, + Value email = const Value.absent(), + String? gender, + bool? active, + DateTime? createdAt, + DateTime? modifiedAt, + DateTime? dateOfBirth, + String? nationalId}) => + UserData( + id: id ?? this.id, + username: username ?? this.username, + firstname: firstname ?? this.firstname, + othernames: othernames.present ? othernames.value : this.othernames, + phone: phone ?? this.phone, + email: email.present ? email.value : this.email, + gender: gender ?? this.gender, + active: active ?? this.active, + createdAt: createdAt ?? this.createdAt, + modifiedAt: modifiedAt ?? this.modifiedAt, + dateOfBirth: dateOfBirth ?? this.dateOfBirth, + nationalId: nationalId ?? this.nationalId, + ); + UserData copyWithCompanion(UserCompanion data) { + return UserData( + id: data.id.present ? data.id.value : this.id, + username: data.username.present ? data.username.value : this.username, + firstname: data.firstname.present ? data.firstname.value : this.firstname, + othernames: + data.othernames.present ? data.othernames.value : this.othernames, + phone: data.phone.present ? data.phone.value : this.phone, + email: data.email.present ? data.email.value : this.email, + gender: data.gender.present ? data.gender.value : this.gender, + active: data.active.present ? data.active.value : this.active, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + modifiedAt: + data.modifiedAt.present ? data.modifiedAt.value : this.modifiedAt, + dateOfBirth: + data.dateOfBirth.present ? data.dateOfBirth.value : this.dateOfBirth, + nationalId: + data.nationalId.present ? data.nationalId.value : this.nationalId, + ); + } + + @override + String toString() { + return (StringBuffer('UserData(') + ..write('id: $id, ') + ..write('username: $username, ') + ..write('firstname: $firstname, ') + ..write('othernames: $othernames, ') + ..write('phone: $phone, ') + ..write('email: $email, ') + ..write('gender: $gender, ') + ..write('active: $active, ') + ..write('createdAt: $createdAt, ') + ..write('modifiedAt: $modifiedAt, ') + ..write('dateOfBirth: $dateOfBirth, ') + ..write('nationalId: $nationalId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, username, firstname, othernames, phone, + email, gender, active, createdAt, modifiedAt, dateOfBirth, nationalId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserData && + other.id == this.id && + other.username == this.username && + other.firstname == this.firstname && + other.othernames == this.othernames && + other.phone == this.phone && + other.email == this.email && + other.gender == this.gender && + other.active == this.active && + other.createdAt == this.createdAt && + other.modifiedAt == this.modifiedAt && + other.dateOfBirth == this.dateOfBirth && + other.nationalId == this.nationalId); +} + +class UserCompanion extends UpdateCompanion { + final Value id; + final Value username; + final Value firstname; + final Value othernames; + final Value phone; + final Value email; + final Value gender; + final Value active; + final Value createdAt; + final Value modifiedAt; + final Value dateOfBirth; + final Value nationalId; + final Value rowid; + const UserCompanion({ + this.id = const Value.absent(), + this.username = const Value.absent(), + this.firstname = const Value.absent(), + this.othernames = const Value.absent(), + this.phone = const Value.absent(), + this.email = const Value.absent(), + this.gender = const Value.absent(), + this.active = const Value.absent(), + this.createdAt = const Value.absent(), + this.modifiedAt = const Value.absent(), + this.dateOfBirth = const Value.absent(), + this.nationalId = const Value.absent(), + this.rowid = const Value.absent(), + }); + UserCompanion.insert({ + required String id, + required String username, + required String firstname, + this.othernames = const Value.absent(), + required String phone, + this.email = const Value.absent(), + required String gender, + this.active = const Value.absent(), + required DateTime createdAt, + required DateTime modifiedAt, + required DateTime dateOfBirth, + required String nationalId, + this.rowid = const Value.absent(), + }) : id = Value(id), + username = Value(username), + firstname = Value(firstname), + phone = Value(phone), + gender = Value(gender), + createdAt = Value(createdAt), + modifiedAt = Value(modifiedAt), + dateOfBirth = Value(dateOfBirth), + nationalId = Value(nationalId); + static Insertable custom({ + Expression? id, + Expression? username, + Expression? firstname, + Expression? othernames, + Expression? phone, + Expression? email, + Expression? gender, + Expression? active, + Expression? createdAt, + Expression? modifiedAt, + Expression? dateOfBirth, + Expression? nationalId, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (username != null) 'username': username, + if (firstname != null) 'firstname': firstname, + if (othernames != null) 'othernames': othernames, + if (phone != null) 'phone': phone, + if (email != null) 'email': email, + if (gender != null) 'gender': gender, + if (active != null) 'active': active, + if (createdAt != null) 'created_at': createdAt, + if (modifiedAt != null) 'modified_at': modifiedAt, + if (dateOfBirth != null) 'date_of_birth': dateOfBirth, + if (nationalId != null) 'national_id': nationalId, + if (rowid != null) 'rowid': rowid, + }); + } + + UserCompanion copyWith( + {Value? id, + Value? username, + Value? firstname, + Value? othernames, + Value? phone, + Value? email, + Value? gender, + Value? active, + Value? createdAt, + Value? modifiedAt, + Value? dateOfBirth, + Value? nationalId, + Value? rowid}) { + return UserCompanion( + id: id ?? this.id, + username: username ?? this.username, + firstname: firstname ?? this.firstname, + othernames: othernames ?? this.othernames, + phone: phone ?? this.phone, + email: email ?? this.email, + gender: gender ?? this.gender, + active: active ?? this.active, + createdAt: createdAt ?? this.createdAt, + modifiedAt: modifiedAt ?? this.modifiedAt, + dateOfBirth: dateOfBirth ?? this.dateOfBirth, + nationalId: nationalId ?? this.nationalId, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (username.present) { + map['username'] = Variable(username.value); + } + if (firstname.present) { + map['firstname'] = Variable(firstname.value); + } + if (othernames.present) { + map['othernames'] = Variable(othernames.value); + } + if (phone.present) { + map['phone'] = Variable(phone.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (gender.present) { + map['gender'] = Variable(gender.value); + } + if (active.present) { + map['active'] = Variable(active.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (modifiedAt.present) { + map['modified_at'] = Variable(modifiedAt.value); + } + if (dateOfBirth.present) { + map['date_of_birth'] = Variable(dateOfBirth.value); + } + if (nationalId.present) { + map['national_id'] = Variable(nationalId.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserCompanion(') + ..write('id: $id, ') + ..write('username: $username, ') + ..write('firstname: $firstname, ') + ..write('othernames: $othernames, ') + ..write('phone: $phone, ') + ..write('email: $email, ') + ..write('gender: $gender, ') + ..write('active: $active, ') + ..write('createdAt: $createdAt, ') + ..write('modifiedAt: $modifiedAt, ') + ..write('dateOfBirth: $dateOfBirth, ') + ..write('nationalId: $nationalId, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $UserProfileTable extends UserProfile + with TableInfo<$UserProfileTable, UserProfileData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $UserProfileTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('REFERENCES user (id)')); + static const VerificationMeta _bioMeta = const VerificationMeta('bio'); + @override + late final GeneratedColumn bio = GeneratedColumn( + 'bio', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _vibePointsMeta = + const VerificationMeta('vibePoints'); + @override + late final GeneratedColumn vibePoints = GeneratedColumn( + 'vibe_points', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + static const VerificationMeta _profilePictureUrlMeta = + const VerificationMeta('profilePictureUrl'); + @override + late final GeneratedColumn profilePictureUrl = + GeneratedColumn('profile_picture_url', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _lastSeenMeta = + const VerificationMeta('lastSeen'); + @override + late final GeneratedColumn lastSeen = GeneratedColumn( + 'last_seen', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: Constant(DateTime.now())); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: Constant(DateTime.now())); + static const VerificationMeta _modifiedAtMeta = + const VerificationMeta('modifiedAt'); + @override + late final GeneratedColumn modifiedAt = GeneratedColumn( + 'modified_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: Constant(DateTime.now())); + static const VerificationMeta _admissionNumberMeta = + const VerificationMeta('admissionNumber'); + @override + late final GeneratedColumn admissionNumber = GeneratedColumn( + 'admission_number', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _campusMeta = const VerificationMeta('campus'); + @override + late final GeneratedColumn campus = GeneratedColumn( + 'campus', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("athi")); + @override + List get $columns => [ + id, + userId, + bio, + vibePoints, + profilePictureUrl, + lastSeen, + createdAt, + modifiedAt, + admissionNumber, + campus + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_profile'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('bio')) { + context.handle( + _bioMeta, bio.isAcceptableOrUnknown(data['bio']!, _bioMeta)); + } + if (data.containsKey('vibe_points')) { + context.handle( + _vibePointsMeta, + vibePoints.isAcceptableOrUnknown( + data['vibe_points']!, _vibePointsMeta)); + } + if (data.containsKey('profile_picture_url')) { + context.handle( + _profilePictureUrlMeta, + profilePictureUrl.isAcceptableOrUnknown( + data['profile_picture_url']!, _profilePictureUrlMeta)); + } + if (data.containsKey('last_seen')) { + context.handle(_lastSeenMeta, + lastSeen.isAcceptableOrUnknown(data['last_seen']!, _lastSeenMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('modified_at')) { + context.handle( + _modifiedAtMeta, + modifiedAt.isAcceptableOrUnknown( + data['modified_at']!, _modifiedAtMeta)); + } + if (data.containsKey('admission_number')) { + context.handle( + _admissionNumberMeta, + admissionNumber.isAcceptableOrUnknown( + data['admission_number']!, _admissionNumberMeta)); + } + if (data.containsKey('campus')) { + context.handle(_campusMeta, + campus.isAcceptableOrUnknown(data['campus']!, _campusMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + UserProfileData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserProfileData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + userId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}user_id'])!, + bio: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}bio']), + vibePoints: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}vibe_points'])!, + profilePictureUrl: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}profile_picture_url']), + lastSeen: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}last_seen'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + modifiedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}modified_at'])!, + admissionNumber: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}admission_number']), + campus: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}campus'])!, + ); + } + + @override + $UserProfileTable createAlias(String alias) { + return $UserProfileTable(attachedDatabase, alias); + } +} + +class UserProfileData extends DataClass implements Insertable { + final int id; + final String userId; + final String? bio; + final int vibePoints; + final String? profilePictureUrl; + final DateTime lastSeen; + final DateTime createdAt; + final DateTime modifiedAt; + final String? admissionNumber; + final String campus; + const UserProfileData( + {required this.id, + required this.userId, + this.bio, + required this.vibePoints, + this.profilePictureUrl, + required this.lastSeen, + required this.createdAt, + required this.modifiedAt, + this.admissionNumber, + required this.campus}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['user_id'] = Variable(userId); + if (!nullToAbsent || bio != null) { + map['bio'] = Variable(bio); + } + map['vibe_points'] = Variable(vibePoints); + if (!nullToAbsent || profilePictureUrl != null) { + map['profile_picture_url'] = Variable(profilePictureUrl); + } + map['last_seen'] = Variable(lastSeen); + map['created_at'] = Variable(createdAt); + map['modified_at'] = Variable(modifiedAt); + if (!nullToAbsent || admissionNumber != null) { + map['admission_number'] = Variable(admissionNumber); + } + map['campus'] = Variable(campus); + return map; + } + + UserProfileCompanion toCompanion(bool nullToAbsent) { + return UserProfileCompanion( + id: Value(id), + userId: Value(userId), + bio: bio == null && nullToAbsent ? const Value.absent() : Value(bio), + vibePoints: Value(vibePoints), + profilePictureUrl: profilePictureUrl == null && nullToAbsent + ? const Value.absent() + : Value(profilePictureUrl), + lastSeen: Value(lastSeen), + createdAt: Value(createdAt), + modifiedAt: Value(modifiedAt), + admissionNumber: admissionNumber == null && nullToAbsent + ? const Value.absent() + : Value(admissionNumber), + campus: Value(campus), + ); + } + + factory UserProfileData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserProfileData( + id: serializer.fromJson(json['id']), + userId: serializer.fromJson(json['user_id']), + bio: serializer.fromJson(json['bio']), + vibePoints: serializer.fromJson(json['vibe_points']), + profilePictureUrl: + serializer.fromJson(json['profile_picture_url']), + lastSeen: serializer.fromJson(json['last_seen']), + createdAt: serializer.fromJson(json['created_at']), + modifiedAt: serializer.fromJson(json['modified_at']), + admissionNumber: serializer.fromJson(json['admission_number']), + campus: serializer.fromJson(json['campus']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'user_id': serializer.toJson(userId), + 'bio': serializer.toJson(bio), + 'vibe_points': serializer.toJson(vibePoints), + 'profile_picture_url': serializer.toJson(profilePictureUrl), + 'last_seen': serializer.toJson(lastSeen), + 'created_at': serializer.toJson(createdAt), + 'modified_at': serializer.toJson(modifiedAt), + 'admission_number': serializer.toJson(admissionNumber), + 'campus': serializer.toJson(campus), + }; + } + + UserProfileData copyWith( + {int? id, + String? userId, + Value bio = const Value.absent(), + int? vibePoints, + Value profilePictureUrl = const Value.absent(), + DateTime? lastSeen, + DateTime? createdAt, + DateTime? modifiedAt, + Value admissionNumber = const Value.absent(), + String? campus}) => + UserProfileData( + id: id ?? this.id, + userId: userId ?? this.userId, + bio: bio.present ? bio.value : this.bio, + vibePoints: vibePoints ?? this.vibePoints, + profilePictureUrl: profilePictureUrl.present + ? profilePictureUrl.value + : this.profilePictureUrl, + lastSeen: lastSeen ?? this.lastSeen, + createdAt: createdAt ?? this.createdAt, + modifiedAt: modifiedAt ?? this.modifiedAt, + admissionNumber: admissionNumber.present + ? admissionNumber.value + : this.admissionNumber, + campus: campus ?? this.campus, + ); + UserProfileData copyWithCompanion(UserProfileCompanion data) { + return UserProfileData( + id: data.id.present ? data.id.value : this.id, + userId: data.userId.present ? data.userId.value : this.userId, + bio: data.bio.present ? data.bio.value : this.bio, + vibePoints: + data.vibePoints.present ? data.vibePoints.value : this.vibePoints, + profilePictureUrl: data.profilePictureUrl.present + ? data.profilePictureUrl.value + : this.profilePictureUrl, + lastSeen: data.lastSeen.present ? data.lastSeen.value : this.lastSeen, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + modifiedAt: + data.modifiedAt.present ? data.modifiedAt.value : this.modifiedAt, + admissionNumber: data.admissionNumber.present + ? data.admissionNumber.value + : this.admissionNumber, + campus: data.campus.present ? data.campus.value : this.campus, + ); + } + + @override + String toString() { + return (StringBuffer('UserProfileData(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('bio: $bio, ') + ..write('vibePoints: $vibePoints, ') + ..write('profilePictureUrl: $profilePictureUrl, ') + ..write('lastSeen: $lastSeen, ') + ..write('createdAt: $createdAt, ') + ..write('modifiedAt: $modifiedAt, ') + ..write('admissionNumber: $admissionNumber, ') + ..write('campus: $campus') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + userId, + bio, + vibePoints, + profilePictureUrl, + lastSeen, + createdAt, + modifiedAt, + admissionNumber, + campus); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserProfileData && + other.id == this.id && + other.userId == this.userId && + other.bio == this.bio && + other.vibePoints == this.vibePoints && + other.profilePictureUrl == this.profilePictureUrl && + other.lastSeen == this.lastSeen && + other.createdAt == this.createdAt && + other.modifiedAt == this.modifiedAt && + other.admissionNumber == this.admissionNumber && + other.campus == this.campus); +} + +class UserProfileCompanion extends UpdateCompanion { + final Value id; + final Value userId; + final Value bio; + final Value vibePoints; + final Value profilePictureUrl; + final Value lastSeen; + final Value createdAt; + final Value modifiedAt; + final Value admissionNumber; + final Value campus; + const UserProfileCompanion({ + this.id = const Value.absent(), + this.userId = const Value.absent(), + this.bio = const Value.absent(), + this.vibePoints = const Value.absent(), + this.profilePictureUrl = const Value.absent(), + this.lastSeen = const Value.absent(), + this.createdAt = const Value.absent(), + this.modifiedAt = const Value.absent(), + this.admissionNumber = const Value.absent(), + this.campus = const Value.absent(), + }); + UserProfileCompanion.insert({ + this.id = const Value.absent(), + required String userId, + this.bio = const Value.absent(), + this.vibePoints = const Value.absent(), + this.profilePictureUrl = const Value.absent(), + this.lastSeen = const Value.absent(), + this.createdAt = const Value.absent(), + this.modifiedAt = const Value.absent(), + this.admissionNumber = const Value.absent(), + this.campus = const Value.absent(), + }) : userId = Value(userId); + static Insertable custom({ + Expression? id, + Expression? userId, + Expression? bio, + Expression? vibePoints, + Expression? profilePictureUrl, + Expression? lastSeen, + Expression? createdAt, + Expression? modifiedAt, + Expression? admissionNumber, + Expression? campus, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (userId != null) 'user_id': userId, + if (bio != null) 'bio': bio, + if (vibePoints != null) 'vibe_points': vibePoints, + if (profilePictureUrl != null) 'profile_picture_url': profilePictureUrl, + if (lastSeen != null) 'last_seen': lastSeen, + if (createdAt != null) 'created_at': createdAt, + if (modifiedAt != null) 'modified_at': modifiedAt, + if (admissionNumber != null) 'admission_number': admissionNumber, + if (campus != null) 'campus': campus, + }); + } + + UserProfileCompanion copyWith( + {Value? id, + Value? userId, + Value? bio, + Value? vibePoints, + Value? profilePictureUrl, + Value? lastSeen, + Value? createdAt, + Value? modifiedAt, + Value? admissionNumber, + Value? campus}) { + return UserProfileCompanion( + id: id ?? this.id, + userId: userId ?? this.userId, + bio: bio ?? this.bio, + vibePoints: vibePoints ?? this.vibePoints, + profilePictureUrl: profilePictureUrl ?? this.profilePictureUrl, + lastSeen: lastSeen ?? this.lastSeen, + createdAt: createdAt ?? this.createdAt, + modifiedAt: modifiedAt ?? this.modifiedAt, + admissionNumber: admissionNumber ?? this.admissionNumber, + campus: campus ?? this.campus, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (bio.present) { + map['bio'] = Variable(bio.value); + } + if (vibePoints.present) { + map['vibe_points'] = Variable(vibePoints.value); + } + if (profilePictureUrl.present) { + map['profile_picture_url'] = Variable(profilePictureUrl.value); + } + if (lastSeen.present) { + map['last_seen'] = Variable(lastSeen.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (modifiedAt.present) { + map['modified_at'] = Variable(modifiedAt.value); + } + if (admissionNumber.present) { + map['admission_number'] = Variable(admissionNumber.value); + } + if (campus.present) { + map['campus'] = Variable(campus.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserProfileCompanion(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('bio: $bio, ') + ..write('vibePoints: $vibePoints, ') + ..write('profilePictureUrl: $profilePictureUrl, ') + ..write('lastSeen: $lastSeen, ') + ..write('createdAt: $createdAt, ') + ..write('modifiedAt: $modifiedAt, ') + ..write('admissionNumber: $admissionNumber, ') + ..write('campus: $campus') + ..write(')')) + .toString(); + } +} + +class $UserCredentialTable extends UserCredential + with TableInfo<$UserCredentialTable, UserCredentialData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $UserCredentialTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('REFERENCES user (id)')); + static const VerificationMeta _admnoMeta = const VerificationMeta('admno'); + @override + late final GeneratedColumn admno = GeneratedColumn( + 'admno', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _accessTokenMeta = + const VerificationMeta('accessToken'); + @override + late final GeneratedColumn accessToken = GeneratedColumn( + 'access_token', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _usernameMeta = + const VerificationMeta('username'); + @override + late final GeneratedColumn username = GeneratedColumn( + 'username', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('REFERENCES user (username)')); + static const VerificationMeta _emailMeta = const VerificationMeta('email'); + @override + late final GeneratedColumn email = GeneratedColumn( + 'email', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('REFERENCES user (email)')); + static const VerificationMeta _passwordMeta = + const VerificationMeta('password'); + @override + late final GeneratedColumn password = GeneratedColumn( + 'password', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _lastLoginMeta = + const VerificationMeta('lastLogin'); + @override + late final GeneratedColumn lastLogin = GeneratedColumn( + 'last_login', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + @override + List get $columns => + [userId, admno, accessToken, username, email, password, lastLogin]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_credential'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } + if (data.containsKey('admno')) { + context.handle( + _admnoMeta, admno.isAcceptableOrUnknown(data['admno']!, _admnoMeta)); + } else if (isInserting) { + context.missing(_admnoMeta); + } + if (data.containsKey('access_token')) { + context.handle( + _accessTokenMeta, + accessToken.isAcceptableOrUnknown( + data['access_token']!, _accessTokenMeta)); + } + if (data.containsKey('username')) { + context.handle(_usernameMeta, + username.isAcceptableOrUnknown(data['username']!, _usernameMeta)); + } else if (isInserting) { + context.missing(_usernameMeta); + } + if (data.containsKey('email')) { + context.handle( + _emailMeta, email.isAcceptableOrUnknown(data['email']!, _emailMeta)); + } else if (isInserting) { + context.missing(_emailMeta); + } + if (data.containsKey('password')) { + context.handle(_passwordMeta, + password.isAcceptableOrUnknown(data['password']!, _passwordMeta)); + } else if (isInserting) { + context.missing(_passwordMeta); + } + if (data.containsKey('last_login')) { + context.handle(_lastLoginMeta, + lastLogin.isAcceptableOrUnknown(data['last_login']!, _lastLoginMeta)); + } + return context; + } + + @override + Set get $primaryKey => {userId}; + @override + UserCredentialData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserCredentialData( + userId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}user_id']), + admno: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}admno'])!, + accessToken: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}access_token']), + username: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}username'])!, + email: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}email'])!, + password: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}password'])!, + lastLogin: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}last_login']), + ); + } + + @override + $UserCredentialTable createAlias(String alias) { + return $UserCredentialTable(attachedDatabase, alias); + } +} + +class UserCredentialData extends DataClass + implements Insertable { + final String? userId; + final String admno; + final String? accessToken; + final String username; + final String email; + final String password; + final DateTime? lastLogin; + const UserCredentialData( + {this.userId, + required this.admno, + this.accessToken, + required this.username, + required this.email, + required this.password, + this.lastLogin}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (!nullToAbsent || userId != null) { + map['user_id'] = Variable(userId); + } + map['admno'] = Variable(admno); + if (!nullToAbsent || accessToken != null) { + map['access_token'] = Variable(accessToken); + } + map['username'] = Variable(username); + map['email'] = Variable(email); + map['password'] = Variable(password); + if (!nullToAbsent || lastLogin != null) { + map['last_login'] = Variable(lastLogin); + } + return map; + } + + UserCredentialCompanion toCompanion(bool nullToAbsent) { + return UserCredentialCompanion( + userId: + userId == null && nullToAbsent ? const Value.absent() : Value(userId), + admno: Value(admno), + accessToken: accessToken == null && nullToAbsent + ? const Value.absent() + : Value(accessToken), + username: Value(username), + email: Value(email), + password: Value(password), + lastLogin: lastLogin == null && nullToAbsent + ? const Value.absent() + : Value(lastLogin), + ); + } + + factory UserCredentialData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserCredentialData( + userId: serializer.fromJson(json['userId']), + admno: serializer.fromJson(json['admission_number']), + accessToken: serializer.fromJson(json['access_token']), + username: serializer.fromJson(json['username']), + email: serializer.fromJson(json['email']), + password: serializer.fromJson(json['password']), + lastLogin: serializer.fromJson(json['last_login']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'admission_number': serializer.toJson(admno), + 'access_token': serializer.toJson(accessToken), + 'username': serializer.toJson(username), + 'email': serializer.toJson(email), + 'password': serializer.toJson(password), + 'last_login': serializer.toJson(lastLogin), + }; + } + + UserCredentialData copyWith( + {Value userId = const Value.absent(), + String? admno, + Value accessToken = const Value.absent(), + String? username, + String? email, + String? password, + Value lastLogin = const Value.absent()}) => + UserCredentialData( + userId: userId.present ? userId.value : this.userId, + admno: admno ?? this.admno, + accessToken: accessToken.present ? accessToken.value : this.accessToken, + username: username ?? this.username, + email: email ?? this.email, + password: password ?? this.password, + lastLogin: lastLogin.present ? lastLogin.value : this.lastLogin, + ); + UserCredentialData copyWithCompanion(UserCredentialCompanion data) { + return UserCredentialData( + userId: data.userId.present ? data.userId.value : this.userId, + admno: data.admno.present ? data.admno.value : this.admno, + accessToken: + data.accessToken.present ? data.accessToken.value : this.accessToken, + username: data.username.present ? data.username.value : this.username, + email: data.email.present ? data.email.value : this.email, + password: data.password.present ? data.password.value : this.password, + lastLogin: data.lastLogin.present ? data.lastLogin.value : this.lastLogin, + ); + } + + @override + String toString() { + return (StringBuffer('UserCredentialData(') + ..write('userId: $userId, ') + ..write('admno: $admno, ') + ..write('accessToken: $accessToken, ') + ..write('username: $username, ') + ..write('email: $email, ') + ..write('password: $password, ') + ..write('lastLogin: $lastLogin') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + userId, admno, accessToken, username, email, password, lastLogin); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserCredentialData && + other.userId == this.userId && + other.admno == this.admno && + other.accessToken == this.accessToken && + other.username == this.username && + other.email == this.email && + other.password == this.password && + other.lastLogin == this.lastLogin); +} + +class UserCredentialCompanion extends UpdateCompanion { + final Value userId; + final Value admno; + final Value accessToken; + final Value username; + final Value email; + final Value password; + final Value lastLogin; + final Value rowid; + const UserCredentialCompanion({ + this.userId = const Value.absent(), + this.admno = const Value.absent(), + this.accessToken = const Value.absent(), + this.username = const Value.absent(), + this.email = const Value.absent(), + this.password = const Value.absent(), + this.lastLogin = const Value.absent(), + this.rowid = const Value.absent(), + }); + UserCredentialCompanion.insert({ + this.userId = const Value.absent(), + required String admno, + this.accessToken = const Value.absent(), + required String username, + required String email, + required String password, + this.lastLogin = const Value.absent(), + this.rowid = const Value.absent(), + }) : admno = Value(admno), + username = Value(username), + email = Value(email), + password = Value(password); + static Insertable custom({ + Expression? userId, + Expression? admno, + Expression? accessToken, + Expression? username, + Expression? email, + Expression? password, + Expression? lastLogin, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (admno != null) 'admno': admno, + if (accessToken != null) 'access_token': accessToken, + if (username != null) 'username': username, + if (email != null) 'email': email, + if (password != null) 'password': password, + if (lastLogin != null) 'last_login': lastLogin, + if (rowid != null) 'rowid': rowid, + }); + } + + UserCredentialCompanion copyWith( + {Value? userId, + Value? admno, + Value? accessToken, + Value? username, + Value? email, + Value? password, + Value? lastLogin, + Value? rowid}) { + return UserCredentialCompanion( + userId: userId ?? this.userId, + admno: admno ?? this.admno, + accessToken: accessToken ?? this.accessToken, + username: username ?? this.username, + email: email ?? this.email, + password: password ?? this.password, + lastLogin: lastLogin ?? this.lastLogin, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (admno.present) { + map['admno'] = Variable(admno.value); + } + if (accessToken.present) { + map['access_token'] = Variable(accessToken.value); + } + if (username.present) { + map['username'] = Variable(username.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (password.present) { + map['password'] = Variable(password.value); + } + if (lastLogin.present) { + map['last_login'] = Variable(lastLogin.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserCredentialCompanion(') + ..write('userId: $userId, ') + ..write('admno: $admno, ') + ..write('accessToken: $accessToken, ') + ..write('username: $username, ') + ..write('email: $email, ') + ..write('password: $password, ') + ..write('lastLogin: $lastLogin, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +abstract class _$AppDatabase extends GeneratedDatabase { + _$AppDatabase(QueryExecutor e) : super(e); + $AppDatabaseManager get managers => $AppDatabaseManager(this); + late final $UserTable user = $UserTable(this); + late final $UserProfileTable userProfile = $UserProfileTable(this); + late final $UserCredentialTable userCredential = $UserCredentialTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => + [user, userProfile, userCredential]; +} + +typedef $$UserTableCreateCompanionBuilder = UserCompanion Function({ + required String id, + required String username, + required String firstname, + Value othernames, + required String phone, + Value email, + required String gender, + Value active, + required DateTime createdAt, + required DateTime modifiedAt, + required DateTime dateOfBirth, + required String nationalId, + Value rowid, +}); +typedef $$UserTableUpdateCompanionBuilder = UserCompanion Function({ + Value id, + Value username, + Value firstname, + Value othernames, + Value phone, + Value email, + Value gender, + Value active, + Value createdAt, + Value modifiedAt, + Value dateOfBirth, + Value nationalId, + Value rowid, +}); + +final class $$UserTableReferences + extends BaseReferences<_$AppDatabase, $UserTable, UserData> { + $$UserTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$UserProfileTable, List> + _userProfileRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.userProfile, + aliasName: $_aliasNameGenerator(db.user.id, db.userProfile.userId)); + + $$UserProfileTableProcessedTableManager get userProfileRefs { + final manager = $$UserProfileTableTableManager($_db, $_db.userProfile) + .filter((f) => f.userId.id($_item.id)); + + final cache = $_typedResult.readTableOrNull(_userProfileRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache)); + } +} + +class $$UserTableFilterComposer extends Composer<_$AppDatabase, $UserTable> { + $$UserTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnFilters(column)); + + ColumnFilters get username => $composableBuilder( + column: $table.username, builder: (column) => ColumnFilters(column)); + + ColumnFilters get firstname => $composableBuilder( + column: $table.firstname, builder: (column) => ColumnFilters(column)); + + ColumnFilters get othernames => $composableBuilder( + column: $table.othernames, builder: (column) => ColumnFilters(column)); + + ColumnFilters get phone => $composableBuilder( + column: $table.phone, builder: (column) => ColumnFilters(column)); + + ColumnFilters get email => $composableBuilder( + column: $table.email, builder: (column) => ColumnFilters(column)); + + ColumnFilters get gender => $composableBuilder( + column: $table.gender, builder: (column) => ColumnFilters(column)); + + ColumnFilters get active => $composableBuilder( + column: $table.active, builder: (column) => ColumnFilters(column)); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, builder: (column) => ColumnFilters(column)); + + ColumnFilters get modifiedAt => $composableBuilder( + column: $table.modifiedAt, builder: (column) => ColumnFilters(column)); + + ColumnFilters get dateOfBirth => $composableBuilder( + column: $table.dateOfBirth, builder: (column) => ColumnFilters(column)); + + ColumnFilters get nationalId => $composableBuilder( + column: $table.nationalId, builder: (column) => ColumnFilters(column)); + + Expression userProfileRefs( + Expression Function($$UserProfileTableFilterComposer f) f) { + final $$UserProfileTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.userProfile, + getReferencedColumn: (t) => t.userId, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$UserProfileTableFilterComposer( + $db: $db, + $table: $db.userProfile, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } +} + +class $$UserTableOrderingComposer extends Composer<_$AppDatabase, $UserTable> { + $$UserTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get username => $composableBuilder( + column: $table.username, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get firstname => $composableBuilder( + column: $table.firstname, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get othernames => $composableBuilder( + column: $table.othernames, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get phone => $composableBuilder( + column: $table.phone, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get email => $composableBuilder( + column: $table.email, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get gender => $composableBuilder( + column: $table.gender, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get active => $composableBuilder( + column: $table.active, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get modifiedAt => $composableBuilder( + column: $table.modifiedAt, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get dateOfBirth => $composableBuilder( + column: $table.dateOfBirth, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get nationalId => $composableBuilder( + column: $table.nationalId, builder: (column) => ColumnOrderings(column)); +} + +class $$UserTableAnnotationComposer + extends Composer<_$AppDatabase, $UserTable> { + $$UserTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get username => + $composableBuilder(column: $table.username, builder: (column) => column); + + GeneratedColumn get firstname => + $composableBuilder(column: $table.firstname, builder: (column) => column); + + GeneratedColumn get othernames => $composableBuilder( + column: $table.othernames, builder: (column) => column); + + GeneratedColumn get phone => + $composableBuilder(column: $table.phone, builder: (column) => column); + + GeneratedColumn get email => + $composableBuilder(column: $table.email, builder: (column) => column); + + GeneratedColumn get gender => + $composableBuilder(column: $table.gender, builder: (column) => column); + + GeneratedColumn get active => + $composableBuilder(column: $table.active, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + GeneratedColumn get modifiedAt => $composableBuilder( + column: $table.modifiedAt, builder: (column) => column); + + GeneratedColumn get dateOfBirth => $composableBuilder( + column: $table.dateOfBirth, builder: (column) => column); + + GeneratedColumn get nationalId => $composableBuilder( + column: $table.nationalId, builder: (column) => column); + + Expression userProfileRefs( + Expression Function($$UserProfileTableAnnotationComposer a) f) { + final $$UserProfileTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.userProfile, + getReferencedColumn: (t) => t.userId, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$UserProfileTableAnnotationComposer( + $db: $db, + $table: $db.userProfile, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } +} + +class $$UserTableTableManager extends RootTableManager< + _$AppDatabase, + $UserTable, + UserData, + $$UserTableFilterComposer, + $$UserTableOrderingComposer, + $$UserTableAnnotationComposer, + $$UserTableCreateCompanionBuilder, + $$UserTableUpdateCompanionBuilder, + (UserData, $$UserTableReferences), + UserData, + PrefetchHooks Function({bool userProfileRefs})> { + $$UserTableTableManager(_$AppDatabase db, $UserTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$UserTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$UserTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$UserTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value id = const Value.absent(), + Value username = const Value.absent(), + Value firstname = const Value.absent(), + Value othernames = const Value.absent(), + Value phone = const Value.absent(), + Value email = const Value.absent(), + Value gender = const Value.absent(), + Value active = const Value.absent(), + Value createdAt = const Value.absent(), + Value modifiedAt = const Value.absent(), + Value dateOfBirth = const Value.absent(), + Value nationalId = const Value.absent(), + Value rowid = const Value.absent(), + }) => + UserCompanion( + id: id, + username: username, + firstname: firstname, + othernames: othernames, + phone: phone, + email: email, + gender: gender, + active: active, + createdAt: createdAt, + modifiedAt: modifiedAt, + dateOfBirth: dateOfBirth, + nationalId: nationalId, + rowid: rowid, + ), + createCompanionCallback: ({ + required String id, + required String username, + required String firstname, + Value othernames = const Value.absent(), + required String phone, + Value email = const Value.absent(), + required String gender, + Value active = const Value.absent(), + required DateTime createdAt, + required DateTime modifiedAt, + required DateTime dateOfBirth, + required String nationalId, + Value rowid = const Value.absent(), + }) => + UserCompanion.insert( + id: id, + username: username, + firstname: firstname, + othernames: othernames, + phone: phone, + email: email, + gender: gender, + active: active, + createdAt: createdAt, + modifiedAt: modifiedAt, + dateOfBirth: dateOfBirth, + nationalId: nationalId, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => + (e.readTable(table), $$UserTableReferences(db, table, e))) + .toList(), + prefetchHooksCallback: ({userProfileRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [if (userProfileRefs) db.userProfile], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (userProfileRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: + $$UserTableReferences._userProfileRefsTable(db), + managerFromTypedResult: (p0) => + $$UserTableReferences(db, table, p0) + .userProfileRefs, + referencedItemsForCurrentItem: (item, + referencedItems) => + referencedItems.where((e) => e.userId == item.id), + typedResults: items) + ]; + }, + ); + }, + )); +} + +typedef $$UserTableProcessedTableManager = ProcessedTableManager< + _$AppDatabase, + $UserTable, + UserData, + $$UserTableFilterComposer, + $$UserTableOrderingComposer, + $$UserTableAnnotationComposer, + $$UserTableCreateCompanionBuilder, + $$UserTableUpdateCompanionBuilder, + (UserData, $$UserTableReferences), + UserData, + PrefetchHooks Function({bool userProfileRefs})>; +typedef $$UserProfileTableCreateCompanionBuilder = UserProfileCompanion + Function({ + Value id, + required String userId, + Value bio, + Value vibePoints, + Value profilePictureUrl, + Value lastSeen, + Value createdAt, + Value modifiedAt, + Value admissionNumber, + Value campus, +}); +typedef $$UserProfileTableUpdateCompanionBuilder = UserProfileCompanion + Function({ + Value id, + Value userId, + Value bio, + Value vibePoints, + Value profilePictureUrl, + Value lastSeen, + Value createdAt, + Value modifiedAt, + Value admissionNumber, + Value campus, +}); + +final class $$UserProfileTableReferences + extends BaseReferences<_$AppDatabase, $UserProfileTable, UserProfileData> { + $$UserProfileTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static $UserTable _userIdTable(_$AppDatabase db) => db.user + .createAlias($_aliasNameGenerator(db.userProfile.userId, db.user.id)); + + $$UserTableProcessedTableManager? get userId { + if ($_item.userId == null) return null; + final manager = $$UserTableTableManager($_db, $_db.user) + .filter((f) => f.id($_item.userId!)); + final item = $_typedResult.readTableOrNull(_userIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item])); + } +} + +class $$UserProfileTableFilterComposer + extends Composer<_$AppDatabase, $UserProfileTable> { + $$UserProfileTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnFilters(column)); + + ColumnFilters get bio => $composableBuilder( + column: $table.bio, builder: (column) => ColumnFilters(column)); + + ColumnFilters get vibePoints => $composableBuilder( + column: $table.vibePoints, builder: (column) => ColumnFilters(column)); + + ColumnFilters get profilePictureUrl => $composableBuilder( + column: $table.profilePictureUrl, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get lastSeen => $composableBuilder( + column: $table.lastSeen, builder: (column) => ColumnFilters(column)); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, builder: (column) => ColumnFilters(column)); + + ColumnFilters get modifiedAt => $composableBuilder( + column: $table.modifiedAt, builder: (column) => ColumnFilters(column)); + + ColumnFilters get admissionNumber => $composableBuilder( + column: $table.admissionNumber, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get campus => $composableBuilder( + column: $table.campus, builder: (column) => ColumnFilters(column)); + + $$UserTableFilterComposer get userId { + final $$UserTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.user, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$UserTableFilterComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$UserProfileTableOrderingComposer + extends Composer<_$AppDatabase, $UserProfileTable> { + $$UserProfileTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get bio => $composableBuilder( + column: $table.bio, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get vibePoints => $composableBuilder( + column: $table.vibePoints, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get profilePictureUrl => $composableBuilder( + column: $table.profilePictureUrl, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get lastSeen => $composableBuilder( + column: $table.lastSeen, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get modifiedAt => $composableBuilder( + column: $table.modifiedAt, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get admissionNumber => $composableBuilder( + column: $table.admissionNumber, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get campus => $composableBuilder( + column: $table.campus, builder: (column) => ColumnOrderings(column)); + + $$UserTableOrderingComposer get userId { + final $$UserTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.user, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$UserTableOrderingComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$UserProfileTableAnnotationComposer + extends Composer<_$AppDatabase, $UserProfileTable> { + $$UserProfileTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get bio => + $composableBuilder(column: $table.bio, builder: (column) => column); + + GeneratedColumn get vibePoints => $composableBuilder( + column: $table.vibePoints, builder: (column) => column); + + GeneratedColumn get profilePictureUrl => $composableBuilder( + column: $table.profilePictureUrl, builder: (column) => column); + + GeneratedColumn get lastSeen => + $composableBuilder(column: $table.lastSeen, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + GeneratedColumn get modifiedAt => $composableBuilder( + column: $table.modifiedAt, builder: (column) => column); + + GeneratedColumn get admissionNumber => $composableBuilder( + column: $table.admissionNumber, builder: (column) => column); + + GeneratedColumn get campus => + $composableBuilder(column: $table.campus, builder: (column) => column); + + $$UserTableAnnotationComposer get userId { + final $$UserTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.user, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$UserTableAnnotationComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$UserProfileTableTableManager extends RootTableManager< + _$AppDatabase, + $UserProfileTable, + UserProfileData, + $$UserProfileTableFilterComposer, + $$UserProfileTableOrderingComposer, + $$UserProfileTableAnnotationComposer, + $$UserProfileTableCreateCompanionBuilder, + $$UserProfileTableUpdateCompanionBuilder, + (UserProfileData, $$UserProfileTableReferences), + UserProfileData, + PrefetchHooks Function({bool userId})> { + $$UserProfileTableTableManager(_$AppDatabase db, $UserProfileTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$UserProfileTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$UserProfileTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$UserProfileTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value id = const Value.absent(), + Value userId = const Value.absent(), + Value bio = const Value.absent(), + Value vibePoints = const Value.absent(), + Value profilePictureUrl = const Value.absent(), + Value lastSeen = const Value.absent(), + Value createdAt = const Value.absent(), + Value modifiedAt = const Value.absent(), + Value admissionNumber = const Value.absent(), + Value campus = const Value.absent(), + }) => + UserProfileCompanion( + id: id, + userId: userId, + bio: bio, + vibePoints: vibePoints, + profilePictureUrl: profilePictureUrl, + lastSeen: lastSeen, + createdAt: createdAt, + modifiedAt: modifiedAt, + admissionNumber: admissionNumber, + campus: campus, + ), + createCompanionCallback: ({ + Value id = const Value.absent(), + required String userId, + Value bio = const Value.absent(), + Value vibePoints = const Value.absent(), + Value profilePictureUrl = const Value.absent(), + Value lastSeen = const Value.absent(), + Value createdAt = const Value.absent(), + Value modifiedAt = const Value.absent(), + Value admissionNumber = const Value.absent(), + Value campus = const Value.absent(), + }) => + UserProfileCompanion.insert( + id: id, + userId: userId, + bio: bio, + vibePoints: vibePoints, + profilePictureUrl: profilePictureUrl, + lastSeen: lastSeen, + createdAt: createdAt, + modifiedAt: modifiedAt, + admissionNumber: admissionNumber, + campus: campus, + ), + withReferenceMapper: (p0) => p0 + .map((e) => ( + e.readTable(table), + $$UserProfileTableReferences(db, table, e) + )) + .toList(), + prefetchHooksCallback: ({userId = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic>>(state) { + if (userId) { + state = state.withJoin( + currentTable: table, + currentColumn: table.userId, + referencedTable: + $$UserProfileTableReferences._userIdTable(db), + referencedColumn: + $$UserProfileTableReferences._userIdTable(db).id, + ) as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + )); +} + +typedef $$UserProfileTableProcessedTableManager = ProcessedTableManager< + _$AppDatabase, + $UserProfileTable, + UserProfileData, + $$UserProfileTableFilterComposer, + $$UserProfileTableOrderingComposer, + $$UserProfileTableAnnotationComposer, + $$UserProfileTableCreateCompanionBuilder, + $$UserProfileTableUpdateCompanionBuilder, + (UserProfileData, $$UserProfileTableReferences), + UserProfileData, + PrefetchHooks Function({bool userId})>; +typedef $$UserCredentialTableCreateCompanionBuilder = UserCredentialCompanion + Function({ + Value userId, + required String admno, + Value accessToken, + required String username, + required String email, + required String password, + Value lastLogin, + Value rowid, +}); +typedef $$UserCredentialTableUpdateCompanionBuilder = UserCredentialCompanion + Function({ + Value userId, + Value admno, + Value accessToken, + Value username, + Value email, + Value password, + Value lastLogin, + Value rowid, +}); + +final class $$UserCredentialTableReferences extends BaseReferences< + _$AppDatabase, $UserCredentialTable, UserCredentialData> { + $$UserCredentialTableReferences( + super.$_db, super.$_table, super.$_typedResult); + + static $UserTable _userIdTable(_$AppDatabase db) => db.user + .createAlias($_aliasNameGenerator(db.userCredential.userId, db.user.id)); + + $$UserTableProcessedTableManager? get userId { + if ($_item.userId == null) return null; + final manager = $$UserTableTableManager($_db, $_db.user) + .filter((f) => f.id($_item.userId!)); + final item = $_typedResult.readTableOrNull(_userIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item])); + } + + static $UserTable _usernameTable(_$AppDatabase db) => db.user.createAlias( + $_aliasNameGenerator(db.userCredential.username, db.user.username)); + + $$UserTableProcessedTableManager? get username { + if ($_item.username == null) return null; + final manager = $$UserTableTableManager($_db, $_db.user) + .filter((f) => f.username($_item.username!)); + final item = $_typedResult.readTableOrNull(_usernameTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item])); + } + + static $UserTable _emailTable(_$AppDatabase db) => db.user.createAlias( + $_aliasNameGenerator(db.userCredential.email, db.user.email)); + + $$UserTableProcessedTableManager? get email { + if ($_item.email == null) return null; + final manager = $$UserTableTableManager($_db, $_db.user) + .filter((f) => f.email($_item.email!)); + final item = $_typedResult.readTableOrNull(_emailTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item])); + } +} + +class $$UserCredentialTableFilterComposer + extends Composer<_$AppDatabase, $UserCredentialTable> { + $$UserCredentialTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get admno => $composableBuilder( + column: $table.admno, builder: (column) => ColumnFilters(column)); + + ColumnFilters get accessToken => $composableBuilder( + column: $table.accessToken, builder: (column) => ColumnFilters(column)); + + ColumnFilters get password => $composableBuilder( + column: $table.password, builder: (column) => ColumnFilters(column)); + + ColumnFilters get lastLogin => $composableBuilder( + column: $table.lastLogin, builder: (column) => ColumnFilters(column)); + + $$UserTableFilterComposer get userId { + final $$UserTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.user, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$UserTableFilterComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } + + $$UserTableFilterComposer get username { + final $$UserTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.username, + referencedTable: $db.user, + getReferencedColumn: (t) => t.username, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$UserTableFilterComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } + + $$UserTableFilterComposer get email { + final $$UserTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.email, + referencedTable: $db.user, + getReferencedColumn: (t) => t.email, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$UserTableFilterComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$UserCredentialTableOrderingComposer + extends Composer<_$AppDatabase, $UserCredentialTable> { + $$UserCredentialTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get admno => $composableBuilder( + column: $table.admno, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get accessToken => $composableBuilder( + column: $table.accessToken, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get password => $composableBuilder( + column: $table.password, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get lastLogin => $composableBuilder( + column: $table.lastLogin, builder: (column) => ColumnOrderings(column)); + + $$UserTableOrderingComposer get userId { + final $$UserTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.user, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$UserTableOrderingComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } + + $$UserTableOrderingComposer get username { + final $$UserTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.username, + referencedTable: $db.user, + getReferencedColumn: (t) => t.username, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$UserTableOrderingComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } + + $$UserTableOrderingComposer get email { + final $$UserTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.email, + referencedTable: $db.user, + getReferencedColumn: (t) => t.email, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$UserTableOrderingComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$UserCredentialTableAnnotationComposer + extends Composer<_$AppDatabase, $UserCredentialTable> { + $$UserCredentialTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get admno => + $composableBuilder(column: $table.admno, builder: (column) => column); + + GeneratedColumn get accessToken => $composableBuilder( + column: $table.accessToken, builder: (column) => column); + + GeneratedColumn get password => + $composableBuilder(column: $table.password, builder: (column) => column); + + GeneratedColumn get lastLogin => + $composableBuilder(column: $table.lastLogin, builder: (column) => column); + + $$UserTableAnnotationComposer get userId { + final $$UserTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: $db.user, + getReferencedColumn: (t) => t.id, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$UserTableAnnotationComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } + + $$UserTableAnnotationComposer get username { + final $$UserTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.username, + referencedTable: $db.user, + getReferencedColumn: (t) => t.username, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$UserTableAnnotationComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } + + $$UserTableAnnotationComposer get email { + final $$UserTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.email, + referencedTable: $db.user, + getReferencedColumn: (t) => t.email, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$UserTableAnnotationComposer( + $db: $db, + $table: $db.user, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return composer; + } +} + +class $$UserCredentialTableTableManager extends RootTableManager< + _$AppDatabase, + $UserCredentialTable, + UserCredentialData, + $$UserCredentialTableFilterComposer, + $$UserCredentialTableOrderingComposer, + $$UserCredentialTableAnnotationComposer, + $$UserCredentialTableCreateCompanionBuilder, + $$UserCredentialTableUpdateCompanionBuilder, + (UserCredentialData, $$UserCredentialTableReferences), + UserCredentialData, + PrefetchHooks Function({bool userId, bool username, bool email})> { + $$UserCredentialTableTableManager( + _$AppDatabase db, $UserCredentialTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$UserCredentialTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$UserCredentialTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$UserCredentialTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value userId = const Value.absent(), + Value admno = const Value.absent(), + Value accessToken = const Value.absent(), + Value username = const Value.absent(), + Value email = const Value.absent(), + Value password = const Value.absent(), + Value lastLogin = const Value.absent(), + Value rowid = const Value.absent(), + }) => + UserCredentialCompanion( + userId: userId, + admno: admno, + accessToken: accessToken, + username: username, + email: email, + password: password, + lastLogin: lastLogin, + rowid: rowid, + ), + createCompanionCallback: ({ + Value userId = const Value.absent(), + required String admno, + Value accessToken = const Value.absent(), + required String username, + required String email, + required String password, + Value lastLogin = const Value.absent(), + Value rowid = const Value.absent(), + }) => + UserCredentialCompanion.insert( + userId: userId, + admno: admno, + accessToken: accessToken, + username: username, + email: email, + password: password, + lastLogin: lastLogin, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => ( + e.readTable(table), + $$UserCredentialTableReferences(db, table, e) + )) + .toList(), + prefetchHooksCallback: ( + {userId = false, username = false, email = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic>>(state) { + if (userId) { + state = state.withJoin( + currentTable: table, + currentColumn: table.userId, + referencedTable: + $$UserCredentialTableReferences._userIdTable(db), + referencedColumn: + $$UserCredentialTableReferences._userIdTable(db).id, + ) as T; + } + if (username) { + state = state.withJoin( + currentTable: table, + currentColumn: table.username, + referencedTable: + $$UserCredentialTableReferences._usernameTable(db), + referencedColumn: $$UserCredentialTableReferences + ._usernameTable(db) + .username, + ) as T; + } + if (email) { + state = state.withJoin( + currentTable: table, + currentColumn: table.email, + referencedTable: + $$UserCredentialTableReferences._emailTable(db), + referencedColumn: + $$UserCredentialTableReferences._emailTable(db).email, + ) as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + )); +} + +typedef $$UserCredentialTableProcessedTableManager = ProcessedTableManager< + _$AppDatabase, + $UserCredentialTable, + UserCredentialData, + $$UserCredentialTableFilterComposer, + $$UserCredentialTableOrderingComposer, + $$UserCredentialTableAnnotationComposer, + $$UserCredentialTableCreateCompanionBuilder, + $$UserCredentialTableUpdateCompanionBuilder, + (UserCredentialData, $$UserCredentialTableReferences), + UserCredentialData, + PrefetchHooks Function({bool userId, bool username, bool email})>; + +class $AppDatabaseManager { + final _$AppDatabase _db; + $AppDatabaseManager(this._db); + $$UserTableTableManager get user => $$UserTableTableManager(_db, _db.user); + $$UserProfileTableTableManager get userProfile => + $$UserProfileTableTableManager(_db, _db.userProfile); + $$UserCredentialTableTableManager get userCredential => + $$UserCredentialTableTableManager(_db, _db.userCredential); +} diff --git a/lib/exports/barrel.dart b/lib/exports/barrel.dart deleted file mode 100644 index 3e22b94..0000000 --- a/lib/exports/barrel.dart +++ /dev/null @@ -1,40 +0,0 @@ -// academia core pages -export '../pages/pages.dart'; - -// Academia core widgets -export '../widgets/widgets.dart'; - -// Academia constants -export '../constants/common.dart'; -export 'package:academia/constants/tools.dart'; -export 'package:academia/tools/tools.dart'; - -// Academis controllers -export '../controllers/controllers.dart'; - -// Academia themes -export '../themes/theme.dart'; - -// Background tasks -export '../workers/workers.dart'; - -// dart core -export 'dart:convert'; -export 'dart:io'; -export 'dart:typed_data'; - -// flutter core -export 'package:flutter/material.dart'; -export 'package:flutter/services.dart'; - -// third party packages -export 'package:magnet/magnet.dart'; -export 'package:awesome_notifications/awesome_notifications.dart'; -export 'package:flutter_pdfview/flutter_pdfview.dart'; -export 'package:cached_network_image/cached_network_image.dart'; -export 'package:ionicons/ionicons.dart'; -export 'package:percent_indicator/percent_indicator.dart'; -export 'package:path_provider/path_provider.dart'; -export 'package:image_picker/image_picker.dart'; -export 'package:image_cropper/image_cropper.dart'; -export 'package:workmanager/workmanager.dart'; diff --git a/lib/features/auth/auth.dart b/lib/features/auth/auth.dart new file mode 100644 index 0000000..b7c0384 --- /dev/null +++ b/lib/features/auth/auth.dart @@ -0,0 +1,3 @@ +export './views/login_page.dart'; +export './views/sign_up_page.dart'; +export './views/widgets/user_selection_page.dart'; diff --git a/lib/features/auth/cubit/auth_cubit.dart b/lib/features/auth/cubit/auth_cubit.dart new file mode 100644 index 0000000..888aa4c --- /dev/null +++ b/lib/features/auth/cubit/auth_cubit.dart @@ -0,0 +1,146 @@ +import 'package:academia/database/database.dart'; +import 'package:academia/utils/network/dio_client.dart'; +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:academia/features/auth/cubit/auth_states.dart'; +import 'package:intl/intl.dart'; +import 'package:magnet/magnet.dart'; + +class AuthCubit extends Cubit { + final AppDatabase appDatabase; + AuthCubit(this.appDatabase) : super(AuthInitialState()) { + _loadStoredUser().then((val) {}); + } + + Future _loadStoredUser() async { + try { + final allUsers = await appDatabase.select(appDatabase.user).get(); + if (allUsers.isEmpty) { + emit(AuthFirstAppLaunch()); + return; + } + emit(AuthCachedUsersRetrieved(cachedUsers: allUsers)); + } catch (e) { + emit(AuthErrorState(e.toString())); + rethrow; + } + } + + /// Authenticates a user + Future> authenticate( + UserCredentialData creds) async { + emit(AuthLoadingState()); + final result = await _fetchUserDataFromMagnet( + creds.admno, + creds.password, + ); + + return result.fold((l) { + emit(AuthErrorState(l)); + return left(l); + }, (r) async { + // authenticate with verisafe + final response = await _authenticateWithVerisafe(creds); + return response.fold((l) { + emit(PartiallyAuthenticatedState(user: r)); + return left(l); + }, (r) async { + /// Write the user data + await appDatabase + .into(appDatabase.user) + .insertOnConflictUpdate(r.toCompanion(true)); + + final credentials = UserCredentialData( + userId: r.id, + username: r.username, + email: r.email!, + admno: creds.admno, + password: creds.password, + lastLogin: DateTime.now(), + ); + + /// Write the credential data + await appDatabase + .into(appDatabase.userCredential) + .insertOnConflictUpdate(credentials); + + // emit the fully autheticated state + emit(FullyAuthenticatedState(user: r, creds: credentials)); + + return right(r); + }); + // emit the authenticated state + }); + } + + Future> _authenticateWithVerisafe( + UserCredentialData creds) async { + final DioClient dioClient = DioClient( + creds: creds, + database: appDatabase, + ); + + try { + final response = await dioClient.dio.post( + "/auth/authenticate", + data: { + "admission_number": creds.admno, + "password": creds.password, + }, + ); + + if (response.statusCode == 200) { + return right(UserData.fromJson(response.data)); + } + + return left(response.data["error"]); + } on DioException catch (e) { + return left(e.toString()); + } catch (e) { + return left(e.toString()); + } + } + + Future> _fetchUserDataFromMagnet( + String admno, String password) async { + final Magnet magnet = Magnet( + admno, + password, + ); + + final loginRes = await magnet.login(); + + return loginRes.fold((l) { + return left(l.toString()); + }, (r) async { + final data = await magnet.fetchUserDetails(); + + return data.fold((l) { + return left(l.toString()); + }, (r) { + var dOB = DateFormat('MM/dd/yyyy').parse(r["dateofbirth"] ?? ""); + String name = r['name'] ?? ""; + List nameParts = name.split(' '); + String firstName = nameParts.first; + String lastName = nameParts.sublist(1).join(' '); + + final userData = UserData( + id: "", + username: "", + firstname: firstName, + othernames: lastName, + phone: "", + email: r["email"] ?? "", + gender: r["gender"] ?? "", + nationalId: r["idno"] ?? "", + active: (r["academicstatus"] ?? "true") == "true" ? true : false, + modifiedAt: DateTime.now(), + dateOfBirth: dOB, + createdAt: DateTime.now(), + ); + return right(userData); + }); + }); + } +} diff --git a/lib/features/auth/cubit/auth_states.dart b/lib/features/auth/cubit/auth_states.dart new file mode 100644 index 0000000..3c77108 --- /dev/null +++ b/lib/features/auth/cubit/auth_states.dart @@ -0,0 +1,50 @@ +import 'package:academia/database/database.dart'; +import 'package:magnet/magnet.dart'; + +/// A base class representing authentication status +abstract class AuthState { + bool busy = false; +} + +/// authentication loading state +final class AuthLoadingState extends AuthState {} + +/// Initial authentication state +final class AuthInitialState extends AuthState {} + +/// First time app launch +final class AuthFirstAppLaunch extends AuthState {} + +/// Cached users retrieved +final class AuthCachedUsersRetrieved extends AuthState { + final List cachedUsers; + AuthCachedUsersRetrieved({required this.cachedUsers}); +} + +// Represents an error on page state +final class AuthErrorState extends AuthState { + final String message; + AuthErrorState(this.message); +} + +// Represents a partially authenticated state in the event that a user +// does not have internet access +class PartiallyAuthenticatedState extends AuthState { + final UserData user; + PartiallyAuthenticatedState({required this.user}); +} + +// Represents a fully authenticated state whereby the user is authenticated +// both by verisafe and magnet +final class FullyAuthenticatedState extends AuthState { + final UserData user; + final UserCredentialData creds; + + FullyAuthenticatedState({ + required this.user, + required this.creds, + }); +} + +// Represents the unauthenticated state +final class UnAuthenticatedState extends AuthState {} diff --git a/lib/features/auth/repository/user.dart b/lib/features/auth/repository/user.dart new file mode 100644 index 0000000..0f43ec0 --- /dev/null +++ b/lib/features/auth/repository/user.dart @@ -0,0 +1,23 @@ +import 'package:drift/drift.dart'; + +class User extends Table { + TextColumn get id => text()(); + TextColumn get username => text().withLength(min: 1, max: 50)(); + TextColumn get firstname => text().withLength(min: 1, max: 100)(); + TextColumn get othernames => text().nullable().withLength(min: 1, max: 100)(); + TextColumn get phone => text().withLength(min: 10, max: 15)(); + TextColumn get email => text().nullable().withLength(min: 5, max: 100)(); + TextColumn get gender => text().withLength(min: 1, max: 10)(); + BoolColumn get active => boolean().withDefault(const Constant(true))(); + @JsonKey("created_at") + DateTimeColumn get createdAt => dateTime()(); + @JsonKey("modified_at") + DateTimeColumn get modifiedAt => dateTime()(); + @JsonKey("date_of_birth") + DateTimeColumn get dateOfBirth => dateTime()(); + @JsonKey("national_id") + TextColumn get nationalId => text().withLength(min: 1, max: 20)(); + + @override + Set get primaryKey => {id}; +} diff --git a/lib/features/auth/repository/user_credentials.dart b/lib/features/auth/repository/user_credentials.dart new file mode 100644 index 0000000..b42fc90 --- /dev/null +++ b/lib/features/auth/repository/user_credentials.dart @@ -0,0 +1,23 @@ +import 'package:drift/drift.dart'; +import './user.dart'; + +class UserCredential extends Table { + // @JsonKey("user_id") + TextColumn get userId => text().references(User, #id).nullable()(); + @JsonKey("admission_number") + TextColumn get admno => text()(); + @JsonKey("access_token") + TextColumn get accessToken => text().nullable()(); + + @JsonKey("username") + TextColumn get username => text().references(User, #username)(); + @JsonKey("email") + TextColumn get email => text().references(User, #email)(); + @JsonKey("password") + TextColumn get password => text()(); + @JsonKey("last_login") + DateTimeColumn get lastLogin => dateTime().nullable()(); + + @override + Set>? get primaryKey => {userId}; +} diff --git a/lib/features/auth/repository/user_profile.dart b/lib/features/auth/repository/user_profile.dart new file mode 100644 index 0000000..852c604 --- /dev/null +++ b/lib/features/auth/repository/user_profile.dart @@ -0,0 +1,28 @@ +import 'package:academia/features/auth/repository/user.dart'; +import 'package:drift/drift.dart'; + +class UserProfile extends Table { + IntColumn get id => integer().autoIncrement()(); + + @JsonKey("user_id") + TextColumn get userId => text().references(User, #id)(); + + TextColumn get bio => text().nullable()(); + @JsonKey("vibe_points") + IntColumn get vibePoints => integer().withDefault(const Constant(0))(); + @JsonKey("profile_picture_url") + TextColumn get profilePictureUrl => text().nullable()(); + @JsonKey("last_seen") + DateTimeColumn get lastSeen => + dateTime().withDefault(Constant(DateTime.now()))(); + @JsonKey("created_at") + DateTimeColumn get createdAt => + dateTime().withDefault(Constant(DateTime.now()))(); + @JsonKey("modified_at") + DateTimeColumn get modifiedAt => + dateTime().withDefault(Constant(DateTime.now()))(); + @JsonKey("admission_number") + TextColumn get admissionNumber => text().nullable()(); + @JsonKey("campus") + TextColumn get campus => text().withDefault(const Constant("athi"))(); +} diff --git a/lib/features/auth/views/login_page.dart b/lib/features/auth/views/login_page.dart new file mode 100644 index 0000000..af3085b --- /dev/null +++ b/lib/features/auth/views/login_page.dart @@ -0,0 +1,228 @@ +import 'package:academia/database/database.dart'; +import 'package:academia/features/auth/cubit/auth_cubit.dart'; +import 'package:academia/features/auth/cubit/auth_states.dart'; +import 'package:academia/utils/router/router.dart'; +import 'package:academia/utils/validator/validator.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:icons_plus/icons_plus.dart'; +import 'package:sliver_tools/sliver_tools.dart'; + +class LoginPage extends StatefulWidget { + const LoginPage({super.key}); + + @override + State createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + late AuthCubit authCubit = BlocProvider.of(context); + final TextEditingController _admissionController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + final _formState = GlobalKey(); + bool _showPassword = true; + + @override + void initState() { + super.initState(); + } + + /// Validates the current sign in form + /// Returns true if there are no errors otherwise + /// it returns false to indicate an error + bool validateForm() { + return _formState.currentState!.validate(); + } + + /// Shows a dialog with [title] and [content] + void _showMessageDialog(String title, String content) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(title), + content: Text(content), + actions: [ + TextButton( + onPressed: () { + context.pop(); + }, + child: const Text("Ok"), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: true, + body: Form( + key: _formState, + child: CustomScrollView( + slivers: [ + SliverAppBar( + centerTitle: true, + floating: true, + snap: true, + pinned: true, + expandedHeight: 250, + flexibleSpace: FlexibleSpaceBar( + centerTitle: true, + title: SizedBox( + height: 40, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset( + "assets/icons/academia.png", + ), + const Text("Academia"), + ], + ), + ), + ), + ), + SliverPadding( + padding: const EdgeInsets.all(12), + sliver: SliverPinnedHeader( + child: Column( + spacing: 12, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Text( + "Sign in", + style: Theme.of(context).textTheme.titleLarge, + ), + const Spacer(), + IconButton( + onPressed: () { + GoRouter.of(context) + .pushNamed(AcademiaRouter.registerRoute); + }, + icon: const Icon(Bootstrap.question_circle), + ), + ], + ), + + const Text( + "Use your school admission number and school portal password to continue to Academia.", + ), + + TextFormField( + controller: _admissionController, + textAlign: TextAlign.center, + maxLength: 7, + keyboardType: TextInputType.number, + inputFormatters: [ + AdmnoDashFormatter(), + ], + validator: (value) { + if (value?.length != 7) { + return "Please provide a valid admission number😡"; + } + return null; + }, + autovalidateMode: AutovalidateMode.onUnfocus, + decoration: InputDecoration( + hintText: "Your school admission number", + label: const Text("Admission number"), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(4), + ), + ), + ), + + TextFormField( + controller: _passwordController, + obscureText: _showPassword, + textAlign: TextAlign.center, + validator: (value) { + if ((value?.length ?? 0) < 3) { + return "Please provide a valid password 😡"; + } + return null; + }, + autovalidateMode: AutovalidateMode.onUserInteraction, + decoration: InputDecoration( + suffixIcon: IconButton( + onPressed: () { + setState(() { + _showPassword = !_showPassword; + }); + }, + icon: Icon(_showPassword + ? Bootstrap.eye + : Bootstrap.eye_slash)), + hintText: "Your school portal password", + label: const Text("School Password"), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(4), + ), + ), + ), + + // Input buttons + + BlocBuilder( + builder: (context, state) { + return FilledButton( + onPressed: state is AuthLoadingState + ? null + : () async { + if (!validateForm()) { + _showMessageDialog( + "Validation error", + "Please ensure that the form was well filled", + ); + return; + } + final result = await authCubit + .authenticate(UserCredentialData( + username: "", + email: "", + admno: _admissionController.text, + password: _passwordController.text, + lastLogin: DateTime.now(), + )); + + result.fold((l) { + _showMessageDialog("Authentication Error", l); + }, (r) { + context.pushReplacementNamed("/home"); + }); + }, + child: state is AuthLoadingState + ? const CircularProgressIndicator.adaptive() + : const Text("Login"), + ); + }), + ], + ), + ), + ), + SliverPadding( + padding: const EdgeInsets.all(12), + sliver: SliverFillRemaining( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () {}, + child: const Text("Privacy and terms of service"), + ), + const SizedBox(height: 12) + ], + ), + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/features/auth/views/sign_up_page.dart b/lib/features/auth/views/sign_up_page.dart new file mode 100644 index 0000000..c31e966 --- /dev/null +++ b/lib/features/auth/views/sign_up_page.dart @@ -0,0 +1,405 @@ +import 'package:academia/database/database.dart'; +import 'package:academia/features/auth/cubit/auth_cubit.dart'; +import 'package:academia/utils/validator/validator.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:icons_plus/icons_plus.dart'; +import 'package:intl/intl.dart'; +import 'package:intl_phone_field/intl_phone_field.dart'; + +class SignUpPage extends StatefulWidget { + const SignUpPage({super.key}); + + @override + State createState() => _SignUpPageState(); +} + +class _SignUpPageState extends State { + final _formKey = GlobalKey(); + final TextEditingController _admissionNumberController = + TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + final TextEditingController _nationalIdController = TextEditingController(); + final TextEditingController _firstNameController = TextEditingController(); + final TextEditingController _lastNameController = TextEditingController(); + final TextEditingController _phoneController = TextEditingController(); + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _usernameController = TextEditingController(); + final TextEditingController _dateOfBirthController = TextEditingController(); + bool _showPassword = false; + bool _dataFetched = false; + DateTime? _dateOfBirth; + + late AuthCubit authCubit; + + @override + void initState() { + super.initState(); + authCubit = BlocProvider.of(context); + } + + Future _selectDate(BuildContext context) async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: _dateOfBirth ?? DateTime.now(), + firstDate: DateTime(1900), + lastDate: DateTime.now(), + ); + + if (picked != null && picked != _dateOfBirth) { + setState(() { + _dateOfBirth = picked; + }); + } + } + + @override + void dispose() { + // Dispose the controllers when the widget is removed + _admissionNumberController.dispose(); + _passwordController.dispose(); + _nationalIdController.dispose(); + _firstNameController.dispose(); + _lastNameController.dispose(); + _phoneController.dispose(); + _emailController.dispose(); + _usernameController.dispose(); + super.dispose(); + } + + void _checkValidity() async { + if (_formKey.currentState?.validate() ?? false) { + // Perform your validity check (e.g., check admission number and password) + // This is a placeholder for your validity check logic + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text("Checking validity...", softWrap: true)), + ); + + final result = await authCubit.authenticate(UserCredentialData( + username: "", + email: "", + admno: _admissionNumberController.text, + password: _passwordController.text, + lastLogin: DateTime.now(), + )); + + result.fold((l) { + _showAlertDialog(l); + }, (r) { + // print(r); + // print(r.toJson()); + + _nationalIdController.text = r.nationalId; + _firstNameController.text = r.firstname; + _lastNameController.text = r.othernames!; + _phoneController.text = r.phone; + _emailController.text = r.email!; + _usernameController.text = r.username; + _dateOfBirth = r.dateOfBirth; + _dateOfBirthController.text = + DateFormat.yMMMMEEEEd().format(r.dateOfBirth); + + setState(() {}); + }); + + setState(() { + _dataFetched = true; + }); + } else { + _showAlertDialog("Please fill in all required fields."); + } + } + + void _getStarted() { + if (_formKey.currentState?.validate() ?? false) { + // Perform sign-up logic + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text("Signing up...")), + ); + } else { + _showAlertDialog("Please fill in all required fields."); + } + } + + void _showAlertDialog(String message) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text("Validation Error"), + content: Text(message), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text("OK"), + ), + ], + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Add your info"), + ), + body: SafeArea( + minimum: const EdgeInsets.all(12), + child: SingleChildScrollView( + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 8, + children: [ + TextFormField( + controller: _admissionNumberController, + textAlign: TextAlign.center, + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp(r'^\d{0,2}-?\d{0,4}$'), + ), + AdmnoDashFormatter(), + ], + decoration: InputDecoration( + label: const Text("Admission Number"), + hintText: "00-0000", + helperText: "Please input your school admission number", + border: OutlineInputBorder( + borderSide: const BorderSide(width: 2), + borderRadius: BorderRadius.circular(4), + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter your admission number.'; + } + return null; + }, + ), + + // Password field + TextFormField( + controller: _passwordController, + textAlign: TextAlign.center, + obscureText: !_showPassword, + decoration: InputDecoration( + suffixIcon: IconButton( + onPressed: () { + setState(() { + _showPassword = !_showPassword; + }); + }, + icon: Icon( + _showPassword ? EvaIcons.eye_off_outline : EvaIcons.eye, + ), + ), + label: const Text("Password"), + hintText: "Provide your school portal password", + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(4), + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter your password.'; + } + return null; + }, + ), + Visibility( + visible: _dataFetched, + child: TextFormField( + controller: _usernameController, + textAlign: TextAlign.center, + inputFormatters: [ + FilteringTextInputFormatter.singleLineFormatter, + ], + decoration: InputDecoration( + label: const Text("Username"), + hintText: "Your username", + helperText: "Provide a username or nickname", + border: OutlineInputBorder( + borderSide: const BorderSide(width: 2), + borderRadius: BorderRadius.circular(4), + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please fill your username'; + } + return null; + }, + ), + ), + + Visibility( + visible: _dataFetched, + child: TextFormField( + controller: _emailController, + textAlign: TextAlign.center, + inputFormatters: [ + EmailInputFormatter(), + ], + decoration: InputDecoration( + label: const Text("National ID"), + hintText: "Your national identity", + helperText: + "In case you're a minor use your admission number", + border: OutlineInputBorder( + borderSide: const BorderSide(width: 2), + borderRadius: BorderRadius.circular(4), + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter your National ID.'; + } + return null; + }, + ), + ), + + Visibility( + visible: _dataFetched, + child: TextFormField( + controller: _firstNameController, + textAlign: TextAlign.center, + decoration: InputDecoration( + label: const Text("First name"), + border: OutlineInputBorder( + borderSide: const BorderSide(width: 2), + borderRadius: BorderRadius.circular(4), + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter your first name.'; + } + return null; + }, + ), + ), + + Visibility( + visible: _dataFetched, + child: TextFormField( + controller: _lastNameController, + textAlign: TextAlign.center, + decoration: InputDecoration( + label: const Text("Last name"), + helperText: + "Please make sure your names appear correctly", + border: OutlineInputBorder( + borderSide: const BorderSide(width: 2), + borderRadius: BorderRadius.circular(4), + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter your last name.'; + } + return null; + }, + ), + ), + + Visibility( + visible: _dataFetched, + child: IntlPhoneField( + controller: _phoneController, + showCountryFlag: true, + keyboardType: TextInputType.phone, + initialCountryCode: "KE", + disableLengthCheck: true, + invalidNumberMessage: "Please enter a valid phone number", + decoration: InputDecoration( + label: const Text("Phone"), + hintText: "712345678", + border: OutlineInputBorder( + borderSide: const BorderSide(width: 2), + borderRadius: BorderRadius.circular(4), + ), + ), + ), + ), + + Visibility( + visible: _dataFetched, + child: TextFormField( + controller: _emailController, + textAlign: TextAlign.center, + inputFormatters: [ + EmailInputFormatter(), + ], + decoration: InputDecoration( + label: const Text("Email"), + hintText: "someone@daystar.ac.ke", + helperText: "Use your school email", + border: OutlineInputBorder( + borderSide: const BorderSide(width: 2), + borderRadius: BorderRadius.circular(4), + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter your email.'; + } + return null; + }, + ), + ), + // Date of Birth Picker + Visibility( + visible: _dataFetched, + child: GestureDetector( + onTap: () => _selectDate(context), + child: AbsorbPointer( + child: TextFormField( + controller: _dateOfBirthController, + textAlign: TextAlign.center, + decoration: InputDecoration( + label: const Text("Date of Birth"), + hintText: _dateOfBirth != null + ? "${_dateOfBirth!.day}/${_dateOfBirth!.month}/${_dateOfBirth!.year}" + : "Select your date of birth", + border: OutlineInputBorder( + borderSide: const BorderSide(width: 2), + borderRadius: BorderRadius.circular(4), + ), + ), + validator: (value) { + if (_dateOfBirth == null) { + return 'Please select your date of birth.'; + } + return null; + }, + ), + ), + ), + ), + + _dataFetched + ? FilledButton.icon( + icon: const Icon(Bootstrap.person_fill_add), + onPressed: _getStarted, + label: const Text("Get started"), + ) + : FilledButton.icon( + icon: const Icon(EvaIcons.person_done_outline), + onPressed: _checkValidity, + label: const Text("Check validity"), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/auth/views/widgets/user_selection_page.dart b/lib/features/auth/views/widgets/user_selection_page.dart new file mode 100644 index 0000000..46bb71f --- /dev/null +++ b/lib/features/auth/views/widgets/user_selection_page.dart @@ -0,0 +1,103 @@ +import 'package:academia/database/database.dart'; +import 'package:academia/features/auth/cubit/auth_cubit.dart'; +import 'package:academia/features/auth/cubit/auth_states.dart'; +import 'package:academia/utils/router/router.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:icons_plus/icons_plus.dart'; +import 'package:sliver_tools/sliver_tools.dart'; + +class UserSelectionPage extends StatefulWidget { + const UserSelectionPage({super.key}); + + @override + State createState() => _UserSelectionPageState(); +} + +class _UserSelectionPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( + centerTitle: true, + floating: true, + snap: true, + pinned: true, + expandedHeight: 250, + flexibleSpace: FlexibleSpaceBar( + centerTitle: true, + title: SizedBox( + height: 40, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset( + "assets/icons/academia.png", + ), + const Text("Academia"), + ], + ), + ), + ), + ), + SliverPadding( + padding: const EdgeInsets.all(12), + sliver: SliverPinnedHeader( + child: Text( + "Please select a user to continue", + style: Theme.of(context).textTheme.titleMedium, + ), + ), + ), + + // Sliverlist + BlocBuilder( + builder: (context, state) { + final List users = + (state as AuthCachedUsersRetrieved).cachedUsers; + + return SliverList.builder( + itemCount: users.length, + itemBuilder: (context, index) { + return ListTile( + onTap: () { + context.pushReplacementNamed('/home'); + }, + leading: const CircleAvatar(), + title: Text( + users[index].username, + ), + subtitle: const Text("The user bio will appear here"), + trailing: const Icon(Bootstrap.person_check), + ); + }, + ); + }, + ), + SliverPadding( + padding: const EdgeInsets.all(12), + sliver: SliverToBoxAdapter( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton.icon( + onPressed: () { + context.pushNamed(AcademiaRouter.auth); + }, + label: const Text("Add Account"), + icon: const Icon(Bootstrap.person_add), + ), + const SizedBox(height: 12) + ], + ), + ), + ) + ], + ), + ); + } +} diff --git a/lib/features/features.dart b/lib/features/features.dart new file mode 100644 index 0000000..6a4ff23 --- /dev/null +++ b/lib/features/features.dart @@ -0,0 +1,4 @@ +export 'onboarding/onboarding.dart'; +export 'auth/auth.dart'; +export 'home/home.dart'; +export 'profile/profile.dart'; diff --git a/lib/features/home/home.dart b/lib/features/home/home.dart new file mode 100644 index 0000000..a4e02a8 --- /dev/null +++ b/lib/features/home/home.dart @@ -0,0 +1 @@ +export 'views/layout.dart'; diff --git a/lib/features/home/views/layout.dart b/lib/features/home/views/layout.dart new file mode 100644 index 0000000..be4e2e5 --- /dev/null +++ b/lib/features/home/views/layout.dart @@ -0,0 +1,122 @@ +import 'package:academia/features/features.dart'; +import 'package:academia/features/profile/profile_page_desktop.dart'; +import 'package:academia/utils/responsive/responsive.dart'; +import 'package:flutter/material.dart'; +import 'package:icons_plus/icons_plus.dart'; + +class Layout extends StatefulWidget { + const Layout({super.key}); + + @override + State createState() => _LayoutState(); +} + +class _LayoutState extends State { + int _selectedIndex = 0; + final pages = const [ + Center( + child: Text("Statistics"), + ), + Center( + child: Text("Statistics"), + ), + Center( + child: Text("Statistics"), + ), + Center( + child: Text("Statistics"), + ), + ProfilePage() + ]; + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) => Scaffold( + body: constraints.maxWidth < ScreenDimension.mobileWidth + ? IndexedStack( + index: _selectedIndex, + children: pages, + ) + : Row( + children: [ + NavigationRail( + leading: Image.asset( + "assets/icons/academia.png", + width: 45, + ), + labelType: NavigationRailLabelType.all, + onDestinationSelected: (index) => setState(() { + _selectedIndex = index; + }), + elevation: 2, + trailing: IconButton( + onPressed: () {}, + icon: const Icon(OctIcons.mark_github), + ), + destinations: const [ + NavigationRailDestination( + icon: Icon(OctIcons.mortar_board), + label: Text("Statistics"), + ), + NavigationRailDestination( + icon: Icon(OctIcons.repo), + label: Text("Courses"), + ), + NavigationRailDestination( + icon: Icon(OctIcons.comment_discussion), + label: Text("Social"), + ), + NavigationRailDestination( + icon: Icon(OctIcons.rocket), + label: Text("Essentials"), + ), + NavigationRailDestination( + icon: Icon(OctIcons.person), + label: Text("Profile"), + ), + ], + selectedIndex: _selectedIndex, + ), + Expanded( + child: IndexedStack( + index: _selectedIndex, + children: pages, + ), + ) + ], + ), + bottomNavigationBar: constraints.maxWidth > ScreenDimension.mobileWidth + ? null + : NavigationBar( + onDestinationSelected: (index) => setState(() { + _selectedIndex = index; + }), + selectedIndex: _selectedIndex, + destinations: const [ + NavigationDestination( + icon: Icon(OctIcons.mortar_board), + selectedIcon: Icon(OctIcons.mortar_board), + label: "Statistics", + ), + NavigationDestination( + icon: Icon(OctIcons.repo), + label: "Courses", + ), + NavigationDestination( + icon: Icon(OctIcons.comment_discussion), + label: "Social", + ), + NavigationDestination( + icon: Icon(OctIcons.rocket), + label: "Essentials", + ), + NavigationDestination( + icon: Icon(OctIcons.person), + label: "Profile", + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/onboarding/onboarding.dart b/lib/features/onboarding/onboarding.dart new file mode 100644 index 0000000..817bfd8 --- /dev/null +++ b/lib/features/onboarding/onboarding.dart @@ -0,0 +1 @@ +export 'views/onboarding_page.dart'; diff --git a/lib/features/onboarding/views/onboarding_page.dart b/lib/features/onboarding/views/onboarding_page.dart new file mode 100644 index 0000000..62f0115 --- /dev/null +++ b/lib/features/onboarding/views/onboarding_page.dart @@ -0,0 +1,83 @@ +import 'package:academia/features/auth/cubit/auth_cubit.dart'; +import 'package:academia/features/auth/cubit/auth_states.dart'; +import 'package:academia/utils/router/router.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:sliver_tools/sliver_tools.dart'; +import 'package:flutter_svg/svg.dart'; + +class OnboardingPage extends StatelessWidget { + const OnboardingPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: CustomScrollView( + slivers: [ + const SliverAppBar( + floating: true, + pinned: true, + snap: true, + expandedHeight: 200, + flexibleSpace: FlexibleSpaceBar( + title: Text("Academia"), + centerTitle: true, + ), + ), + SliverPadding( + padding: const EdgeInsets.all(12), + sliver: SliverStack( + positionedAlignment: Alignment.center, + children: [ + SliverPositioned( + top: 0, + child: SliverPinnedHeader( + child: CircleAvatar( + radius: 120, + child: SvgPicture.asset("assets/images/studying.svg"), + ), + ), + ), + ], + ), + ), + SliverPadding( + padding: const EdgeInsets.all(12), + sliver: SliverFillRemaining( + hasScrollBody: true, + child: Column( + children: [ + const Spacer(), + Text( + "Welcome to Academia! A platform for students by students 🔥", + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + BlocBuilder(builder: (context, state) { + return state is AuthInitialState || + state is AuthLoadingState + ? const CircularProgressIndicator.adaptive() + : SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: () { + context.push( + AcademiaRouter.auth, + ); + }, + child: const Text("Get Started"), + ), + ); + }), + const SizedBox(height: 16), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/profile/profile.dart b/lib/features/profile/profile.dart new file mode 100644 index 0000000..988ef68 --- /dev/null +++ b/lib/features/profile/profile.dart @@ -0,0 +1 @@ +export 'profile_page.dart'; diff --git a/lib/features/profile/profile_page.dart b/lib/features/profile/profile_page.dart new file mode 100644 index 0000000..b72d985 --- /dev/null +++ b/lib/features/profile/profile_page.dart @@ -0,0 +1,23 @@ +import 'package:academia/features/profile/profile_page_desktop.dart'; +import 'package:academia/features/profile/profile_page_mobile.dart'; +import 'package:academia/utils/responsive/responsive.dart'; +import 'package:flutter/material.dart'; + +class ProfilePage extends StatefulWidget { + const ProfilePage({super.key}); + + @override + State createState() => _ProfilePageState(); +} + +class _ProfilePageState extends State { + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) => + constraints.maxWidth < ScreenDimension.mobileWidth + ? const ProfilePageMobile() + : const ProfilePageDesktop(), + ); + } +} diff --git a/lib/features/profile/profile_page_desktop.dart b/lib/features/profile/profile_page_desktop.dart new file mode 100644 index 0000000..1cba8e4 --- /dev/null +++ b/lib/features/profile/profile_page_desktop.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import 'package:icons_plus/icons_plus.dart'; +import 'package:sliver_tools/sliver_tools.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class ProfilePageDesktop extends StatefulWidget { + const ProfilePageDesktop({super.key}); + + @override + State createState() => _ProfilePageDesktopState(); +} + +class _ProfilePageDesktopState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Academia"), + actions: [ + IconButton( + onPressed: () {}, + icon: const Icon(Bootstrap.arrow_clockwise), + ), + ], + ), + body: CustomScrollView( + slivers: [ + MultiSliver( + children: const [ + Wrap( + children: [ + SizedBox( + width: 500, + child: ProfileCard(), + ), + ], + ) + ], + ), + ], + ), + ); + } +} + +class ProfileCard extends StatelessWidget { + const ProfileCard({super.key}); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12, + children: [ + const CircleAvatar( + radius: 60, + ), + Column( + spacing: 8, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "John Doe", + style: + Theme.of(context).textTheme.headlineMedium?.copyWith( + fontFamily: + GoogleFonts.libreBaskerville().fontFamily, + ), + ), + Text( + "Student", + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + ), + const Text( + "Athi River Campus", + ), + ], + ), + FilledButton.icon( + label: const Text("Edit Profile"), + onPressed: () {}, + icon: const Icon(Bootstrap.pen), + ) + ], + ), + const Divider(), + ListTile( + title: const Text("Email"), + trailing: const Text("someone@example.com"), + leadingAndTrailingTextStyle: + Theme.of(context).textTheme.bodyMedium, + leading: const Icon(Bootstrap.envelope_at), + ), + ListTile( + title: const Text("Phone"), + trailing: const Text("0717171771"), + leadingAndTrailingTextStyle: + Theme.of(context).textTheme.bodyMedium, + leading: const Icon(Bootstrap.phone), + ), + ListTile( + title: const Text("School"), + trailing: const Text("School of Science Health and Engineering"), + leadingAndTrailingTextStyle: + Theme.of(context).textTheme.bodyMedium, + leading: const Icon(Bootstrap.git), + ) + ], + ), + ), + ); + } +} diff --git a/lib/features/profile/profile_page_mobile.dart b/lib/features/profile/profile_page_mobile.dart new file mode 100644 index 0000000..26db170 --- /dev/null +++ b/lib/features/profile/profile_page_mobile.dart @@ -0,0 +1,92 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:icons_plus/icons_plus.dart'; +import 'package:sliver_tools/sliver_tools.dart'; + +class ProfilePageMobile extends StatefulWidget { + const ProfilePageMobile({super.key}); + + @override + State createState() => _ProfilePageMobileState(); +} + +class _ProfilePageMobileState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( + pinned: true, + floating: true, + snap: true, + title: const Text("My Profile"), + actions: [ + IconButton( + onPressed: () {}, + icon: const Icon(Bootstrap.pen), + ), + ], + ), + SliverPinnedHeader( + child: Container( + width: MediaQuery.of(context).size.width, + padding: const EdgeInsets.all(12), + child: Column( + spacing: 12, + children: [ + const CircleAvatar( + radius: 80, + ), + Text( + "John Doe Sang Kipkemboi", + style: Theme.of(context).textTheme.headlineSmall, + overflow: TextOverflow.ellipsis, + ), + const Text( + "Some long bio here to be here", + overflow: TextOverflow.visible, + ), + FilledButton( + onPressed: () {}, + child: const Text("Preview my school ID"), + ), + ], + ), + ), + ), + SliverPadding( + padding: const EdgeInsets.all(12), + sliver: SliverClip( + child: MultiSliver( + children: const [ + ListTile( + leading: Icon(Bootstrap.hash), + title: Text("Admission Number"), + subtitle: Text("21-1000"), + ), + ListTile( + leading: Icon(Bootstrap.compass), + title: Text("Campus"), + subtitle: Text("Athi River"), + ), + ListTile( + leading: Icon(Bootstrap.phone), + title: Text("Phone Number"), + subtitle: Text("078277272"), + ), + ListTile( + leading: Icon(Bootstrap.envelope_heart), + title: Text("Email"), + subtitle: Text("someone@example.com"), + ) + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 940c8c9..955dc3a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,107 +1,21 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/notifier/notifier.dart'; -import 'package:get/get.dart'; -import 'package:lottie/lottie.dart'; +import 'package:academia/app.dart'; +import 'package:academia/database/database.dart'; +import 'package:academia/features/auth/cubit/auth_cubit.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; -void main() async { +void main(List args) { WidgetsFlutterBinding.ensureInitialized(); - // Initialize the background services - await LocalNotifierService().initialize(); - await LocalNotificationStatusManager().initialize(); - await BackgroundWorker().initialize(); - - // initialize the controllers - Get.put(UserController()); - Get.put(SettingsController()); - Get.put(NotificationsController()); - Get.put(NetworkController()); - Get.put(TodoController()); - Get.put(RewardController()); - Get.put(CoursesController()); - Get.put(EventsController()); - Get.put(OrganizationController()); - - // launch the application + final db = AppDatabase(); runApp( - const Academia(), + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => AuthCubit(db), + ), + ], + child: const Academia(), + ), ); } - -class Academia extends StatefulWidget { - const Academia({super.key}); - - @override - State createState() => _AcademiaState(); -} - -class _AcademiaState extends State { - late Future _authState; - final UserController _userController = Get.find(); - final SettingsController _settingsController = Get.find(); - - @override - void initState() { - super.initState(); - LocalNotifierService().requestPermission(); - - if (_settingsController.settings.value.requireAppUnlock) { - _authState = _settingsController.performLocalAuthentication( - "Your app is locked, to continue unloack it using your authentication mechanism", - ); - } else { - _authState = Future.value(true); - } - } - - @override - Widget build(BuildContext context) { - /// Init the controllers here - return GetMaterialApp( - theme: ThemeData( - useMaterial3: true, - colorSchemeSeed: const Color(0xFF3D5A80), - ), - darkTheme: ThemeData( - brightness: Brightness.dark, - useMaterial3: true, - colorSchemeSeed: const Color(0xFF3D5A80), - ), - home: FutureBuilder( - future: _authState, - builder: (context, snapshot) { - if (snapshot.data == true) { - return Obx( - () => _userController.isLoggedIn.value - ? const LayoutPage() - : const IntroPage(), - ); - } - return Scaffold( - body: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Lottie.asset( - "assets/lotties/studying.json", - ), - const SizedBox(height: 22), - Text( - "Please authenticate 🔐 with Academia to continue", - style: Theme.of(context).textTheme.headlineSmall, - ), - FilledButton.icon( - onPressed: () { - _authState = _settingsController.performLocalAuthentication( - "Your app is locked, to continue unloack it using your authentication mechanism", - ); - }, - label: const Text("Authenticate"), - ) - ], - ), - ); - }, - ), - ); - } -} diff --git a/lib/models/core/course/course.dart b/lib/models/core/course/course.dart deleted file mode 100644 index 6ad085a..0000000 --- a/lib/models/core/course/course.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'course_model.dart'; -export 'course_helper.dart'; -export 'course_topic.dart'; -export 'course_topic_helper.dart'; diff --git a/lib/models/core/course/course_helper.dart b/lib/models/core/course/course_helper.dart deleted file mode 100644 index 2216619..0000000 --- a/lib/models/core/course/course_helper.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/storage/storage.dart'; -import 'package:sqflite/sqlite_api.dart'; - -class CourseModelHelper implements DatabaseOperations { - static final CourseModelHelper _instance = CourseModelHelper._internal(); - - factory CourseModelHelper() { - return _instance; - } - - CourseModelHelper._internal(); - - @override - Future create(Map data) async { - final db = await DatabaseHelper().database; - final id = await db.insert( - 'courses', - data, - conflictAlgorithm: ConflictAlgorithm.replace, - ); - - debugPrint("[+] Course written successfully"); - return id; - } - - @override - Future>> queryAll() async { - final db = await DatabaseHelper().database; - final courses = await db.query('courses'); - return courses; - } - - @override - Future delete(Map data) async { - final db = await DatabaseHelper().database; - return await db.delete('courses', where: 'id =?', whereArgs: [data["id"]]); - } - - @override - Future update(Map data) async { - final db = await DatabaseHelper().database; - return await db - .update('courses', data, where: 'id =?', whereArgs: [data['id']]); - } - - @override - Future truncate() async { - final db = await DatabaseHelper().database; - await db.execute('DELETE FROM courses'); - } -} diff --git a/lib/models/core/course/course_model.dart b/lib/models/core/course/course_model.dart deleted file mode 100644 index f57fc9d..0000000 --- a/lib/models/core/course/course_model.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'course_model.freezed.dart'; -part 'course_model.g.dart'; - -@freezed -class Course with _$Course { - const factory Course({ - required String unit, - required String section, - @JsonKey(name: "day_of_the_week") required String dayOfWeek, - required String period, - required String campus, - required String room, - required String lecturer, - @JsonKey(name: "start_time") DateTime? startTime, - @JsonKey(name: "stop_time") DateTime? stopTime, - }) = _Course; - - factory Course.fromJson(Map json) => _$CourseFromJson(json); -} diff --git a/lib/models/core/course/course_model.freezed.dart b/lib/models/core/course/course_model.freezed.dart deleted file mode 100644 index f7ee2d4..0000000 --- a/lib/models/core/course/course_model.freezed.dart +++ /dev/null @@ -1,338 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'course_model.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -Course _$CourseFromJson(Map json) { - return _Course.fromJson(json); -} - -/// @nodoc -mixin _$Course { - String get unit => throw _privateConstructorUsedError; - String get section => throw _privateConstructorUsedError; - @JsonKey(name: "day_of_the_week") - String get dayOfWeek => throw _privateConstructorUsedError; - String get period => throw _privateConstructorUsedError; - String get campus => throw _privateConstructorUsedError; - String get room => throw _privateConstructorUsedError; - String get lecturer => throw _privateConstructorUsedError; - @JsonKey(name: "start_time") - DateTime? get startTime => throw _privateConstructorUsedError; - @JsonKey(name: "stop_time") - DateTime? get stopTime => throw _privateConstructorUsedError; - - /// Serializes this Course to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of Course - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $CourseCopyWith get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $CourseCopyWith<$Res> { - factory $CourseCopyWith(Course value, $Res Function(Course) then) = - _$CourseCopyWithImpl<$Res, Course>; - @useResult - $Res call( - {String unit, - String section, - @JsonKey(name: "day_of_the_week") String dayOfWeek, - String period, - String campus, - String room, - String lecturer, - @JsonKey(name: "start_time") DateTime? startTime, - @JsonKey(name: "stop_time") DateTime? stopTime}); -} - -/// @nodoc -class _$CourseCopyWithImpl<$Res, $Val extends Course> - implements $CourseCopyWith<$Res> { - _$CourseCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of Course - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? unit = null, - Object? section = null, - Object? dayOfWeek = null, - Object? period = null, - Object? campus = null, - Object? room = null, - Object? lecturer = null, - Object? startTime = freezed, - Object? stopTime = freezed, - }) { - return _then(_value.copyWith( - unit: null == unit - ? _value.unit - : unit // ignore: cast_nullable_to_non_nullable - as String, - section: null == section - ? _value.section - : section // ignore: cast_nullable_to_non_nullable - as String, - dayOfWeek: null == dayOfWeek - ? _value.dayOfWeek - : dayOfWeek // ignore: cast_nullable_to_non_nullable - as String, - period: null == period - ? _value.period - : period // ignore: cast_nullable_to_non_nullable - as String, - campus: null == campus - ? _value.campus - : campus // ignore: cast_nullable_to_non_nullable - as String, - room: null == room - ? _value.room - : room // ignore: cast_nullable_to_non_nullable - as String, - lecturer: null == lecturer - ? _value.lecturer - : lecturer // ignore: cast_nullable_to_non_nullable - as String, - startTime: freezed == startTime - ? _value.startTime - : startTime // ignore: cast_nullable_to_non_nullable - as DateTime?, - stopTime: freezed == stopTime - ? _value.stopTime - : stopTime // ignore: cast_nullable_to_non_nullable - as DateTime?, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$CourseImplCopyWith<$Res> implements $CourseCopyWith<$Res> { - factory _$$CourseImplCopyWith( - _$CourseImpl value, $Res Function(_$CourseImpl) then) = - __$$CourseImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String unit, - String section, - @JsonKey(name: "day_of_the_week") String dayOfWeek, - String period, - String campus, - String room, - String lecturer, - @JsonKey(name: "start_time") DateTime? startTime, - @JsonKey(name: "stop_time") DateTime? stopTime}); -} - -/// @nodoc -class __$$CourseImplCopyWithImpl<$Res> - extends _$CourseCopyWithImpl<$Res, _$CourseImpl> - implements _$$CourseImplCopyWith<$Res> { - __$$CourseImplCopyWithImpl( - _$CourseImpl _value, $Res Function(_$CourseImpl) _then) - : super(_value, _then); - - /// Create a copy of Course - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? unit = null, - Object? section = null, - Object? dayOfWeek = null, - Object? period = null, - Object? campus = null, - Object? room = null, - Object? lecturer = null, - Object? startTime = freezed, - Object? stopTime = freezed, - }) { - return _then(_$CourseImpl( - unit: null == unit - ? _value.unit - : unit // ignore: cast_nullable_to_non_nullable - as String, - section: null == section - ? _value.section - : section // ignore: cast_nullable_to_non_nullable - as String, - dayOfWeek: null == dayOfWeek - ? _value.dayOfWeek - : dayOfWeek // ignore: cast_nullable_to_non_nullable - as String, - period: null == period - ? _value.period - : period // ignore: cast_nullable_to_non_nullable - as String, - campus: null == campus - ? _value.campus - : campus // ignore: cast_nullable_to_non_nullable - as String, - room: null == room - ? _value.room - : room // ignore: cast_nullable_to_non_nullable - as String, - lecturer: null == lecturer - ? _value.lecturer - : lecturer // ignore: cast_nullable_to_non_nullable - as String, - startTime: freezed == startTime - ? _value.startTime - : startTime // ignore: cast_nullable_to_non_nullable - as DateTime?, - stopTime: freezed == stopTime - ? _value.stopTime - : stopTime // ignore: cast_nullable_to_non_nullable - as DateTime?, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$CourseImpl implements _Course { - const _$CourseImpl( - {required this.unit, - required this.section, - @JsonKey(name: "day_of_the_week") required this.dayOfWeek, - required this.period, - required this.campus, - required this.room, - required this.lecturer, - @JsonKey(name: "start_time") this.startTime, - @JsonKey(name: "stop_time") this.stopTime}); - - factory _$CourseImpl.fromJson(Map json) => - _$$CourseImplFromJson(json); - - @override - final String unit; - @override - final String section; - @override - @JsonKey(name: "day_of_the_week") - final String dayOfWeek; - @override - final String period; - @override - final String campus; - @override - final String room; - @override - final String lecturer; - @override - @JsonKey(name: "start_time") - final DateTime? startTime; - @override - @JsonKey(name: "stop_time") - final DateTime? stopTime; - - @override - String toString() { - return 'Course(unit: $unit, section: $section, dayOfWeek: $dayOfWeek, period: $period, campus: $campus, room: $room, lecturer: $lecturer, startTime: $startTime, stopTime: $stopTime)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$CourseImpl && - (identical(other.unit, unit) || other.unit == unit) && - (identical(other.section, section) || other.section == section) && - (identical(other.dayOfWeek, dayOfWeek) || - other.dayOfWeek == dayOfWeek) && - (identical(other.period, period) || other.period == period) && - (identical(other.campus, campus) || other.campus == campus) && - (identical(other.room, room) || other.room == room) && - (identical(other.lecturer, lecturer) || - other.lecturer == lecturer) && - (identical(other.startTime, startTime) || - other.startTime == startTime) && - (identical(other.stopTime, stopTime) || - other.stopTime == stopTime)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, unit, section, dayOfWeek, period, - campus, room, lecturer, startTime, stopTime); - - /// Create a copy of Course - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$CourseImplCopyWith<_$CourseImpl> get copyWith => - __$$CourseImplCopyWithImpl<_$CourseImpl>(this, _$identity); - - @override - Map toJson() { - return _$$CourseImplToJson( - this, - ); - } -} - -abstract class _Course implements Course { - const factory _Course( - {required final String unit, - required final String section, - @JsonKey(name: "day_of_the_week") required final String dayOfWeek, - required final String period, - required final String campus, - required final String room, - required final String lecturer, - @JsonKey(name: "start_time") final DateTime? startTime, - @JsonKey(name: "stop_time") final DateTime? stopTime}) = _$CourseImpl; - - factory _Course.fromJson(Map json) = _$CourseImpl.fromJson; - - @override - String get unit; - @override - String get section; - @override - @JsonKey(name: "day_of_the_week") - String get dayOfWeek; - @override - String get period; - @override - String get campus; - @override - String get room; - @override - String get lecturer; - @override - @JsonKey(name: "start_time") - DateTime? get startTime; - @override - @JsonKey(name: "stop_time") - DateTime? get stopTime; - - /// Create a copy of Course - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$CourseImplCopyWith<_$CourseImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/models/core/course/course_model.g.dart b/lib/models/core/course/course_model.g.dart deleted file mode 100644 index ef5ec98..0000000 --- a/lib/models/core/course/course_model.g.dart +++ /dev/null @@ -1,36 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'course_model.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$CourseImpl _$$CourseImplFromJson(Map json) => _$CourseImpl( - unit: json['unit'] as String, - section: json['section'] as String, - dayOfWeek: json['day_of_the_week'] as String, - period: json['period'] as String, - campus: json['campus'] as String, - room: json['room'] as String, - lecturer: json['lecturer'] as String, - startTime: json['start_time'] == null - ? null - : DateTime.parse(json['start_time'] as String), - stopTime: json['stop_time'] == null - ? null - : DateTime.parse(json['stop_time'] as String), - ); - -Map _$$CourseImplToJson(_$CourseImpl instance) => - { - 'unit': instance.unit, - 'section': instance.section, - 'day_of_the_week': instance.dayOfWeek, - 'period': instance.period, - 'campus': instance.campus, - 'room': instance.room, - 'lecturer': instance.lecturer, - 'start_time': instance.startTime?.toIso8601String(), - 'stop_time': instance.stopTime?.toIso8601String(), - }; diff --git a/lib/models/core/course/course_topic.dart b/lib/models/core/course/course_topic.dart deleted file mode 100644 index 53d34aa..0000000 --- a/lib/models/core/course/course_topic.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'course_topic.freezed.dart'; -part 'course_topic.g.dart'; - -@freezed -class CourseTopic with _$CourseTopic { - const factory CourseTopic({ - required String course, - required String name, - required String description, - }) = _CourseTopic; - - factory CourseTopic.fromJson(Map json) => - _$CourseTopicFromJson(json); -} diff --git a/lib/models/core/course/course_topic.freezed.dart b/lib/models/core/course/course_topic.freezed.dart deleted file mode 100644 index efa56f5..0000000 --- a/lib/models/core/course/course_topic.freezed.dart +++ /dev/null @@ -1,201 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'course_topic.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -CourseTopic _$CourseTopicFromJson(Map json) { - return _CourseTopic.fromJson(json); -} - -/// @nodoc -mixin _$CourseTopic { - String get course => throw _privateConstructorUsedError; - String get name => throw _privateConstructorUsedError; - String get description => throw _privateConstructorUsedError; - - /// Serializes this CourseTopic to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of CourseTopic - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $CourseTopicCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $CourseTopicCopyWith<$Res> { - factory $CourseTopicCopyWith( - CourseTopic value, $Res Function(CourseTopic) then) = - _$CourseTopicCopyWithImpl<$Res, CourseTopic>; - @useResult - $Res call({String course, String name, String description}); -} - -/// @nodoc -class _$CourseTopicCopyWithImpl<$Res, $Val extends CourseTopic> - implements $CourseTopicCopyWith<$Res> { - _$CourseTopicCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of CourseTopic - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? course = null, - Object? name = null, - Object? description = null, - }) { - return _then(_value.copyWith( - course: null == course - ? _value.course - : course // ignore: cast_nullable_to_non_nullable - as String, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - description: null == description - ? _value.description - : description // ignore: cast_nullable_to_non_nullable - as String, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$CourseTopicImplCopyWith<$Res> - implements $CourseTopicCopyWith<$Res> { - factory _$$CourseTopicImplCopyWith( - _$CourseTopicImpl value, $Res Function(_$CourseTopicImpl) then) = - __$$CourseTopicImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({String course, String name, String description}); -} - -/// @nodoc -class __$$CourseTopicImplCopyWithImpl<$Res> - extends _$CourseTopicCopyWithImpl<$Res, _$CourseTopicImpl> - implements _$$CourseTopicImplCopyWith<$Res> { - __$$CourseTopicImplCopyWithImpl( - _$CourseTopicImpl _value, $Res Function(_$CourseTopicImpl) _then) - : super(_value, _then); - - /// Create a copy of CourseTopic - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? course = null, - Object? name = null, - Object? description = null, - }) { - return _then(_$CourseTopicImpl( - course: null == course - ? _value.course - : course // ignore: cast_nullable_to_non_nullable - as String, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - description: null == description - ? _value.description - : description // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$CourseTopicImpl implements _CourseTopic { - const _$CourseTopicImpl( - {required this.course, required this.name, required this.description}); - - factory _$CourseTopicImpl.fromJson(Map json) => - _$$CourseTopicImplFromJson(json); - - @override - final String course; - @override - final String name; - @override - final String description; - - @override - String toString() { - return 'CourseTopic(course: $course, name: $name, description: $description)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$CourseTopicImpl && - (identical(other.course, course) || other.course == course) && - (identical(other.name, name) || other.name == name) && - (identical(other.description, description) || - other.description == description)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, course, name, description); - - /// Create a copy of CourseTopic - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$CourseTopicImplCopyWith<_$CourseTopicImpl> get copyWith => - __$$CourseTopicImplCopyWithImpl<_$CourseTopicImpl>(this, _$identity); - - @override - Map toJson() { - return _$$CourseTopicImplToJson( - this, - ); - } -} - -abstract class _CourseTopic implements CourseTopic { - const factory _CourseTopic( - {required final String course, - required final String name, - required final String description}) = _$CourseTopicImpl; - - factory _CourseTopic.fromJson(Map json) = - _$CourseTopicImpl.fromJson; - - @override - String get course; - @override - String get name; - @override - String get description; - - /// Create a copy of CourseTopic - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$CourseTopicImplCopyWith<_$CourseTopicImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/models/core/course/course_topic.g.dart b/lib/models/core/course/course_topic.g.dart deleted file mode 100644 index b1462a4..0000000 --- a/lib/models/core/course/course_topic.g.dart +++ /dev/null @@ -1,21 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'course_topic.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$CourseTopicImpl _$$CourseTopicImplFromJson(Map json) => - _$CourseTopicImpl( - course: json['course'] as String, - name: json['name'] as String, - description: json['description'] as String, - ); - -Map _$$CourseTopicImplToJson(_$CourseTopicImpl instance) => - { - 'course': instance.course, - 'name': instance.name, - 'description': instance.description, - }; diff --git a/lib/models/core/course/course_topic_helper.dart b/lib/models/core/course/course_topic_helper.dart deleted file mode 100644 index 11828fc..0000000 --- a/lib/models/core/course/course_topic_helper.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:sqflite/sqflite.dart'; -import 'package:flutter/foundation.dart'; -import 'package:academia/exports/barrel.dart'; -import 'package:academia/storage/storage.dart'; - -class CourseTopicModelHelper implements DatabaseOperations { - static final CourseTopicModelHelper _instance = - CourseTopicModelHelper._internal(); - - factory CourseTopicModelHelper() { - return _instance; - } - - CourseTopicModelHelper._internal(); - - @override - Future create(Map data) async { - final db = await DatabaseHelper().database; - final id = await db.insert( - 'course_topics', - data, - conflictAlgorithm: ConflictAlgorithm.replace, - ); - - debugPrint("[+] Course Topic written successfully"); - return id; - } - - @override - Future>> queryAll() async { - final db = await DatabaseHelper().database; - final courseTopics = await db.query('course_topics'); - return courseTopics; - } - - @override - Future delete(Map data) async { - final db = await DatabaseHelper().database; - return await db - .delete('course_topics', where: 'id = ?', whereArgs: [data["id"]]); - } - - @override - Future update(Map data) async { - final db = await DatabaseHelper().database; - return await db.update('course_topics', data, - where: 'id = ?', whereArgs: [data['id']]); - } - - @override - Future truncate() async { - final db = await DatabaseHelper().database; - await db.execute('DELETE FROM course_topics'); - } -} diff --git a/lib/models/core/reward/reward.dart b/lib/models/core/reward/reward.dart deleted file mode 100644 index 0b6c80c..0000000 --- a/lib/models/core/reward/reward.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:intl/intl.dart'; -export 'reward_helper.dart'; - -class Reward { - String? id; - String studentId; - int points; - String reason; - DateTime awardedAt; - - Reward({ - this.id, - required this.studentId, - required this.points, - required this.reason, - required this.awardedAt, - }); - - factory Reward.fromJson(Map json) { - return Reward( - id: json['id'], - studentId: json['student_id'], - points: json['points'], - reason: json['reason'], - awardedAt: DateTime.parse(json['awarded_at']), - ); - } - - Map toJson() { - return { - 'id': id, - 'student_id': studentId, - 'points': points, - 'reason': reason, - 'awarded_at': DateFormat("yyyy-MM-ddTHH:mm:ss.SSS'Z'").format( - awardedAt.toUtc(), - ), - }; - } -} diff --git a/lib/models/core/reward/reward_helper.dart b/lib/models/core/reward/reward_helper.dart deleted file mode 100644 index a1c678a..0000000 --- a/lib/models/core/reward/reward_helper.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:academia/storage/storage.dart'; -import 'package:sqflite/sqflite.dart'; - -class RewardModelHelper implements DatabaseOperations { - static final RewardModelHelper _instance = RewardModelHelper._internal(); - - factory RewardModelHelper() { - return _instance; - } - - RewardModelHelper._internal(); - - @override - Future create(Map data) async { - final db = await DatabaseHelper().database; - final id = await db.insert( - 'rewards', - data, - conflictAlgorithm: ConflictAlgorithm.replace, - ); - - return id; - } - - @override - Future>> queryAll() async { - final db = await DatabaseHelper().database; - final rewards = await db.query('rewards'); - return rewards; - } - - @override - Future delete(Map data) async { - final db = await DatabaseHelper().database; - return await db.delete('rewards', where: 'id = ?', whereArgs: [data['id']]); - } - - @override - Future update(Map data) async { - final db = await DatabaseHelper().database; - return await db - .update('rewards', data, where: 'id = ?', whereArgs: [data['id']]); - } - - @override - Future truncate() async { - final db = await DatabaseHelper().database; - await db.execute('DELETE FROM rewards'); - } -} diff --git a/lib/models/core/settings/settings.dart b/lib/models/core/settings/settings.dart deleted file mode 100644 index 39800c9..0000000 --- a/lib/models/core/settings/settings.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'settings_model.dart'; -export 'settings_helper.dart'; diff --git a/lib/models/core/settings/settings_helper.dart b/lib/models/core/settings/settings_helper.dart deleted file mode 100644 index 3ff13fc..0000000 --- a/lib/models/core/settings/settings_helper.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'dart:convert'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'settings.dart'; - -class SettingsHelper { - static final SettingsHelper _instance = SettingsHelper._internal(); - late SharedPreferences _prefs; - - factory SettingsHelper() { - // SharedPreferences.getInstance().then((value) { - // _instance._prefs = value; - // }); - return _instance; - } - - SettingsHelper._internal(); - - Future init() async { - _prefs = await SharedPreferences.getInstance(); - } - - Settings getSettings() { - String? settingsJsonString = _prefs.getString('settings'); - if (settingsJsonString != null) { - Map json = jsonDecode(settingsJsonString); - return Settings.fromJson(json); - } else { - // Return default settings if not found - return Settings.empty(); - } - } - - Future saveSettings(Settings settings) async { - String json = jsonEncode(settings.toJson()); - return await _prefs.setString('settings', json); - } -} diff --git a/lib/models/core/settings/settings_model.dart b/lib/models/core/settings/settings_model.dart deleted file mode 100644 index c2d08eb..0000000 --- a/lib/models/core/settings/settings_model.dart +++ /dev/null @@ -1,24 +0,0 @@ -/// Represents the settings in the application. - -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'settings_model.freezed.dart'; -part 'settings_model.g.dart'; - -@freezed -class Settings with _$Settings { - const factory Settings({ - @Default(false) bool showProfilePicture, - @Default(false) bool showFeeStatistics, - @Default(true) bool showBirthDay, - @Default(false) bool showTranscript, - @Default(false) bool showAudit, - @Default(false) bool showExamTimetable, - @Default(false) bool requireAppUnlock, - }) = _Settings; - - factory Settings.fromJson(Map json) => - _$SettingsFromJson(json); - - factory Settings.empty() => const Settings(); -} diff --git a/lib/models/core/settings/settings_model.freezed.dart b/lib/models/core/settings/settings_model.freezed.dart deleted file mode 100644 index 3fabbd2..0000000 --- a/lib/models/core/settings/settings_model.freezed.dart +++ /dev/null @@ -1,309 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'settings_model.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -Settings _$SettingsFromJson(Map json) { - return _Settings.fromJson(json); -} - -/// @nodoc -mixin _$Settings { - bool get showProfilePicture => throw _privateConstructorUsedError; - bool get showFeeStatistics => throw _privateConstructorUsedError; - bool get showBirthDay => throw _privateConstructorUsedError; - bool get showTranscript => throw _privateConstructorUsedError; - bool get showAudit => throw _privateConstructorUsedError; - bool get showExamTimetable => throw _privateConstructorUsedError; - bool get requireAppUnlock => throw _privateConstructorUsedError; - - /// Serializes this Settings to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of Settings - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $SettingsCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $SettingsCopyWith<$Res> { - factory $SettingsCopyWith(Settings value, $Res Function(Settings) then) = - _$SettingsCopyWithImpl<$Res, Settings>; - @useResult - $Res call( - {bool showProfilePicture, - bool showFeeStatistics, - bool showBirthDay, - bool showTranscript, - bool showAudit, - bool showExamTimetable, - bool requireAppUnlock}); -} - -/// @nodoc -class _$SettingsCopyWithImpl<$Res, $Val extends Settings> - implements $SettingsCopyWith<$Res> { - _$SettingsCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of Settings - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? showProfilePicture = null, - Object? showFeeStatistics = null, - Object? showBirthDay = null, - Object? showTranscript = null, - Object? showAudit = null, - Object? showExamTimetable = null, - Object? requireAppUnlock = null, - }) { - return _then(_value.copyWith( - showProfilePicture: null == showProfilePicture - ? _value.showProfilePicture - : showProfilePicture // ignore: cast_nullable_to_non_nullable - as bool, - showFeeStatistics: null == showFeeStatistics - ? _value.showFeeStatistics - : showFeeStatistics // ignore: cast_nullable_to_non_nullable - as bool, - showBirthDay: null == showBirthDay - ? _value.showBirthDay - : showBirthDay // ignore: cast_nullable_to_non_nullable - as bool, - showTranscript: null == showTranscript - ? _value.showTranscript - : showTranscript // ignore: cast_nullable_to_non_nullable - as bool, - showAudit: null == showAudit - ? _value.showAudit - : showAudit // ignore: cast_nullable_to_non_nullable - as bool, - showExamTimetable: null == showExamTimetable - ? _value.showExamTimetable - : showExamTimetable // ignore: cast_nullable_to_non_nullable - as bool, - requireAppUnlock: null == requireAppUnlock - ? _value.requireAppUnlock - : requireAppUnlock // ignore: cast_nullable_to_non_nullable - as bool, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$SettingsImplCopyWith<$Res> - implements $SettingsCopyWith<$Res> { - factory _$$SettingsImplCopyWith( - _$SettingsImpl value, $Res Function(_$SettingsImpl) then) = - __$$SettingsImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {bool showProfilePicture, - bool showFeeStatistics, - bool showBirthDay, - bool showTranscript, - bool showAudit, - bool showExamTimetable, - bool requireAppUnlock}); -} - -/// @nodoc -class __$$SettingsImplCopyWithImpl<$Res> - extends _$SettingsCopyWithImpl<$Res, _$SettingsImpl> - implements _$$SettingsImplCopyWith<$Res> { - __$$SettingsImplCopyWithImpl( - _$SettingsImpl _value, $Res Function(_$SettingsImpl) _then) - : super(_value, _then); - - /// Create a copy of Settings - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? showProfilePicture = null, - Object? showFeeStatistics = null, - Object? showBirthDay = null, - Object? showTranscript = null, - Object? showAudit = null, - Object? showExamTimetable = null, - Object? requireAppUnlock = null, - }) { - return _then(_$SettingsImpl( - showProfilePicture: null == showProfilePicture - ? _value.showProfilePicture - : showProfilePicture // ignore: cast_nullable_to_non_nullable - as bool, - showFeeStatistics: null == showFeeStatistics - ? _value.showFeeStatistics - : showFeeStatistics // ignore: cast_nullable_to_non_nullable - as bool, - showBirthDay: null == showBirthDay - ? _value.showBirthDay - : showBirthDay // ignore: cast_nullable_to_non_nullable - as bool, - showTranscript: null == showTranscript - ? _value.showTranscript - : showTranscript // ignore: cast_nullable_to_non_nullable - as bool, - showAudit: null == showAudit - ? _value.showAudit - : showAudit // ignore: cast_nullable_to_non_nullable - as bool, - showExamTimetable: null == showExamTimetable - ? _value.showExamTimetable - : showExamTimetable // ignore: cast_nullable_to_non_nullable - as bool, - requireAppUnlock: null == requireAppUnlock - ? _value.requireAppUnlock - : requireAppUnlock // ignore: cast_nullable_to_non_nullable - as bool, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$SettingsImpl implements _Settings { - const _$SettingsImpl( - {this.showProfilePicture = false, - this.showFeeStatistics = false, - this.showBirthDay = true, - this.showTranscript = false, - this.showAudit = false, - this.showExamTimetable = false, - this.requireAppUnlock = false}); - - factory _$SettingsImpl.fromJson(Map json) => - _$$SettingsImplFromJson(json); - - @override - @JsonKey() - final bool showProfilePicture; - @override - @JsonKey() - final bool showFeeStatistics; - @override - @JsonKey() - final bool showBirthDay; - @override - @JsonKey() - final bool showTranscript; - @override - @JsonKey() - final bool showAudit; - @override - @JsonKey() - final bool showExamTimetable; - @override - @JsonKey() - final bool requireAppUnlock; - - @override - String toString() { - return 'Settings(showProfilePicture: $showProfilePicture, showFeeStatistics: $showFeeStatistics, showBirthDay: $showBirthDay, showTranscript: $showTranscript, showAudit: $showAudit, showExamTimetable: $showExamTimetable, requireAppUnlock: $requireAppUnlock)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$SettingsImpl && - (identical(other.showProfilePicture, showProfilePicture) || - other.showProfilePicture == showProfilePicture) && - (identical(other.showFeeStatistics, showFeeStatistics) || - other.showFeeStatistics == showFeeStatistics) && - (identical(other.showBirthDay, showBirthDay) || - other.showBirthDay == showBirthDay) && - (identical(other.showTranscript, showTranscript) || - other.showTranscript == showTranscript) && - (identical(other.showAudit, showAudit) || - other.showAudit == showAudit) && - (identical(other.showExamTimetable, showExamTimetable) || - other.showExamTimetable == showExamTimetable) && - (identical(other.requireAppUnlock, requireAppUnlock) || - other.requireAppUnlock == requireAppUnlock)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash( - runtimeType, - showProfilePicture, - showFeeStatistics, - showBirthDay, - showTranscript, - showAudit, - showExamTimetable, - requireAppUnlock); - - /// Create a copy of Settings - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$SettingsImplCopyWith<_$SettingsImpl> get copyWith => - __$$SettingsImplCopyWithImpl<_$SettingsImpl>(this, _$identity); - - @override - Map toJson() { - return _$$SettingsImplToJson( - this, - ); - } -} - -abstract class _Settings implements Settings { - const factory _Settings( - {final bool showProfilePicture, - final bool showFeeStatistics, - final bool showBirthDay, - final bool showTranscript, - final bool showAudit, - final bool showExamTimetable, - final bool requireAppUnlock}) = _$SettingsImpl; - - factory _Settings.fromJson(Map json) = - _$SettingsImpl.fromJson; - - @override - bool get showProfilePicture; - @override - bool get showFeeStatistics; - @override - bool get showBirthDay; - @override - bool get showTranscript; - @override - bool get showAudit; - @override - bool get showExamTimetable; - @override - bool get requireAppUnlock; - - /// Create a copy of Settings - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$SettingsImplCopyWith<_$SettingsImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/models/core/settings/settings_model.g.dart b/lib/models/core/settings/settings_model.g.dart deleted file mode 100644 index ea8c807..0000000 --- a/lib/models/core/settings/settings_model.g.dart +++ /dev/null @@ -1,29 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'settings_model.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$SettingsImpl _$$SettingsImplFromJson(Map json) => - _$SettingsImpl( - showProfilePicture: json['showProfilePicture'] as bool? ?? false, - showFeeStatistics: json['showFeeStatistics'] as bool? ?? false, - showBirthDay: json['showBirthDay'] as bool? ?? true, - showTranscript: json['showTranscript'] as bool? ?? false, - showAudit: json['showAudit'] as bool? ?? false, - showExamTimetable: json['showExamTimetable'] as bool? ?? false, - requireAppUnlock: json['requireAppUnlock'] as bool? ?? false, - ); - -Map _$$SettingsImplToJson(_$SettingsImpl instance) => - { - 'showProfilePicture': instance.showProfilePicture, - 'showFeeStatistics': instance.showFeeStatistics, - 'showBirthDay': instance.showBirthDay, - 'showTranscript': instance.showTranscript, - 'showAudit': instance.showAudit, - 'showExamTimetable': instance.showExamTimetable, - 'requireAppUnlock': instance.requireAppUnlock, - }; diff --git a/lib/models/core/user/user.dart b/lib/models/core/user/user.dart deleted file mode 100644 index 2b24d22..0000000 --- a/lib/models/core/user/user.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'user_helper.dart'; -export 'user_model.dart'; diff --git a/lib/models/core/user/user_helper.dart b/lib/models/core/user/user_helper.dart deleted file mode 100644 index e8e14a0..0000000 --- a/lib/models/core/user/user_helper.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/storage/storage.dart'; -import 'package:sqflite/sqflite.dart'; - -class UserModelHelper implements DatabaseOperations { - static final UserModelHelper _instance = UserModelHelper._internal(); - - factory UserModelHelper() { - return _instance; - } - - UserModelHelper._internal(); - - @override - - /// Create. - /// Calling this appends a user model onto the users table - /// incase such a user exists it replaces the user data with the new - /// data - Future create(Map data) async { - final db = await DatabaseHelper().database; - final id = await db.insert( - 'users', - data, - conflictAlgorithm: ConflictAlgorithm.replace, - ); - - debugPrint("[+] User written successfully"); - return id; - } - - @override - Future>> queryAll() async { - final db = await DatabaseHelper().database; - final users = await db.query('users'); - return users; - } - - @override - Future delete(Map data) async { - final db = await DatabaseHelper().database; - return await db.delete('users', where: 'id =?', whereArgs: [data["id"]]); - } - - @override - Future update(Map data) async { - final db = await DatabaseHelper().database; - return await db - .update('users', data, where: 'id =?', whereArgs: [data['id']]); - } - - @override - Future truncate() async { - final db = await DatabaseHelper().database; - await db.execute('DELETE FROM users'); - } -} diff --git a/lib/models/core/user/user_model.dart b/lib/models/core/user/user_model.dart deleted file mode 100644 index 175a2fc..0000000 --- a/lib/models/core/user/user_model.dart +++ /dev/null @@ -1,183 +0,0 @@ -// The user model -import 'package:intl/intl.dart'; - -/// Represents a user's model as well as provides -/// JSON to and fro serialization functionality -class User { - String? id; - String username; - String firstName; - String lastName; - String admissionNumber; - String nationalId; - String gender; - String address; - String email; - DateTime dateOfBirth; - String campus; - String profileUrl; - String schoolProfile; - String password; - bool active; - int vibePoints; - dynamic pointTransactions; // It can be a List or any other appropriate type - DateTime? dateCreated; - DateTime? dateUpdated; - - User({ - this.id, - required this.username, - required this.firstName, - required this.lastName, - required this.admissionNumber, - required this.nationalId, - required this.gender, - required this.address, - required this.email, - required this.dateOfBirth, - required this.campus, - required this.profileUrl, - required this.password, - required this.active, - required this.vibePoints, - required this.pointTransactions, - required this.schoolProfile, - this.dateCreated, - this.dateUpdated, - }); - - factory User.fromJson(Map json) { - return User( - id: json['id'], - username: json['username'], - firstName: json['first_name'], - lastName: json['last_name'], - admissionNumber: json['admission_number'], - nationalId: json['national_id'], - gender: json['gender'], - address: json['address'], - email: json['email'], - dateOfBirth: DateTime.parse(json['date_of_birth']), - campus: json['campus'], - profileUrl: json['profile_url'], - schoolProfile: json['school_profile'], - password: json['password'], - active: json["active"] is int - ? json["active"] == 1 - ? true - : false - : json["active"], - vibePoints: json['vibe_points'], - pointTransactions: json['point_transactions'], - dateCreated: DateTime.parse(json['date_created']), - dateUpdated: DateTime.parse(json['date_updated']), - ); - } - - /// Facilitates the creation of a user model from magnet's output - /// You must add the user's username and password to the [magnetData] - /// manually to facilitate the creation of a user instance - factory User.fromMagnet(Map magnetData) { - // Split the name into first name and last name - String name = magnetData['name']; - List nameParts = name.split(' '); - String firstName = nameParts.first; - String lastName = nameParts.sublist(1).join(' '); - - String dateOfBirthString = magnetData['dateofbirth']; - List dateParts = dateOfBirthString.split('/'); - int month = int.parse(dateParts[0]); - int day = int.parse(dateParts[1]); - int year = int.parse(dateParts[2]); - - DateTime dateOfBirth = DateTime(year, month, day); - - String admissionNumber = magnetData['regno']; - String nationalId = magnetData['idno']; - String gender = magnetData['gender']; - String address = magnetData['address']; - String email = magnetData['email']; - String campus = magnetData['campus']; - bool active = magnetData['academicstatus'].toLowerCase() == 'active'; - String profileUrl = magnetData['profile']; - String password = magnetData['password']; - String username = magnetData['username']; - - return User( - admissionNumber: admissionNumber, - firstName: firstName, - lastName: lastName, - nationalId: nationalId, - gender: gender, - address: address, - email: email, - dateOfBirth: dateOfBirth, - campus: campus, - active: active, - profileUrl: "", - schoolProfile: profileUrl, - pointTransactions: null, - vibePoints: 0, - password: password, - username: username, - ); - } - - /// Used specifically for data storage - Map toMap() { - return { - 'id': id, - 'username': username, - 'first_name': firstName, - 'last_name': lastName, - 'admission_number': admissionNumber, - 'national_id': nationalId, - 'gender': gender, - 'address': address, - 'email': email, - 'date_of_birth': DateFormat("yyyy-MM-ddTHH:mm:ss.SSS'Z'").format( - dateOfBirth.toUtc(), - ), - 'campus': campus, - 'profile_url': profileUrl, - 'school_profile': schoolProfile, - 'password': password, - 'active': active == true ? 1 : 0, - 'vibe_points': vibePoints, - 'point_transactions': pointTransactions, - 'date_created': DateFormat("yyyy-MM-ddTHH:mm:ss.SSS'Z'") - .format(dateCreated?.toUtc() ?? DateTime.now().toUtc()), - 'date_updated': DateFormat("yyyy-MM-ddTHH:mm:ss.SSS'Z'") - .format(dateUpdated?.toUtc() ?? DateTime.now()), - }; - } - - /// For api sending - Map toJson() { - return { - 'id': id, - 'username': username, - 'first_name': firstName, - 'last_name': lastName, - 'admission_number': admissionNumber, - 'national_id': nationalId, - 'gender': gender, - 'address': address, - 'email': email, - 'date_of_birth': DateFormat("yyyy-MM-ddTHH:mm:ss.SSS'Z'").format( - dateOfBirth.toUtc(), - ), - 'campus': campus, - 'profile_url': profileUrl, - 'school_profile': schoolProfile, - 'password': password, - 'active': active, - 'vibe_points': vibePoints, - 'point_transactions': pointTransactions, - 'date_created': DateFormat("yyyy-MM-ddTHH:mm:ss.SSS'Z'") - .format(dateCreated?.toUtc() ?? DateTime.now().toUtc()), - 'date_updated': DateFormat("yyyy-MM-ddTHH:mm:ss.SSS'Z'") - .format(dateUpdated?.toUtc() ?? DateTime.now()), - }; - } -} diff --git a/lib/models/models.dart b/lib/models/models.dart deleted file mode 100644 index b107b04..0000000 --- a/lib/models/models.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'core/user/user.dart'; -export 'core/course/course.dart'; -export 'core/settings/settings.dart'; -export 'core/reward/reward.dart'; diff --git a/lib/models/services/rewards_service.dart b/lib/models/services/rewards_service.dart deleted file mode 100644 index 8b8df90..0000000 --- a/lib/models/services/rewards_service.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'dart:convert'; - -import 'package:academia/models/core/reward/reward.dart'; -import 'package:academia/models/core/user/user.dart'; -import 'package:academia/models/services/services.dart'; -import 'package:http/http.dart' as http; -import 'package:dartz/dartz.dart'; - -class RewardsService with VerisafeService { - /// Fetches all student rewards stored in the server - Future>> fetchUserRewards( - String userID, - Map tokenHeaders, - ) async { - try { - final response = await http.get( - Uri.parse( - "${VerisafeService.urlPrefix}/rewards/awards/$userID", - ), - headers: tokenHeaders, - ); - - if (response.statusCode == 200) { - final body = json.decode(response.body).cast>(); - final rewards = - body.map((e) => Reward.fromJson(e)).toList().cast(); - return right(rewards); - } - - return Left( - json.decode(response.body)["error"], - ); - } catch (e) { - if (e is http.ClientException) { - return const Left( - "Error communicating to server please check your network and try again later"); - } - return Left(e.toString()); - } - } - - Future> award( - Reward r, Map tokenHeaders) async { - try { - final response = await http.post( - Uri.parse("${VerisafeService.urlPrefix}/rewards/award"), - body: json.encode(r.toJson()), - headers: tokenHeaders, - ); - - if (response.statusCode == 201) { - final body = json.decode(response.body); - return right(Reward.fromJson(body)); - } - return Left( - json.decode(response.body)["error"], - ); - } catch (e) { - if (e is http.ClientException) { - return const Left( - "Error communicating to server please check your network and try again later"); - } - return Left(e.toString()); - } - } - - Future>> fetchLeaderBoard( - Map tokenHeaders) async { - try { - final response = await http.get( - Uri.parse( - "${VerisafeService.urlPrefix}/rewards/leaderboard", - ), - headers: tokenHeaders, - ); - - if (response.statusCode == 200) { - final body = json.decode(response.body).cast>(); - final users = body.map((e) => User.fromJson(e)).toList().cast(); - - return right(users); - } - - return Left( - json.decode(response.body)["error"], - ); - } catch (e) { - if (e is http.ClientException) { - return const Left( - "Error communicating to server please check your network and try again later"); - } - return Left(e.toString()); - } - } -} diff --git a/lib/models/services/services.dart b/lib/models/services/services.dart deleted file mode 100644 index 8633817..0000000 --- a/lib/models/services/services.dart +++ /dev/null @@ -1,9 +0,0 @@ -export 'user_service.dart'; -export 'rewards_service.dart'; - -/// The verisafe service provides the base url -/// for user authentication -mixin VerisafeService { - // static const String urlPrefix = "http://192.168.100.2:8000"; - static const String urlPrefix = "http://62.169.16.219:82"; -} diff --git a/lib/models/services/user_service.dart b/lib/models/services/user_service.dart deleted file mode 100644 index f2646c7..0000000 --- a/lib/models/services/user_service.dart +++ /dev/null @@ -1,147 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/models/services/services.dart'; -import 'package:academia/models/models.dart'; -import 'package:http/http.dart' as http; -import 'package:dartz/dartz.dart'; - -class UserService with VerisafeService { - String token = ""; - - Map getTokenHeaders() { - return {"Token": token}; - } - - Future> isStudentRegistered(String admission) async { - try { - final response = await http.get( - Uri.parse( - "${VerisafeService.urlPrefix}/students/registered/$admission"), - ); - - if (response.statusCode == 200) { - return right(true); - } - - return const Left( - "Student record does not exist!", - ); - } catch (e) { - if (e is http.ClientException) { - return const Left( - "Error communicating to server please check your network and try again later"); - } - return Left(e.toString()); - } - } - - Future> deleteStudent( - Map authheaders, String userID) async { - try { - final response = await http.delete( - Uri.parse("${VerisafeService.urlPrefix}/students/delete/$userID"), - headers: authheaders, - ); - - if (response.statusCode == 200) { - return right(true); - } - - return const Left( - "We ran into an error please retry again later!", - ); - } catch (e) { - if (e is http.ClientException) { - return const Left( - "Error communicating to server please check your network and try again later"); - } - return Left(e.toString()); - } - } - - /// Register - /// Registers a user to verisafe - Future> register(Map data) async { - try { - final response = await http.post( - Uri.parse("${VerisafeService.urlPrefix}/students/register/"), - body: json.encode(data), - ); - - if (response.statusCode == 201) { - return right(User.fromJson(json.decode(response.body))); - } - - return left(json.decode(response.body)["error"]); - } catch (e) { - if (e is http.ClientException) { - return const Left( - "Error communicating to server please check your network and try again later", - ); - } - return Left(e.toString()); - } - } - - /// Register - /// Registers a user to verisafe - Future> login(String admno, String password) async { - try { - final response = await http.post( - Uri.parse("${VerisafeService.urlPrefix}/users/login/"), - body: json.encode({ - "admission_number": admno, - "password": password, - }), - ); - - if (response.statusCode == 200) { - token = response.headers["token"]!; - return right(User.fromJson(json.decode(response.body))); - } - - return left(json.decode(response.body)["error"]); - } catch (e) { - if (e is http.ClientException) { - return const Left( - "Error communicating to server please check your network and try again later", - ); - } - return Left(e.toString()); - } - } - - // Upload profile picture - Future> uploadProfilePicture( - String id, Uint8List imageFile, String filename) async { - try { - final request = http.MultipartRequest( - "PATCH", - Uri.parse("${VerisafeService.urlPrefix}/users/profile/upload/$id"), - ); - - request.headers.addAll({"Token": token}); - request.files.add(http.MultipartFile.fromBytes( - "profile", - imageFile, - filename: filename, - )); - - final response = await request.send(); - - if (response.statusCode == 200) { - return right( - User.fromJson(json.decode(await response.stream.bytesToString())), - ); - } - - return Left(json.decode(await response.stream.bytesToString())["error"]); - } catch (e) { - if (e is http.ClientException) { - return const Left( - "Error communicating to server please check your network and try again later", - ); - } - return Left(e.toString()); - } - } -} diff --git a/lib/notifier/local_notification_channel.dart b/lib/notifier/local_notification_channel.dart deleted file mode 100644 index fe65d0c..0000000 --- a/lib/notifier/local_notification_channel.dart +++ /dev/null @@ -1,34 +0,0 @@ -/// Enum representing different notification channels within the app. -/// -/// Each channel contains a `channelKey`, `channelName`, and -/// `channelDescription` which are required for initializing -/// notification channels in the app. -enum LocalNotificationChannelType { - reminders( - channelKey: 'reminders', - channelName: 'Academia Reminders', - channelDescription: "Academia's reminder notifications", - ), - general( - channelKey: 'general', - channelName: 'Academia General Notifications', - channelDescription: "Academia's general notifications", - ), - social( - channelKey: 'social', - channelName: 'Academia Social Notifications', - channelDescription: "Academia's social notifications", - ); - - // Notification channel properties - final String channelKey; - final String channelName; - final String channelDescription; - - // Constructor for the enum, initializing the properties - const LocalNotificationChannelType({ - required this.channelKey, - required this.channelName, - required this.channelDescription, - }); -} diff --git a/lib/notifier/local_notification_status_manager.dart b/lib/notifier/local_notification_status_manager.dart deleted file mode 100644 index 8c75e30..0000000 --- a/lib/notifier/local_notification_status_manager.dart +++ /dev/null @@ -1,103 +0,0 @@ -import 'package:shared_preferences/shared_preferences.dart'; - -class LocalNotificationStatusManager { - static final LocalNotificationStatusManager _instance = - LocalNotificationStatusManager._internal(); - - factory LocalNotificationStatusManager() { - return _instance; - } - - /// Private named constructor that prevents external instantiation. - LocalNotificationStatusManager._internal(); - - // Maps to hold notification status and counters - final Map _notificationStatus = {}; - int _notificationIdCounter = 1; // Start from 1 and increment indefinitely - - /// Initializes the notification status manager. - /// - /// This method should be called during the app's startup to load - /// the notification ID counter and any existing notification statuses - /// from shared preferences. - /// - /// Example: - /// ```dart - /// await LocalNotificationStatusManager().initialize(); - /// ``` - Future initialize() async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - _notificationIdCounter = prefs.getInt('notificationIdCounter') ?? 1; - - await loadNotificationStatuses(); - } - - /// Saves the current notification ID counter to shared preferences. - Future saveCounters() async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - prefs.setInt('notificationIdCounter', _notificationIdCounter); - } - - /// Gets the next available notification ID. - /// - /// This method increments the notification ID counter, saves it, - /// and returns the new ID, which can be used to create a new notification. - /// - /// Returns: - /// - `int`: The next notification ID. - int getNextId() { - int newId = _notificationIdCounter++; - saveCounters(); // Save immediately after generating the ID - return newId; - } - - /// Updates the shown status of a notification. - /// - /// Parameters: - /// - `int id`: The ID of the notification to update. - /// - `bool isShown`: The new shown status of the notification. - void setNotificationShown(int id, bool isShown) { - _notificationStatus[id] = isShown; - } - - /// Checks if a notification has been shown. - /// - /// Parameters: - /// - `int id`: The ID of the notification to check. - /// - /// Returns: - /// - `bool`: True if the notification has been shown, false otherwise. - bool isNotificationShown(int id) { - return _notificationStatus[id] ?? false; - } - - /// Resets the shown status of a notification. - /// - /// Parameters: - /// - `int id`: The ID of the notification to reset. - void resetNotificationStatus(int id) { - _notificationStatus[id] = false; - } - - /// Loads stored notification statuses from shared preferences. - /// - /// This method populates the `_notificationStatus` map with - /// previously stored statuses. - Future loadNotificationStatuses() async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - for (int i = 1; i < _notificationIdCounter; i++) { - _notificationStatus[i] = prefs.getBool('notification_$i') ?? false; - } - } - - /// Saves notification statuses to shared preferences. - /// - /// This method persists the current notification statuses in - /// shared preferences for future retrieval. - Future saveNotificationStatuses() async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - _notificationStatus.forEach((key, value) { - prefs.setBool('notification_$key', value); - }); - } -} diff --git a/lib/notifier/local_notification_type.dart b/lib/notifier/local_notification_type.dart deleted file mode 100644 index e383767..0000000 --- a/lib/notifier/local_notification_type.dart +++ /dev/null @@ -1,5 +0,0 @@ -enum LocalNotificationType { - course, - todo, - general, -} diff --git a/lib/notifier/local_notifier_service.dart b/lib/notifier/local_notifier_service.dart deleted file mode 100644 index a06f214..0000000 --- a/lib/notifier/local_notifier_service.dart +++ /dev/null @@ -1,224 +0,0 @@ -import 'package:academia/notifier/notifier.dart'; -import 'package:awesome_notifications/awesome_notifications.dart'; -import 'package:flutter/material.dart'; - -import './local_notification_channel.dart'; - -enum NotificationType { - defaultNotification, - bigText, - bigPicture, -} - -/// A singleton class responsible for managing local notifications -/// in the Flutter application using Awesome Notifications. -/// -/// This service supports multiple notification channels and ensures -/// that notifications are properly initialized and managed. -class LocalNotifierService { - static final LocalNotifierService _instance = - LocalNotifierService._internal(); - - factory LocalNotifierService() { - return _instance; - } - - /// Private named constructor that prevents external instantiation. - LocalNotifierService._internal(); - - /// Initializes the notification service by setting up the required - /// notification channels for the application. - /// - /// This method should be called during the app's startup to ensure - /// that notifications can be created for the required channels. - /// - /// Example: - /// ```dart - /// await LocalNotifierService().initialize(); - /// ``` - Future initialize() async { - /// Initialize the flutter notifications plugin - AwesomeNotifications().initialize( - 'resource://drawable/academia', - [ - NotificationChannel( - channelKey: LocalNotificationChannelType.general.channelKey, - channelName: LocalNotificationChannelType.general.channelName, - channelDescription: - LocalNotificationChannelType.general.channelDescription, - importance: NotificationImportance.High, - enableLights: true, - defaultColor: Colors.blueGrey, - playSound: false, - enableVibration: true, - channelShowBadge: true, - ), - NotificationChannel( - channelKey: LocalNotificationChannelType.reminders.channelKey, - channelName: LocalNotificationChannelType.reminders.channelName, - channelDescription: - LocalNotificationChannelType.reminders.channelDescription, - importance: NotificationImportance.High, - enableLights: true, - defaultColor: Colors.blueGrey, - playSound: true, - enableVibration: true, - channelShowBadge: true, - ), - ], - ); - } - - /// Requests permission from the user to display notifications. - /// - /// This method checks if notifications are allowed and if not, - /// it prompts the user to enable notifications for the app. - /// - /// Example: - /// ```dart - /// await LocalNotifierService().requestPermission(); - /// ``` - Future requestPermission() async { - await AwesomeNotifications().isNotificationAllowed().then((isAllowed) { - if (!isAllowed) { - AwesomeNotifications().requestPermissionToSendNotifications(); - } - }); - } - - /// Displays an instant notification with the provided content. - /// - /// Parameters: - /// - `id`: A unique identifier for the notification. - /// - `title`: The title of the notification. - /// - `body`: The body content of the notification. - /// - `payload`: Additional data that can be included in the notification. - /// - /// Example: - /// ```dart - /// await LocalNotifierService().showNotification( - /// id: 1, - /// title: 'Reminder', - /// body: 'You have a new task!', - /// ); - /// ``` - - Future showNotification({ - required int id, - required String title, - required String body, - required String channelKey, // New parameter for the channelKey - NotificationType notificationType = NotificationType.defaultNotification, - String? imageUrl, // For BigPicture notifications - String? payload, - NotificationCategory? category, - Color? color, - bool criticalAlert = false, - }) async { - NotificationLayout layout = _getNotificationLayout(notificationType); - - await AwesomeNotifications().createNotification( - content: NotificationContent( - id: id, - channelKey: channelKey, - title: title, - body: body, - notificationLayout: layout, - criticalAlert: criticalAlert, - bigPicture: - notificationType == NotificationType.bigPicture ? imageUrl : null, - payload: {'payload_data': payload ?? 'No data'}, - category: category, - color: color, - ), - ); - - LocalNotificationStatusManager().setNotificationShown(id, true); - } - - /// Schedules a notification to be displayed at a specific time. - /// - /// Parameters: - /// - `id`: A unique identifier for the notification. - /// - `title`: The title of the notification. - /// - `body`: The body content of the notification. - /// - `scheduledTime`: The time at which the notification should be displayed. - /// - /// Example: - /// ```dart - /// await LocalNotifierService().scheduleNotification( - /// id: 2, - /// title: 'Scheduled Task', - /// body: 'This is a scheduled notification.', - /// scheduledTime: DateTime.now().add(Duration(hours: 2)), - /// ); - /// ``` - Future scheduleNotification({ - required int id, - required String title, - required String body, - required DateTime scheduledTime, - required String channelKey, // New parameter for the channelKey - NotificationType notificationType = NotificationType.defaultNotification, - NotificationCategory? category, - Color? color, - String? imageUrl, // For BigPicture notifications - - bool criticalAlert = false, - }) async { - NotificationLayout layout = _getNotificationLayout(notificationType); - - await AwesomeNotifications().createNotification( - content: NotificationContent( - id: id, - channelKey: channelKey, - title: title, - body: body, - notificationLayout: layout, - bigPicture: - notificationType == NotificationType.bigPicture ? imageUrl : null, - category: category, - color: color, - criticalAlert: criticalAlert, - ), - schedule: NotificationCalendar.fromDate(date: scheduledTime), - ); - - LocalNotificationStatusManager().setNotificationShown(id, true); - } - - /// Cancels a specific notification by its unique ID. - /// - /// Parameters: - /// - `id`: The unique identifier of the notification to cancel. - /// - /// Example: - /// ```dart - /// await LocalNotifierService().cancelNotification(1); - /// ``` - Future cancelNotification(int id) async { - LocalNotificationStatusManager().resetNotificationStatus(id); - await AwesomeNotifications().cancel(id); - } - - /// Cancels all notifications created by the app. - /// - /// Example: - /// ```dart - /// await LocalNotifierService().cancelAllNotifications(); - /// ``` - Future cancelAllNotifications() async { - await AwesomeNotifications().cancelAll(); - } - - NotificationLayout _getNotificationLayout(NotificationType notificationType) { - switch (notificationType) { - case NotificationType.bigText: - return NotificationLayout.BigText; - case NotificationType.bigPicture: - return NotificationLayout.BigPicture; - default: - return NotificationLayout.Default; - } - } -} diff --git a/lib/notifier/notifier.dart b/lib/notifier/notifier.dart deleted file mode 100644 index 476e465..0000000 --- a/lib/notifier/notifier.dart +++ /dev/null @@ -1,2 +0,0 @@ -export './local_notifier_service.dart'; -export './local_notification_status_manager.dart'; diff --git a/lib/pages/auth/auth.dart b/lib/pages/auth/auth.dart deleted file mode 100644 index 6d6f482..0000000 --- a/lib/pages/auth/auth.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'login_page.dart'; -export 'register_page.dart'; diff --git a/lib/pages/auth/login_page.dart b/lib/pages/auth/login_page.dart deleted file mode 100644 index 525c3c2..0000000 --- a/lib/pages/auth/login_page.dart +++ /dev/null @@ -1,233 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/notifier/local_notification_channel.dart'; -import 'package:academia/notifier/local_notifier_service.dart'; -import 'package:get/get.dart'; -import 'package:lottie/lottie.dart'; - -class LoginPage extends StatefulWidget { - const LoginPage({super.key}); - - @override - State createState() => _LoginPageState(); -} - -class _LoginPageState extends State { - final formKey = GlobalKey(); - final userController = Get.find(); - - TextEditingController usernameController = TextEditingController(); - TextEditingController admnoEditingController = TextEditingController(); - TextEditingController passwordEditingController = TextEditingController(); - - bool showUsernameField = false; - bool hidePassword = true; - bool isLoading = false; - - Future login() async { - final result = await userController.login( - admnoEditingController.text, - passwordEditingController.text, - ); - - result.fold( - (l) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Error"), - content: Text(l), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Oh ok"), - ), - ], - ), - ); - }, - (r) { - HapticFeedback.heavyImpact().then((value) { - if (userController.isLoggedIn.value) { - if (!mounted) return; - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute(builder: (context) => const LayoutPage()), - (predicate) => false - // (Route) => false, - ); - LocalNotifierService().showNotification( - id: 0, - title: "Welcome", - body: - "Welcome back ${userController.user.value!.firstName.title()} to Academia!, we've missed you", - channelKey: LocalNotificationChannelType.general.channelKey, - notificationType: NotificationType.defaultNotification, - ); - return; - } - }); - r["password"] = passwordEditingController.text; - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => RegisterPage(userData: r), - ), - ); - }, - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - minimum: const EdgeInsets.all(12), - child: Form( - key: formKey, - child: Column( - children: [ - Text( - "Lets find you and set up things for you", - style: Theme.of(context).textTheme.displaySmall, - ), - Lottie.asset( - "assets/lotties/search.json", - height: 200, - ), - const SizedBox(height: 22), - TextFormField( - controller: admnoEditingController, - maxLength: 7, - keyboardType: TextInputType.number, - inputFormatters: [ - AdmissionNumberFormatter(), - ], - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (value) { - if (value?.length != 7) { - return "Please enter a valid admission number"; - } - return null; - }, - textAlign: TextAlign.center, - decoration: InputDecoration( - hintText: "xx-xxxx", - label: const Text("Admisson Number"), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - ), - ), - ), - const SizedBox(height: 8), - TextFormField( - controller: passwordEditingController, - obscureText: hidePassword, - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (value) { - if ((value?.length ?? 0) <= 4) { - return "Please enter a valid password"; - } - return null; - }, - textAlign: TextAlign.center, - decoration: InputDecoration( - hintText: "Your secret password", - label: const Text("Password"), - suffixIcon: IconButton( - onPressed: () { - setState(() { - hidePassword = !hidePassword; - }); - }, - icon: Icon( - hidePassword - ? Ionicons.lock_closed - : Ionicons.lock_open, - )), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - ), - ), - ), - const Spacer(), - isLoading - ? Lottie.asset( - "assets/lotties/loading.json", - height: 45, - ) - : FilledButton.icon( - onPressed: () async { - if (!formKey.currentState!.validate()) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Error"), - content: - const Text("Please ensure you fill the form"), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Oh ok"), - ), - ], - ), - ); - return; - } - - setState(() { - isLoading = true; - }); - - await login(); - - setState(() { - isLoading = false; - }); - }, - // onPressed: () { - // Navigator.of(context).push( - // MaterialPageRoute( - // builder: (context) => const LayoutPage(), - // ), - // ); - // }, - icon: const Icon(Ionicons.lock_closed), - label: const Text("Login"), - ), - ], - ), - ), - ), - ); - } -} - -class AdmissionNumberFormatter extends TextInputFormatter { - @override - TextEditingValue formatEditUpdate( - TextEditingValue oldValue, TextEditingValue newValue) { - // final oldValueText = oldValue.text; - final newValueText = newValue.text; - - if (newValueText == '') { - return newValue; - } else if (newValueText.length <= 2) { - return newValue; - } else if (newValueText.length == 3) { - return newValueText.endsWith('-') - ? newValue - : TextEditingValue( - text: - "${newValueText.substring(0, 2)}-${newValueText.substring(2)}", - ); - } else if (newValueText.length <= 7) { - return newValue; - } else { - return oldValue; - } - } -} diff --git a/lib/pages/auth/register_page.dart b/lib/pages/auth/register_page.dart deleted file mode 100644 index fa8dac5..0000000 --- a/lib/pages/auth/register_page.dart +++ /dev/null @@ -1,183 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/notifier/local_notification_channel.dart'; -import 'package:academia/notifier/local_notification_type.dart'; -import 'package:academia/notifier/local_notifier_service.dart'; -import 'package:get/get.dart'; -import 'package:lottie/lottie.dart'; - -class RegisterPage extends StatefulWidget { - const RegisterPage({super.key, required this.userData}); - final Map userData; - - @override - State createState() => _RegisterPageState(); -} - -class _RegisterPageState extends State { - final TextEditingController usernameController = TextEditingController(); - - final userController = Get.find(); - final rewardsController = Get.find(); - final formKey = GlobalKey(); - bool isLoading = false; - - Future register(Map data) async { - data["username"] = usernameController.text; - final result = await userController.register(data); - result.fold((l) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Error"), - content: Text(l), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Oh ok"), - ), - ], - ), - ); - }, (r) { - if (r && userController.isLoggedIn.value) { - HapticFeedback.heavyImpact().then((value) {}); - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (context) => const LayoutPage(), - ), - (predicate) => false, - ); - - LocalNotifierService().showNotification( - id: 0, - title: "Welcome", - body: "Hi @${data['username']}! Welcome to Academia", - channelKey: LocalNotificationChannelType.general.channelKey, - notificationType: NotificationType.defaultNotification, - ); - return; - } - - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Snap"), - content: const Text("Something went terribly wrong"), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Oh ok"), - ), - ], - ), - ); - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Form( - key: formKey, - child: SafeArea( - minimum: const EdgeInsets.all(12), - child: Column( - children: [ - Text( - "Hi there ${widget.userData['name'].toString().split(" ")[0].title()}, just one more step", - style: Theme.of(context).textTheme.displaySmall, - textAlign: TextAlign.start, - ), - const SizedBox(height: 22), - TextFormField( - controller: usernameController, - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (value) { - if ((value?.length ?? 0) <= 4) { - return "Please try another cool nickname"; - } - return null; - }, - textAlign: TextAlign.center, - decoration: InputDecoration( - suffixIcon: IconButton( - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Clarification"), - content: const Text( - "Nicknames are a way to publically show your info without compromising your identity", - ), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Ooh wow")) - ], - ), - ); - }, - icon: const Icon( - Ionicons.information_circle, - ), - ), - hintText: "Please select your new nickname", - label: const Text("Nickname"), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - ), - ), - ), - const Spacer(), - isLoading - ? Lottie.asset( - "assets/lotties/loading.json", - height: 45, - ) - : FilledButton.icon( - onPressed: () { - if (!formKey.currentState!.validate()) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Error"), - content: - const Text("Please ensure you fill the form"), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Oh ok"), - ), - ], - ), - ); - return; - } - - setState(() { - isLoading = true; - }); - register(widget.userData).then((value) {}); - - setState(() { - isLoading = false; - }); - }, - icon: const Icon(Ionicons.flame_outline), - label: const Text("Let the fun begin"), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/pages/courses/course_view_page.dart b/lib/pages/courses/course_view_page.dart deleted file mode 100644 index 9a31803..0000000 --- a/lib/pages/courses/course_view_page.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/models/core/course/course_model.dart'; -import 'package:academia/models/models.dart'; -import 'package:get/get.dart'; -import 'widgets/course_topic_form.dart'; - -class CourseViewPage extends StatelessWidget { - const CourseViewPage({ - super.key, - required this.course, - }); - final Course course; - - @override - Widget build(BuildContext context) { - final CoursesController coursesController = Get.find(); - return Scaffold( - resizeToAvoidBottomInset: true, - body: CustomScrollView( - slivers: [ - SliverAppBar( - automaticallyImplyLeading: true, - expandedHeight: 250, - snap: true, - pinned: true, - floating: true, - flexibleSpace: FlexibleSpaceBar( - background: Container( - color: Theme.of(context).colorScheme.tertiaryContainer, - ), - title: Text(course.unit), - ), - ), - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverFillRemaining( - child: Obx( - () => coursesController.coursesTopics - .where((p0) => p0.course == course.unit) - .toList() - .isEmpty - ? Center( - child: Text( - "Add topics you learn in class for easier revision ${Emojis.smile_nerd_face}", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineSmall, - ), - ) - : ListView.separated( - separatorBuilder: (context, index) => - const SizedBox(height: 4), - itemBuilder: (context, index) { - final data = coursesController.coursesTopics - .where((p0) => p0.course == course.unit) - .toList()[index]; - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: Theme.of(context) - .colorScheme - .secondaryContainer, - ), - ), - child: ListTile( - title: Text(data.name), - subtitle: Text( - data.description == "" - ? "A description, there is not. Lost, it seems." - : data.description, - style: Theme.of(context).textTheme.bodySmall, - ), - leading: CircleAvatar( - child: Text((index + 1).toString()), - ), - trailing: IconButton( - onPressed: () async { - await coursesController - .deleteCourseTopic(data); - - HapticFeedback.heavyImpact().then((value) { - if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: - Text("Deleted successfully"))); - }); - }, - icon: const Icon(Ionicons.trash), - ), - ), - ); - }, - itemCount: coursesController.coursesTopics - .where((p0) => p0.course == course.unit) - .toList() - .length, - shrinkWrap: true, - ), - ), - ), - ) - ], - ), - floatingActionButton: FloatingActionButton( - onPressed: () { - showDialog( - context: context, - // elevation: 0, - builder: (context) => Dialog( - child: TopicForm( - course: course, - )), - ); - }, - child: const Icon(Ionicons.add), - ), - ); - } -} diff --git a/lib/pages/courses/courses.dart b/lib/pages/courses/courses.dart deleted file mode 100644 index d12d17d..0000000 --- a/lib/pages/courses/courses.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'courses_page.dart'; -export 'service/courses_background_service.dart'; diff --git a/lib/pages/courses/courses_page.dart b/lib/pages/courses/courses_page.dart deleted file mode 100644 index 0028ea8..0000000 --- a/lib/pages/courses/courses_page.dart +++ /dev/null @@ -1,162 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import 'widgets/course_card.dart'; - -class CoursesPage extends StatefulWidget { - const CoursesPage({super.key}); - - @override - State createState() => _CoursesPageState(); -} - -class _CoursesPageState extends State { - final coursesController = Get.find(); - @override - Widget build(BuildContext context) { - return RefreshIndicator( - onRefresh: () async { - final result = await coursesController.fetchUserCourses(); - result.fold( - (l) => showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Oops!"), - content: Text(l), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Ooh, ok"), - ) - ], - ), - ), (r) { - setState(() {}); - }); - }, - child: CustomScrollView( - slivers: [ - SliverAppBar( - floating: true, - snap: true, - pinned: true, - title: const Text("Courses"), - actions: [ - IconButton( - onPressed: () async { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Courses"), - content: const Text( - "Here you can view your courses and everything in between", - ), - actions: [ - FilledButton( - onPressed: () async { - Navigator.of(context).pop(); - }, - child: const Text("Cool"), - ) - ], - ), - ); - }, - icon: const Icon(Ionicons.information_circle_outline), - ), - ], - ), - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverToBoxAdapter( - child: SizedBox( - height: 80, - width: MediaQuery.of(context).size.width, - child: FutureBuilder( - future: magnet.fetchUserClassAttendance(), - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return Center( - child: Text( - "Fetching your courses we are", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleMedium, - )); - } - - if (snapshot.hasError) { - return Center( - child: Text( - "Failed to fetch course attendance. Check your connection", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleMedium, - )); - } - - return snapshot.data!.fold((l) { - return const Center( - child: Text( - "Failed to fetch courses. Check your connection", - ), - ); - }, (r) { - return ListView.builder( - itemBuilder: (context, index) { - final data = r[index]; - return ListTile( - leading: CircleAvatar( - child: Text((index + 1).toString()), - ), - title: Text(data.keys.first), - subtitle: LinearProgressIndicator( - backgroundColor: Theme.of(context) - .colorScheme - .primaryContainer, - color: Theme.of(context).colorScheme.primary, - value: data.values.first.toDouble(), - ), - ); - }, - itemCount: r.length, - ); - }); - }), - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.symmetric(horizontal: 12), - sliver: SliverFillRemaining( - child: coursesController.courses.isEmpty - ? Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Lottie.asset("assets/lotties/empty.json"), - Image.asset("assets/images/study.gif"), - Text( - "No courses available right now, pull to refresh", - style: Theme.of(context).textTheme.headlineSmall, - textAlign: TextAlign.center, - ) - ], - ) - : ListView.separated( - itemBuilder: (context, index) { - final course = coursesController.courses[index]; - return CourseCard(course: course); - }, - separatorBuilder: (context, index) { - return const SizedBox( - height: 12, - ); - }, - itemCount: coursesController.courses.length, - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/pages/courses/service/courses_background_service.dart b/lib/pages/courses/service/courses_background_service.dart deleted file mode 100644 index e227f0f..0000000 --- a/lib/pages/courses/service/courses_background_service.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/models/core/course/course.dart'; -import 'package:academia/notifier/local_notification_channel.dart'; -import 'package:academia/notifier/local_notification_status_manager.dart'; -import 'package:academia/notifier/local_notifier_service.dart'; - -class CoursesBackgroundService { - static final CoursesBackgroundService _instance = - CoursesBackgroundService._internal(); - - factory CoursesBackgroundService() { - return _instance; - } - - /// Private named constructor that prevents external instantiation. - CoursesBackgroundService._internal(); - - void notifyTodaysCourses() { - CourseModelHelper().queryAll().then((value) { - int coursestoday = 0; - value.map((e) { - final course = Course.fromJson(e); - if (course.dayOfWeek == DateTime.now().weekdayToString()) { - coursestoday++; - } - }); - - if (coursestoday > 0) { - LocalNotifierService().showNotification( - id: LocalNotificationStatusManager().getNextId(), - title: "Class! Class! Class! ${Emojis.building_school}", - body: - "Hey, you have $coursestoday classes today ${Emojis.smile_face_screaming_in_fear}", - channelKey: LocalNotificationChannelType.reminders.channelKey, - ); - } - }); - } -} diff --git a/lib/pages/courses/widgets/course_card.dart b/lib/pages/courses/widgets/course_card.dart deleted file mode 100644 index 0c17787..0000000 --- a/lib/pages/courses/widgets/course_card.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/models/models.dart'; -import '../course_view_page.dart'; - -class CourseCard extends StatelessWidget { - const CourseCard({ - super.key, - required this.course, - }); - final Course course; - - @override - Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.secondaryContainer, - border: Border.all( - color: Theme.of(context).colorScheme.primaryContainer, - width: 2, - ), - borderRadius: BorderRadius.circular(12)), - child: ListTile( - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => CourseViewPage( - course: course, - )), - ); - }, - title: Text( - course.unit, - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: Theme.of(context).colorScheme.primary, - ), - ), - subtitle: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 22), - Row( - children: [ - const Icon( - Ionicons.today, - size: 16, - ), - const SizedBox(width: 4), - Text( - "Every: ", - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.primary, - ), - ), - Text( - course.dayOfWeek.title(), - ), - const Spacer(), - const Icon( - Ionicons.location, - size: 16, - ), - const SizedBox(width: 4), - Text( - "Venue: ", - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.primary, - ), - ), - Text( - course.room, - ), - ], - ), - const SizedBox(height: 12), - Row( - children: [ - const Icon( - Ionicons.person_circle, - size: 16, - ), - const SizedBox(width: 4), - Text( - "Lecturer: ", - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.primary, - ), - ), - Text( - course.lecturer.title(), - ), - ], - ), - const SizedBox(height: 12), - Row( - children: [ - const Icon( - Ionicons.time, - size: 16, - ), - const SizedBox(width: 4), - Text( - "Time: ", - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.primary, - ), - ), - Text( - course.period, - ), - ], - ), - ], - ), - ), - ); - } -} diff --git a/lib/pages/courses/widgets/course_topic_form.dart b/lib/pages/courses/widgets/course_topic_form.dart deleted file mode 100644 index 68f57d0..0000000 --- a/lib/pages/courses/widgets/course_topic_form.dart +++ /dev/null @@ -1,101 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/models/core/course/course_model.dart'; -import 'package:academia/models/core/course/course_topic.dart'; -import 'package:get/get.dart'; - -class TopicForm extends StatelessWidget { - const TopicForm({ - super.key, - required this.course, - }); - final Course course; - - @override - Widget build(BuildContext context) { - final CoursesController coursesController = Get.find(); - final topicNameController = TextEditingController(); - final topicDescriptionController = TextEditingController(); - final formState = GlobalKey(); - return Container( - width: double.infinity, - padding: const EdgeInsets.all(12), - child: Form( - key: formState, - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - "Add A topic", - style: Theme.of(context).textTheme.headlineSmall, - ), - Container( - padding: const EdgeInsets.all(8), - color: Theme.of(context).colorScheme.primaryContainer, - child: Text( - "Note down topics after class to help you revise for cats and exams", - style: Theme.of(context).textTheme.bodySmall, - ), - ), - const SizedBox( - height: 22, - ), - TextFormField( - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (value) { - if (value!.length < 3) { - return "Please input a valid topic name"; - } - return null; - }, - controller: topicNameController, - decoration: InputDecoration( - label: const Text("Topic name"), - hintText: "Waves and Electromagnetism", - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - ), - ), - ), - const SizedBox(height: 12), - TextFormField( - controller: topicDescriptionController, - maxLines: 5, - textAlignVertical: TextAlignVertical.top, - decoration: InputDecoration( - label: const Text("Addittional Data"), - hintText: "Read topic from Scientists and Engineers book", - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - ), - ), - ), - const SizedBox( - height: 22, - ), - FilledButton.icon( - onPressed: () async { - if (!formState.currentState!.validate()) { - return; - } - final ok = await coursesController.createCourseTopic( - CourseTopic( - course: course.unit, - name: topicNameController.text, - description: topicDescriptionController.text), - ); - - if (ok) { - if (context.mounted) Navigator.pop(context); - } - }, - label: const Text("Add Topic"), - icon: const Icon(Ionicons.clipboard), - ) - ], - ), - ), - ), - ); - } -} diff --git a/lib/pages/dashboard.dart b/lib/pages/dashboard.dart deleted file mode 100644 index c3a7815..0000000 --- a/lib/pages/dashboard.dart +++ /dev/null @@ -1,305 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/pages/courses/widgets/course_card.dart'; -import 'package:get/get.dart'; -import 'package:lottie/lottie.dart'; - -class DashBoard extends StatelessWidget { - const DashBoard({super.key}); - - @override - Widget build(BuildContext context) { - final EventsController eventsController = Get.find(); - final CoursesController coursesController = Get.find(); - final TodoController todoController = Get.find(); - return Scaffold( - body: CustomScrollView( - slivers: [ - SliverAppBar( - elevation: 0, - pinned: true, - floating: false, - snap: false, - leading: const SizedBox(width: 18), - expandedHeight: 250, - automaticallyImplyLeading: false, - flexibleSpace: FlexibleSpaceBar( - background: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.secondaryContainer, - image: const DecorationImage( - image: AssetImage( - "assets/images/sketchbook-young-man-studying.png", - ), - ), - ), - ), - title: const Text( - "Life at glance", - ), - ), - ), - SliverToBoxAdapter( - child: Container( - padding: const EdgeInsets.all(22), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Quick statistics", - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - color: Theme.of(context).colorScheme.surfaceTint), - ), - const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Stat(title: "Day", percentage: dayPercentGone() * 0.01), - Stat(title: "Week", percentage: weekPercentGone() * 0.01), - Obx( - () => Stat( - title: "Semester", - percentage: calculateSemesterPercent( - eventsController - .currentSemester.value?.startDate ?? - DateTime.now(), - eventsController - .currentSemester.value?.endDate ?? - DateTime.now(), - ) * - 0.01, - ), - ), - ], - ), - ], - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverToBoxAdapter( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Container( - width: 180, - height: 250, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.tertiaryContainer, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Image.asset( - "assets/images/sketchbook-workspace-taking-notes-during-call-1.png", - ), - Text( - coursesController.courses.length.toString(), - style: Theme.of(context).textTheme.headlineSmall, - ), - const Text("Number of courses") - ], - ), - ), - Container( - width: 180, - height: 250, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.tertiaryContainer, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Image.asset( - "assets/images/sketchbook-young-woman-chooses-book-1.png", - ), - Text( - coursesController.numberOfCoursesToday.toString(), - style: Theme.of(context).textTheme.headlineSmall, - ), - const Text("Courses today") - ], - ), - ), - ], - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverVisibility( - sliver: SliverToBoxAdapter( - child: ExpansionTile( - initiallyExpanded: true, - maintainState: true, - enableFeedback: true, - title: Text( - "Today's classes", - style: Theme.of(context).textTheme.headlineSmall, - ), - children: [ - Container( - height: MediaQuery.of(context).size.height * .25, - width: MediaQuery.of(context).size.width, - padding: const EdgeInsets.all(12), - child: coursesController.numberOfCoursesToday == 0 - ? Column( - children: [ - Text( - "You have no classes today, take the day off complete assignments ${Emojis.smile_face_with_tongue}", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleSmall, - ) - ], - ) - : ListView.separated( - itemBuilder: (context, index) { - final data = - coursesController.coursesToday[index]; - return CourseCard(course: data); - }, - separatorBuilder: (context, index) => - const SizedBox(height: 4), - itemCount: coursesController.coursesToday.length, - ), - ) - ], - ), - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverVisibility( - sliver: SliverToBoxAdapter( - child: ExpansionTile( - initiallyExpanded: false, - maintainState: true, - enableFeedback: true, - title: Text( - "Current Semester Events", - style: Theme.of(context).textTheme.headlineSmall, - ), - children: [ - Container( - padding: const EdgeInsets.all(12), - height: MediaQuery.of(context).size.height * 0.2, - child: FutureBuilder( - future: eventsController.semesterService - .fetchSemesterEvent( - eventsController.currentSemester.value?.id ?? - ""), - builder: (context, snapshot) { - if (snapshot.connectionState != - ConnectionState.done) { - return const Center( - child: CircularProgressIndicator(), - ); - } - return snapshot.data!.fold( - (l) => SingleChildScrollView( - child: Column( - children: [ - Lottie.asset( - "assets/lotties/error.json"), - const SizedBox(height: 12), - Text( - "Holy .. $l", - style: Theme.of(context) - .textTheme - .headlineSmall, - ), - ], - ), - ), (r) { - return ListView.separated( - itemBuilder: (context, index) { - final data = r[index]; - return ListTile( - leading: CircleAvatar( - child: Text((index + 1).toString()), - ), - title: Text(data.name), - trailing: Icon( - data.endDate.isBefore(DateTime.now()) - ? Ionicons.checkmark_done_circle - : Ionicons.chevron_up_circle_outline, - ), - subtitle: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "Starts: ${formatDateTime(data.startDate)}", - style: Theme.of(context) - .textTheme - .bodySmall, - ), - const SizedBox(height: 4), - Text( - "Ends: ${formatDateTime(data.endDate)}", - style: Theme.of(context) - .textTheme - .bodySmall, - ), - ], - ), - ); - }, - separatorBuilder: (context, index) => - const SizedBox(height: 4), - itemCount: r.length); - }); - }, - ), - ) - ], - ), - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverVisibility( - sliver: SliverToBoxAdapter( - child: ExpansionTile( - initiallyExpanded: true, - maintainState: true, - enableFeedback: true, - title: Text( - "Today's tasks", - style: Theme.of(context).textTheme.headlineSmall, - ), - children: [ - Container( - color: Theme.of(context).colorScheme.primaryContainer, - padding: const EdgeInsets.all(12), - width: double.infinity, - child: Obx( - () => todoController.filterTodosByDate("today").isEmpty - ? Text( - "You are clear for the day try visiting the library ${Emojis.smile_ghost}", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleSmall, - ) - : Text( - "You have ${todoController.filterTodosByDate("today").length} due today", - style: Theme.of(context).textTheme.titleMedium, - textAlign: TextAlign.center, - ), - ), - ) - ], - ), - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/pages/image_config_screen.dart b/lib/pages/image_config_screen.dart deleted file mode 100644 index 27406e7..0000000 --- a/lib/pages/image_config_screen.dart +++ /dev/null @@ -1,153 +0,0 @@ -import 'package:academia/exports/barrel.dart'; - -class ImageConfigScreen extends StatefulWidget { - const ImageConfigScreen({ - super.key, - this.title = "Configure image", - required this.onImagePicked, - }); - final String title; - final void Function(XFile?) onImagePicked; - - @override - State createState() => _ImageConfigScreenState(); -} - -class _ImageConfigScreenState extends State { - final ImagePicker _picker = ImagePicker(); - XFile? _imageFile; - bool isPicked = false; - - // Future _pickImage(ImageSource source) async { - // final XFile? image = await _picker.pickImage(source: source); - // setState(() { - // _imageFile = image; - // }); - // } - - Future _pickAndCropImage(ImageSource source) async { - // Pick image - final XFile? pickedImage = await _picker.pickImage(source: source); - - if (pickedImage != null) { - // Crop image - final croppedFile = await ImageCropper().cropImage( - sourcePath: pickedImage.path, - uiSettings: [ - AndroidUiSettings( - toolbarTitle: 'Crop image', - toolbarColor: mounted - ? Theme.of(context).colorScheme.primary - : Colors.deepPurple, - toolbarWidgetColor: mounted - ? Theme.of(context).colorScheme.onPrimary - : Colors.white, - aspectRatioPresets: [ - CropAspectRatioPreset.original, - CropAspectRatioPreset.square, - CropAspectRatioPreset.ratio16x9, - CropAspectRatioPreset.ratio4x3, - ], - ), - IOSUiSettings( - title: "Crop image", - minimumAspectRatio: 1.0, - aspectRatioPresets: [ - CropAspectRatioPreset.original, - CropAspectRatioPreset.square, - CropAspectRatioPreset.ratio16x9, - CropAspectRatioPreset.ratio4x3, - ]), - ], - ); - - setState(() { - _imageFile = croppedFile != null ? XFile(croppedFile.path) : null; - isPicked = true; - }); - widget.onImagePicked(null); - } - } - - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(widget.title), - actions: _imageFile == null - ? null - : [ - FilledButton( - onPressed: () { - setState(() { - _imageFile = null; - isPicked = false; - }); - }, - child: const Text("Clear")) - ], - ), - body: _imageFile == null - ? Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Tap to pick an image", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineSmall, - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - IconButton( - onPressed: () async { - await _pickAndCropImage(ImageSource.gallery); - }, - icon: const Icon(Ionicons.images), - ), - IconButton( - onPressed: () async { - await _pickAndCropImage(ImageSource.camera); - }, - icon: const Icon(Ionicons.camera), - ), - ], - ) - ], - ) - : FutureBuilder( - future: _imageFile!.readAsBytes(), - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return Text( - "Processing image", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineSmall, - ); - } - return Center( - child: Image.memory( - snapshot.data!, - height: 400, - ), - ); - }, - ), - floatingActionButton: isPicked - ? FloatingActionButton( - child: const Icon(Ionicons.checkmark), - onPressed: () { - widget.onImagePicked(_imageFile); - Navigator.pop(context); - }, - ) - : null, - ); - } -} diff --git a/lib/pages/layout_page.dart b/lib/pages/layout_page.dart deleted file mode 100644 index e8c69c9..0000000 --- a/lib/pages/layout_page.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:academia/exports/barrel.dart'; - -class LayoutPage extends StatefulWidget { - const LayoutPage({super.key}); - - @override - State createState() => _LayoutPageState(); -} - -class _LayoutPageState extends State { - int currentIndex = 2; - @override - Widget build(BuildContext context) { - return Scaffold( - extendBodyBehindAppBar: true, - extendBody: true, - body: SafeArea( - child: IndexedStack( - index: currentIndex, - children: const [ - DashBoard(), - CoursesPage(), - ChirpHomePage(), - ToolsPage(), - ProfilePage(), - ], - ), - ), - bottomNavigationBar: NavigationBar( - elevation: 2, - selectedIndex: currentIndex, - onDestinationSelected: (index) => setState(() => currentIndex = index), - destinations: const [ - NavigationDestination( - icon: Icon(Ionicons.pulse_outline), - label: 'Stats', - ), - NavigationDestination( - icon: Icon(Ionicons.golf_outline), - label: 'Courses', - ), - NavigationDestination( - icon: Icon(Ionicons.logo_wechat), - label: 'Chirp', - ), - NavigationDestination( - icon: Icon(Ionicons.hammer_outline), - label: 'Tools', - ), - NavigationDestination( - icon: Icon(Ionicons.person_outline), - label: 'Profile', - ), - ], - ), - ); - } -} diff --git a/lib/pages/pages.dart b/lib/pages/pages.dart deleted file mode 100644 index 95d2371..0000000 --- a/lib/pages/pages.dart +++ /dev/null @@ -1,11 +0,0 @@ -export 'dashboard.dart'; -export 'layout_page.dart'; -export 'profile_page.dart'; -export 'settings_page.dart'; -export 'tool_page.dart'; -export 'image_config_screen.dart'; - -// Supporting pages -export 'auth/auth.dart'; -export 'courses/courses.dart'; -export 'util_pages/util_pages.dart'; diff --git a/lib/pages/profile_page.dart b/lib/pages/profile_page.dart deleted file mode 100644 index 7b2ae7a..0000000 --- a/lib/pages/profile_page.dart +++ /dev/null @@ -1,155 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:get/get.dart'; - -class ProfilePage extends StatelessWidget { - const ProfilePage({super.key}); - - @override - Widget build(BuildContext context) { - final userController = Get.find(); - final SettingsController settingsController = - Get.find(); - return SafeArea( - minimum: const EdgeInsets.symmetric(horizontal: 8), - child: CustomScrollView( - slivers: [ - SliverAppBar( - automaticallyImplyLeading: false, - pinned: true, - snap: true, - floating: true, - expandedHeight: 200, - flexibleSpace: const FlexibleSpaceBar( - title: Text("Your profile"), - ), - actions: [ - IconButton( - onPressed: () async { - if (await settingsController.performLocalAuthentication( - "Attempting to change app settings") && - context.mounted) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const SettingsPage()), - ); - } - }, - icon: const Icon(Ionicons.settings), - ), - ], - ), - SliverToBoxAdapter( - child: Container( - padding: const EdgeInsets.symmetric(vertical: 22), - // height:, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const ProfilePictureWidget( - profileSize: 60, - ), - const SizedBox(height: 16), - Text( - "@${userController.user.value!.username}", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - SizedBox( - child: Column( - children: [ - const Icon(Ionicons.id_card_outline), - const SizedBox(height: 4), - Text( - userController.user.value!.admissionNumber, - style: GoogleFonts.jetBrainsMono(), - ), - ], - ), - ), - SizedBox( - child: Column( - children: [ - const Icon(Ionicons.person_outline), - const SizedBox(height: 4), - Text( - userController.user.value!.nationalId, - style: GoogleFonts.jetBrainsMono(), - ), - ], - ), - ), - ], - ), - const SizedBox(height: 16), - TextButton( - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const MembershipPage(), - ), - ); - }, - child: const Text("Show Memberships"), - ), - const SizedBox(height: 16), - ], - ), - ), - ), - SliverFillRemaining( - hasScrollBody: true, - child: ListView( - children: [ - InfoCard( - title: "Official Name", - content: - "${userController.user.value!.firstName.title()} ${userController.user.value!.lastName.title()}", - icon: Ionicons.person, - ), - InfoCard( - title: "Gender", - content: (userController.user.value!.gender).title(), - icon: Ionicons.male_female, - ), - InfoCard( - title: "Address", - content: userController.user.value!.address, - icon: Ionicons.compass, - ), - InfoCard( - title: "Email", - content: userController.user.value!.email, - icon: Ionicons.mail, - ), - InfoCard( - title: "Campus", - content: userController.user.value!.campus.title(), - icon: Ionicons.telescope, - ), - InfoCard( - title: "Academic Status", - content: - userController.user.value!.active ? "Active" : "Inactive", - icon: Ionicons.calendar, - ), - Obx( - () => InfoCard( - title: "Vibe Points", - content: userController.user.value!.vibePoints.toString(), - icon: Ionicons.wallet, - ), - ), - ], - ), - ), - ], - ), - ); - } -} diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart deleted file mode 100644 index 7b69bb7..0000000 --- a/lib/pages/settings_page.dart +++ /dev/null @@ -1,347 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/main.dart'; -import 'package:academia/notifier/local_notification_channel.dart'; -import 'package:academia/notifier/local_notifier_service.dart'; -import 'package:get/get.dart'; - -class SettingsPage extends StatefulWidget { - const SettingsPage({super.key}); - - @override - State createState() => _SettingsPageState(); -} - -class _SettingsPageState extends State { - final settingsController = Get.find(); - final UserController userController = Get.find(); - @override - Widget build(BuildContext context) { - return Scaffold( - body: CustomScrollView( - slivers: [ - SliverAppBar( - snap: true, - pinned: true, - floating: true, - expandedHeight: 250, - flexibleSpace: FlexibleSpaceBar( - background: Container( - color: Theme.of(context).colorScheme.primaryContainer, - ), - title: const Text("Settings"), - ), - ), - SliverFillRemaining( - child: SingleChildScrollView( - child: Obx( - () => Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Padding( - padding: const EdgeInsets.all(12), - child: Text( - "Personal Preferences", - style: Theme.of(context).textTheme.headlineSmall, - ), - ), - ListTile( - leading: const Icon(Ionicons.lock_closed), - title: const Text("Require authentication for academia"), - subtitle: const Text( - "Will require you authenticate the app each time you launch academia", - ), - trailing: Switch( - value: - settingsController.settings.value.requireAppUnlock, - onChanged: (value) { - settingsController.settings.value = settingsController - .settings.value - .copyWith(requireAppUnlock: value); - }, - ), - ), - ListTile( - leading: const Icon(Ionicons.person_circle), - title: const Text("Show Profile Picture"), - trailing: Switch( - value: settingsController - .settings.value.showProfilePicture, - onChanged: (value) { - settingsController.settings.value = settingsController - .settings.value - .copyWith(showProfilePicture: value); - }, - ), - ), - ListTile( - leading: const Icon(Ionicons.image), - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => ImageConfigScreen( - title: "Profile Picture Configuration", - onImagePicked: (file) async { - final result = - await userController.uploadProfilePicture( - file!, - ); - - result.fold((l) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Error"), - content: Text(l), - ), - ); - }, (r) { - showDialog( - context: context, - builder: (context) => const AlertDialog( - title: Text("Success"), - content: Text( - "Successfully updated profile picture"), - ), - ); - }); - }, - ), - ), - ); - }, - title: const Text("Change your profile picture"), - ), - ListTile( - leading: const Icon(Ionicons.wallet), - title: const Text("Show Fee Statistics"), - trailing: Switch( - value: - settingsController.settings.value.showFeeStatistics, - onChanged: (value) { - settingsController.settings.value = settingsController - .settings.value - .copyWith(showFeeStatistics: value); - }, - ), - ), - ListTile( - leading: const Icon(Ionicons.balloon), - title: const Text("Wish me happy birthday"), - trailing: Switch( - value: settingsController.settings.value.showBirthDay, - onChanged: (value) { - settingsController.settings.value = settingsController - .settings.value - .copyWith(showBirthDay: value); - }, - ), - ), - Padding( - padding: const EdgeInsets.all(12), - child: Text( - "Student Performance", - style: Theme.of(context).textTheme.headlineSmall, - ), - ), - ListTile( - leading: const Icon(Ionicons.newspaper), - title: const Text("Enable transcript feature"), - trailing: Switch( - value: settingsController.settings.value.showTranscript, - onChanged: (value) { - settingsController.settings.value = settingsController - .settings.value - .copyWith(showTranscript: value); - }, - ), - ), - ListTile( - leading: const Icon(Ionicons.document), - title: const Text("Enable student audit feature"), - trailing: Switch( - value: settingsController.settings.value.showAudit, - onChanged: (value) { - settingsController.settings.value = settingsController - .settings.value - .copyWith(showAudit: value); - }, - ), - ), - ListTile( - leading: const Icon(Ionicons.sync), - title: const Text("Show exam timetable"), - subtitle: Text( - "Shows exam timetable instead of homescreen during exam period", - style: Theme.of(context).textTheme.bodySmall, - ), - trailing: Switch( - value: - settingsController.settings.value.showExamTimetable, - onChanged: (value) { - settingsController.settings.value = settingsController - .settings.value - .copyWith(showExamTimetable: value); - }, - ), - ), - Padding( - padding: const EdgeInsets.all(12), - child: Text( - "Dangerous Actions", - style: Theme.of(context) - .textTheme - .headlineSmall - ?.copyWith( - color: Theme.of(context).colorScheme.error), - ), - ), - ListTile( - leading: const Icon(Ionicons.alarm_outline), - title: const Text("Cancel All notifications"), - onTap: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Confirmation"), - content: const Text( - "Cancel all notifications? You won't get any reminders and updates for your existing tasks and assignments", - ), - actions: [ - OutlinedButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Cancel"), - ), - FilledButton( - onPressed: () async { - await HapticFeedback.heavyImpact(); - await LocalNotifierService() - .cancelAllNotifications(); - await LocalNotifierService().showNotification( - id: 0, - title: "Notifications", - color: Colors.red, - body: - "You have successfully cleared all pending notifications", - channelKey: LocalNotificationChannelType - .general.channelKey, - ); - }, - child: const Text("Yes cancel them"), - ) - ], - ), - ); - }, - ), - ListTile( - leading: const Icon(Ionicons.trash), - title: const Text("Delete my account"), - onTap: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Confirmation"), - content: const Text( - "Are you sure you want to delete this acount? Doing this will result to data loss and cannot be restored!", - ), - actions: [ - OutlinedButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Cancel"), - ), - FilledButton( - onPressed: () async { - await HapticFeedback.heavyImpact(); - await settingsController.logout(); - await userController.deleteUser(); - await LocalNotifierService().showNotification( - id: 0, - title: "Goodbye see you soon!", - notificationType: NotificationType.bigText, - body: - "Goodbye, your entire user information has been deleted from our platform, it was a pleasure being a part of your academic journey", - channelKey: LocalNotificationChannelType - .general.channelKey, - ); - - if (context.mounted) { - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (context) => const IntroPage(), - ), - (predicate) => false, - ); - } - }, - child: const Text("Yes delete it"), - ) - ], - ), - ); - }, - ), - Padding( - padding: const EdgeInsets.all(12), - child: FilledButton( - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Confirmation"), - content: const Text( - "Are you sure you want to logout? Doing this will result to data loss!", - ), - actions: [ - OutlinedButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Cancel"), - ), - FilledButton( - onPressed: () async { - await HapticFeedback.heavyImpact(); - await settingsController.logout(); - await LocalNotifierService() - .showNotification( - id: 0, - title: "Goodbye", - body: - "Bye ${userController.user.value!.firstName.title()}, why did you leave? we hope to see you again", - channelKey: LocalNotificationChannelType - .general.channelKey, - ); - if (context.mounted) { - Navigator.pop(context); - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (context) => - const IntroPage(), - ), - (predicate) => false, - ); - } - }, - child: const Text("Yes leave"), - ) - ], - ), - ); - }, - child: const Text("Logout"), - ), - ), - ], - ), - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/pages/tool_page.dart b/lib/pages/tool_page.dart deleted file mode 100644 index 4494309..0000000 --- a/lib/pages/tool_page.dart +++ /dev/null @@ -1,122 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import 'package:intl/intl.dart'; - -class ToolsPage extends StatefulWidget { - const ToolsPage({super.key}); - - @override - State createState() => _ToolsPageState(); -} - -class _ToolsPageState extends State { - var settingsController = Get.find(); - final UserController userController = Get.find(); - // all available tools - final List> _allTools = allTools; - - List> foundTools = []; - - @override - void initState() { - super.initState(); - // At the beginning all tools are visible - foundTools = _allTools; - } - - void runToolFilter(String enteredKeyWord) { - List> results = []; - if (enteredKeyWord.isEmpty) { - results = _allTools; - } else { - results = _allTools - .where((element) => element["name"] - .toString() - .toLowerCase() - .contains(enteredKeyWord.toLowerCase())) - .toList(); - } - - // refresh the ui - setState(() { - foundTools = results; - }); - } - - bool get isBirthDay { - DateFormat inputFormat = DateFormat('dd/MM/yyyy'); - var dob = inputFormat.parse( - userController.user.value!.dateOfBirth.toString(), - ); - - if (dob.day == DateTime.now().day && dob.month == DateTime.now().month) { - return true; - } - return false; - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: CustomScrollView( - slivers: [ - SliverAppBar( - pinned: true, - floating: true, - snap: true, - expandedHeight: 120, - flexibleSpace: const FlexibleSpaceBar( - title: Text("Tools"), - ), - actions: [ - IconButton( - onPressed: () { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text("Tools"), - content: const Text( - "From calculating your hypothetical gpa to booking your ride, weve got you covered", - ), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Oh, ok")) - ], - ); - }, - ); - }, - icon: const Icon(Ionicons.information_circle), - ), - ], - ), - SliverPadding( - padding: const EdgeInsets.all(8), - sliver: SliverFillRemaining( - hasScrollBody: true, - child: ListView.builder( - itemCount: _allTools.length, - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: ToolCard( - heading: _allTools[index]["name"], - description: _allTools[index]["description"], - image: _allTools[index]["image"], - ontap: _allTools[index]["ontap"], - action: _allTools[index]["action"], - ), - ); - }, - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/pages/util_pages/intro_page.dart b/lib/pages/util_pages/intro_page.dart deleted file mode 100644 index 18a33c1..0000000 --- a/lib/pages/util_pages/intro_page.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import 'package:lottie/lottie.dart'; - -class IntroPage extends StatelessWidget { - const IntroPage({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Colors.transparent, - automaticallyImplyLeading: false, - systemOverlayStyle: const SystemUiOverlayStyle( - statusBarColor: Colors.transparent, - statusBarIconBrightness: Brightness.dark, - ), - ), - body: SingleChildScrollView( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - // Welcome image ... - Lottie.asset( - "assets/lotties/study.json", - ), - - // Welcoming message - Padding( - padding: const EdgeInsets.all(16), - child: Text( - "School doesn't have to be boring anymore", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.displayMedium, - ), - ), - - // subtitle - const Padding( - padding: EdgeInsets.all(16), - child: Text( - 'Academia is for students by students ${Emojis.game_heart_suit}', - textAlign: TextAlign.center, - ), - ), - ], - ), - ), - ), - floatingActionButton: FutureBuilder( - future: Future.delayed( - const Duration(seconds: 7), - ), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - return FloatingActionButton( - onPressed: () { - HapticFeedback.heavyImpact().then((value) => Get.off( - const LoginPage(), - duration: const Duration(seconds: 3), - transition: Transition.fade, - )); - }, - tooltip: "Get Started", - child: const Icon(Ionicons.arrow_forward), - ); - } - return const SizedBox(); - })); - } -} diff --git a/lib/pages/util_pages/pdf_viewer.dart b/lib/pages/util_pages/pdf_viewer.dart deleted file mode 100644 index 5854172..0000000 --- a/lib/pages/util_pages/pdf_viewer.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import 'package:http/http.dart' as http; - -class PdfViewer extends StatefulWidget { - const PdfViewer({ - super.key, - required this.title, - required this.url, - }); - final String title, url; - - @override - State createState() => _PdfViewerState(); -} - -class _PdfViewerState extends State { - int currentPage = 0, totalPages = 0; - bool isReady = false; - String errorMessage = ''; - - Future _loadPdf() async { - try { - setState(() { - isReady = false; - }); - final response = await http.get(Uri.parse(widget.url)); - if (response.statusCode != 200) { - throw "Error while fetching content"; - } - - setState(() { - isReady = true; - }); - return response.bodyBytes; - } catch (e) { - setState(() { - errorMessage = e.toString(); - }); - } - return Uint8List(0); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(widget.title), - actions: [ - IconButton( - onPressed: () { - Get.defaultDialog( - title: "Information", - content: Column( - children: [ - Image.asset("assets/images/bot_love.png", height: 100), - const Text( - "Swipe down to reveal more pages, pinch out to zoom out, pinch in to zoom", - ) - ], - ), - ); - }, - icon: const Icon(Icons.info)), - ], - ), - body: FutureBuilder( - future: _loadPdf(), - builder: (context, snapshot) { - return snapshot.hasData - ? snapshot.hasError - ? Center( - child: Column( - children: [ - Image.asset("assets/images/bot_sad.png"), - Text(snapshot.error.toString()), - ], - ), - ) - : Stack( - children: [ - PDFView( - fitPolicy: FitPolicy.BOTH, - pdfData: snapshot.data, - pageFling: true, - defaultPage: currentPage, - onPageChanged: (page, total) { - setState(() { - currentPage = page!; - }); - }, - onRender: (pages) { - setState(() {}); - }, - onError: (error) { - debugPrint("Error"); - }, - ), - ], - ) - : const Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text("Loading .."), - ], - ), - ); - }), - ); - } -} diff --git a/lib/pages/util_pages/util_pages.dart b/lib/pages/util_pages/util_pages.dart deleted file mode 100644 index bff6b6b..0000000 --- a/lib/pages/util_pages/util_pages.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'intro_page.dart'; -export 'pdf_viewer.dart'; -export 'webview_page.dart'; diff --git a/lib/pages/util_pages/webview_page.dart b/lib/pages/util_pages/webview_page.dart deleted file mode 100644 index 9b2427a..0000000 --- a/lib/pages/util_pages/webview_page.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import 'package:webview_flutter/webview_flutter.dart'; - -class WebviewPage extends StatefulWidget { - const WebviewPage({ - super.key, - required this.title, - required this.url, - }); - final String url; - final String title; - - @override - State createState() => _WebViewPageState(); -} - -class _WebViewPageState extends State { - late final WebViewController _controller; - double _loadingPercent = 0.0; - - @override - void initState() { - super.initState(); - _controller = WebViewController() - ..loadRequest(Uri.parse(widget.url)) - ..setNavigationDelegate(NavigationDelegate(onWebResourceError: (error) { - Get.snackbar("Error", error.description); - }, onPageFinished: (url) { - setState(() { - _loadingPercent = 100; - }); - }, onProgress: (progress) { - setState(() { - _loadingPercent = progress.toDouble(); - }); - }, onPageStarted: ((url) { - setState(() { - _loadingPercent = 0; - }); - }))); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), - body: Stack( - children: [ - WebViewWidget( - controller: _controller, - ), - LinearProgressIndicator( - value: _loadingPercent / 100.0, - ) - ], - ), - ); - } -} diff --git a/lib/storage/database.dart b/lib/storage/database.dart deleted file mode 100644 index a4d87a3..0000000 --- a/lib/storage/database.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/storage/schemas.dart'; -import 'package:path/path.dart'; -import 'package:sqflite_common_ffi/sqflite_ffi.dart'; - -/// Defines a singleton class that will handles database connection. -/// -/// The database helper class is a singleton class that provides -/// -/// 1. The database connection instance. -/// 2. The database name -/// -/// By default the database should be stored in the application's -/// document directory - -class DatabaseHelper { - /// The database connection instace - static Database? _database; - - /// Database name - static const String databaseName = "academia.db"; - - static final DatabaseHelper _instance = DatabaseHelper._internal(); - - factory DatabaseHelper() { - return _instance; - } - - DatabaseHelper._internal(); - - /// A getter for the database connection instance - /// If the database connection was not established earlier, - /// it attempts to create the database connection instance using [_initDatabase] - /// then returns the connection instance that was established - Future get database async { - if (_database != null) { - return _database!; - } - _database = await _initDatabase(); - return _database!; - } - - /// _initDatabase is a private method that will be calle - /// by the getter [database] if no connection was alive - /// at the moment of calling. - /// - /// _initDatabase attempts to create a database connection - /// and returns it if it was successful. - /// - Future _initDatabase() async { - if (_database != null) { - return _database!; - } - String path = join( - (await getApplicationDocumentsDirectory()).path, - databaseName, - ); - - if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) { - sqfliteFfiInit(); - databaseFactory = databaseFactoryFfi; - final db = await databaseFactoryFfi.openDatabase(path); - - // call create manually since ffi does not call onCreate - _create(db, 1); - return db; - } - return await openDatabase( - path, - version: 1, - onCreate: _create, - ); - } - - void _create(Database db, int newVersion) async { - schemas.forEach((key, value) async { - await db.execute(value); - }); - } - - /// Truncates the database - Future truncateDataBase() async { - schemas.forEach((key, value) async { - final db = await database; - db.delete(key); - }); - } -} diff --git a/lib/storage/database_operations.dart b/lib/storage/database_operations.dart deleted file mode 100644 index 74994e1..0000000 --- a/lib/storage/database_operations.dart +++ /dev/null @@ -1,26 +0,0 @@ -/// DatabaseOperations. -/// -/// An abstract class that defines the CRUD operations. -/// All model helpers should implement this class to -/// ensure consistency within the codebase. -abstract class DatabaseOperations { - /// Create. - /// For inserting data. - Future create(Map data); - - /// queryAll. - /// should return all data in a table. - Future>> queryAll(); - - /// update. - /// Should update an entity in a table. - Future update(Map data); - - /// Delete. - /// Should delete an entity from a table. - Future delete(Map data); - - // Trucncate. - /// Should truncate all entities in the table - Future truncate(); -} diff --git a/lib/storage/schemas.dart b/lib/storage/schemas.dart deleted file mode 100644 index a56a12d..0000000 --- a/lib/storage/schemas.dart +++ /dev/null @@ -1,151 +0,0 @@ -/// The schemas variable stores a table - schema relationship -/// with the key being the table name and the value being the -/// schema sql. -/// This will be invoked on create which is same as on app lauch and -/// create the tables for your -const schemas = { - /// The user's table - "users": """ - CREATE TABLE IF NOT EXISTS users ( - id TEXT PRIMARY KEY, - username TEXT NOT NULL UNIQUE, - first_name TEXT NOT NULL, - last_name TEXT NOT NULL, - admission_number TEXT UNIQUE, - national_id TEXT UNIQUE, - gender TEXT, - address TEXT, - email TEXT UNIQUE, - date_of_birth TEXT, - campus TEXT, - profile_url TEXT, - school_profile TEXT, - password TEXT, - active INTEGER, - vibe_points INTEGER, - point_transactions TEXT, - date_created TEXT, - date_updated TEXT - ); - """, - - // Courses table - "courses": """ - CREATE TABLE IF NOT EXISTS courses ( - unit TEXT PRIMARY KEY, - section TEXT NOT NULL, - day_of_the_week TEXT NOT NULL, - period TEXT NOT NULL, - campus TEXT NOT NULL, - room TEXT NOT NULL, - lecturer TEXT NOT NULL, - start_time TEXT, - stop_time TEXT - ); - """, - - // 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 ( - id TEXT PRIMARY KEY, - student_id TEXT NOT NULL, - points INTEGER NOT NULL, - reason TEXT NOT NULL, - awarded_at TEXT NOT NULL - ); - """, - // todos - "todos": """ - CREATE TABLE IF NOT EXISTS todos ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - sub_tasks INTEGER NOT NULL, - complete INTEGER NOT NULL, - notify INTEGER NOT NULL, - description TEXT NOT NULL, - due TEXT NOT NULL, - dateAdded TEXT NOT NULL, - dateCompleted TEXT - ); - """, - "events": """ - CREATE TABLE IF NOT EXISTS events ( - id TEXT PRIMARY KEY, - date_added TEXT NOT NULL, - name TEXT NOT NULL, - phone TEXT NOT NULL, - email TEXT NOT NULL, - location TEXT NOT NULL, - likes INTEGER NOT NULL, - description TEXT NOT NULL, - media TEXT NOT NULL, - media_type TEXT NOT NULL, - url TEXT, - start_date TEXT NOT NULL, - end_date TEXT NOT NULL - ); - """, - "course_topics": """ - CREATE TABLE IF NOT EXISTS course_topics ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - course TEXT NOT NULL, - name TEXT NOT NULL, - description TEXT NOT NULL - ); - """, - // Anki - // Topic - "ankiTopics": """ - CREATE TABLE IF NOT EXISTS ankiTopics ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT UNIQUE NOT NULL, - desc TEXT NOT NULL, - is_favourite INTEGER DEFAULT 0 NOT NULL, - num_cards INTERGER DEFAULT 0 NOT NULL - ); - """, - // AnkiCard - "ankiCards": """ - CREATE TABLE IF NOT EXISTS ankiCards ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - topic_id INTERGER NOT NULL, - question TEXT NOT NULL, - answer TEXT NOT NULL, - FOREIGN KEY(topic_id) REFERENCES ankiTopics(id) - ); - """, - - //Ask Me - //Files from Ask Me from which questions are being generated - "askme_files": """ - CREATE TABLE IF NOT EXISTS askme_files ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - title TEXT NOT NULL, - filePath TEXT NOT NULL, - avgScore INTEGER NOT NULL - ); - """, - //AskMe Scores - "askme_scores": """ - CREATE TABLE IF NOT EXISTS askme_scores ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - score INTEGER NOT NULL, - filesId INTEGER, - FOREIGN KEY (filesId) REFERENCES askme_files(id) ON DELETE CASCADE - ); - """, -}; diff --git a/lib/storage/storage.dart b/lib/storage/storage.dart deleted file mode 100644 index d2f2756..0000000 --- a/lib/storage/storage.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'database.dart'; -export 'database_operations.dart'; diff --git a/lib/themes/theme.dart b/lib/themes/theme.dart deleted file mode 100644 index 139597f..0000000 --- a/lib/themes/theme.dart +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/lib/tools/anki/anki.dart b/lib/tools/anki/anki.dart deleted file mode 100644 index 44fd4d1..0000000 --- a/lib/tools/anki/anki.dart +++ /dev/null @@ -1 +0,0 @@ -export 'pages/anki_home.dart'; diff --git a/lib/tools/anki/controllers/ankicard_controller.dart b/lib/tools/anki/controllers/ankicard_controller.dart deleted file mode 100644 index 74ef13c..0000000 --- a/lib/tools/anki/controllers/ankicard_controller.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:academia/tools/anki/models/models.dart'; -import 'package:get/get.dart'; - -class AnkiCardController extends GetxController { - final RxList allCards = [].obs; - - // returns the number of topics - int numAnkiCards() { - return allCards.length; - } - - Future addAnkiCard(AnkiCard ankiCard) async { - int value = await AnkiCardModelHelper().create(ankiCard.toJson()); - return value == 0 ? false : true; - } - - Future updateAnkiCard(AnkiCard ankiCard) async { - int value = await AnkiCardModelHelper().update(ankiCard.toJson()); - return value == 0 ? false : true; - } - - Future> getAllTopicCards(int topicId) async { - AnkiCardModelHelper().queryAnkiCardsByTopic(topicId).then((values) { - allCards.clear(); - values = values.reversed.toList(); - for (final val in values) { - allCards.add(AnkiCard.fromJson(val)); - } - }); - return allCards; - } - - Future deleteCard(AnkiCard ankiCard) async { - int value = await AnkiCardModelHelper().delete(ankiCard.toJson()); - getAllTopicCards(ankiCard.topicId); - return value == 0 ? false : true; - } -} diff --git a/lib/tools/anki/controllers/answer_controller.dart b/lib/tools/anki/controllers/answer_controller.dart deleted file mode 100644 index d905c5b..0000000 --- a/lib/tools/anki/controllers/answer_controller.dart +++ /dev/null @@ -1,7 +0,0 @@ -// import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; - -class AnswerCardController extends GetxController { - var ansCard = "".obs; - var ansSwitch = true.obs; -} diff --git a/lib/tools/anki/controllers/controllers.dart b/lib/tools/anki/controllers/controllers.dart deleted file mode 100644 index f6acfdc..0000000 --- a/lib/tools/anki/controllers/controllers.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'answer_controller.dart'; -export 'topic_controller.dart'; -export 'ankicard_controller.dart'; diff --git a/lib/tools/anki/controllers/topic_controller.dart b/lib/tools/anki/controllers/topic_controller.dart deleted file mode 100644 index cdd501a..0000000 --- a/lib/tools/anki/controllers/topic_controller.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/anki/models/models.dart'; -import 'package:get/get.dart'; - -class TopicController extends GetxController { - final RxList allTopics = [].obs; - final RxList allFavourites = [].obs; - - @override - void onInit() { - super.onInit(); - getAllTopics().then((value) { - debugPrint("[+] Topics Loaded"); - }); - getAllFavourites().then((value) { - debugPrint("[+] Favourite Topics Loaded"); - }); - } - - // returns the number of topics - int numTopics() { - return allTopics.length; - } - - Future addTopic(AnkiTopic topic) async { - int value = await TopicModelHelper().create(topic.toJson()); - return value == 0 ? false : true; - } - - // Marking topic as favourite and vice versa - Future favouriteTopic(AnkiTopic topic) async { - topic.isFavourite = !topic.isFavourite; - // update topic list - if (allFavourites.length >= 5) { - updateFavourites(); - } - int value = await TopicModelHelper().update(topic.toJson()); - return value == 0 ? false : true; - } - - // updating favourite lists by popping one - void updateFavourites() async { - // pops the last favourited topic when more than five - AnkiTopic removedTopic = allFavourites.removeAt(0); - removedTopic.isFavourite = false; - // update the topic in db - await updateTopic(removedTopic); - } - - // increments topics number of cards by one - Future incTopicNumCards(AnkiTopic topic) async { - topic.numCards++; - int value = await TopicModelHelper().update(topic.toJson()); - return value == 0 ? false : true; - } - - // decrements topics number of cards by one - Future descTopicNumCards(AnkiTopic topic) async { - topic.numCards--; - int value = await TopicModelHelper().update(topic.toJson()); - return value == 0 ? false : true; - } - - Future updateTopic(AnkiTopic topic) async { - int value = await TopicModelHelper().update(topic.toJson()); - return value == 0 ? false : true; - } - - Future> getAllFavourites() async { - TopicModelHelper().getFavourites().then((values) { - allFavourites.clear(); - values = values.reversed.toList(); - for (final val in values) { - allFavourites.add(AnkiTopic.fromJson(val)); - } - }); - return allFavourites.reversed.toList(); - } - - Future> getAllTopics() async { - TopicModelHelper().queryAll().then((values) { - allTopics.clear(); - values = values.reversed.toList(); - for (final val in values) { - allTopics.add(AnkiTopic.fromJson(val)); - } - update(); - }); - return allTopics; - } - - Future deleteTopic(AnkiTopic topic) async { - int value = await TopicModelHelper().delete(topic.toJson()); - getAllTopics(); - return value == 0 ? false : true; - } -} diff --git a/lib/tools/anki/models/ankicard_model.dart b/lib/tools/anki/models/ankicard_model.dart deleted file mode 100644 index de2613a..0000000 --- a/lib/tools/anki/models/ankicard_model.dart +++ /dev/null @@ -1,28 +0,0 @@ -// ankiTCard model - -class AnkiCard { - int? id; - int topicId; - String question; - String answer; - - AnkiCard({ - this.id, - required this.topicId, - required this.question, - required this.answer, - }); - - AnkiCard.fromJson(Map json) - : id = json['id'], - topicId = json['topic_id'], - question = json['question'], - answer = json['answer']; - - Map toJson() => { - 'id': id, - 'topic_id': topicId, - 'question': question, - 'answer': answer, - }; -} diff --git a/lib/tools/anki/models/ankicard_model_helper.dart b/lib/tools/anki/models/ankicard_model_helper.dart deleted file mode 100644 index f8ac4a8..0000000 --- a/lib/tools/anki/models/ankicard_model_helper.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:academia/storage/storage.dart'; -import 'package:academia/exports/barrel.dart'; -import 'package:sqflite/sqflite.dart'; - -class AnkiCardModelHelper implements DatabaseOperations { - static final AnkiCardModelHelper _instance = AnkiCardModelHelper._internal(); - - factory AnkiCardModelHelper() { - return _instance; - } - - AnkiCardModelHelper._internal(); - - @override - Future create(Map data) async { - final db = await DatabaseHelper().database; - final id = await db.insert( - 'ankiCards', - data, - conflictAlgorithm: ConflictAlgorithm.replace, - ); - - debugPrint("[+] Anki Card Created Succcessfuly"); - return id; - } - - @override - Future>> queryAll() async { - final db = await DatabaseHelper().database; - final ankiCards = await db.query('ankiCards'); - return ankiCards; - } - - @override - Future delete(Map data) async { - final db = await DatabaseHelper().database; - return await db - .delete('ankiCards', where: 'id = ?', whereArgs: [data["id"]]); - } - - @override - Future update(Map data) async { - final db = await DatabaseHelper().database; - return await db - .update('ankiCards', data, where: 'id = ?', whereArgs: [data['id']]); - } - - Future>> queryAnkiCardsByTopic(int topicId) async { - final db = await DatabaseHelper().database; - return await db.query( - 'ankiCards', - where: 'topic_id = ?', - whereArgs: [topicId], - ); - } - - // make sure to call in the logout function - @override - Future truncate() async { - final db = await DatabaseHelper().database; - await db.execute('DELETE FROM ankiCards'); - } -} diff --git a/lib/tools/anki/models/models.dart b/lib/tools/anki/models/models.dart deleted file mode 100644 index 6e88551..0000000 --- a/lib/tools/anki/models/models.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'topic_model.dart'; -export 'topic_model_helper.dart'; -export 'ankicard_model.dart'; -export 'ankicard_model_helper.dart'; diff --git a/lib/tools/anki/models/topic_model.dart b/lib/tools/anki/models/topic_model.dart deleted file mode 100644 index df5a037..0000000 --- a/lib/tools/anki/models/topic_model.dart +++ /dev/null @@ -1,32 +0,0 @@ -// ankiTopic model - -class AnkiTopic { - int? id; - String name; - String desc; - bool isFavourite; - int numCards; - - AnkiTopic({ - this.id, - required this.name, - required this.desc, - this.isFavourite = false, - this.numCards = 0, - }); - - AnkiTopic.fromJson(Map json) - : id = json['id'], - name = json['name'], - desc = json['desc'], - isFavourite = json['is_favourite'] == 0 ? false : true, - numCards = json['num_cards']; - - Map toJson() => { - 'id': id, - 'name': name, - 'desc': desc, - 'is_favourite': isFavourite ? 1 : 0, - 'num_cards': numCards, - }; -} diff --git a/lib/tools/anki/models/topic_model_helper.dart b/lib/tools/anki/models/topic_model_helper.dart deleted file mode 100644 index 1698f3d..0000000 --- a/lib/tools/anki/models/topic_model_helper.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:academia/storage/storage.dart'; -import 'package:academia/exports/barrel.dart'; -import 'package:sqflite/sqflite.dart'; - -class TopicModelHelper implements DatabaseOperations { - static final TopicModelHelper _instance = TopicModelHelper._internal(); - - factory TopicModelHelper() { - return _instance; - } - - TopicModelHelper._internal(); - - @override - Future create(Map data) async { - final db = await DatabaseHelper().database; - final id = await db.insert( - 'ankiTopics', - data, - conflictAlgorithm: ConflictAlgorithm.replace, - ); - - debugPrint("[+] Anki Topic Created Succcessfuly"); - return id; - } - - @override - Future>> queryAll() async { - final db = await DatabaseHelper().database; - final todos = await db.query('ankiTopics'); - return todos; - } - - @override - Future delete(Map data) async { - final db = await DatabaseHelper().database; - return await db - .delete('ankiTopics', where: 'id = ?', whereArgs: [data["id"]]); - } - - @override - Future update(Map data) async { - final db = await DatabaseHelper().database; - return await db - .update('ankiTopics', data, where: 'id = ?', whereArgs: [data['id']]); - } - - Future>> getFavourites() async { - final db = await DatabaseHelper().database; - return await db.query( - 'ankiTopics', - where: 'is_favourite = ?', - whereArgs: [1], - ); - } - - // make sure to call in the logout function - @override - Future truncate() async { - final db = await DatabaseHelper().database; - await db.execute('DELETE FROM ankiTopics'); - } -} diff --git a/lib/tools/anki/pages/anki_home.dart b/lib/tools/anki/pages/anki_home.dart deleted file mode 100644 index 37254f4..0000000 --- a/lib/tools/anki/pages/anki_home.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/anki/controllers/controllers.dart'; -import 'package:get/get.dart'; -import './empty_anki_home_screen.dart'; -import './populated_anki_home_screen.dart'; -import './create_topic_form.dart'; - -class AnkiHomePage extends StatelessWidget { - const AnkiHomePage({super.key}); - - @override - Widget build(BuildContext context) { - // topic controller - final TopicController topicController = Get.put(TopicController()); - // putting ankicard controller - Get.put(AnkiCardController()); - - return Scaffold( - resizeToAvoidBottomInset: false, - body: CustomScrollView( - slivers: [ - SliverAppBar( - expandedHeight: 250, - backgroundColor: Theme.of(context).colorScheme.tertiaryContainer, - snap: true, - pinned: true, - floating: true, - flexibleSpace: const FlexibleSpaceBar( - title: Text("Anki"), - ), - actions: [ - IconButton( - onPressed: () { - Get.defaultDialog( - title: "Academia Help", - content: const Text( - "Mark a topic as your favorite for easy access by tapping the star icon.\nTap on the cards to view their corresponding Anki cards.", - ), - ); - }, - icon: const Icon(Ionicons.help), - ), - ], - ), - SliverFillRemaining( - child: Obx( - () => topicController.allTopics.isEmpty - ? const EmptyAnkiHomeScreen() - : const PopulatedAnkiHomeScreen(), - ), - ), - ], - ), - floatingActionButton: FloatingActionButton( - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const CreateTopicForm(), - ), - ); - }, - child: const Icon(Ionicons.add), - ), - ); - } -} diff --git a/lib/tools/anki/pages/anki_swap.dart b/lib/tools/anki/pages/anki_swap.dart deleted file mode 100644 index d5b5692..0000000 --- a/lib/tools/anki/pages/anki_swap.dart +++ /dev/null @@ -1,171 +0,0 @@ -import 'package:academia/tools/anki/models/ankicard_model.dart'; -import 'package:appinio_swiper/appinio_swiper.dart'; -import 'package:flutter/material.dart'; - -class AnkiSwap extends StatefulWidget { - const AnkiSwap({ - super.key, - required this.ankiCards, - }); - - final List ankiCards; - - @override - State createState() => _AnkiSwapState(); -} - -class _AnkiSwapState extends State { - // holds the mutable list - List cards = []; - bool showAnswer = false; - final AppinioSwiperController controller = AppinioSwiperController(); - - @override - void initState() { - cards.clear(); - cards.addAll(widget.ankiCards); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - automaticallyImplyLeading: true, - title: const Text( - "Academia Anki", - ), - centerTitle: true, - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text("Double Tap To Show Answer"), - Padding( - padding: const EdgeInsets.all(8.0), - child: SizedBox( - height: MediaQuery.of(context).size.height * 0.5, - child: AppinioSwiper( - backgroundCardOffset: Offset.zero, - backgroundCardCount: 0, - allowUnSwipe: false, - onSwipeBegin: (previousIndex, targetIndex, activity) { - setState(() { - showAnswer = false; - }); - if (activity.direction == AxisDirection.left) { - cards.insert(targetIndex + 2, cards[previousIndex]); - } else { - cards.add(cards[previousIndex]); - } - }, - maxAngle: 60, - swipeOptions: const SwipeOptions.symmetric(horizontal: true), - cardCount: cards.length, - cardBuilder: (context, index) { - var colorDet = index % 4; - return GestureDetector( - onDoubleTap: () { - setState(() { - showAnswer = true; - }); - }, - child: Container( - height: MediaQuery.of(context).size.height * 0.45, - decoration: BoxDecoration( - color: colorDet == 0 - ? Theme.of(context).colorScheme.primaryContainer - : colorDet == 1 - ? Theme.of(context) - .colorScheme - .secondaryContainer - : colorDet == 2 - ? Theme.of(context) - .colorScheme - .tertiaryContainer - : Theme.of(context) - .colorScheme - .errorContainer, - borderRadius: const BorderRadius.all( - Radius.circular(12), - ), - ), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - const Text( - "Question", - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - ), - ), - Text( - cards[index].question, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 18, - ), - ), - const Text( - "Answer", - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - ), - ), - showAnswer - ? Text( - cards[index].answer, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 18, - ), - ) - : const SizedBox(), - ], - ), - ), - ), - ); - }, - ), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Row( - children: [ - Image.asset( - "assets/images/left_arrow.png", - height: 70, - ), - const Padding( - padding: EdgeInsets.all(8.0), - child: Text("If Good"), - ), - ], - ), - Row( - children: [ - const Padding( - padding: EdgeInsets.all(8.0), - child: Text("If Bad"), - ), - Image.asset( - "assets/images/right_arrow.png", - height: 70, - ), - ], - ), - ], - ) - ], - ), - ), - ); - } -} diff --git a/lib/tools/anki/pages/create_ankicard.dart b/lib/tools/anki/pages/create_ankicard.dart deleted file mode 100644 index d6a3a56..0000000 --- a/lib/tools/anki/pages/create_ankicard.dart +++ /dev/null @@ -1,198 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/anki/controllers/controllers.dart'; -import 'package:academia/tools/anki/models/models.dart'; -import 'package:get/get.dart'; - -class CreateAnkicard extends StatelessWidget { - const CreateAnkicard({ - super.key, - required this.topicId, - }); - - final int topicId; - - @override - Widget build(BuildContext context) { - TextEditingController cardInfo = TextEditingController(); - TextEditingController cardAns = TextEditingController(); - AnswerCardController ansCardController = Get.put(AnswerCardController()); - AnkiCardController ankiCardController = Get.find(); - - return Scaffold( - appBar: AppBar( - title: const Text("Create Card"), - actions: [ - IconButton( - onPressed: () { - Get.defaultDialog( - title: "Academia Help", - content: const Text( - "To create an Anki card, you can either type a question and its answer, then tap \"Create Card\" to save it, or type a statement, highlight the portion you want to hide, tap outside the text box to preview the hidden answer, and then tap \"Create Card\" to save. Both options let you quickly generate flashcards for efficient studying.", - ), - ); - }, - icon: const Icon(Ionicons.help), - ), - ], - ), - body: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Obx( - () => TextField( - controller: cardInfo, - maxLines: 5, - decoration: InputDecoration( - hintText: ansCardController.ansSwitch.value - ? "Some question you want to remember" - : "A stetement to highlight a section that you want to remember", - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - onTapOutside: (event) { - ansCardController.ansCard.value = - cardInfo.selection.textInside(cardInfo.text); - cardAns.text = ansCardController.ansCard.value; - }, - ), - ), - ), - Obx( - () => Padding( - padding: const EdgeInsets.all(8.0), - child: ansCardController.ansSwitch.value - ? const Text( - "Selected Answer", - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ) - : const Text( - "Type Answer", - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - ), - ), - Obx( - () => !ansCardController.ansSwitch.value - // TextField for Showing Highlighted Answer - ? Padding( - padding: const EdgeInsets.all(8.0), - child: TextField( - enabled: false, - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - ), - hintText: ansCardController.ansCard.isEmpty - ? "Your Answer To be Hidden" - : ansCardController.ansCard.value, - ), - ), - ) - // TextField For Writing An Answer - : Padding( - padding: const EdgeInsets.all(8.0), - child: TextField( - controller: cardAns, - maxLines: 3, - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - ), - hintText: "Your Answer To be Hidden", - ), - ), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - OutlinedButton( - onPressed: () { - ansCardController.ansSwitch.value = - !ansCardController.ansSwitch.value; - // clearing the text fields - cardInfo.clear(); - cardAns.clear(); - ansCardController.ansCard.value = ""; - }, - child: Obx( - () => Text( - "Switch To ${!ansCardController.ansSwitch.value ? "Writing" : "Highlight"}", - ), - ), - ), - const SizedBox(width: 8), - FilledButton( - onPressed: () async { - // check for user inputs - if (cardInfo.text.trim().isEmpty || - cardAns.text.trim().isEmpty) { - // tell user that all fields are required - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text("All Fields Are Required"), - duration: Duration(seconds: 1), - ), - ); - } - // user chose to write the answer - else if (ansCardController.ansSwitch.value) { - AnkiCard ankiCard = AnkiCard( - topicId: topicId, - question: cardInfo.text.trim(), - answer: cardAns.text.trim()); - await ankiCardController.addAnkiCard(ankiCard); - // clearing the text fields - cardInfo.clear(); - cardAns.clear(); - ansCardController.ansCard.value = ""; - // reload the anki cards - await ankiCardController.getAllTopicCards(topicId); - // ignore: use_build_context_synchronously - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text("AnkiCard Created Successfully"), - duration: Duration(seconds: 1), - ), - ); - } else { - // remove highlighted answer from the question - String question = cardInfo.text.trim(); - question = question.replaceAll(cardAns.text.trim(), "_"); - AnkiCard ankiCard = AnkiCard( - topicId: topicId, - question: question, - answer: cardAns.text.trim()); - await ankiCardController.addAnkiCard(ankiCard); - // clearing the text fields - cardInfo.clear(); - cardAns.clear(); - ansCardController.ansCard.value = ""; - // reload the anki cards - await ankiCardController.getAllTopicCards(topicId); - - // ignore: use_build_context_synchronously - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text("AnkiCard Created Successfully"), - duration: Duration(seconds: 1), - ), - ); - } - }, - child: const Text("Create Card"), - ), - ], - ), - ], - ), - ); - } -} diff --git a/lib/tools/anki/pages/create_topic_form.dart b/lib/tools/anki/pages/create_topic_form.dart deleted file mode 100644 index 532066c..0000000 --- a/lib/tools/anki/pages/create_topic_form.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/anki/controllers/controllers.dart'; -import 'package:get/get.dart'; -import '../models/models.dart'; - -class CreateTopicForm extends StatelessWidget { - const CreateTopicForm({super.key}); - - @override - Widget build(BuildContext context) { - final TextEditingController titleController = TextEditingController(); - final TextEditingController descController = TextEditingController(); - final TopicController topicController = Get.find(); - - return Scaffold( - appBar: AppBar( - title: const Text( - "Create a topic", - ), - ), - body: SafeArea( - minimum: const EdgeInsets.symmetric(horizontal: 12), - child: SingleChildScrollView( - child: Column( - children: [ - TextFormField( - maxLength: 20, - controller: titleController, - decoration: InputDecoration( - hintText: "Write a description for your topic", - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - ), - ), - ), - const SizedBox(height: 8), - TextFormField( - controller: descController, - maxLength: 50, - maxLines: 10, - decoration: InputDecoration( - hintText: "Write a description for your topic", - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - ), - ), - ), - const SizedBox(height: 22), - FilledButton( - onPressed: () { - if (titleController.text.trim().isEmpty || - descController.text.trim().isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text("All Above Fields are required"), - duration: Duration(seconds: 1), - ), - ); - } else if (titleController.text.trim().length < 3) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - "Topic Length should be more than 2 characters"), - duration: Duration(seconds: 1), - ), - ); - } else if (descController.text.trim().length <= 12) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - "Desc Length should be more than 11 characters"), - duration: Duration(seconds: 1), - ), - ); - } else { - // create topic - AnkiTopic ankiTopic = AnkiTopic( - name: titleController.text, - desc: descController.text, - ); - // add to database - if (topicController.numTopics() <= 5) { - ankiTopic.isFavourite = true; - } - topicController.addTopic(ankiTopic); - - if (topicController.numTopics() < 5) { - // updating favourites - topicController.getAllFavourites(); - } - // updating topic list and favorites - topicController.getAllTopics(); - topicController.getAllFavourites(); - // informing user successful creation - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text("Topic successfully created!!"), - duration: Duration(seconds: 1), - ), - ); - Navigator.of(context).pop(); - } - }, - child: const Text("Create topic"), - ) - ], - ), - ), - ), - ); - } -} diff --git a/lib/tools/anki/pages/edit_anki_card.dart b/lib/tools/anki/pages/edit_anki_card.dart deleted file mode 100644 index 89ad3e5..0000000 --- a/lib/tools/anki/pages/edit_anki_card.dart +++ /dev/null @@ -1,234 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/anki/controllers/controllers.dart'; -import 'package:academia/tools/anki/models/ankicard_model.dart'; -import 'package:academia/tools/anki/widgets/widgets.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -class EditAnkiCard extends StatefulWidget { - const EditAnkiCard({ - super.key, - required this.ankiCard, - }); - - final AnkiCard ankiCard; - - @override - State createState() => _EditAnkiCardState(); -} - -class _EditAnkiCardState extends State { - bool editQuestion = false; - bool editAnswer = false; - // true when the user is highlighting answer otherwise false - bool switchToWriting = false; - bool showAnswer = false; - late TextEditingController questionController; - late TextEditingController ansController; - late AnkiCardController ankiCardController; - - @override - void initState() { - // intializing controllers - ankiCardController = Get.find(); - questionController = TextEditingController(); - ansController = TextEditingController(); - // setting the correct value for switchToWriting - if (widget.ankiCard.question.contains("_")) { - switchToWriting = true; - } - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text( - "Edit Anki Card", - style: Theme.of(context).textTheme.headlineMedium, - ), - automaticallyImplyLeading: true, - ), - body: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextButton( - onPressed: () { - setState(() { - switchToWriting = !switchToWriting; - // setting all enable edits to false - editAnswer = false; - editQuestion = false; - }); - }, - child: Text( - "Switch to ${switchToWriting ? "Writing" : "Highlighting"}", - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - "Question", - style: Theme.of(context).textTheme.titleMedium, - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - width: MediaQuery.of(context).size.width * 0.7, - child: TextField( - controller: questionController, - enabled: editQuestion, - maxLines: 5, - decoration: InputDecoration( - hintText: widget.ankiCard.question, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - onTapOutside: (event) { - // checks whether user has highlighted new answers - if (switchToWriting && - questionController.selection - .textInside(questionController.text) - .trim() - .isNotEmpty) { - setState(() { - // remove previous _ - widget.ankiCard.question = widget.ankiCard.question - .replaceAll("_", widget.ankiCard.answer); - // changing answer - widget.ankiCard.answer = questionController.selection - .textInside(questionController.text); - // changing question - widget.ankiCard.question = widget.ankiCard.question - .replaceAll(widget.ankiCard.answer, "_"); - }); - } - }, - ), - ), - TextButton( - onPressed: () { - setState(() { - editQuestion = true; - // setting the text - if (widget.ankiCard.question.contains("_")) { - // answer was highlited - String newQuiz = widget.ankiCard.question - .replaceAll("_", widget.ankiCard.answer); - questionController.text = newQuiz; - } else { - questionController.text = widget.ankiCard.question; - } - }); - }, - child: const Text("Edit")) - ], - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - "Answer", - style: Theme.of(context).textTheme.titleMedium, - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: !switchToWriting - ? Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - width: MediaQuery.of(context).size.width * 0.7, - child: TextField( - controller: ansController, - enabled: editAnswer, - decoration: InputDecoration( - hintText: widget.ankiCard.answer, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - onTapOutside: (event) { - // user has typed new answer - if (ansController.text.trim().isNotEmpty) { - setState(() { - widget.ankiCard.answer = ansController.text; - }); - } - }, - ), - ), - TextButton( - onPressed: () { - setState(() { - editAnswer = true; - }); - }, - child: const Text("Edit")) - ], - ) - : TextField( - enabled: false, - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - ), - hintText: widget.ankiCard.answer, - ), - ), - ), - Align( - alignment: Alignment.center, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ElevatedButton( - onPressed: () async { - // updating anki card - await ankiCardController.updateAnkiCard(widget.ankiCard); - await ankiCardController - .getAllTopicCards(widget.ankiCard.topicId); - if (!mounted) return; - // ignore: use_build_context_synchronously - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text("Card Successfully editted"), - duration: Duration(seconds: 1), - ), - ); - }, - child: const Text("Update"), - ), - ), - ), - ), - Align( - alignment: Alignment.center, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ElevatedButton( - onPressed: () { - showDialog( - context: context, - builder: (context) { - return PreviewAnkiCard(ankiCard: widget.ankiCard); - }, - ); - }, - child: const Text("Preview"), - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/tools/anki/pages/empty_anki_home_screen.dart b/lib/tools/anki/pages/empty_anki_home_screen.dart deleted file mode 100644 index 534a9a6..0000000 --- a/lib/tools/anki/pages/empty_anki_home_screen.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:flutter/material.dart'; -import 'package:lottie/lottie.dart'; - -class EmptyAnkiHomeScreen extends StatelessWidget { - const EmptyAnkiHomeScreen({super.key}); - - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Lottie.asset( - "assets/lotties/study.json", - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - "Your anki space is a clean slate! 📝 Create your first topic and let Academia Anki help you ace those grades!", - style: Theme.of(context).textTheme.headlineSmall, - textAlign: TextAlign.center, - ), - ), - ], - ), - ); - } -} diff --git a/lib/tools/anki/pages/flashcards.dart b/lib/tools/anki/pages/flashcards.dart deleted file mode 100644 index e551216..0000000 --- a/lib/tools/anki/pages/flashcards.dart +++ /dev/null @@ -1,153 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/anki/pages/anki_swap.dart'; -import 'package:academia/tools/anki/pages/create_ankicard.dart'; -import 'package:academia/tools/anki/widgets/widgets.dart'; -import 'package:academia/tools/anki/controllers/controllers.dart'; -import 'package:get/get.dart'; -import 'package:lottie/lottie.dart'; - -class TopicFlashCards extends StatelessWidget { - const TopicFlashCards({ - super.key, - required this.topicId, - }); - - final int topicId; - - @override - Widget build(BuildContext context) { - // finding AnkiCardController - AnkiCardController ankiCardController = Get.find(); - - return Scaffold( - resizeToAvoidBottomInset: false, - appBar: AppBar( - title: const Text("Topic flash cards"), - actions: [ - IconButton( - onPressed: () { - Get.defaultDialog( - title: "Academia Help", - content: const Text( - "Double Tap On AnkiCard To Delete\nTap On AnkiCard To Preview", - ), - ); - }, - icon: const Icon(Ionicons.help), - ), - ], - ), - floatingActionButton: SizedBox( - height: MediaQuery.of(context).size.height * 0.18, - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - // add card button - FloatingActionButton( - heroTag: "btn1", - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => CreateAnkicard( - topicId: topicId, - ), - ), - ); - }, - child: const Icon(Icons.add), - ), - const Spacer(), - // play cards button - FloatingActionButton( - heroTag: "btn2", - onPressed: () { - if (ankiCardController.allCards.length >= 5) { - // opens the anki swap page - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - AnkiSwap(ankiCards: ankiCardController.allCards), - ), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: - Text("Anki Cards Should be More than 4, To Play"), - duration: Duration(seconds: 1), - ), - ); - } - }, - child: const Icon(Icons.play_arrow), - ), - ], - ), - ), - body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Obx( - () => ankiCardController.allCards.isEmpty - ? Align( - alignment: Alignment.center, - child: SizedBox( - height: MediaQuery.of(context).size.height * 0.64, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - height: MediaQuery.of(context).size.height * 0.4, - child: Lottie.asset( - "assets/lotties/study.json", - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - "Your card deck is blank! 🎴", - style: Theme.of(context).textTheme.titleSmall, - textAlign: TextAlign.center, - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - "Add your first card to start", - style: Theme.of(context).textTheme.titleSmall, - textAlign: TextAlign.center, - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - "mastering your topics!", - style: Theme.of(context).textTheme.titleSmall, - textAlign: TextAlign.center, - ), - ), - ], - ), - ), - ) - : SizedBox( - height: MediaQuery.of(context).size.height * 0.64, - child: ListView.builder( - itemBuilder: (context, idx) { - return FlashCardTile( - ankiCard: ankiCardController.allCards[idx], - ); - }, - itemCount: ankiCardController.allCards.length, - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/tools/anki/pages/populated_anki_home_screen.dart b/lib/tools/anki/pages/populated_anki_home_screen.dart deleted file mode 100644 index 4601b9d..0000000 --- a/lib/tools/anki/pages/populated_anki_home_screen.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:academia/tools/anki/controllers/controllers.dart'; -import 'package:academia/tools/anki/widgets/starred_topic.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import '../widgets/grid_topics.dart'; - -class PopulatedAnkiHomeScreen extends StatelessWidget { - const PopulatedAnkiHomeScreen({super.key}); - - @override - Widget build(BuildContext context) { - final TopicController topicController = Get.find(); - - return SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - // displays favourite topics - Obx( - () => Visibility( - visible: topicController.allFavourites.isNotEmpty, - child: Align( - alignment: Alignment.center, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 8), - height: MediaQuery.of(context).size.height * 0.27, - width: MediaQuery.of(context).size.width * 0.87, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemBuilder: (context, idx) { - return StarredTopics( - topicId: topicController.allFavourites[idx].id!, - topic: topicController.allFavourites[idx].name, - desc: topicController.allFavourites[idx].desc, - ); - }, - itemCount: topicController.allFavourites.length, - ), - ), - ), - ), - ), - // displays topicics - Container( - height: MediaQuery.of(context).size.height * - (topicController.allFavourites.isNotEmpty ? 0.6 : 1), - padding: const EdgeInsets.all(12), - child: Obx( - () => GridView.builder( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: 10, - mainAxisSpacing: 10, - ), - itemBuilder: (context, idx) { - return GridViewTopic( - idx: idx, - topicId: topicController.allTopics[idx].id!, - topic: topicController.allTopics[idx].name, - topicDesc: topicController.allTopics[idx].desc, - isFavourite: topicController.allTopics[idx].isFavourite, - ); - }, - itemCount: topicController.allTopics.length, - scrollDirection: Axis.vertical, - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/tools/anki/widgets/eclipses.dart b/lib/tools/anki/widgets/eclipses.dart deleted file mode 100644 index ac86929..0000000 --- a/lib/tools/anki/widgets/eclipses.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'package:academia/exports/barrel.dart'; - -Widget blackcircle = Padding( - padding: const EdgeInsets.all(1.0), - child: Container( - width: 15, - height: 15, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: Color(0xff444c55), - ), - ), -); - -class CustomEclipse extends StatelessWidget { - const CustomEclipse({super.key}); - - @override - Widget build(BuildContext context) { - return SizedBox( - width: MediaQuery.of(context).size.width * 0.2, - height: MediaQuery.of(context).size.height * 0.05, - child: Row( - children: [ - blackcircle, - blackcircle, - blackcircle, - ], - ), - ); - } -} - -class CustomMixEclipse extends StatelessWidget { - const CustomMixEclipse({super.key}); - - @override - Widget build(BuildContext context) { - return SizedBox( - width: MediaQuery.of(context).size.width * 0.2, - height: MediaQuery.of(context).size.height * 0.05, - child: Row( - children: [ - Padding( - padding: const EdgeInsets.all(1.0), - child: Container( - width: 15, - height: 15, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Theme.of(context).colorScheme.secondaryContainer, - ), - ), - ), - Padding( - padding: const EdgeInsets.all(1.0), - child: Container( - width: 15, - height: 15, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Theme.of(context).colorScheme.tertiaryContainer, - ), - ), - ), - Padding( - padding: const EdgeInsets.all(1.0), - child: Container( - width: 15, - height: 15, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Theme.of(context).colorScheme.primaryContainer, - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/tools/anki/widgets/grid_topics.dart b/lib/tools/anki/widgets/grid_topics.dart deleted file mode 100644 index f2bff40..0000000 --- a/lib/tools/anki/widgets/grid_topics.dart +++ /dev/null @@ -1,157 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/anki/controllers/controllers.dart'; -import 'package:academia/tools/anki/models/models.dart'; -import 'package:academia/tools/anki/pages/flashcards.dart'; -import 'package:get/get.dart'; - -class GridViewTopic extends StatelessWidget { - const GridViewTopic({ - super.key, - required this.idx, - required this.topicId, - required this.topic, - required this.topicDesc, - required this.isFavourite, - }); - - final int idx; - final int topicId; - final String topic; - final String topicDesc; - final bool isFavourite; - - @override - Widget build(BuildContext context) { - var colorDet = idx % 4; - final TopicController controller = Get.find(); - final AnkiCardController ankiCardController = - Get.find(); - - return GestureDetector( - onTap: () async { - // getting all topic cards before opening flashcard page - await ankiCardController.getAllTopicCards(topicId); - Navigator.push( - // ignore: use_build_context_synchronously - context, - MaterialPageRoute( - builder: (builder) => TopicFlashCards( - topicId: topicId, - ), - ), - ); - }, - child: Container( - decoration: BoxDecoration( - color: colorDet == 0 - ? Theme.of(context).colorScheme.primaryContainer - : colorDet == 1 - ? Theme.of(context).colorScheme.secondaryContainer - : colorDet == 2 - ? Theme.of(context).colorScheme.tertiaryContainer - : Theme.of(context).colorScheme.errorContainer, - borderRadius: const BorderRadius.all( - Radius.circular(8), - ), - ), - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - topic, - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 4), - Text( - topicDesc, - ), - const Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - // favourite a topic - IconButton( - onPressed: () async { - // create a topic object - AnkiTopic topic = AnkiTopic( - id: topicId, - name: this.topic, - desc: topicDesc, - isFavourite: isFavourite, - ); - await controller.favouriteTopic(topic); - // update favourites and all topics - await controller.getAllFavourites(); - await controller.getAllTopics(); - // ignore: use_build_context_synchronously - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: !isFavourite - ? const Text("Topic Successfully Favourited") - : const Text("Topic Successfully Unfavourited"), - duration: const Duration(seconds: 1), - ), - ); - }, - icon: Icon( - isFavourite ? Icons.star : Icons.star_border_outlined, - ), - ), - // delete for topics - IconButton( - onPressed: () => showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Confirmation"), - content: const Text( - "Are you sure you want to delete topic?", - ), - actions: [ - OutlinedButton( - onPressed: () async { - AnkiTopic topic = AnkiTopic( - id: topicId, - name: this.topic, - desc: topicDesc, - ); - bool deleted = await controller.deleteTopic(topic); - // ignore: use_build_context_synchronously - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: (deleted) - ? const Text("Topic Successfully Deleted") - : const Text( - "Something happened! Kindly Retry!!"), - duration: const Duration(seconds: 1), - ), - ); - // update favourites and all topics - await controller.getAllFavourites(); - await controller.getAllTopics(); - // ignore: use_build_context_synchronously - Navigator.of(context).pop(); - }, - child: const Text("Yes"), - ), - FilledButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text("No"), - ), - ], - ), - ), - icon: const Icon( - Icons.delete, - ), - ), - ], - ), - ], - ), - ), - ); - } -} diff --git a/lib/tools/anki/widgets/preview_card.dart b/lib/tools/anki/widgets/preview_card.dart deleted file mode 100644 index bda4db3..0000000 --- a/lib/tools/anki/widgets/preview_card.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/anki/models/models.dart'; - -class PreviewAnkiCard extends StatefulWidget { - const PreviewAnkiCard({ - super.key, - required this.ankiCard, - }); - - final AnkiCard ankiCard; - - @override - State createState() => _PreviewAnkiCardState(); -} - -class _PreviewAnkiCardState extends State { - bool showAnswer = false; - @override - Widget build(BuildContext context) { - return AlertDialog( - title: const Text( - "Anki Card Preview", - ), - content: IntrinsicHeight( - child: Column( - children: [ - const Padding( - padding: EdgeInsets.all(8.0), - child: Text( - "Question", - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - widget.ankiCard.question, - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextButton( - onPressed: () { - setState(() { - showAnswer = !showAnswer; - }); - }, - child: showAnswer - ? const Text( - "hide answer", - ) - : const Text( - "show answer", - ), - ), - ), - showAnswer - ? Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - widget.ankiCard.answer, - ), - ) - : const SizedBox(), - ], - ), - ), - ); - } -} diff --git a/lib/tools/anki/widgets/starred_topic.dart b/lib/tools/anki/widgets/starred_topic.dart deleted file mode 100644 index 5a848b5..0000000 --- a/lib/tools/anki/widgets/starred_topic.dart +++ /dev/null @@ -1,181 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/anki/controllers/controllers.dart'; -import 'package:academia/tools/anki/pages/flashcards.dart'; -import 'package:academia/tools/anki/widgets/widgets.dart'; -import 'package:get/get.dart'; -import '../models/models.dart'; - -class StarredTopics extends StatelessWidget { - const StarredTopics({ - super.key, - required this.topicId, - required this.topic, - required this.desc, - }); - - final int topicId; - final String topic; - final String desc; - - @override - Widget build(BuildContext context) { - TopicController topicController = Get.find(); - - return GestureDetector( - onTap: () => Navigator.push( - context, - MaterialPageRoute( - builder: (builder) => TopicFlashCards( - topicId: topicId, - ), - ), - ), - child: SizedBox( - height: MediaQuery.of(context).size.height * 0.27, - width: MediaQuery.of(context).size.width * 0.87, - child: Stack( - clipBehavior: Clip.antiAlias, - children: [ - Positioned( - top: 9, - left: 3, - right: 4, - child: Container( - height: MediaQuery.of(context).size.height * 0.267, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.tertiaryContainer, - borderRadius: const BorderRadius.all( - Radius.circular(16), - ), - ), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Topic", - style: Theme.of(context).textTheme.titleMedium, - ), - Text(topic), - Text( - "Description", - style: Theme.of(context).textTheme.titleMedium, - ), - Text(desc), - ], - ), - ), - ), - ), - Positioned( - top: 9, - right: 5, - left: 150, - child: Container( - height: 95, - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: Theme.of(context).colorScheme.primaryContainer, - width: 10, - style: BorderStyle.solid, - ), - left: BorderSide( - color: Theme.of(context).colorScheme.primaryContainer, - width: 10, - style: BorderStyle.solid, - ), - ), - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(500), - ), - ), - ), - ), - Positioned( - bottom: 19, - right: 0, - child: Row( - children: [ - // unfavourite a topic from favourited topics - IconButton( - onPressed: () async { - AnkiTopic topic = AnkiTopic( - id: topicId, - name: this.topic, - desc: desc, - isFavourite: true, - ); - await topicController.favouriteTopic(topic); - // update favourites and all topics - await topicController.getAllFavourites(); - await topicController.getAllTopics(); - }, - icon: const Icon(Ionicons.star), - ), - // delete for topics - IconButton( - onPressed: () => showDialog( - context: context, - builder: (context) => AlertDialog( - content: const Text( - "Are You Sure You Want To Delete Topic?", - ), - actions: [ - OutlinedButton( - onPressed: () async { - AnkiTopic topic = AnkiTopic( - id: topicId, - name: this.topic, - desc: desc, - ); - bool? deleted = - await topicController.deleteTopic(topic); - - HapticFeedback.heavyImpact().then((val) { - if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: (deleted) - ? const Text( - "Topic Successfully Deleted") - : const Text( - "Damn! We crashed please try again!"), - ), - ); - }); - // update favourites and all topics - await topicController.getAllFavourites(); - await topicController.getAllTopics(); - // ignore: use_build_context_synchronously - Navigator.of(context).pop(); - }, - child: const Text("Yes, delete it"), - ), - FilledButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text("No"), - ), - ], - ), - ), - icon: const Icon(Ionicons.trash), - ) - ], - ), - ), - const Positioned( - top: 10, - left: 16, - child: CustomEclipse(), - ), - ], - ), - ), - ); - } -} diff --git a/lib/tools/anki/widgets/topic_flashcard.dart b/lib/tools/anki/widgets/topic_flashcard.dart deleted file mode 100644 index 0857889..0000000 --- a/lib/tools/anki/widgets/topic_flashcard.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'package:academia/tools/anki/controllers/controllers.dart'; -import 'package:academia/tools/anki/models/ankicard_model.dart'; -import 'package:academia/tools/anki/pages/edit_anki_card.dart'; -import 'package:academia/tools/anki/widgets/eclipses.dart'; -import 'package:academia/tools/anki/widgets/preview_card.dart'; -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; - -class FlashCardTile extends StatelessWidget { - const FlashCardTile({ - super.key, - required this.ankiCard, - }); - - final AnkiCard ankiCard; - - @override - Widget build(BuildContext context) { - AnkiCardController ankiCardController = Get.find(); - - return Padding( - padding: const EdgeInsets.all(8.0), - child: GestureDetector( - onTap: () { - showDialog( - context: context, - builder: (context) { - return PreviewAnkiCard(ankiCard: ankiCard); - }, - ); - }, - onDoubleTap: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - content: const Text( - "Are You Sure You Want To Delete AnkiCard?", - ), - actions: [ - TextButton( - onPressed: () async { - bool? deleted = - await ankiCardController.deleteCard(ankiCard); - // ignore: use_build_context_synchronously - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: deleted - ? const Text("AnkiCard Successfully Deleted") - : const Text("Something happened! Kindly Retry!!"), - duration: const Duration(seconds: 1), - ), - ); - // update favourites and all topics - await ankiCardController.getAllTopicCards(ankiCard.topicId); - // ignore: use_build_context_synchronously - Navigator.of(context).pop(); - }, - child: const Text("Yes"), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text("No"), - ), - ], - ), - ); - }, - child: Container( - decoration: BoxDecoration( - border: Border.all( - color: const Color(0xff444c55), - ), - borderRadius: const BorderRadius.all( - Radius.circular(12), - ), - ), - child: ListTile( - leading: const CustomMixEclipse(), - title: ankiCard.question.length > 31 - ? Text( - "${ankiCard.question.substring(0, 29)}...", - ) - : Text("${ankiCard.question}..."), - dense: false, - trailing: IconButton( - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (builder) => EditAnkiCard( - ankiCard: ankiCard, - ), - ), - ), - icon: const Icon( - Icons.edit_note, - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/tools/anki/widgets/widgets.dart b/lib/tools/anki/widgets/widgets.dart deleted file mode 100644 index 66165af..0000000 --- a/lib/tools/anki/widgets/widgets.dart +++ /dev/null @@ -1,5 +0,0 @@ -export 'grid_topics.dart'; -export 'starred_topic.dart'; -export 'eclipses.dart'; -export 'topic_flashcard.dart'; -export 'preview_card.dart'; diff --git a/lib/tools/ask_me/ask_me.dart b/lib/tools/ask_me/ask_me.dart deleted file mode 100644 index 2d9f976..0000000 --- a/lib/tools/ask_me/ask_me.dart +++ /dev/null @@ -1 +0,0 @@ -export 'pages/pages.dart'; \ No newline at end of file diff --git a/lib/tools/ask_me/controllers/ask_me_controller.dart b/lib/tools/ask_me/controllers/ask_me_controller.dart deleted file mode 100644 index 6e79fa0..0000000 --- a/lib/tools/ask_me/controllers/ask_me_controller.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/ask_me/models/services/askme_service.dart.dart'; -import 'package:dartz/dartz.dart'; -import 'package:get/get.dart'; -import 'controllers.dart'; - -class AskMeController extends GetxController { - final AskMeService _service = AskMeService(); - UserController userController = Get.find(); - QuizSettingsController quizSettingsController = - Get.find(); - // final userId = 'MQ4'; - /* - For testing purposes as of now, I would recommend you to uncomment the above - while comment the userId gotten from the userController since there is an issue - at the moment on the backend since the backend does not expect the current number - of characters for the user id which is 32 which is a really large number. - This the error I am getting: - Error: {"user_id":["Ensure this field has no more than 12 characters."]} - The above issue is currently being fixed though. - */ - - Future> fetchQuestions() async { - final result = await _service.fetchQuestions( - userController.user.value?.id?.substring(0, 12), - // userId, - quizSettingsController.fileTitle.value, - quizSettingsController.filePath.value, - quizSettingsController.multipleChoice.value); - - return result.fold((l) { - return left(l); - }, (r) { - return right(r); - }); - } -} diff --git a/lib/tools/ask_me/controllers/controllers.dart b/lib/tools/ask_me/controllers/controllers.dart deleted file mode 100644 index 3996f76..0000000 --- a/lib/tools/ask_me/controllers/controllers.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'files_and_scores_controller.dart'; -export 'quiz_settings_controller.dart'; -export 'ask_me_controller.dart'; \ No newline at end of file diff --git a/lib/tools/ask_me/controllers/files_and_scores_controller.dart b/lib/tools/ask_me/controllers/files_and_scores_controller.dart deleted file mode 100644 index 40bf870..0000000 --- a/lib/tools/ask_me/controllers/files_and_scores_controller.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:get/get.dart'; -import '../models/models.dart'; - -class FilesAndScoresController extends GetxController { - RxList files = [].obs; - RxList scores = [].obs; - - @override - void onInit() { - super.onInit(); - loadFilesAndScores(); - } - - Future loadFilesAndScores() async { - // Fetch files - FilesModelHelper().queryAll().then((value) { - files.value = value.map((e) => AskMeFiles.fromJson(e)).toList(); - }); - - //Fetch all scores - ScoresModelHelper().queryAll().then((value) { - scores.value = value.map((e) => AskMeScores.fromJson(e)).toList(); - }); - } - - // Fetch scores by file ID - // Future fetchScoresByFileId(int fileId) async { - // final scoreList = await ScoresModelHelper().queryScoresByFileId(fileId); - // scores.value = scoreList.map((score) => AskMeScores.fromJson(score)).toList(); - // } - - //Adds Scores - Future addScores(AskMeScores score) async { - final id = await ScoresModelHelper().create(score.toJson()); - score.id = id; - scores.add(score); - - // Reload files and scores - await loadFilesAndScores(); - return id == 0 ? false : true; - } - - Future addFile(AskMeFiles file) async { - final id = await FilesModelHelper().create(file.toJson()); - file.id = id; - files.add(file); - await loadFilesAndScores(); - return id == 0 ? false : true; - } - //Update an existing file - Future updateFile(AskMeFiles file) async { - await FilesModelHelper().update(file.toJson()); - files[files.indexWhere((f) => f.id == file.id)] = file; - - await loadFilesAndScores(); - return file; - } - - // Delete a file and its associated scores - Future deleteFile(AskMeFiles file) async { - files.removeWhere((value) => file.id == value.id); - int value = await FilesModelHelper().delete(file.toJson()); - // files.remove(file); - - await loadFilesAndScores(); - return value == 0 ? false : true; - } - -} - diff --git a/lib/tools/ask_me/controllers/quiz_settings_controller.dart b/lib/tools/ask_me/controllers/quiz_settings_controller.dart deleted file mode 100644 index dbf8a7a..0000000 --- a/lib/tools/ask_me/controllers/quiz_settings_controller.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:get/get.dart'; -class QuizSettingsController extends GetxController { - var fileTitle = ''.obs; - var filePath = ''.obs; - var questionType = 'Multiple choice'.obs; - var multipleChoice = true.obs; - var minute = 2.obs; - var seconds = 30.obs; - - void setQuestionType(String type, bool choice) { - questionType.value = type; - multipleChoice.value = choice; - } - - void setFileTitle(String title) { - fileTitle.value = title; - } - void setFilePath(String path) { - filePath.value = path; - } - - void setTimer(int min, sec) { - minute.value = min; - seconds.value = sec; - } -} \ No newline at end of file diff --git a/lib/tools/ask_me/models/core/askme_files.dart b/lib/tools/ask_me/models/core/askme_files.dart deleted file mode 100644 index b530e03..0000000 --- a/lib/tools/ask_me/models/core/askme_files.dart +++ /dev/null @@ -1,31 +0,0 @@ -class AskMeFiles { - int? id; - final String title; - final String filePath; - int avgScore; - - AskMeFiles({ - this.id, - required this.title, - required this.filePath, - required this.avgScore, - }); - - factory AskMeFiles.fromJson(Map json) { - return AskMeFiles( - id: json["id"], - title: json["title"], - filePath: json['filePath'], - avgScore: json['avgScore'], - ); - } - - Map toJson() { - return { - 'id': id, - 'title': title, - 'filePath': filePath, - 'avgScore': avgScore, - }; - } -} diff --git a/lib/tools/ask_me/models/core/askme_scores.dart b/lib/tools/ask_me/models/core/askme_scores.dart deleted file mode 100644 index 2ed4378..0000000 --- a/lib/tools/ask_me/models/core/askme_scores.dart +++ /dev/null @@ -1,28 +0,0 @@ -class AskMeScores { - int? id; - final int score; - final int filesId; - - AskMeScores({ - this.id, - required this.score, - required this.filesId, - }); - - - factory AskMeScores.fromJson(Map json) { - return AskMeScores( - id: json['id'], - score: json['score'], - filesId: json['filesId'], - ); - } - - Map toJson() { - return { - 'id': id, - 'score': score, - 'filesId': filesId, - }; - } -} diff --git a/lib/tools/ask_me/models/core/files_model_helper.dart b/lib/tools/ask_me/models/core/files_model_helper.dart deleted file mode 100644 index 0683109..0000000 --- a/lib/tools/ask_me/models/core/files_model_helper.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:academia/storage/storage.dart'; -import 'package:sqflite/sqflite.dart'; -import 'package:flutter/material.dart'; - -class FilesModelHelper implements DatabaseOperations { - static final FilesModelHelper _instance = - FilesModelHelper._internal(); - - factory FilesModelHelper() { - return _instance; - } - - FilesModelHelper._internal(); - - @override - Future create(Map data) async { - final db = await DatabaseHelper().database; - final id = await db.insert( - 'askme_files', - data, - conflictAlgorithm: ConflictAlgorithm.replace, - ); - - debugPrint("[+] Files written successfully"); - return id; - } - - @override - Future>> queryAll() async { - final db = await DatabaseHelper().database; - final files = await db.query('askme_files'); - return files; - } - - @override - Future delete(Map data) async { - final db = await DatabaseHelper().database; - return await db - .delete('askme_files', where: 'id =?', whereArgs: [data["id"]]); - } - - @override - Future update(Map data) async { - final db = await DatabaseHelper().database; - return await db - .update('askme_files', data, where: 'id =?', whereArgs: [data['id']]); - } - - @override - Future truncate() async { - final db = await DatabaseHelper().database; - await db.execute('DELETE FROM askme_files'); - } -} diff --git a/lib/tools/ask_me/models/core/multiple_choice.dart b/lib/tools/ask_me/models/core/multiple_choice.dart deleted file mode 100644 index 640c579..0000000 --- a/lib/tools/ask_me/models/core/multiple_choice.dart +++ /dev/null @@ -1,28 +0,0 @@ -class MultipleChoiceQuestion{ - String question; - List choices; - String correctAnswer; - - MultipleChoiceQuestion({ - required this.question, - required this.choices, - required this.correctAnswer, - }); - - factory MultipleChoiceQuestion.fromJson(Map json) { - return MultipleChoiceQuestion( - question: json['question'], - choices: List.from(json['multiple_choice']), - correctAnswer: json['correct_answer'], - ); - } - - Map toJson() { - return { - 'question': question, - 'multiple_choice': choices, - 'correct_answer': correctAnswer, - }; - } -} - diff --git a/lib/tools/ask_me/models/core/multiple_choice_quiz.dart b/lib/tools/ask_me/models/core/multiple_choice_quiz.dart deleted file mode 100644 index 5ed24da..0000000 --- a/lib/tools/ask_me/models/core/multiple_choice_quiz.dart +++ /dev/null @@ -1,21 +0,0 @@ -import '../models.dart'; - -class MultipleChoiceQuiz { - List questions; - - MultipleChoiceQuiz({required this.questions}); - - factory MultipleChoiceQuiz.fromJson(Map json) { - return MultipleChoiceQuiz( - questions: List.from( - json['questions'].map((questionJson) => MultipleChoiceQuestion.fromJson(questionJson)), - ), - ); - } - - Map toJson() { - return { - 'questions': questions.map((question) => question.toJson()).toList(), - }; - } -} \ No newline at end of file diff --git a/lib/tools/ask_me/models/core/question.dart b/lib/tools/ask_me/models/core/question.dart deleted file mode 100644 index 15af9a0..0000000 --- a/lib/tools/ask_me/models/core/question.dart +++ /dev/null @@ -1,26 +0,0 @@ -// class Question { -// final String question; -// final String answer; - -// const Question({required this.question, required this.answer}); - -// factory Question.fromJson(Map json) { -// return Question( -// question: json['question'], -// answer: json['answer'], -// ); -// } - -// Map toJson() { -// return { -// 'question': question, -// 'answer': answer -// }; -// } -// } - - - - - - diff --git a/lib/tools/ask_me/models/core/quiz.dart b/lib/tools/ask_me/models/core/quiz.dart deleted file mode 100644 index 5521c3a..0000000 --- a/lib/tools/ask_me/models/core/quiz.dart +++ /dev/null @@ -1,23 +0,0 @@ -// import '../models.dart'; - -// class Quiz { -// final List questions; - -// Quiz({required this.questions}); - -// // Factory method to create a Quiz from JSON -// factory Quiz.fromJson(Map json) { -// var questionsJson = json['questions'] as List; -// List questionList = questionsJson.map((q) => Question.fromJson(q)).toList(); - -// return Quiz(questions: questionList); -// } - -// // Convert the Quiz object to JSON -// Map toJson() { -// return { -// 'questions': questions.map((q) => q.toJson()).toList(), -// }; -// } -// } - diff --git a/lib/tools/ask_me/models/core/scores_model_helper.dart b/lib/tools/ask_me/models/core/scores_model_helper.dart deleted file mode 100644 index cf7dcf1..0000000 --- a/lib/tools/ask_me/models/core/scores_model_helper.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:academia/storage/storage.dart'; -import 'package:sqflite/sqflite.dart'; -import 'package:flutter/material.dart'; - -class ScoresModelHelper implements DatabaseOperations { - static final ScoresModelHelper _instance = - ScoresModelHelper._internal(); - - factory ScoresModelHelper() { - return _instance; - } - - ScoresModelHelper._internal(); - - @override - Future create(Map data) async { - final db = await DatabaseHelper().database; - final id = await db.insert( - 'askme_scores', - data, - conflictAlgorithm: ConflictAlgorithm.replace, - ); - - debugPrint("[+] Scores written successfully"); - return id; - } - - Future>> queryScoresByFileId(int fileId) async { - final db = await DatabaseHelper().database; - return await db.query('askme_scores', where: 'filesId = ?', whereArgs: [fileId]); - } - - @override - Future>> queryAll() async { - final db = await DatabaseHelper().database; - final scores = await db.query('askme_scores'); - return scores; - } - - @override - Future delete(Map data) async { - final db = await DatabaseHelper().database; - return await db - .delete('askme_scores', where: 'id =?', whereArgs: [data["id"]]); - } - - @override - Future update(Map data) async { - final db = await DatabaseHelper().database; - return await db - .update('askme_scores', data, where: 'id =?', whereArgs: [data['id']]); - } - - @override - Future truncate() async { - final db = await DatabaseHelper().database; - await db.execute('DELETE FROM askme_scores'); - } -} diff --git a/lib/tools/ask_me/models/core/true_false.dart b/lib/tools/ask_me/models/core/true_false.dart deleted file mode 100644 index 214911f..0000000 --- a/lib/tools/ask_me/models/core/true_false.dart +++ /dev/null @@ -1,27 +0,0 @@ -class TrueFalseQuestion { - String question; - List choices; - String answer; - - TrueFalseQuestion({ - required this.question, - this.choices = const ['True', 'False'], - required this.answer, - }); - - factory TrueFalseQuestion.fromJson(Map json) { - final question = json[json.keys.first]; - final answer = json[json.keys.toList()[1]]; - return TrueFalseQuestion( - question: question, - answer: answer, - ); - } - - Map toJson() { - return { - 'question': question, - 'answer': answer, - }; - } -} diff --git a/lib/tools/ask_me/models/core/true_or_false_quiz.dart b/lib/tools/ask_me/models/core/true_or_false_quiz.dart deleted file mode 100644 index b81e017..0000000 --- a/lib/tools/ask_me/models/core/true_or_false_quiz.dart +++ /dev/null @@ -1,23 +0,0 @@ -import '../models.dart'; - -class TrueFalseQuiz { - List questions; - - TrueFalseQuiz({required this.questions}); - - factory TrueFalseQuiz.fromJson(Map json) { - return TrueFalseQuiz( - questions: List.from( - json['questions'].map((questionJson) { - return TrueFalseQuestion.fromJson(questionJson); - }), - ), - ); - } - - Map toJson() { - return { - 'questions': questions.map((question) => question.toJson()).toList(), - }; - } -} diff --git a/lib/tools/ask_me/models/models.dart b/lib/tools/ask_me/models/models.dart deleted file mode 100644 index 3e928a5..0000000 --- a/lib/tools/ask_me/models/models.dart +++ /dev/null @@ -1,11 +0,0 @@ -export 'core/question.dart'; -export 'core/quiz.dart'; -export 'core/askme_files.dart'; -export 'core/files_model_helper.dart'; -export 'core/askme_scores.dart'; -export 'core/scores_model_helper.dart'; -export 'core/multiple_choice.dart'; -export 'core/true_false.dart'; -export 'core/multiple_choice_quiz.dart'; -export 'core/true_or_false_quiz.dart'; -export 'services/askme_service.dart.dart'; \ No newline at end of file diff --git a/lib/tools/ask_me/models/services/askme_service.dart.dart b/lib/tools/ask_me/models/services/askme_service.dart.dart deleted file mode 100644 index 5959014..0000000 --- a/lib/tools/ask_me/models/services/askme_service.dart.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:dartz/dartz.dart'; -import 'package:get/get.dart'; -import 'package:http/http.dart' as http; -import 'package:path/path.dart'; -import '../models.dart'; - -class AskMeService { - static const String askMeUrlPrefix = "http://62.169.16.219:83"; - - final UserController userController = Get.find(); - - Future> fetchQuestions( - String? userId, - String title, - String filePath, - bool multipleChoice, - ) async { - debugPrint("User Id: $userId"); - try { - // Preparing the request using multipart/form-data - var request = http.MultipartRequest( - 'POST', - Uri.parse("${AskMeService.askMeUrlPrefix}/api/upload/"), - ); - - // Adding fields to the request - request.fields['user_id'] = userId!; - request.fields['title'] = title; - request.fields['multi_choice'] = multipleChoice.toString(); - - // Attach the file - File file = File(filePath); - request.files.add( - await http.MultipartFile.fromPath( - 'pdf_file', - file.path, - filename: basename(file.path), - ), - ); - - // Send the request - var response = await request.send(); - - // Handle the response - if (response.statusCode == 201) { - debugPrint("${response.statusCode}: Request Successful"); - var responseData = await http.Response.fromStream(response); - var jsonData = jsonDecode(responseData.body); - - // Log the received JSON response for debugging - debugPrint("Response JSON: $jsonData"); - - if (multipleChoice) { - return right(MultipleChoiceQuiz.fromJson(jsonData)); - } else { - return right(TrueFalseQuiz.fromJson(jsonData)); - } - } - else { - var responseData = await http.Response.fromStream(response); - debugPrint("Error: ${responseData.body}"); - - return left('Failed to fetch questions. Status code: ${response.statusCode}'); - } - } catch (e) { - return left('Error fetching questions: $e'); - } - } -} - diff --git a/lib/tools/ask_me/pages/askme_home.dart b/lib/tools/ask_me/pages/askme_home.dart deleted file mode 100644 index 14981a1..0000000 --- a/lib/tools/ask_me/pages/askme_home.dart +++ /dev/null @@ -1,246 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import '../controllers/controllers.dart'; -import '../widgets/widgets.dart'; - -class AskMeHome extends StatefulWidget { - const AskMeHome({super.key}); - - @override - State createState() => _AskMeHomeState(); -} - -class _AskMeHomeState extends State { - TextEditingController titleController = TextEditingController(); - final FilesAndScoresController filesAndScoresController = - Get.put(FilesAndScoresController()); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text("Ask Me"), - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () { - Navigator.pop(context); - }, - ), - actions: [ - IconButton( - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Information"), - content: const Text( - "Want to test whether you have understood the notes from class? Don't worry, we have got you covered by simply uploading your pdf notes from class and our AI model will generate ten questions for you to test yourself. "), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Oh ok")), - ], - ), - ); - }, - icon: const Icon(Ionicons.information_circle), - ) - ], - ), - body: Column( - children: [ - Center( - child: Image.asset( - 'assets/images/study.gif', - fit: BoxFit.cover, - height: 400, - ), - ), - const SizedBox( - height: 20, - ), - Expanded( - flex: 1, - child: Container( - //color: lightColorScheme.tertiary, - padding: const EdgeInsets.all(8.0), - child: Obx(() { - if (filesAndScoresController.files.isEmpty) { - return CustomPaint( - painter: DottedBorderPainter(), - child: const SizedBox( - width: double.infinity, - height: double.infinity, - child: Center( - child: Text( - 'Interact with our AI model to get started', - style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold), - ), - ), - ), - ); - } else { - return ListView.builder( - shrinkWrap: true, - itemCount: filesAndScoresController.files.length, - itemBuilder: (context, index) { - final file = filesAndScoresController.files[index]; - final fileScores = filesAndScoresController.scores - .where((score) => score.filesId == file.id) - .toList(); - // final fileScores = filesAndScoresController.fetchScoresByFileId(file.id!); - return ExpansionTile( - title: Text(file.title), - children: [ - ListTile( - title: Text( - 'Average score is: ${file.avgScore}/10', - style: const TextStyle( - fontWeight: FontWeight.bold), - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0), - child: Wrap( - spacing: 8.0, - children: fileScores.map((score) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Score ${fileScores.indexOf(score) + 1}:', - style: const TextStyle( - fontWeight: FontWeight.bold), - ), - const Spacer(), - Text( - '${score.score}', - style: const TextStyle( - fontWeight: FontWeight.bold), - ), - ], - ); - }).toList(), - ), - ), - Padding( - padding: - const EdgeInsets.symmetric(vertical: 4.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.start, - children: [ - TextButton( - onPressed: () { - _bottomSheet( - context, - id: file.id, - title: file.title, - filepath: file.filePath, - avgScore: file.avgScore, - ); - }, - child: const Text( - '+ Generate more questions from this file', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold)), - ), - ], - ), - Row( - mainAxisAlignment: - MainAxisAlignment.start, - children: [ - TextButton( - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text( - 'Confirm Deletion'), - content: const Text( - 'Are you sure you want to clear this file?'), - actions: [ - TextButton( - child: - const Text('Cancel'), - onPressed: () { - Navigator.of(context) - .pop(); // Close the dialog - }, - ), - TextButton( - child: - const Text('Clear'), - onPressed: () { - filesAndScoresController - .deleteFile(file); - Navigator.of(context) - .pop(); // Close the dialog after deletion - }, - ), - ], - ); - }, - ); - }, - child: const Text( - 'x Clear this file from the history list', - style: TextStyle( - fontSize: 16, - color: Colors.redAccent, - fontWeight: FontWeight.bold), - ), - ), - ], - ) - ], - ), - ), - ], - ); - }); - } - })), - ), - ], - ), - floatingActionButton: FloatingActionButton( - onPressed: () { - _bottomSheet(context); - }, - child: const Icon(Icons.add), - ), - ); - } - - Future _bottomSheet(BuildContext context, - {int? id, String? title, String? filepath, int? avgScore}) { - return showModalBottomSheet( - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(25.0)), - ), - isScrollControlled: true, - showDragHandle: true, - builder: (context) => FractionallySizedBox( - heightFactor: 0.7, - child: ModalContent( - id: id, - title: title, - filepath: filepath, - avgScore: avgScore, - ), - ), - ); - } -} diff --git a/lib/tools/ask_me/pages/modal_content.dart b/lib/tools/ask_me/pages/modal_content.dart deleted file mode 100644 index 33616c8..0000000 --- a/lib/tools/ask_me/pages/modal_content.dart +++ /dev/null @@ -1,492 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/ask_me/models/models.dart'; -import 'package:file_picker/file_picker.dart'; -import 'package:get/get.dart'; -import 'package:lottie/lottie.dart'; -import '../controllers/controllers.dart'; -import '../widgets/widgets.dart'; -import 'package:path/path.dart' as path; - -class ModalContent extends StatefulWidget { - final int? id; - final String? title; - final String? filepath; - final int? avgScore; - const ModalContent({ - super.key, - this.id, - this.title, - this.filepath, - this.avgScore, - }); - - @override - State createState() => _ModalContentState(); -} - -class _ModalContentState extends State { - String? _filePath; - String? _fileName; - bool isLoading = false; - - final QuizSettingsController quizSettingsController = - Get.put(QuizSettingsController()); - final FilesAndScoresController filesAndScoresController = - Get.put(FilesAndScoresController()); - final askMeController = Get.put(AskMeController()); - - late TextEditingController titleController; - //TextEditingController titleController = TextEditingController(); - final TextEditingController minuteController = TextEditingController(); - final TextEditingController secondsController = TextEditingController(); - - @override - void initState() { - super.initState(); - // Initialize titleController with the provided title or an empty string if null - titleController = TextEditingController(text: widget.title ?? ''); - - // Set _filePath and _fileName if filepath is provided - if (widget.filepath != null) { - _filePath = widget.filepath; - _fileName = widget.filepath?.split('/').last; - } - } - - //Function to handle uploading of files - Future _pickFile() async { - try { - debugPrint("Upload button pressed"); - // Opens the file picker and allows user to select files - FilePickerResult? result = await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ['pdf'], - ); - - if (result != null) { - // If a file is selected, update the file name and path - PlatformFile pickedFile = result.files.first; - if (pickedFile.path == null) { - throw Exception("File path is null"); - } - // Get the file size in bytes - int fileSizeInBytes = pickedFile.size; - - // Define the maximum file size (10MB) - const int maxFileSizeInBytes = 10 * 1024 * 1024; // 10MB - - // Check if the file size exceeds the maximum allowed size - if (fileSizeInBytes > maxFileSizeInBytes) { - throw Exception("File size exceeds the maximum limit of 10MB."); - } - - File originalFile = File(pickedFile.path!); - - //Get the app's documents directory - Directory appDocDir = await getApplicationDocumentsDirectory(); - String appDocPath = appDocDir.path; - - // Create a unique file name to prevent conflicts - String uniqueFileName = - '${DateTime.now().millisecondsSinceEpoch}_${pickedFile.name}'; - - // Define the new path - String newPath = path.join(appDocPath, uniqueFileName); - - // Copy the file - File copiedFile = await originalFile.copy(newPath); - - setState(() { - _filePath = copiedFile.path; - _fileName = copiedFile.path.split('/').last; - }); - debugPrint("File: $_filePath"); - debugPrint("File Name: $_fileName"); - } else { - // User canceled the picker - setState(() { - _filePath = null; - _fileName = null; - }); - } - } catch (e) { - debugPrint("Error picking file: $e"); - showDialog( - // ignore: use_build_context_synchronously - context: context, - builder: (context) => AlertDialog( - title: const Text("Error"), - content: - const Text("Failed to upload the file. Please try again"), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Ok"), - ), - ], - )); - } - } - - @override - Widget build(BuildContext context) { - // The maximum allowed time in seconds - const int maxTimeInSeconds = 1800; // 30 minutes - - return SizedBox( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - "Enter a title for your File", - style: TextStyle( - fontSize: 20.0, - fontWeight: FontWeight.bold, - ), - ), - TextFormField( - controller: titleController, - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (value) { - if (value?.length == null) { - return "Please enter a title"; - } - return null; - }, - textAlign: TextAlign.start, - decoration: InputDecoration( - hintText: "Please Enter Title for your Document", - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - ), - ), - ), - const SizedBox(height: 20), - const Text( - 'Question type', - style: TextStyle( - fontSize: 20.0, - fontWeight: FontWeight.bold, - ), - ), - Row( - children: [ - ChoiceWidget(label: 'Multiple choice', multipleChoice: true), - const SizedBox(width: 10), - ChoiceWidget( - label: 'True/False', - multipleChoice: false, - ), - ], - ), - const SizedBox(height: 20.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - "Set a timer", - style: TextStyle( - fontSize: 20.0, - fontWeight: FontWeight.bold, - ), - ), - Row(children: [ - Obx(() { - // Update the controller values whenever the observed values change - minuteController.text = - quizSettingsController.minute.value.toString(); - return TimeInputField( - label: "Minutes", - controller: minuteController, - onChanged: (value) { - quizSettingsController.minute.value = - int.tryParse(value) ?? 2; - }, - ); - }), - const Text(" : ", style: TextStyle(fontSize: 20)), - Obx(() { - secondsController.text = - quizSettingsController.seconds.value.toString(); - return TimeInputField( - label: "Seconds", - controller: secondsController, - onChanged: (value) { - quizSettingsController.seconds.value = - int.tryParse(value) ?? 00; - }, - ); - }), - ]), - ], - ), - const SizedBox( - height: 20.0, - ), - const Text( - "Select a PDF file to upload (Max 10MB):", - style: TextStyle( - fontSize: 18.0, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox( - height: 5, - ), - Row( - children: [ - FilledButton( - onPressed: () async { - await _pickFile(); - }, - style: FilledButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20.0), - ), - ), - child: const Text("Upload a File"), - ), - ], - ), - if (_filePath != null) - Padding( - padding: const EdgeInsets.all(8.0), - child: Text('$_fileName'), - ), - Expanded( - child: Align( - alignment: Alignment.bottomCenter, - child: isLoading - ? Lottie.asset( - "assets/lotties/loading.json", - height: 45, - ) - : FilledButton( - style: FilledButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30.0), - ), - ), - onPressed: () async { - int? minuteValue = - int.tryParse(minuteController.text); - int? secondsValue = - int.tryParse(secondsController.text); - int totalTime = - (minuteValue ?? 0) * 60 + (secondsValue ?? 0); - //Ensuring title and file path are not null - if (titleController.text.isEmpty || - _filePath == null) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Error"), - content: const Text( - "Please fill all the fields please"), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Oh ok"), - ), - ], - ), - ); - return; - } - //Ensuring time selected does not exceed specified limit - if (totalTime > maxTimeInSeconds) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Oops!"), - content: const Text( - "You can't set more than 30 minutes"), - actions: [ - FilledButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text("Ok"), - ), - ], - ), - ); - return; - } - //Validating file existence - File file = File(_filePath!); - if (!await file.exists()) { - showDialog( - // ignore: use_build_context_synchronously - context: context, - builder: (context) => AlertDialog( - title: const Text("File Not Found"), - content: const Text( - "The uploaded file could not be found. Please re-upload the file."), - actions: [ - FilledButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text("Ok"), - ), - ], - ), - ); - return; - } - quizSettingsController.setTimer( - minuteValue!, secondsValue!); - quizSettingsController - .setFileTitle(titleController.text); - quizSettingsController.setFilePath(_filePath!); - try { - setState(() { - isLoading = true; - }); - int? fileId; - AskMeFiles? newFile; - //Existing file in the local db hence the file will be updated - if (widget.id != null) { - final sameFile = AskMeFiles( - id: widget.id, - title: titleController.text, - filePath: _filePath!, - avgScore: widget.avgScore!, - ); - await filesAndScoresController - .updateFile(sameFile); - fileId = widget.id; // Use the existing file ID - debugPrint( - "Field Id of existing file is $fileId"); - } else { - //Executing this block means the file does not exist, hence the entry would be added to local db - AskMeFiles file = AskMeFiles( - title: titleController.text, - filePath: _filePath!, - avgScore: 0, - ); - await filesAndScoresController.addFile(file); - - // Reload files to get the updated list - await filesAndScoresController - .loadFilesAndScores(); - - // Find the newly added file - final addedFiles = filesAndScoresController.files - .where((f) => f.filePath == file.filePath) - .toList(); - if (addedFiles.isNotEmpty) { - newFile = addedFiles - .last; // Assuming the newly added file is the last one - fileId = newFile.id; - } else { - throw Exception( - 'Failed to retrieve the new file ID.'); - } - } - - final questionsResponse = - await askMeController.fetchQuestions(); - setState(() { - isLoading = false; - }); - debugPrint("After clicking Generate Button"); - debugPrint( - "Title: ${quizSettingsController.fileTitle.value}"); - debugPrint( - "File Path: ${quizSettingsController.filePath.value}"); - debugPrint( - "Question Type: ${quizSettingsController.multipleChoice.value}"); - - questionsResponse.fold((failure) { - debugPrint( - "Error Generated from getting questions is $failure"); - showDialog( - // ignore: use_build_context_synchronously - context: context, - builder: (context) => AlertDialog( - title: const Text("Oh-no!!!"), - content: const Text( - "Something went wrong. Try again, and if it keeps happening, some documents might not work right now. We're fixing it!"), - actions: [ - FilledButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text("Oh ok"), - ), - ], - ), - ); - setState(() { - isLoading = false; - }); - }, (success) { - Navigator.pushReplacement( - // ignore: use_build_context_synchronously - context, - MaterialPageRoute(builder: (context) { - return QuestionScreen( - multipleChoiceQuiz: quizSettingsController - .multipleChoice.value == - true - ? success as MultipleChoiceQuiz - : null, - trueFalseQuiz: quizSettingsController - .multipleChoice.value == - true - ? null - : success as TrueFalseQuiz, - title: titleController.text, - id: fileId, - filePath: _filePath!, - ); - }), - ); - setState(() { - isLoading = false; - }); - }); - } catch (e) { - debugPrint( - "Error Generated from getting questions not related to Ask Me Service that is $e"); - showDialog( - // ignore: use_build_context_synchronously - context: context, - builder: (context) => AlertDialog( - title: const Text("Oh-no!!!"), - content: const Text( - "An Error has occurred at the moment 😔, please try Again."), - actions: [ - FilledButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text("Oh ok"), - ), - ], - ), - ); - setState(() { - isLoading = false; - }); - } - }, - child: const Text("Generate"), - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/tools/ask_me/pages/pages.dart b/lib/tools/ask_me/pages/pages.dart deleted file mode 100644 index ebe3bb8..0000000 --- a/lib/tools/ask_me/pages/pages.dart +++ /dev/null @@ -1,6 +0,0 @@ -export 'quiz.dart'; -export 'quiz_settings.dart'; -export 'question_screen.dart'; -export 'score_section.dart'; -export 'askme_home.dart'; -export 'modal_content.dart'; \ No newline at end of file diff --git a/lib/tools/ask_me/pages/question_screen.dart b/lib/tools/ask_me/pages/question_screen.dart deleted file mode 100644 index 87ce9b2..0000000 --- a/lib/tools/ask_me/pages/question_screen.dart +++ /dev/null @@ -1,454 +0,0 @@ -import 'dart:async'; -import 'package:academia/tools/tools.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import '../controllers/controllers.dart'; -import '../models/models.dart'; - -class QuestionScreen extends StatefulWidget { - final MultipleChoiceQuiz? multipleChoiceQuiz; - final TrueFalseQuiz? trueFalseQuiz; - final int? id; - final String title; - final String filePath; - - const QuestionScreen({ - super.key, - this.multipleChoiceQuiz, - this.trueFalseQuiz, - this.id, - required this.title, - required this.filePath, - }); - - @override - State createState() => _QuestionScreenState(); -} - -class _QuestionScreenState extends State { - int currentIndex = 0; - int? selectedOptionIndex; - bool isAnswered = false; - int progressvalue = 1; - String? correctAnswer; - bool isNextButton = false; - int score = 0; - late Timer _timer; - int _totalTime = 0; - int _timeLeft = 0; - List scores = []; - - final quizSettingsController = Get.find(); - final filesAndScoresController = Get.find(); - - @override - void initState() { - super.initState(); - _totalTime = quizSettingsController.minute.value * 60 + - quizSettingsController.seconds.value; // use minutes and seconds - _timeLeft = _totalTime; - - _timer = Timer.periodic(const Duration(seconds: 1), (timer) { - setState(() { - if (_timeLeft > 0) { - _timeLeft--; - } else { - _timer.cancel(); - _saveScores(); - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => ScoreSection(score: score)), - ); - } - }); - }); - } - - @override - void dispose() { - _timer.cancel(); //Cancelling the timer when the widget is disposed - super.dispose(); - } - - Future _saveScores() async { - try { - // Save the current score - scores.add(score); - - // Save the score in the AskMeScores table - for (int scoreValue in scores) { - AskMeScores newScore = AskMeScores( - score: scoreValue, - filesId: widget.id!, // ID of the associated file - ); - await filesAndScoresController.addScores(newScore); - } - //loading both the files and the scores to get the latest changes - await filesAndScoresController.loadFilesAndScores(); - // Load the scores associated with the current file ID (widget.id) - final fileScores = filesAndScoresController.scores - .where((score) => score.filesId == widget.id) - .map((e) => e.score) - .toList(); - // Calculating the average score for the file - int totalScores = fileScores.reduce((a, b) => a + b); - int avgScore = totalScores ~/ fileScores.length; - - // Update the average score in the AskMeFiles table - AskMeFiles updatedFile = AskMeFiles( - id: widget.id!, - title: widget.title, - filePath: widget.filePath, - avgScore: avgScore, - ); - await filesAndScoresController.updateFile(updatedFile); - - // Optionally, handle success (e.g., show a message) - } catch (e) { - // Optionally, handle errors (e.g., show an error message) - debugPrint('Error saving scores: $e'); - } - } - - void _submitAnswer() { - if (selectedOptionIndex == null) { - showDialog( - // ignore: use_build_context_synchronously - context: context, - builder: (context) => AlertDialog( - title: const Text("Error!!"), - content: const Text( - "Please choose an answer to proceed to the next question."), - actions: [ - FilledButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text("Oh ok"), - ), - ], - ), - ); - } else { - setState(() { - isAnswered = true; - correctAnswer = - widget.multipleChoiceQuiz?.questions[currentIndex].correctAnswer ?? - widget.trueFalseQuiz?.questions[currentIndex].answer; - if (widget.multipleChoiceQuiz != null) { - if (widget.multipleChoiceQuiz!.questions[currentIndex] - .choices[selectedOptionIndex!] == - correctAnswer) { - score++; - } - } else if (widget.trueFalseQuiz != null) { - if (widget.trueFalseQuiz!.questions[currentIndex] - .choices[selectedOptionIndex!] == - correctAnswer) { - score++; - } - } - isNextButton = true; - }); - } - } - - void _nextQuestion() { - setState(() { - if (currentIndex < - (widget.multipleChoiceQuiz?.questions.length ?? - widget.trueFalseQuiz?.questions.length ?? - 0) - - 1) { - currentIndex++; - selectedOptionIndex = null; - isAnswered = false; - progressvalue++; - correctAnswer = null; - isNextButton = false; - } else { - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => ScoreSection(score: score)), - ); - } - }); - } - - @override - Widget build(BuildContext context) { - final questions = widget.multipleChoiceQuiz?.questions ?? - widget.trueFalseQuiz?.questions ?? - []; - - if (currentIndex >= questions.length) { - return ScoreSection( - score: score, - ); - } - final currentQuestion = questions[currentIndex]; - // Check the type of the current question and cast accordingly - final isMultipleChoice = widget.multipleChoiceQuiz != null; - final isTrueFalse = widget.trueFalseQuiz != null; - - final questionText = isMultipleChoice - ? (currentQuestion as MultipleChoiceQuestion).question - : (widget.trueFalseQuiz != null) - ? (currentQuestion as TrueFalseQuestion).question - : ''; - - final choices = isMultipleChoice - ? (currentQuestion as MultipleChoiceQuestion).choices - : isTrueFalse - ? (currentQuestion as TrueFalseQuestion).choices - : []; - - final correctAnswer = isMultipleChoice - ? (currentQuestion as MultipleChoiceQuestion).correctAnswer - : (widget.trueFalseQuiz != null) - ? (currentQuestion as TrueFalseQuestion).answer - : ''; - int minutes = _timeLeft ~/ 60; - int seconds = _timeLeft % 60; - - return Scaffold( - body: Column( - children: [ - Expanded( - flex: 1, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(16, 30, 16, 30), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text("Confirm Exit"), - content: const Text( - "Are you sure you want to quit?"), - actions: [ - TextButton( - onPressed: () { - // Dismiss the dialog and stay on the current screen - Navigator.of(context).pop(); - }, - child: const Text("Cancel"), - ), - TextButton( - onPressed: () { - // Dismiss the dialog and navigate to the previous screen - Navigator.of(context) - .pop(); // Dismiss the dialog first - Navigator.pop( - context); // Then pop the current screen - }, - child: const Text("Quit"), - ), - ], - ); - }, - ); - }, - icon: const Icon(Icons.arrow_back), - ), - Text( - "${currentIndex + 1} of ${questions.length}", - style: const TextStyle(fontSize: 18), - ), - Text( - "${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}", - style: const TextStyle(fontSize: 18), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: LinearProgressIndicator( - value: progressvalue / questions.length, - minHeight: 10, - backgroundColor: const Color(0xFF006399), - valueColor: - const AlwaysStoppedAnimation(Color(0xFF934171)), - ), - ), - ], - ), - ), - Expanded( - flex: 3, - child: Stack( - alignment: Alignment.center, - children: [ - Positioned.fill( - child: Column( - children: [ - Expanded( - child: Container( - color: Colors.white, - ), - ), - Expanded( - child: Container( - color: const Color(0xFF006399), - ), - ), - ], - ), - ), - Container( - padding: const EdgeInsets.all(20), - margin: const EdgeInsets.symmetric(horizontal: 16.0), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 5, - blurRadius: 7, - offset: const Offset(0, 3), - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: Text( - questionText, - style: const TextStyle(fontSize: 18), - ), - ), - const SizedBox(height: 20), - Wrap( - spacing: 8.0, - runSpacing: 8.0, - children: List.generate(choices.length, (index) { - return GestureDetector( - onTap: () { - setState(() { - selectedOptionIndex = index; - }); - }, - child: Container( - width: MediaQuery.of(context).size.width * 0.85, - padding: const EdgeInsets.all(10.0), - decoration: BoxDecoration( - color: selectedOptionIndex == index - ? Colors.blue.shade100 - : Colors.white, - border: Border.all( - color: Colors.grey, - ), - borderRadius: BorderRadius.circular(10), - ), - child: Row( - children: [ - Radio( - value: index, - groupValue: selectedOptionIndex, - onChanged: (value) { - setState(() { - selectedOptionIndex = value; - }); - }, - ), - Expanded( - child: Text( - choices[index], - style: const TextStyle(fontSize: 14), - ), - ), - if (isAnswered) - Icon( - choices[index] == correctAnswer - ? Icons.check - : Icons.close, - color: choices[index] == correctAnswer - ? Colors.green - : Colors.red, - ), - ], - ), - ), - ); - }), - ), - if (isAnswered) - Padding( - padding: const EdgeInsets.only(top: 10.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Correct Answer:', - style: TextStyle( - fontSize: 16, fontWeight: FontWeight.bold), - ), - Flexible( - child: Text( - correctAnswer, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold), - maxLines: null, - ), - ), - ], - ), - ), - ], - ), - ), - ], - ), - ), - Expanded( - flex: 1, - child: Container( - color: const Color(0xFF006399), - child: Align( - alignment: Alignment.bottomCenter, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: ElevatedButton( - onPressed: isNextButton - ? (currentIndex == questions.length - 1 - ? () { - setState(() { - isNextButton = false; - _saveScores(); - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => - ScoreSection(score: score)), - ); - }); - } - : _nextQuestion) - : _submitAnswer, - child: Text( - isNextButton - ? (currentIndex == questions.length - 1 - ? "Score" - : "Next") - : "Submit", - ), - ), - ), - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/tools/ask_me/pages/quiz.dart b/lib/tools/ask_me/pages/quiz.dart deleted file mode 100644 index 52dcb5d..0000000 --- a/lib/tools/ask_me/pages/quiz.dart +++ /dev/null @@ -1,92 +0,0 @@ -// import 'package:academia/tools/tools.dart'; -// import 'package:flutter/material.dart'; - -// class Quiz extends StatelessWidget { -// const Quiz({super.key}); - -// @override -// Widget build(BuildContext context) { -// return Scaffold( -// appBar: AppBar( -// leading: IconButton( -// icon: const Icon(Icons.arrow_back), -// onPressed: () { -// Navigator.of(context).pop(); -// }, -// ), - -// ), -// body: Column( -// mainAxisAlignment: MainAxisAlignment.center, -// children: [ -// Expanded( -// flex: 2, -// child: Padding( -// padding: const EdgeInsets.all(2.0), -// child: Image.asset( -// 'assets/images/think.jpeg', -// fit: BoxFit.contain, -// ), -// ), -// ), -// Expanded( -// flex: 1, -// child: Container( -// width: double.infinity, -// padding: const EdgeInsets.all(2.0), -// decoration: const BoxDecoration( -// color: Color(0xFF934171), -// borderRadius: BorderRadius.only( -// topLeft: Radius.circular(20.0), -// topRight: Radius.circular(20.0), -// ), -// ), -// child: Column( -// mainAxisAlignment: MainAxisAlignment.center, -// children: [ -// const Text( -// 'Welcome!', -// style: TextStyle( -// fontSize: 24.0, -// color: Colors.white, -// ), -// ), -// const SizedBox(height: 10.0,), -// const Text( -// 'Are you ready\nfor your quiz?', -// textAlign: TextAlign.center, -// style: TextStyle( -// fontSize: 28.0, -// color: Colors.white, -// fontWeight: FontWeight.bold, -// ), -// ), -// const SizedBox(height: 10,), -// ElevatedButton( -// onPressed: () { -// Navigator.push( -// context, -// MaterialPageRoute(builder: (context) => QuizSettings()) -// ); -// }, -// style: ElevatedButton.styleFrom( -// shape: const CircleBorder(), -// padding: const EdgeInsets.all(20), -// //foregroundColor: Colors.white, -// backgroundColor: Colors.white, -// ), -// child: const Icon( -// Icons.arrow_forward, -// color: Colors.pinkAccent, -// size: 30.0, -// ), -// ), -// ], -// ), -// ), -// ), -// ], -// ), -// ); -// } -// } \ No newline at end of file diff --git a/lib/tools/ask_me/pages/quiz_settings.dart b/lib/tools/ask_me/pages/quiz_settings.dart deleted file mode 100644 index 6686126..0000000 --- a/lib/tools/ask_me/pages/quiz_settings.dart +++ /dev/null @@ -1,174 +0,0 @@ -// import 'package:academia/tools/ask_me/controllers/quizSettings_controller.dart'; -// import 'package:academia/tools/ask_me/pages/questionScreen.dart'; -// import 'package:academia/tools/tools.dart'; -// import 'package:flutter/material.dart'; -// import 'package:get/get.dart'; - -// import '../models/models.dart'; -// import '../widgets/widgets.dart'; - - - -// class QuizSettings extends StatelessWidget { -// QuizSettings({super.key}); - -// final QuizSettingsController settingsController = Get.put(QuizSettingsController()); - -// List questions = [ -// HardCodedQuestion( -// id: '1', -// question: 'What is the capital of France?', -// choices: ['Berlin', 'Madrid', 'Paris', 'Rome'], -// correctAnswer: 'Paris', -// ), -// HardCodedQuestion( -// id: '2', -// question: 'What is the largest planet in our solar system?', -// choices: ['Earth', 'Jupiter', 'Mars', 'Saturn'], -// correctAnswer: 'Jupiter', -// ), -// HardCodedQuestion( -// id: '3', -// question: 'What is the chemical symbol for water?', -// choices: ['H2O', 'O2', 'CO2', 'NaCl'], -// correctAnswer: 'H2O', -// ), -// HardCodedQuestion( -// id: '4', -// question: 'What is the hardest natural substance on Earth?', -// choices: ['Gold', 'Iron', 'Diamond', 'Platinum'], -// correctAnswer: 'Diamond', -// ), -// HardCodedQuestion( -// id: '5', -// question: 'Who wrote "To Kill a Mockingbird"?', -// choices: ['Harper Lee', 'J.K. Rowling', 'Ernest Hemingway', 'Mark Twain'], -// correctAnswer: 'Harper Lee', -// ), -// HardCodedQuestion( -// id: '6', -// question: 'Which element has the chemical symbol "Au"?', -// choices: ['Silver', 'Gold', 'Iron', 'Copper'], -// correctAnswer: 'Gold', -// ), -// HardCodedQuestion( -// id: '7', -// question: 'What is the largest ocean on Earth?', -// choices: ['Atlantic Ocean', 'Indian Ocean', 'Arctic Ocean', 'Pacific Ocean'], -// correctAnswer: 'Pacific Ocean', -// ), -// HardCodedQuestion( -// id: '8', -// question: 'Who painted the Mona Lisa?', -// choices: ['Vincent van Gogh', 'Leonardo da Vinci', 'Pablo Picasso', 'Claude Monet'], -// correctAnswer: 'Leonardo da Vinci', -// ), -// HardCodedQuestion( -// id: '9', -// question: 'What is the smallest prime number?', -// choices: ['0', '1', '2', '3'], -// correctAnswer: '2', -// ), -// HardCodedQuestion( -// id: '10', -// question: 'Which planet is known as the Red Planet?', -// choices: ['Venus', 'Mars', 'Mercury', 'Neptune'], -// correctAnswer: 'Mars', -// ), -// ]; - -// @override -// Widget build(BuildContext context) { -// return Scaffold( -// appBar: AppBar( -// backgroundColor: Colors.transparent, -// elevation: 0, -// leading: IconButton( -// onPressed: () { -// Get.back(); -// }, -// icon: const Icon( -// Icons.close, -// color: Colors.black, -// ), -// ), -// ), -// body: Padding( -// padding: const EdgeInsets.all(20.0), -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// const Text( -// 'Settings', -// style: TextStyle( -// fontSize: 32.0, -// fontWeight: FontWeight.bold, -// ), -// ), -// const SizedBox(height: 10.0), -// const Text( -// 'Choose the question type and set a timer.', -// style: TextStyle( -// fontSize: 16.0, -// ), -// ), -// const SizedBox(height: 40.0), -// const Text( -// 'Question type', -// style: TextStyle( -// fontSize: 20.0, -// fontWeight: FontWeight.bold, -// ), -// ), -// const SizedBox(height: 20.0), -// Row( -// children: [ -// ChoiceWidget(label: 'Multiple choice'), -// const SizedBox(width: 10), -// ChoiceWidget(label: 'True/False'), -// ], -// ), -// const SizedBox(height: 40.0), -// const Text( -// 'Timer', -// style: TextStyle( -// fontSize: 20.0, -// fontWeight: FontWeight.bold, -// ), -// ), -// const SizedBox(height: 20.0), -// TimerOptions(), -// const SizedBox(height: 20.0), -// Expanded( -// child: Align( -// alignment: Alignment.bottomCenter, -// child: ElevatedButton( -// onPressed: () { -// Navigator.push( -// context, -// MaterialPageRoute(builder: (context) => QuestionScreen(questions: questions,)) -// ); -// }, -// style: ElevatedButton.styleFrom( -// padding: const EdgeInsets.symmetric(horizontal: 100.0, vertical: 20.0), -// backgroundColor: Colors.lightBlue[100], -// shape: RoundedRectangleBorder( -// borderRadius: BorderRadius.circular(30.0), -// ), -// ), -// child: const Text( -// 'Start quiz', -// style: TextStyle( -// fontSize: 18.0, -// color: Colors.black, -// ), -// ), -// ), -// ), -// ), -// ], -// ), -// ), -// ); -// } -// } diff --git a/lib/tools/ask_me/pages/score_section.dart b/lib/tools/ask_me/pages/score_section.dart deleted file mode 100644 index 052f36d..0000000 --- a/lib/tools/ask_me/pages/score_section.dart +++ /dev/null @@ -1,147 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'pages.dart'; - -class ScoreSection extends StatelessWidget { - final int score; - const ScoreSection({super.key, required this.score}); - - @override - Widget build(BuildContext context) { - String quotes; - String emoji; - if (score >= 0 && score <= 3) { - quotes = 'Keep Trying, You’re Almost There!'; - emoji = '😅'; - } else if (score >= 4 && score <= 6) { - quotes = 'Well Done, You’re Making Progress!'; - emoji = '👏'; - } else if (score >= 7 && score <= 9) { - quotes = 'Great Job, Keep Up the Good Work!'; - emoji = '👍'; - } else { - quotes = 'Outstanding, You’ve Really Excelled!'; - emoji = '🏆'; - } - - return Scaffold( - // appBar: AppBar( - // title: const Text("The End"), - // ), - body: Column( - children: [ - Expanded( - flex: 2, - child: Image.asset( - 'assets/images/congratulations_askMe.jpeg', - width: 400, - fit: BoxFit.cover, - ), - ), - Expanded( - flex: 3, - child: Stack( - alignment: Alignment.center, - children: [ - Positioned.fill( - child: Column( - children: [ - Expanded( - flex: 1, - child: Container( - //color: const Color(0xFFB9D9EB), - color: const Color(0xFF006399), - ), - ), - Expanded( - flex: 1, - child: Container( - color: const Color(0xFF006399), - ), - ), - ], - ), - ), - Container( - padding: const EdgeInsets.all(20), - margin: const EdgeInsets.symmetric(horizontal: 16.0), - width: 500, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 5, - blurRadius: 7, - offset: const Offset(0, 3), - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - "Congratulations", - style: TextStyle( - fontSize: 22, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 8), - const Text( - "You completed the test!", - style: TextStyle( - fontSize: 18, fontWeight: FontWeight.w300), - ), - const SizedBox(height: 20), - Text( - "your score".toUpperCase(), - style: const TextStyle( - fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - Text( - "$score/10", - style: const TextStyle( - fontSize: 24, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - Text( - emoji, - style: const TextStyle(fontSize: 30), - ), - const SizedBox(height: 10), - Text( - quotes, - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w300), - ), - ], - ), - ), - ], - ), - ), - Container( - height: 100, - color: const Color(0xFF006399), - child: Align( - alignment: Alignment.bottomCenter, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: ElevatedButton( - onPressed: () { - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => const AskMeHome()), - ); - }, - child: const Text("Complete"), - ), - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/tools/ask_me/widgets/choice_widget.dart b/lib/tools/ask_me/widgets/choice_widget.dart deleted file mode 100644 index 563344e..0000000 --- a/lib/tools/ask_me/widgets/choice_widget.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:academia/tools/ask_me/controllers/quiz_settings_controller.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -class ChoiceWidget extends StatelessWidget { - final String label; - final bool multipleChoice; - ChoiceWidget({super.key, required this.label, required this.multipleChoice}); - - final QuizSettingsController settingsController = Get.put(QuizSettingsController()); - - @override - Widget build(BuildContext context) { - return Obx(() => ChoiceChip( - label: Text(label), - selected: settingsController.questionType.value == label, - onSelected: (bool selected) { - settingsController.setQuestionType(label, multipleChoice); - }, - selectedColor: Colors.pink[100], - backgroundColor: Colors.grey[100], - )); - } -} \ No newline at end of file diff --git a/lib/tools/ask_me/widgets/custom_painter.dart b/lib/tools/ask_me/widgets/custom_painter.dart deleted file mode 100644 index b2d35bd..0000000 --- a/lib/tools/ask_me/widgets/custom_painter.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; - -class DottedBorderPainter extends CustomPainter { - @override - void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = Colors.black - ..strokeWidth = 2 - ..style = PaintingStyle.stroke; - - final path = Path() - ..moveTo(0, 0) - ..lineTo(size.width, 0) - ..lineTo(size.width, size.height) - ..lineTo(0, size.height) - ..close(); - - // Create dotted effect using PathMetric - final pathMetrics = path.computeMetrics(); - for (final metric in pathMetrics) { - final length = metric.length; - for (double i = 0; i < length; i += 10) { - final start = i; - final end = (i + 5).clamp(0.0, length); - final segmentPath = metric.extractPath(start, end); - canvas.drawPath(segmentPath, paint); - } - } - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return false; - } -} \ No newline at end of file diff --git a/lib/tools/ask_me/widgets/time_input_field.dart b/lib/tools/ask_me/widgets/time_input_field.dart deleted file mode 100644 index db1bf5f..0000000 --- a/lib/tools/ask_me/widgets/time_input_field.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:flutter/material.dart'; - -class TimeInputField extends StatelessWidget { - final String label; - final double width; - final TextEditingController controller; - final void Function(String) onChanged; - - const TimeInputField({ - super.key, - required this.label, - this.width = 50, - required this.controller, - required this.onChanged, - }); - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - width: width, - child: TextFormField( - controller: controller, - keyboardType: TextInputType.number, - textAlign: TextAlign.center, - decoration: const InputDecoration( - border: OutlineInputBorder(), - contentPadding: EdgeInsets.symmetric(vertical: 8.0), - ), - onChanged: onChanged, - ), - ), - const SizedBox(height: 5,), - Text(label), - ], - ); - } -} \ No newline at end of file diff --git a/lib/tools/ask_me/widgets/timer_options.dart b/lib/tools/ask_me/widgets/timer_options.dart deleted file mode 100644 index 159b696..0000000 --- a/lib/tools/ask_me/widgets/timer_options.dart +++ /dev/null @@ -1,47 +0,0 @@ -// import 'package:flutter/material.dart'; -// import 'package:get/get.dart'; -// import '../controllers/quizSettings_controller.dart'; - -// class TimerOptions extends StatelessWidget { -// TimerOptions({super.key}); - -// final QuizSettingsController settingsController = Get.put(QuizSettingsController()); - -// @override -// Widget build(BuildContext context) { -// return Column( -// children: List.generate(5, (index) { -// int minute = index + 1; -// return GestureDetector( -// onTap: () { -// settingsController.setSelectedTimer(minute); -// }, -// child: Obx( -// () => Container( -// width: double.infinity, -// padding: const EdgeInsets.all(15.0), -// margin: const EdgeInsets.symmetric(vertical: 5.0), -// decoration: BoxDecoration( -// color: settingsController.selectedTimer.value == minute -// ? Colors.pink[50] -// : Colors.transparent, -// borderRadius: BorderRadius.circular(10.0), -// ), -// child: Center( -// child: Text( -// '$minute minute', -// style: TextStyle( -// fontSize: 16.0, -// color: settingsController.selectedTimer.value == minute -// ? Colors.black -// : Colors.grey, -// ), -// ), -// ), -// ), -// ), -// ); -// }), -// ); -// } -// } \ No newline at end of file diff --git a/lib/tools/ask_me/widgets/widgets.dart b/lib/tools/ask_me/widgets/widgets.dart deleted file mode 100644 index ff68a0f..0000000 --- a/lib/tools/ask_me/widgets/widgets.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'choice_widget.dart'; -export 'timer_options.dart'; -export 'time_input_field.dart'; -export 'custom_painter.dart'; \ No newline at end of file diff --git a/lib/tools/birthday/birthday.dart b/lib/tools/birthday/birthday.dart deleted file mode 100644 index 9171956..0000000 --- a/lib/tools/birthday/birthday.dart +++ /dev/null @@ -1 +0,0 @@ -export 'pages/pages.dart'; diff --git a/lib/tools/birthday/pages/birthday_page.dart b/lib/tools/birthday/pages/birthday_page.dart deleted file mode 100644 index 0f33b28..0000000 --- a/lib/tools/birthday/pages/birthday_page.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:get/get.dart'; -import 'package:intl/intl.dart'; -import 'package:academia/exports/barrel.dart'; -import 'package:lottie/lottie.dart'; - -class BirthDayPage extends StatelessWidget { - BirthDayPage({super.key}); - - final UserController userController = Get.find(); - int get years { - DateFormat inputFormat = DateFormat('yyyy-MM-dd'); - - var dob = - inputFormat.parse(userController.user.value!.dateOfBirth.toString()); - return DateTime.now().year - dob.year; - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: CustomScrollView( - slivers: [ - SliverAppBar( - leading: IconButton.outlined( - onPressed: () { - Navigator.pop(context); - }, - icon: const Icon(Ionicons.close), - ), - ), - const SliverPadding( - padding: EdgeInsets.all(16), - sliver: SliverToBoxAdapter( - child: ProfilePictureWidget( - profileSize: 80, - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.all(16), - sliver: SliverToBoxAdapter( - child: Text( - "Happy birthday ${userController.user.value!.firstName} ${Emojis.activites_confetti_ball}", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineLarge, - ), - ), - ), - SliverToBoxAdapter( - child: Lottie.asset("assets/lotties/birthday.json"), - ), - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverToBoxAdapter( - child: Text( - """Academia wishes you a happy $years birthday!Remember you're one year older from your birth and one closer to your death\nWe celebrate you!""", - textAlign: TextAlign.center, - ), - ), - ) - ], - ), - ); - } -} diff --git a/lib/tools/birthday/pages/pages.dart b/lib/tools/birthday/pages/pages.dart deleted file mode 100644 index ef29a9a..0000000 --- a/lib/tools/birthday/pages/pages.dart +++ /dev/null @@ -1 +0,0 @@ -export 'birthday_page.dart'; diff --git a/lib/tools/chirp/chirp.dart b/lib/tools/chirp/chirp.dart deleted file mode 100644 index 1889e01..0000000 --- a/lib/tools/chirp/chirp.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'pages/chirp_home_page.dart'; -export 'models/models.dart'; diff --git a/lib/tools/chirp/controllers/chirp_controller.dart b/lib/tools/chirp/controllers/chirp_controller.dart deleted file mode 100644 index 6d2b65b..0000000 --- a/lib/tools/chirp/controllers/chirp_controller.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import 'package:dartz/dartz.dart'; - -class ChirpController extends GetxController { - final PostService _service = PostService(); - UserController userController = Get.find(); - // Loading posts comments - - Future>> fetchUserPosts() async { - final result = await _service.fetchUserPosts( - userController.authHeaders, userController.user.value!.id!); - - return result.fold((l) { - return left(l); - }, (r) { - return right(r); - }); - } - - Future>> fetchPostComments(Post post) async { - final result = - await _service.fetchPostComments(userController.authHeaders, post.id); - - return result.fold((l) { - return left(l); - }, (r) { - return right(r); - }); - } - - Future> createPost(String title, String content) async { - final Post newPost = Post( - id: "", - title: title, - content: content, - upvotes: 0, - downvotes: 0, - isEdited: false, - isDeleted: false, - createdAt: DateTime.now(), - modifiedAt: DateTime.now(), - commentsCount: 0, - postAttachmentMedia: [], - ); - - return _service - .createPost( - userController.authHeaders, - newPost, - userController.user.value!.id!, - ) - .then((value) { - return value.fold((l) { - return left(l); - }, (r) { - return right(r); - }); - }); - } - - Future> postComment(String userID, String postID, - String? parentCommentID, String content) async { - final result = await _service.postComment( - userController.authHeaders, - userID, - postID, - parentCommentID, - content, - ); - - return result.fold((l) { - return left(l); - }, (r) { - return right(r); - }); - } -} diff --git a/lib/tools/chirp/models/core/chirp_user.dart b/lib/tools/chirp/models/core/chirp_user.dart deleted file mode 100644 index 6e210c8..0000000 --- a/lib/tools/chirp/models/core/chirp_user.dart +++ /dev/null @@ -1,71 +0,0 @@ -class ChirpUser { - final String id; - final String password; - final DateTime? lastLogin; - final bool isSuperuser; - final String username; - final bool isStaff; - final bool isActive; - final DateTime dateJoined; - final String email; - final String? profilePhoto; - final String firstName; - final String lastName; - final int upvotes; - - ChirpUser({ - required this.id, - required this.password, - this.lastLogin, - required this.isSuperuser, - required this.username, - required this.isStaff, - required this.isActive, - required this.dateJoined, - required this.email, - this.profilePhoto, - required this.firstName, - required this.lastName, - required this.upvotes, - }); - - // Factory method to create a ChirpUser instance from JSON - factory ChirpUser.fromJson(Map json) { - return ChirpUser( - id: json['id'] as String, - password: json['password'] as String, - lastLogin: json['last_login'] != null - ? DateTime.parse(json['last_login']) - : null, - isSuperuser: json['is_superuser'] as bool, - username: json['username'] as String, - isStaff: json['is_staff'] as bool, - isActive: json['is_active'] as bool, - dateJoined: DateTime.parse(json['date_joined'] as String), - email: json['email'] as String, - profilePhoto: json['profile_url'] as String?, - firstName: json['first_name'] as String, - lastName: json['last_name'] as String, - upvotes: json['upvotes'] as int, - ); - } - - // Method to convert ChirpUser instance to JSON - Map toJson() { - return { - 'id': id, - 'password': password, - 'last_login': lastLogin?.toIso8601String(), - 'is_superuser': isSuperuser, - 'username': username, - 'is_staff': isStaff, - 'is_active': isActive, - 'date_joined': dateJoined.toIso8601String(), - 'email': email, - 'profile_photo': profilePhoto, - 'first_name': firstName, - 'last_name': lastName, - 'upvotes': upvotes, - }; - } -} diff --git a/lib/tools/chirp/models/core/comment.dart b/lib/tools/chirp/models/core/comment.dart deleted file mode 100644 index 18eaa48..0000000 --- a/lib/tools/chirp/models/core/comment.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'core.dart'; - -class Comment { - final String id; - final String content; - final bool isEdited; - final bool isDeleted; - final DateTime createdAt; - final DateTime modifiedAt; - final ChirpUser user; - final Post post; - final String? parent; - final List replies; - - Comment({ - required this.id, - required this.content, - required this.isEdited, - required this.isDeleted, - required this.createdAt, - required this.modifiedAt, - required this.user, - required this.post, - this.parent, - required this.replies, - }); - - factory Comment.fromJson(Map json) { - return Comment( - id: json['id'], - content: json['content'], - isEdited: json['is_edited'], - isDeleted: json['is_deleted'], - createdAt: DateTime.parse(json['created_at']), - modifiedAt: DateTime.parse(json['modified_at']), - user: ChirpUser.fromJson(json['user']), - post: Post.fromJson(json['post']), - parent: json['parent'], - replies: (json['replies'] as List) - .map((item) => Comment.fromJson(item)) - .toList(), - ); - } - - Map toJson() { - return { - 'id': id, - 'content': content, - 'is_edited': isEdited, - 'is_deleted': isDeleted, - 'created_at': createdAt.toIso8601String(), - 'modified_at': modifiedAt.toIso8601String(), - 'user': user.toJson(), - 'post': post.toJson(), - 'parent': parent, - 'replies': replies.map((item) => item.toJson()).toList(), - }; - } -} diff --git a/lib/tools/chirp/models/core/core.dart b/lib/tools/chirp/models/core/core.dart deleted file mode 100644 index d7766d7..0000000 --- a/lib/tools/chirp/models/core/core.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'post_attachment_media.dart'; -export 'chirp_user.dart'; -export 'post.dart'; -export 'comment.dart'; diff --git a/lib/tools/chirp/models/core/post.dart b/lib/tools/chirp/models/core/post.dart deleted file mode 100644 index a498914..0000000 --- a/lib/tools/chirp/models/core/post.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'core.dart'; - -class Post { - final String id; - final String title; - final String content; - final int upvotes; - final int downvotes; - final bool isEdited; - final bool isDeleted; - final DateTime createdAt; - final DateTime modifiedAt; - final String? link; - final ChirpUser? user; - final int commentsCount; - final List postAttachmentMedia; - - Post({ - required this.id, - required this.title, - required this.content, - required this.upvotes, - required this.downvotes, - required this.isEdited, - required this.isDeleted, - required this.createdAt, - required this.modifiedAt, - required this.commentsCount, - this.link, - this.user, - required this.postAttachmentMedia, - }); - - factory Post.fromJson(Map json) { - return Post( - id: json['id'] as String, - title: json['title'] as String, - content: json['content'] as String, - upvotes: json['upvote_count'] as int, - downvotes: json['downvote_count'] as int, - commentsCount: json['comments_count'] as int, - isEdited: json['is_edited'] as bool, - isDeleted: json['is_deleted'] as bool, - createdAt: DateTime.parse(json['created_at'] as String), - modifiedAt: DateTime.parse(json['modified_at'] as String), - link: json['link'] as String?, - user: ChirpUser.fromJson(json["user"]), - postAttachmentMedia: (json['post_attachment_media'] as List) - .map((e) => PostAttachmentMedia.fromJson(e as Map)) - .toList(), - ); - } - - Map toJson() { - return { - 'id': id, - 'title': title, - 'content': content, - 'upvotes': upvotes, - 'downvotes': downvotes, - 'is_edited': isEdited, - 'is_deleted': isDeleted, - 'created_at': createdAt.toIso8601String(), - 'modified_at': modifiedAt.toIso8601String(), - 'link': link, - 'post_attachment_media': - postAttachmentMedia.map((e) => e.toJson()).toList(), - }; - } -} diff --git a/lib/tools/chirp/models/core/post_attachment_media.dart b/lib/tools/chirp/models/core/post_attachment_media.dart deleted file mode 100644 index fa56a5b..0000000 --- a/lib/tools/chirp/models/core/post_attachment_media.dart +++ /dev/null @@ -1,31 +0,0 @@ -class PostAttachmentMedia { - final String id; - final String? image; - final DateTime createdAt; - final String post; - - PostAttachmentMedia({ - required this.id, - required this.image, - required this.createdAt, - required this.post, - }); - - factory PostAttachmentMedia.fromJson(Map json) { - return PostAttachmentMedia( - id: json['id'] as String, - image: json['image'] as String?, - createdAt: DateTime.parse(json['created_at'] as String), - post: json['post'] as String, - ); - } - - Map toJson() { - return { - 'id': id, - 'image': image, - 'created_at': createdAt.toIso8601String(), - 'post': post, - }; - } -} diff --git a/lib/tools/chirp/models/models.dart b/lib/tools/chirp/models/models.dart deleted file mode 100644 index 1ef9213..0000000 --- a/lib/tools/chirp/models/models.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'core/core.dart'; -export 'services/services.dart'; diff --git a/lib/tools/chirp/models/services/chirp_service.dart b/lib/tools/chirp/models/services/chirp_service.dart deleted file mode 100644 index 71494a7..0000000 --- a/lib/tools/chirp/models/services/chirp_service.dart +++ /dev/null @@ -1,4 +0,0 @@ -class ChirpService { - // static const urlPrefix = "http://127.0.0.1:8080"; - static const urlPrefix = "http://62.169.16.219:84"; -} diff --git a/lib/tools/chirp/models/services/post_service.dart b/lib/tools/chirp/models/services/post_service.dart deleted file mode 100644 index f0a1c27..0000000 --- a/lib/tools/chirp/models/services/post_service.dart +++ /dev/null @@ -1,179 +0,0 @@ -import 'dart:convert'; - -import 'package:dartz/dartz.dart'; -import 'package:http/http.dart' as http; -import '../models.dart'; - -class PostService with ChirpService { - Future>> fetchPosts( - Map authHeaders, { - int page = 1, - }) async { - try { - final response = await http.get( - Uri.parse("${ChirpService.urlPrefix}/posts/all?page=$page"), - headers: authHeaders, - ); - - if (response.statusCode == 200) { - int nextPage = page; - // parse the posts and the next page - final Map rawData = json.decode(response.body); - if (rawData.containsKey("next") && rawData["next"] != null) { - nextPage += 1; - } - - final List posts = rawData["results"] - .map((e) => Post.fromJson(e)) - .toList() - .cast(); - - return right({ - "posts": posts, - "nextPage": nextPage, - }); - } - return left( - json.decode(response.body)["error"], - ); - } catch (e) { - return left( - "Encountered an error we did, check your connection you must, ${e.toString()}", - ); - } - } - - Future> createPost( - Map authHeaders, - Post post, - String userID, - ) async { - try { - final rawPost = post.toJson(); - rawPost["user"] = userID; - - authHeaders.addAll({"Content-Type": "application/json"}); - final response = await http.post( - Uri.parse("${ChirpService.urlPrefix}/posts/"), - headers: authHeaders, - body: json.encode(rawPost), - ); - - if (response.statusCode == 201) { - final Map rawPost = json.decode(response.body); - return right(Post.fromJson(rawPost)); - } - return left( - json.decode(response.body)["error"] ?? "unknown error", - ); - } catch (e) { - return const Left("Please check your internet connection and try again"); - } - } - - Future>> fetchUserPosts( - Map authHeaders, String userID) async { - try { - final response = await http.get( - Uri.parse("${ChirpService.urlPrefix}/posts/user/$userID"), - headers: authHeaders, - ); - if (response.statusCode == 200) { - final List> rawData = - json.decode(response.body).cast>(); - - final List comments = - rawData.map((e) => Post.fromJson(e)).toList().cast(); - - return right(comments); - } - return left( - json.decode(response.body)["error"], - ); - } catch (e) { - return left( - "Encountered an error we did, check your connection you must, ${e.toString()}", - ); - } - } - - Future>> fetchPostComments( - Map authHeaders, - String post, { - int page = 1, - }) async { - try { - final response = await http.get( - Uri.parse( - "${ChirpService.urlPrefix}/comments/find-by-post/$post?pages=$page"), - headers: authHeaders, - ); - if (response.statusCode == 200) { - final List> rawData = - json.decode(response.body).cast>(); - - final List comments = - rawData.map((e) => Comment.fromJson(e)).toList().cast(); - - return right(comments); - } - return left( - json.decode(response.body)["error"], - ); - } catch (e) { - return left( - "Encountered an error we did, check your connection you must, ${e.toString()}", - ); - } - } - - Future> postComment( - Map authHeaders, - String userID, - String postID, - String? parentCommentID, - String content) async { - try { - authHeaders.addAll({"content-type": "application/json"}); - final response = await http.post( - Uri.parse("${ChirpService.urlPrefix}/comments/create/"), - headers: authHeaders, - body: json.encode({ - "user": userID, - "post": postID, - "parent": parentCommentID, - "content": content, - })); - - if (response.statusCode == 201) { - final Map rawComment = json.decode(response.body); - return right(Comment.fromJson(rawComment)); - } - return left(json.decode(response.body.toString())); - } catch (e) { - return Left("Failed to post comment error: ${e.toString()}"); - } - } - - Future> postVote( - Map authHeaders, String action, String postID) async { - try { - authHeaders.addAll({"content-type": "application/json"}); - final response = await http.get( - Uri.parse("${ChirpService.urlPrefix}/posts/vote/$action/$postID"), - headers: authHeaders, - ); - - if (response.statusCode == 200) { - return right(true); - } - if (response.statusCode == 404) { - return right(false); - } - - return left("Something went wrong while attempting to vote for post"); - } catch (e) { - return left("Please check your internet connection and try again"); - } - } -} diff --git a/lib/tools/chirp/models/services/services.dart b/lib/tools/chirp/models/services/services.dart deleted file mode 100644 index 7c74f63..0000000 --- a/lib/tools/chirp/models/services/services.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'chirp_service.dart'; -export 'post_service.dart'; diff --git a/lib/tools/chirp/pages/chirp_home_page.dart b/lib/tools/chirp/pages/chirp_home_page.dart deleted file mode 100644 index 1b4a94f..0000000 --- a/lib/tools/chirp/pages/chirp_home_page.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/chirp/controllers/chirp_controller.dart'; -import '../pages/post_create_page.dart'; -import '../widgets/widgets.dart'; -import './feed_page.dart'; -import './personal_posts.dart'; -import 'package:get/get.dart'; - -class ChirpHomePage extends StatelessWidget { - const ChirpHomePage({super.key}); - @override - Widget build(BuildContext context) { - Get.put(ChirpController()); - return Scaffold( - body: DefaultTabController( - length: 3, - child: CustomScrollView( - slivers: [ - SliverAppBar( - title: const Text("Chirp"), - leading: IconButton( - onPressed: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const LeaderBoardPage())); - }, - icon: const Icon(Ionicons.trophy_outline), - ), - actions: [ - IconButton( - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const PostCreatePage(), - ), - ); - }, - icon: const Icon(Ionicons.add), - ), - IconButton( - onPressed: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const EventsPage())); - }, - icon: const Icon(Ionicons.flame_outline), - ), - ], - expandedHeight: 200, - pinned: true, - floating: true, - snap: true, - bottom: PreferredSize( - preferredSize: Size(MediaQuery.of(context).size.width, 160), - child: const Column( - children: [ - StoryHeader(), - TabBar(tabs: [ - Tab(text: 'Trending'), - Tab(text: "Your Posts"), - Tab(text: "Organizations"), - ]), - ], - )), - ), - const SliverFillRemaining( - hasScrollBody: true, - fillOverscroll: false, - child: TabBarView( - children: [ - FeedPage(), - PersonalPostsPage(), - OrganizationsPage(), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/tools/chirp/pages/feed_page.dart b/lib/tools/chirp/pages/feed_page.dart deleted file mode 100644 index ad21b7d..0000000 --- a/lib/tools/chirp/pages/feed_page.dart +++ /dev/null @@ -1,145 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import '../widgets/widgets.dart'; - -class FeedPage extends StatefulWidget { - const FeedPage({super.key}); - - @override - State createState() => _FeedPageState(); -} - -class _FeedPageState extends State - with AutomaticKeepAliveClientMixin { - bool pageLoading = true; - bool morePostsLoading = false; - List feedPosts = []; - int nextPage = 1; - bool hasMorePages = true; - - final UserController userController = Get.find(); - final PostService _service = PostService(); - - @override - bool get wantKeepAlive => true; - - @override - void initState() { - super.initState(); - setState(() { - pageLoading = true; - }); - _service.fetchPosts(userController.authHeaders).then((value) { - value.fold((l) { - debugPrint(l); - }, (r) { - feedPosts.addAll(r["posts"]); - if (nextPage == r["nextPage"]) { - setState(() { - hasMorePages = false; - }); - return; - } - - nextPage = r["nextPage"]; - setState(() {}); - }); - }); - setState(() { - pageLoading = false; - }); - } - - @override - Widget build(BuildContext context) { - super.build(context); - return RefreshIndicator( - onRefresh: () async { - setState(() { - pageLoading = true; - }); - - feedPosts.clear(); - final result = await _service.fetchPosts(userController.authHeaders); - return result.fold((l) { - debugPrint(l); - setState(() { - pageLoading = false; - }); - }, (r) { - feedPosts.addAll(r["posts"]); - if (nextPage == r["nextPage"]) { - setState(() { - hasMorePages = false; - pageLoading = false; - }); - return; - } - - nextPage = r["nextPage"]; - setState(() { - pageLoading = false; - }); - }); - }, - child: pageLoading - ? ListView.separated( - itemBuilder: (context, index) { - return const EmptyPostCard(); - }, - separatorBuilder: (context, index) => const SizedBox( - height: 4, - ), - itemCount: 12, - ) - : feedPosts.isEmpty - ? const Center( - child: Text("No posts here yet"), - ) - : ListView.separated( - itemBuilder: (context, index) { - if (index < feedPosts.length) { - final post = feedPosts[index]; - return PostCard(post: post); - } - return hasMorePages - ? morePostsLoading - ? const LinearProgressIndicator() - : TextButton( - onPressed: fetchMorePosts, - child: const Text("Load more"), - ) - : const Text("You have reached the end of feed!"); - }, - separatorBuilder: (context, index) => - const SizedBox(height: 2), - itemCount: feedPosts.length + 1), - ); - } - - Future fetchMorePosts() async { - setState(() { - morePostsLoading = true; - }); - - _service - .fetchPosts(userController.authHeaders, page: nextPage) - .then((value) { - value.fold((l) { - debugPrint(l); - }, (r) { - feedPosts.addAll(r["posts"]); - setState(() {}); - if (nextPage == r["nextPage"]) { - hasMorePages = false; - return; - } - nextPage = r["nextPage"]; - }); - }); - - setState(() { - morePostsLoading = false; - }); - } -} diff --git a/lib/tools/chirp/pages/personal_posts.dart b/lib/tools/chirp/pages/personal_posts.dart deleted file mode 100644 index 5810615..0000000 --- a/lib/tools/chirp/pages/personal_posts.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/chirp/widgets/widgets.dart'; -import 'package:dartz/dartz.dart' as dartz; -import '../controllers/chirp_controller.dart'; -import 'package:get/get.dart'; -import 'package:lottie/lottie.dart'; - -class PersonalPostsPage extends StatefulWidget { - const PersonalPostsPage({super.key}); - - @override - State createState() => _PersonalPostsPageState(); -} - -class _PersonalPostsPageState extends State - with AutomaticKeepAliveClientMixin { - late Future>> posts; - @override - bool get wantKeepAlive => true; - - final controller = Get.find(); - @override - void initState() { - super.initState(); - posts = controller.fetchUserPosts(); - } - - @override - Widget build(BuildContext context) { - super.build(context); - return RefreshIndicator( - onRefresh: () async { - posts = controller.fetchUserPosts(); - setState(() {}); - }, - child: FutureBuilder( - future: posts, - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return ListView.separated( - itemBuilder: (context, index) { - return const EmptyPostCard(); - }, - separatorBuilder: (context, index) => const SizedBox( - height: 4, - ), - itemCount: 12); - } - return snapshot.data!.fold((l) { - return Center( - child: Text("Snap! $l"), - ); - }, (r) { - if (r.isEmpty) { - return SingleChildScrollView( - child: Column( - children: [ - Lottie.asset( - "assets/lotties/empty.json", - height: 250, - ), - const SizedBox(height: 22), - Text( - "You have no posts, create a post to get started", - style: Theme.of(context).textTheme.headlineSmall, - textAlign: TextAlign.center, - ), - ], - ), - ); - } - return ListView.separated( - itemBuilder: (context, index) { - final post = r[index]; - return PostCard(post: post); - }, - separatorBuilder: (context, index) => const SizedBox(height: 2), - itemCount: r.length); - }); - }, - ), - ); - } -} diff --git a/lib/tools/chirp/pages/post_create_page.dart b/lib/tools/chirp/pages/post_create_page.dart deleted file mode 100644 index 10ac414..0000000 --- a/lib/tools/chirp/pages/post_create_page.dart +++ /dev/null @@ -1,211 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/chirp/controllers/chirp_controller.dart'; -import 'package:get/get.dart'; - -class PostCreatePage extends StatefulWidget { - const PostCreatePage({super.key}); - - @override - State createState() => _PostCreatePageState(); -} - -class _PostCreatePageState extends State { - final formState = GlobalKey(); - final TextEditingController titleController = TextEditingController(); - final TextEditingController bodyController = TextEditingController(); - final List imageFiles = []; - final ChirpController chirpController = Get.find(); - - bool isLoading = false; - - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: true, - appBar: AppBar( - leading: IconButton( - onPressed: () { - Navigator.pop(context); - }, - icon: const Icon(Ionicons.close), - ), - ), - body: SafeArea( - minimum: const EdgeInsets.all(12), - child: isLoading - ? const Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - CircularProgressIndicator(), - SizedBox(height: 22), - Text("Creating post") - ], - ), - ) - : Form( - key: formState, - child: Column( - children: [ - TextFormField( - controller: titleController, - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (input) { - if (input!.isEmpty) { - return "Please input a memorable title to your post"; - } - return null; - }, - style: Theme.of(context).textTheme.headlineSmall, - decoration: InputDecoration( - hintText: "Title", - hintStyle: Theme.of(context).textTheme.headlineSmall, - border: const OutlineInputBorder( - borderSide: BorderSide.none, - ), - ), - ), - const SizedBox(height: 22), - TextFormField( - controller: bodyController, - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (input) { - if (input!.isEmpty) { - return "Your body cannot be empty"; - } - return null; - }, - maxLines: 15, - decoration: const InputDecoration( - hintText: "Body Text", - border: OutlineInputBorder( - borderSide: BorderSide.none, - ), - ), - ), - const Spacer(), - Container( - padding: const EdgeInsets.all(12), - width: MediaQuery.of(context).size.width, - height: 60, - child: ListView.separated( - scrollDirection: Axis.horizontal, - shrinkWrap: true, - itemBuilder: (context, index) { - return PostAttachmentImageWidget( - media: imageFiles[index], - onDoubleTap: () { - setState(() { - imageFiles.removeAt(index); - }); - }, - ); - }, - separatorBuilder: (context, index) { - return const SizedBox(width: 8); - }, - itemCount: imageFiles.length, - ), - ), - FilledButton( - onPressed: () async { - if (!formState.currentState!.validate()) { - return; - } - // Create post - setState(() { - isLoading = true; - }); - final postresponse = await chirpController.createPost( - titleController.text, - bodyController.text, - ); - postresponse.fold((l) { - showDialog( - barrierDismissible: false, - context: context, - builder: (context) => AlertDialog( - title: const Text("Error"), - content: Text("Error uploading post: $l"), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("okay")) - ], - )); - }, (r) { - showDialog( - barrierDismissible: false, - context: context, - builder: (context) => AlertDialog( - title: const Text("Success"), - content: - const Text("Post uploaded sucessfully"), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - Navigator.pop(context); - }, - child: const Text("okay")) - ], - )); - }); - setState(() { - isLoading = false; - }); - // Navigator.pop(context); - }, - child: const Text("Post"), - ), - ], - ), - ), - ), - ); - } -} - -class PostAttachmentImageWidget extends StatelessWidget { - const PostAttachmentImageWidget({ - super.key, - required this.media, - this.onDoubleTap, - }); - final XFile media; - final VoidCallback? onDoubleTap; - - @override - Widget build(BuildContext context) { - return GestureDetector( - onDoubleTap: onDoubleTap, - child: Container( - color: Colors.blue, - width: 80, - height: 80, - decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)), - child: FutureBuilder( - future: media.readAsBytes(), - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return Text( - "Processing image", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineSmall, - ); - } - return Center( - child: Image.memory( - snapshot.data!, - height: 400, - ), - ); - }, - ), - ), - ); - } -} diff --git a/lib/tools/chirp/pages/post_view_page.dart b/lib/tools/chirp/pages/post_view_page.dart deleted file mode 100644 index 4316362..0000000 --- a/lib/tools/chirp/pages/post_view_page.dart +++ /dev/null @@ -1,252 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import '../controllers/chirp_controller.dart'; -import '../widgets/widgets.dart'; - -class PostViewPage extends StatefulWidget { - const PostViewPage({ - super.key, - required this.post, - }); - final Post post; - @override - State createState() => _PostViewPageState(); -} - -class _PostViewPageState extends State { - final controller = Get.find(); - final userController = Get.find(); - final TextEditingController replyController = TextEditingController(); - - int upvotes = 0; - int downvotes = 0; - PostService ps = PostService(); - - @override - void initState() { - super.initState(); - upvotes = widget.post.upvotes; - downvotes = widget.post.downvotes; - } - - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: true, - body: CustomScrollView( - slivers: [ - SliverAppBar( - pinned: true, - floating: true, - snap: true, - backgroundColor: Theme.of(context).colorScheme.primaryContainer, - flexibleSpace: FlexibleSpaceBar( - title: - Text("@${widget.post.user?.username ?? 'anon'}${"'"}s post"), - ), - ), - SliverPadding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - sliver: SliverToBoxAdapter( - child: Text( - utf8convert(widget.post.title), - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - ), - ), - SliverVisibility( - visible: widget.post.postAttachmentMedia.isNotEmpty, - sliver: SliverToBoxAdapter( - child: SizedBox( - height: 300, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemBuilder: (context, index) { - final data = widget.post.postAttachmentMedia[index]; - return CachedNetworkImage( - imageUrl: data.image ?? "", - fit: BoxFit.cover, - width: MediaQuery.of(context).size.width, - ); - }, - itemCount: widget.post.postAttachmentMedia.length, - ), - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.only(left: 12), - sliver: SliverToBoxAdapter( - child: Text( - utf8convert(widget.post.content), - ), - ), - ), - SliverToBoxAdapter( - child: Row( - children: [ - IconButton( - onPressed: () { - ps - .postVote(userController.authHeaders, "upvote", - widget.post.id) - .then((value) { - value.fold((l) { - debugPrint(l); - }, (r) { - if (r) { - setState(() { - upvotes++; - }); - } else { - setState(() { - upvotes--; - }); - } - }); - }); - }, - icon: Row( - children: [ - const Icon( - Ionicons.arrow_up_circle_outline, - ), - const SizedBox(width: 4), - Text( - upvotes.toString(), - ) - ], - ), - ), - const SizedBox(width: 2), - IconButton( - onPressed: () { - ps - .postVote(userController.authHeaders, "downvote", - widget.post.id) - .then((value) { - value.fold((l) { - debugPrint(l); - }, (r) { - if (r) { - setState(() { - downvotes++; - }); - } else { - setState(() { - downvotes--; - }); - } - }); - }); - }, - icon: const Icon(Ionicons.arrow_down_circle_outline), - ), - ], - ), - ), - SliverPadding( - padding: const EdgeInsets.all(8), - sliver: SliverToBoxAdapter( - child: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: Theme.of(context).colorScheme.tertiaryContainer, - ), - child: const Text( - "Swipe a comment to reply to it 😉", - textAlign: TextAlign.center, - ), - ), - ), - ), - SliverFillRemaining( - hasScrollBody: true, - child: Stack( - children: [ - FutureBuilder( - future: controller.fetchPostComments(widget.post), - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return const Center( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CircularProgressIndicator(), - SizedBox(height: 12), - Text("Fetching awesome comments"), - ], - ), - ); - } - return snapshot.data!.fold((l) { - return Text(l); - }, (r) { - return ListView.separated( - itemBuilder: (context, index) { - final data = r[index]; - return CommentWidget(comment: data); - }, - separatorBuilder: (context, index) => - const SizedBox(), - itemCount: r.length); - }); - }), - Align( - alignment: Alignment.bottomCenter, - child: Container( - color: Theme.of(context).colorScheme.surface, - padding: const EdgeInsets.all(12), - child: TextFormField( - controller: replyController, - decoration: InputDecoration( - fillColor: - Theme.of(context).colorScheme.surfaceContainer, - suffixIcon: IconButton( - onPressed: () async { - final result = await controller.postComment( - userController.user.value!.id!, - widget.post.id, - null, - replyController.text, - ); - result.fold((l) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Error"), - content: Text(l), - )); - }, (r) { - setState(() { - replyController.clear(); - }); - }); - FocusManager.instance.primaryFocus?.unfocus(); - }, - icon: const Icon(Ionicons.send), - ), - hintText: "Send a reply", - border: OutlineInputBorder( - borderSide: const BorderSide(width: 1), - borderRadius: BorderRadius.circular(24), - ), - ), - ), - ), - ) - ], - ), - ), - ], - ), - ); - } -} diff --git a/lib/tools/chirp/pages/story_view_page.dart b/lib/tools/chirp/pages/story_view_page.dart deleted file mode 100644 index 678b9ba..0000000 --- a/lib/tools/chirp/pages/story_view_page.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import 'package:story_view/story_view.dart'; - -class StoryViewPage extends StatefulWidget { - const StoryViewPage({ - super.key, - required this.organization, - required this.stories, - }); - - final Organization organization; - final List stories; - - @override - State createState() => _StoryViewPageState(); -} - -class _StoryViewPageState extends State { - final StoryController _storyController = StoryController(); - final OrganizationController _organizationController = - Get.find(); - final UserController _userController = Get.find(); - - List buildStoryItems(List stories) { - List storyItems = []; - for (final story in stories) { - switch (story.fileType) { - case "image": - storyItems.add( - StoryItem.pageImage( - url: "${ChirpService.urlPrefix}${story.media}", - controller: _storyController, - caption: Text( - story.description, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Colors.white, - ), - ), - loadingWidget: const CircularProgressIndicator.adaptive(), - ), - ); - - break; - case "video": - storyItems.add( - StoryItem.pageVideo( - "${ChirpService.urlPrefix}${story.media}", - controller: _storyController, - caption: Text( - story.description, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Colors.white, - ), - ), - loadingWidget: const CircularProgressIndicator.adaptive(), - ), - ); - break; - - case "text": - storyItems.add( - StoryItem.text( - title: story.description, - backgroundColor: - Theme.of(context).colorScheme.tertiaryContainer), - ); - break; - - default: - } - } - - return storyItems; - } - - @override - Widget build(BuildContext context) { - return Scaffold( - extendBodyBehindAppBar: true, - appBar: AppBar( - backgroundColor: Colors.transparent, - title: Row( - children: [ - CircleAvatar( - backgroundImage: CachedNetworkImageProvider( - widget.organization.logo!, - ), - ), - const SizedBox(width: 8), - Text( - widget.organization.name, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Colors.white, - ), - ), - ], - ), - ), - body: StoryView( - repeat: false, - onStoryShow: (storyitem, index) async { - await _organizationController.markStoryAsViewed( - _userController.authHeaders, - widget.stories.elementAt(index).id, - ); - }, - onComplete: () { - Navigator.pop(context); - }, - indicatorColor: Theme.of(context).colorScheme.secondaryContainer, - indicatorForegroundColor: Theme.of(context).primaryColor, - storyItems: buildStoryItems(widget.stories), - controller: _storyController, - ), - ); - } -} diff --git a/lib/tools/chirp/widgets/comment_widget.dart b/lib/tools/chirp/widgets/comment_widget.dart deleted file mode 100644 index 1a336e8..0000000 --- a/lib/tools/chirp/widgets/comment_widget.dart +++ /dev/null @@ -1,182 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import '../controllers/chirp_controller.dart'; -import 'package:swipe_to/swipe_to.dart'; -import 'package:get/get.dart'; -import 'package:timeago/timeago.dart' as timeago; - -class CommentWidget extends StatefulWidget { - const CommentWidget({ - super.key, - required this.comment, - this.depth = 0, - }); - final Comment comment; - final int depth; - - @override - State createState() => _CommentWidgetState(); -} - -class _CommentWidgetState extends State { - bool showSubPosts = false; - final userController = Get.find(); - final chirpController = Get.find(); - final TextEditingController replyController = TextEditingController(); - @override - void initState() { - super.initState(); - setState(() { - showSubPosts = widget.depth > 10 ? false : true; - }); - } - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () { - setState(() { - showSubPosts = !showSubPosts; - }); - }, - child: SwipeTo( - onRightSwipe: (data) { - showModalBottomSheet( - useSafeArea: true, - context: context, - isScrollControlled: true, - builder: (context) => SingleChildScrollView( - child: Container( - padding: EdgeInsets.only( - top: 8, - right: 8, - left: 8, - bottom: MediaQuery.of(context).viewInsets.bottom, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "Send a reply to @${widget.comment.user.username}", - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 22), - TextFormField( - controller: replyController, - decoration: InputDecoration( - suffixIcon: IconButton( - onPressed: () async { - final result = await chirpController.postComment( - userController.user.value!.id!, - widget.comment.post.id, - widget.comment.id, - replyController.text, - ); - result.fold((l) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Error"), - content: Text(l), - )); - }, (r) { - setState(() { - widget.comment.replies.add(r); - }); - }); - }, - icon: const Icon(Ionicons.send), - ), - hintText: "Send a reply", - border: const OutlineInputBorder( - borderSide: BorderSide(width: 1), - ), - ), - ), - ], - ), - ), - ), - ); - }, - child: Container( - decoration: BoxDecoration( - border: Border( - left: BorderSide( - color: Theme.of(context).colorScheme.tertiary, - width: 1.0, - ), - ), - ), - padding: const EdgeInsets.all(4), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - widget.comment.user.profilePhoto != null && - widget.comment.user.profilePhoto != "" - ? CircleAvatar( - backgroundImage: CachedNetworkImageProvider( - widget.comment.user.profilePhoto ?? '', - ), - ) - : Image.asset( - "assets/images/male_student.png", - height: 30, - ), - const SizedBox(width: 4), - Text((userController.user.value?.id ?? "") == - widget.comment.user.id - ? "You" - : "@${widget.comment.user.username}"), - const SizedBox(width: 12), - Text( - timeago.format(widget.comment.createdAt), - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - const SizedBox(height: 4), - Text( - utf8convert( - widget.comment.content, - ), - style: Theme.of(context).textTheme.bodySmall, - ), - const SizedBox(height: 4), - Padding( - padding: const EdgeInsets.only( - left: 8, - ), - child: showSubPosts - ? ListView.builder( - shrinkWrap: true, - itemBuilder: (context, index) { - final data = widget.comment.replies[index]; - return CommentWidget( - comment: data, - depth: widget.depth + 1, - ); - }, - itemCount: widget.comment.replies.length, - ) - : Visibility( - visible: widget.comment.replies.isNotEmpty, - child: TextButton( - onPressed: () { - setState(() { - showSubPosts = !showSubPosts; - }); - }, - child: const Text("Show replies"), - ), - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/tools/chirp/widgets/empty_post_card.dart b/lib/tools/chirp/widgets/empty_post_card.dart deleted file mode 100644 index b7fae46..0000000 --- a/lib/tools/chirp/widgets/empty_post_card.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:flutter/material.dart'; - -class EmptyPostCard extends StatelessWidget { - const EmptyPostCard({super.key}); - - @override - Widget build(BuildContext context) { - return Container( - width: MediaQuery.of(context).size.width, - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 8, - ), - child: Column( - children: [ - Row( - children: [ - const CircleAvatar( - radius: 20, - ), - const SizedBox(width: 4), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - color: Theme.of(context).colorScheme.primaryContainer, - height: 10, - width: 60, - ), - const SizedBox(height: 2), - Container( - color: Theme.of(context).colorScheme.primaryContainer, - height: 10, - width: 120, - ), - ], - ), - const Spacer(), - const CircleAvatar(radius: 10) - ], - ), - const SizedBox(height: 4), - Container( - color: Theme.of(context).colorScheme.primaryContainer, - height: 60, - width: double.infinity, - ), - const SizedBox(height: 4), - Row( - children: [ - Container( - color: Theme.of(context).colorScheme.primaryContainer, - height: 20, - width: 60, - ), - const Spacer(), - Container( - color: Theme.of(context).colorScheme.primaryContainer, - height: 20, - width: 60, - ), - ], - ), - ], - ), - ); - } -} diff --git a/lib/tools/chirp/widgets/post_card.dart b/lib/tools/chirp/widgets/post_card.dart deleted file mode 100644 index 5ff8a57..0000000 --- a/lib/tools/chirp/widgets/post_card.dart +++ /dev/null @@ -1,213 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import '../pages/post_view_page.dart'; -import 'package:timeago/timeago.dart' as timeago; - -class PostCard extends StatefulWidget { - const PostCard({ - super.key, - required this.post, - }); - - final Post post; - - @override - State createState() => _PostCardState(); -} - -class _PostCardState extends State { - int upvotes = 0; - int downvotes = 0; - PostService ps = PostService(); - final UserController userController = Get.find(); - - @override - void initState() { - super.initState(); - upvotes = widget.post.upvotes; - downvotes = widget.post.downvotes; - } - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => PostViewPage( - post: widget.post, - ), - )); - }, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - widget.post.user!.profilePhoto != null && - widget.post.user!.profilePhoto != "" - ? CircleAvatar( - backgroundImage: CachedNetworkImageProvider( - widget.post.user?.profilePhoto ?? '', - ), - ) - : Image.asset( - "assets/images/male_student.png", - height: 30, - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "@${widget.post.user?.username ?? 'anon'}", - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith(fontWeight: FontWeight.w800), - ), - Text( - timeago.format(widget.post.createdAt), - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - ), - const Spacer(), - Visibility( - visible: widget.post.upvotes > 1000 ? true : false, - child: IconButton( - onPressed: () { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text("Awesome post ✨✨"), - content: const Text( - "This post has been handpicked by the community"), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Cool"), - ), - ], - ); - }); - }, - icon: const Icon(Ionicons.ribbon), - ), - ), - ], - ), - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - utf8convert(widget.post.title), - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w700, - ), - ), - const SizedBox(height: 4), - Visibility( - visible: widget.post.postAttachmentMedia.isNotEmpty, - child: SizedBox( - height: 200, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemBuilder: (context, index) { - final data = widget.post.postAttachmentMedia[index]; - return CachedNetworkImage( - imageUrl: data.image ?? "", - fit: BoxFit.fitWidth, - width: MediaQuery.of(context).size.width, - ); - }, - separatorBuilder: (context, index) => - const SizedBox(width: 4), - itemCount: widget.post.postAttachmentMedia.length, - ), - ), - ), - const SizedBox(height: 4), - Text( - utf8convert(trimTo99Characters(widget.post.content)), - style: Theme.of(context).textTheme.bodyMedium, - ), - ], - ), - const SizedBox(height: 4), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - OutlinedButton.icon( - icon: const Icon(Ionicons.arrow_up_circle_outline), - onPressed: () { - ps - .postVote(userController.authHeaders, "upvote", - widget.post.id) - .then((value) { - value.fold((l) { - debugPrint(l); - }, (r) { - if (r) { - setState(() { - upvotes++; - }); - } else { - setState(() { - upvotes--; - }); - } - }); - }); - }, - label: Text(upvotes.toString()), - ), - const SizedBox(width: 4), - OutlinedButton.icon( - label: Text(downvotes.toString()), - onPressed: () { - ps - .postVote(userController.authHeaders, "downvote", - widget.post.id) - .then((value) { - value.fold((l) { - debugPrint(l); - }, (r) { - if (r) { - setState(() { - downvotes++; - }); - } else { - setState(() { - downvotes--; - }); - } - }); - }); - }, - icon: const Icon(Ionicons.arrow_down_circle_outline), - ), - const Spacer(), - Row( - children: [ - const Icon( - Ionicons.chatbubble_ellipses, - ), - const SizedBox(width: 8), - Text(widget.post.commentsCount.toString()) - ], - ) - ], - ), - ], - ), - ), - ); - } -} diff --git a/lib/tools/chirp/widgets/story_header.dart b/lib/tools/chirp/widgets/story_header.dart deleted file mode 100644 index 70ece2b..0000000 --- a/lib/tools/chirp/widgets/story_header.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/chirp/pages/story_view_page.dart'; -import 'package:get/get.dart'; - -class StoryHeader extends StatelessWidget { - const StoryHeader({super.key}); - - @override - Widget build(BuildContext context) { - final OrganizationController organizationController = - Get.find(); - final UserController userController = Get.find(); - - return Container( - padding: const EdgeInsets.all(12), - height: 112, - child: FutureBuilder( - future: organizationController.fetchStories(userController.authHeaders), - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return ListView.builder( - shrinkWrap: true, - scrollDirection: Axis.horizontal, - itemBuilder: (context, index) => const CircleAvatar(radius: 35), - ); - } - - return Obx( - () => ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: organizationController.stories.keys.length, - separatorBuilder: (context, index) => const SizedBox(width: 4), - itemBuilder: (context, index) { - final data = - organizationController.stories.keys.elementAt(index); - return Visibility( - visible: organizationController.stories.values - .elementAt(index) - .isNotEmpty, - child: GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StoryViewPage( - organization: data, - stories: organizationController.stories.values - .elementAt(index), - ), - ), - ); - }, - child: Column( - children: [ - Badge( - backgroundColor: Colors.blue, - label: Text( - organizationController.stories.values - .elementAt(index) - .length - .toString(), - ), - child: CircleAvatar( - radius: 35, - backgroundImage: - CachedNetworkImageProvider(data.logo!), - ), - ), - Text( - data.name, - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - ), - ); - }, - ), - ); - }, - ), - ); - } -} diff --git a/lib/tools/chirp/widgets/widgets.dart b/lib/tools/chirp/widgets/widgets.dart deleted file mode 100644 index 85f4f4a..0000000 --- a/lib/tools/chirp/widgets/widgets.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'story_header.dart'; -export 'post_card.dart'; -export 'comment_widget.dart'; -export 'empty_post_card.dart'; diff --git a/lib/tools/events/controllers/events_controller.dart b/lib/tools/events/controllers/events_controller.dart deleted file mode 100644 index 7a5db36..0000000 --- a/lib/tools/events/controllers/events_controller.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'dart:convert'; -import 'package:get/get.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import '../models/models.dart'; - -class EventsController extends GetxController { - late SharedPreferences prefs; - Rxn currentSemester = Rxn(); - final SemesterService semesterService = SemesterService(); - - @override - void onInit() async { - super.onInit(); - prefs = await SharedPreferences.getInstance(); - final semData = prefs.getString("semester"); - - if (semData != null) { - currentSemester.value = Semester.fromJson(json.decode(semData)); - return; - } - - if (semData == null || - currentSemester.value!.endDate.isBefore(DateTime.now())) { - // fetch the current semester - final result = await semesterService.fetchCurrentSemester(); - result.fold((l) {}, (r) async { - await prefs.setString("semester", json.encode(r.toJson())); - currentSemester.value = r; - }); - } - } -} diff --git a/lib/tools/events/events.dart b/lib/tools/events/events.dart deleted file mode 100644 index 9a29513..0000000 --- a/lib/tools/events/events.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'pages/pages.dart'; -export 'controllers/events_controller.dart'; diff --git a/lib/tools/events/models/core/event.dart b/lib/tools/events/models/core/event.dart deleted file mode 100644 index ef5876e..0000000 --- a/lib/tools/events/models/core/event.dart +++ /dev/null @@ -1,67 +0,0 @@ -class Event { - String id; - DateTime dateAdded; - String name; - String phone; - String email; - String location; - int likes; - String description; - String media; - String mediaType; - String? url; - DateTime startDate; - DateTime endDate; - - Event({ - required this.id, - required this.dateAdded, - required this.name, - required this.phone, - required this.email, - required this.location, - required this.likes, - required this.description, - required this.media, - required this.mediaType, - this.url, - required this.startDate, - required this.endDate, - }); - - factory Event.fromJson(Map json) { - return Event( - id: json['id'], - dateAdded: DateTime.parse(json['date_added']), - name: json['name'], - phone: json['phone'], - email: json['email'], - location: json['location'], - likes: json['likes'], - description: json['description'], - media: json['media'], - mediaType: json['media_type'], - url: json['url'], - startDate: DateTime.parse(json['start_date']), - endDate: DateTime.parse(json['end_date']), - ); - } - - Map toJson() { - return { - 'id': id, - 'date_added': dateAdded.toIso8601String(), - 'name': name, - 'phone': phone, - 'email': email, - 'location': location, - 'likes': likes, - 'description': description, - 'media': media, - 'media_type': mediaType, - 'url': url, - 'start_date': startDate.toIso8601String(), - 'end_date': endDate.toIso8601String(), - }; - } -} diff --git a/lib/tools/events/models/core/semester.dart b/lib/tools/events/models/core/semester.dart deleted file mode 100644 index 64b41d3..0000000 --- a/lib/tools/events/models/core/semester.dart +++ /dev/null @@ -1,39 +0,0 @@ -class Semester { - String id; - DateTime dateAdded; - String name; - String code; - DateTime startDate; - DateTime endDate; - - Semester({ - required this.id, - required this.dateAdded, - required this.name, - required this.code, - required this.startDate, - required this.endDate, - }); - - factory Semester.fromJson(Map json) { - return Semester( - id: json['id'], - dateAdded: DateTime.parse(json['date_added']), - name: json['name'], - code: json['code'], - startDate: DateTime.parse(json['start_date']), - endDate: DateTime.parse(json['end_date']), - ); - } - - Map toJson() { - return { - 'id': id, - 'date_added': dateAdded.toIso8601String(), - 'name': name, - 'code': code, - 'start_date': startDate.toIso8601String(), - 'end_date': endDate.toIso8601String(), - }; - } -} diff --git a/lib/tools/events/models/core/semester_event.dart b/lib/tools/events/models/core/semester_event.dart deleted file mode 100644 index 89bc3f5..0000000 --- a/lib/tools/events/models/core/semester_event.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:intl/intl.dart'; - -class SemesterEvent { - String id; - String name; - DateTime startDate; - DateTime endDate; - String semester; - - SemesterEvent({ - required this.id, - required this.name, - required this.startDate, - required this.endDate, - required this.semester, - }); - - factory SemesterEvent.fromJson(Map json) { - return SemesterEvent( - id: json['id'], - name: json['name'], - startDate: DateTime.parse(json['start_date']), - endDate: DateTime.parse(json['end_date']), - semester: json['semester'], - ); - } - - Map toJson() { - final DateFormat formatter = DateFormat('yyyy-MM-ddTHH:mm:ssZ'); - return { - 'id': id, - 'name': name, - 'start_date': formatter.format(startDate), - 'end_date': formatter.format(endDate), - 'semester': semester, - }; - } -} diff --git a/lib/tools/events/models/models.dart b/lib/tools/events/models/models.dart deleted file mode 100644 index ad17d2b..0000000 --- a/lib/tools/events/models/models.dart +++ /dev/null @@ -1,5 +0,0 @@ -export 'core/event.dart'; -export 'core/semester.dart'; -export 'core/semester_event.dart'; -export 'services/event_service.dart'; -export 'services/semester_service.dart'; diff --git a/lib/tools/events/models/services/event_service.dart b/lib/tools/events/models/services/event_service.dart deleted file mode 100644 index 4826506..0000000 --- a/lib/tools/events/models/services/event_service.dart +++ /dev/null @@ -1,109 +0,0 @@ -import 'dart:convert'; - -import 'package:dartz/dartz.dart'; -import 'package:http/http.dart' as http; -import '../models.dart'; -import './notifier_service.dart'; - -class EventsService with NotifierService { - Future>> fetchDueEvents() async { - try { - final response = - await http.get(Uri.parse("$notifierUrlPrefix/events/due")); - - if (response.statusCode == 200) { - final body = json.decode(response.body).cast>(); - return right(body.map((e) => Event.fromJson(e)).toList().cast()); - } - - return const Left("We are maintaining the server please try again later"); - } catch (e) { - return const Left( - "Error communicating to server please check your connection", - ); - } - } - - Future>> fetchAllEvents() async { - try { - final response = - await http.get(Uri.parse("$notifierUrlPrefix/events/all")); - - if (response.statusCode == 200) { - final body = json.decode(response.body).cast>(); - return right(body.map((e) => Event.fromJson(e)).toList().cast()); - } - - return const Left("We are maintaining the server please try again later"); - } catch (e) { - return const Left( - "Error communicating to server please check your connection", - ); - } - } - - Future>> fetchLikedEvents(String userID) async { - try { - final response = await http - .get(Uri.parse("$notifierUrlPrefix/events/liked-by/$userID")); - - if (response.statusCode == 200) { - final body = json.decode(response.body).cast>(); - return right(body.map((e) => Event.fromJson(e)).toList().cast()); - } - - return const Left("We are maintaining the server please try again later"); - } catch (e) { - return const Left( - "Error communicating to server please check your connection", - ); - } - } - - Future> isEventLikedByUser( - String userID, String eventID) async { - try { - final response = await http.get( - Uri.parse("$notifierUrlPrefix/events/is-liked-by/$eventID/$userID")); - - if (response.statusCode == 200) { - return right(true); - } - return right(false); - } catch (e) { - return const Left( - "Error communicating to server please check your connection", - ); - } - } - - Future> likeEvent( - String userID, - String eventID, - bool attending, - ) async { - try { - final response = await http.post( - Uri.parse("$notifierUrlPrefix/events/like"), - headers: {"Content-Type": "application/json"}, - body: json.encode( - {"user": userID, "event": eventID, "attending": attending})); - - if (response.statusCode == 201) { - final body = json.decode(response.body) as Map; - // if the id is null the user has unliked the pose - if (body["id"] == null) { - return right(false); - } - // otherwise the user likes the post - return right(true); - } - - return const Left("We are maintaining the server please try again later"); - } catch (e) { - return const Left( - "Error communicating to server please check your connection", - ); - } - } -} diff --git a/lib/tools/events/models/services/notifier_service.dart b/lib/tools/events/models/services/notifier_service.dart deleted file mode 100644 index 3060d43..0000000 --- a/lib/tools/events/models/services/notifier_service.dart +++ /dev/null @@ -1,4 +0,0 @@ -mixin NotifierService { - final String notifierUrlPrefix = "http://notifier.erick.serv00.net"; - // final String notifierUrlPrefix = "http://localhost:8000"; -} diff --git a/lib/tools/events/models/services/semester_service.dart b/lib/tools/events/models/services/semester_service.dart deleted file mode 100644 index 5a10cdd..0000000 --- a/lib/tools/events/models/services/semester_service.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'dart:convert'; - -import 'package:dartz/dartz.dart'; -import 'package:http/http.dart' as http; -import '../models.dart'; -import 'notifier_service.dart'; - -class SemesterService with NotifierService { - Future>> fetchAllSemesters() async { - try { - final response = - await http.get(Uri.parse("$notifierUrlPrefix/semesters/all")); - - if (response.statusCode == 200) { - final body = json.decode(response.body).cast>(); - return right( - body.map((e) => Semester.fromJson(e)).toList().cast()); - } - - return const Left("We are maintaining the server please try again later"); - } catch (e) { - return const Left( - "Error communicating to server please check your connection", - ); - } - } - - Future>> fetchSemesterEvent( - String semester) async { - try { - final response = await http - .get(Uri.parse("$notifierUrlPrefix/semesters/events/$semester")); - - if (response.statusCode == 200) { - final body = json.decode(response.body).cast>(); - return right(body - .map((e) => SemesterEvent.fromJson(e)) - .toList() - .cast()); - } - - return const Left("We are maintaining the server please try again later"); - } catch (e) { - return const Left( - "Error communicating to server please check your connection", - ); - } - } - - Future> fetchCurrentSemester() async { - try { - final response = - await http.get(Uri.parse("$notifierUrlPrefix/semesters/current")); - - if (response.statusCode == 200) { - final List> body = - json.decode(response.body).cast>(); - return right(Semester.fromJson(body.first)); - } - - return const Left("We are maintaining the server please try again later"); - } catch (e) { - return const Left( - "Error communicating to server please check your connection", - ); - } - } -} diff --git a/lib/tools/events/pages/event_view_page.dart b/lib/tools/events/pages/event_view_page.dart deleted file mode 100644 index ce0476f..0000000 --- a/lib/tools/events/pages/event_view_page.dart +++ /dev/null @@ -1,114 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import '../models/models.dart'; - -class EventViewPage extends StatelessWidget { - EventViewPage({ - super.key, - required this.event, - }); - final Event event; - final EventsService es = EventsService(); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - minimum: const EdgeInsets.only(bottom: 12), - child: CustomScrollView( - slivers: [ - SliverAppBar( - title: Text( - event.name, - ), - expandedHeight: 250, - snap: true, - pinned: true, - floating: true, - flexibleSpace: FlexibleSpaceBar( - background: CachedNetworkImage( - imageUrl: event.media, - fit: BoxFit.cover, - errorWidget: (context, error, child) => Container( - color: Theme.of(context).colorScheme.surface, - ), - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverToBoxAdapter( - child: Text( - "Event Information", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineSmall, - ), - ), - ), - SliverToBoxAdapter( - child: Column( - children: [ - ListTile( - leading: const Icon(Ionicons.heart), - title: const Text("Likes"), - subtitle: Text(event.likes.toString()), - subtitleTextStyle: Theme.of(context).textTheme.bodySmall, - ), - ListTile( - leading: const Icon(Ionicons.mail), - title: const Text("Contact Email"), - subtitle: Text(event.email), - subtitleTextStyle: Theme.of(context).textTheme.bodySmall, - ), - ListTile( - leading: const Icon(Ionicons.phone_portrait), - title: const Text("Contact Phone"), - subtitle: Text(event.phone), - subtitleTextStyle: Theme.of(context).textTheme.bodySmall, - ), - ListTile( - leading: const Icon(Ionicons.compass), - title: const Text("Location"), - subtitle: Text(event.location), - subtitleTextStyle: Theme.of(context).textTheme.bodySmall, - ), - ListTile( - leading: const Icon(Ionicons.today), - title: const Text("Starts"), - subtitle: Text(formatDateTime(event.startDate)), - subtitleTextStyle: Theme.of(context).textTheme.bodySmall, - ), - ListTile( - leading: const Icon(Ionicons.calendar), - title: const Text("Ends"), - subtitle: Text(formatDateTime(event.endDate)), - subtitleTextStyle: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - ), - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverFillRemaining( - hasScrollBody: true, - child: ListView( - // crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - "Event Description", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineSmall, - ), - Text( - event.description, - textAlign: TextAlign.justify, - ) - ], - ), - ), - ) - ], - ), - ), - ); - } -} diff --git a/lib/tools/events/pages/events_feed_page.dart b/lib/tools/events/pages/events_feed_page.dart deleted file mode 100644 index a9927ac..0000000 --- a/lib/tools/events/pages/events_feed_page.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:flutter/material.dart'; -import 'package:lottie/lottie.dart'; -import '../models/models.dart'; -import '../widgets/widgets.dart'; - -class EventsFeedPage extends StatelessWidget { - EventsFeedPage({super.key}); - - final EventsService es = EventsService(); - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: es.fetchAllEvents(), - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return Column( - children: [ - Lottie.asset("assets/lotties/fetching.json"), - const SizedBox(height: 12), - Text( - "Fetching awesome events", - style: Theme.of(context).textTheme.headlineSmall, - ), - ], - ); - } - return snapshot.data!.fold((l) { - return Column( - children: [ - Lottie.asset("assets/lotties/error.json"), - const SizedBox(height: 12), - Text( - "Arg snap: $l", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineSmall, - ), - ], - ); - }, (r) { - return ListView.separated( - itemBuilder: (context, index) { - final event = r[index]; - return EventCard(event: event); - }, - separatorBuilder: (context, index) => const SizedBox(height: 2), - itemCount: r.length, - ); - }); - }, - ); - } -} diff --git a/lib/tools/events/pages/events_page.dart b/lib/tools/events/pages/events_page.dart deleted file mode 100644 index 55c7070..0000000 --- a/lib/tools/events/pages/events_page.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; - -import 'events_feed_page.dart'; -import 'liked_events_page.dart'; -import 'semester_page.dart'; - -class EventsPage extends StatefulWidget { - const EventsPage({super.key}); - - @override - State createState() => _EventsPageState(); -} - -class _EventsPageState extends State - with AutomaticKeepAliveClientMixin { - @override - bool get wantKeepAlive => true; - - @override - Widget build(BuildContext context) { - super.build(context); - return DefaultTabController( - length: 3, - child: Scaffold( - appBar: AppBar( - title: Text( - "Events", - style: GoogleFonts.oleoScript(), - ), - bottom: const TabBar( - tabs: [ - Tab(text: "All Events"), - Tab(text: "Timeline"), - Tab(text: "Liked Events"), - ], - ), - ), - body: TabBarView( - children: [ - EventsFeedPage(), - SemesterPage(), - LikedEventsPage(), - ], - ), - ), - ); - } -} diff --git a/lib/tools/events/pages/liked_events_page.dart b/lib/tools/events/pages/liked_events_page.dart deleted file mode 100644 index 4d06e6f..0000000 --- a/lib/tools/events/pages/liked_events_page.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:academia/controllers/user_controller.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import '../models/models.dart'; -import '../widgets/widgets.dart'; -import 'package:lottie/lottie.dart'; - -class LikedEventsPage extends StatelessWidget { - LikedEventsPage({super.key}); - - final EventsService es = EventsService(); - final userController = Get.find(); - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: es.fetchLikedEvents(userController.user.value!.id!), - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return Column( - children: [ - Lottie.asset("assets/lotties/fetching.json"), - const SizedBox(height: 12), - Text( - "Fetching awesome events", - style: Theme.of(context).textTheme.headlineSmall, - ), - ], - ); - } - return snapshot.data!.fold((l) { - return Column( - children: [ - Lottie.asset("assets/lotties/error.json"), - const SizedBox(height: 12), - Text( - "Arg snap: $l", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineSmall, - ), - ], - ); - }, (r) { - return r.isEmpty - ? Column( - children: [ - Lottie.asset("assets/lotties/empty.json"), - const SizedBox(height: 12), - Text( - "Like an event to see it here", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineSmall, - ), - ], - ) - : ListView.separated( - itemBuilder: (context, index) { - final event = r[index]; - return EventCard(event: event); - }, - separatorBuilder: (context, index) => - const SizedBox(height: 2), - itemCount: r.length, - ); - }); - }, - ); - } -} diff --git a/lib/tools/events/pages/pages.dart b/lib/tools/events/pages/pages.dart deleted file mode 100644 index 7604a9f..0000000 --- a/lib/tools/events/pages/pages.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'events_feed_page.dart'; -export 'events_page.dart'; -export 'liked_events_page.dart'; -export 'semester_event_view.dart'; diff --git a/lib/tools/events/pages/semester_event_view.dart b/lib/tools/events/pages/semester_event_view.dart deleted file mode 100644 index 1236047..0000000 --- a/lib/tools/events/pages/semester_event_view.dart +++ /dev/null @@ -1,152 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:lottie/lottie.dart'; -import '../models/models.dart'; - -class SemesterEventView extends StatelessWidget { - const SemesterEventView({ - super.key, - required this.semester, - }); - final Semester semester; - static SemesterService ss = SemesterService(); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: CustomScrollView( - slivers: [ - SliverAppBar( - snap: true, - pinned: true, - floating: true, - expandedHeight: 250, - flexibleSpace: FlexibleSpaceBar( - title: const Text("Semester details"), - background: Container( - color: Theme.of(context).colorScheme.tertiaryContainer, - ), - ), - ), - SliverPadding( - padding: EdgeInsets.zero, //.all(12), - sliver: SliverToBoxAdapter( - child: Column( - children: [ - ListTile( - leading: const Icon(Ionicons.extension_puzzle), - title: const Text( - "Semester name", - ), - subtitle: Text( - semester.name, - style: Theme.of(context).textTheme.bodySmall, - ), - ), - ListTile( - leading: const Icon(Ionicons.code_slash_sharp), - title: const Text( - "Semester code", - ), - subtitle: Text( - semester.code, - style: Theme.of(context).textTheme.bodySmall, - ), - ), - ListTile( - leading: const Icon(Ionicons.calendar), - title: const Text( - "Start date", - ), - subtitle: Text( - formatDateTime(semester.startDate), - style: Theme.of(context).textTheme.bodySmall, - ), - ), - ListTile( - leading: const Icon(Ionicons.today), - title: const Text( - "End date", - ), - subtitle: Text( - formatDateTime(semester.endDate), - style: Theme.of(context).textTheme.bodySmall, - ), - ), - ], - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverToBoxAdapter( - child: Text( - "Semester events", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineSmall, - ), - ), - ), - SliverFillRemaining( - child: FutureBuilder( - future: ss.fetchSemesterEvent(semester.id), - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return const Center( - child: CircularProgressIndicator(), - ); - } - return snapshot.data!.fold( - (l) => SingleChildScrollView( - child: Column( - children: [ - Lottie.asset("assets/lotties/error.json"), - const SizedBox(height: 12), - Text( - "Holy .. $l", - style: - Theme.of(context).textTheme.headlineSmall, - ), - ], - ), - ), (r) { - return ListView.separated( - itemBuilder: (context, index) { - final data = r[index]; - return ListTile( - leading: CircleAvatar( - child: Text((index + 1).toString()), - ), - title: Text(data.name), - trailing: Icon( - data.endDate.isBefore(DateTime.now()) - ? Ionicons.checkmark_done_circle - : Ionicons.chevron_up_circle_outline, - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Starts: ${formatDateTime(data.startDate)}", - style: Theme.of(context).textTheme.bodySmall, - ), - const SizedBox(height: 4), - Text( - "Ends: ${formatDateTime(data.endDate)}", - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - ); - }, - separatorBuilder: (context, index) => - const SizedBox(height: 4), - itemCount: r.length); - }); - }, - ), - ) - ], - ), - ); - } -} diff --git a/lib/tools/events/pages/semester_page.dart b/lib/tools/events/pages/semester_page.dart deleted file mode 100644 index 5db78bf..0000000 --- a/lib/tools/events/pages/semester_page.dart +++ /dev/null @@ -1,102 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:lottie/lottie.dart'; -import '../models/models.dart'; - -class SemesterPage extends StatelessWidget { - SemesterPage({super.key}); - final SemesterService ss = SemesterService(); - - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: ss.fetchAllSemesters(), - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return Column( - children: [ - Lottie.asset("assets/lotties/loading.json"), - const SizedBox( - height: 12, - ), - Text( - "Fetching available semesters", - style: Theme.of(context).textTheme.headlineSmall, - ), - ], - ); - } - if (snapshot.hasError) { - return Column( - children: [ - Lottie.asset("assets/lotties/error.json"), - const SizedBox( - height: 12, - ), - Text( - "We ran into a problem loading pages", - style: Theme.of(context).textTheme.headlineSmall, - ), - ], - ); - } - - return snapshot.data!.fold((l) { - return Column( - children: [ - Lottie.asset("assets/lotties/error.json"), - const SizedBox( - height: 12, - ), - Text( - "Jeez: $l", - style: Theme.of(context).textTheme.headlineSmall, - ), - ], - ); - }, (r) { - if (r.isEmpty) { - return Column( - children: [ - Lottie.asset("assets/lotties/empty.json"), - const SizedBox( - height: 12, - ), - Text( - "No semesters are in session please try again later", - style: Theme.of(context).textTheme.headlineSmall, - ), - ], - ); - } - - return ListView.separated( - itemBuilder: (context, index) { - final data = r[index]; - return ListTile( - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => SemesterEventView(semester: data), - ), - ); - }, - title: Text(data.name), - leading: CircleAvatar( - child: Text((index + 1).toString()), - ), - subtitle: Text( - "Code: ${data.code}", - style: Theme.of(context).textTheme.bodySmall, - ), - trailing: const Icon(Ionicons.arrow_redo_outline), - ); - }, - separatorBuilder: (context, index) { - return const SizedBox(height: 4); - }, - itemCount: r.length, - ); - }); - }); - } -} diff --git a/lib/tools/events/widgets/event_feed_widget.dart b/lib/tools/events/widgets/event_feed_widget.dart deleted file mode 100644 index 695cabe..0000000 --- a/lib/tools/events/widgets/event_feed_widget.dart +++ /dev/null @@ -1,172 +0,0 @@ -import 'package:academia/controllers/controllers.dart'; -import 'package:academia/pages/util_pages/webview_page.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:ionicons/ionicons.dart'; -import 'package:cached_network_image/cached_network_image.dart'; -import '../models/models.dart'; -import '../pages/event_view_page.dart'; - -class EventCard extends StatefulWidget { - const EventCard({ - super.key, - required this.event, - }); - final Event event; - - @override - State createState() => _EventCardState(); -} - -class _EventCardState extends State { - final userController = Get.find(); - final EventsService es = EventsService(); - int likes = 0; - @override - void initState() { - super.initState(); - setState(() { - likes = widget.event.likes; - }); - } - - Future like() async {} - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => EventViewPage( - event: widget.event, - ))); - }, - child: Container( - padding: const EdgeInsets.symmetric( - vertical: 2, - horizontal: 0, - ), - child: Column( - children: [ - Row( - children: [ - const CircleAvatar( - radius: 20, - child: Icon( - Ionicons.at_circle_outline, - ), - ), - const SizedBox(width: 4), - Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.event.name, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - Text( - widget.event.email, - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - const Spacer(), - IconButton( - onPressed: () {}, - icon: const Icon(Ionicons.arrow_redo_outline), - ) - ], - ), - const SizedBox(height: 4), - CachedNetworkImage( - imageUrl: widget.event.media, - height: 300, - fit: BoxFit.fill, - errorWidget: (context, str, error) => Container( - color: Theme.of(context).colorScheme.tertiaryContainer, - width: MediaQuery.of(context).size.width, - child: Center( - child: Text( - "Tap to view more", - style: Theme.of(context).textTheme.headlineSmall, - ), - ), - ), - ), - const SizedBox(height: 4), - Row( - children: [ - FutureBuilder( - future: es.isEventLikedByUser( - userController.user.value!.id!, widget.event.id), - builder: (context, snapshot) { - return IconButton( - onPressed: - snapshot.connectionState != ConnectionState.done - ? null - : () async { - es - .likeEvent( - userController.user.value!.id!, - widget.event.id, - false, - ) - .then((value) { - value.fold((l) { - setState(() { - likes--; - }); - }, (r) { - setState(() { - if (r) { - likes++; - } else { - likes--; - } - }); - }); - }); - }, - icon: Icon( - Ionicons.heart_outline, - color: snapshot.data?.fold((l) { - return null; - }, (r) { - if (r) { - return Colors.red; - } - return null; - }), - ), - ); - }, - ), - Text("$likes Likes "), - const Spacer(), - Visibility( - visible: widget.event.url != null, - child: TextButton( - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => WebviewPage( - title: "More about Event", - url: widget.event.url!, - ), - ), - ); - }, - child: const Text("More"), - ), - ) - ], - ) - ], - ), - ), - ); - } -} diff --git a/lib/tools/events/widgets/widgets.dart b/lib/tools/events/widgets/widgets.dart deleted file mode 100644 index f317626..0000000 --- a/lib/tools/events/widgets/widgets.dart +++ /dev/null @@ -1 +0,0 @@ -export 'event_feed_widget.dart'; diff --git a/lib/tools/exam_timetable/controllers/exam_timetable_controller.dart b/lib/tools/exam_timetable/controllers/exam_timetable_controller.dart deleted file mode 100644 index ad87ecb..0000000 --- a/lib/tools/exam_timetable/controllers/exam_timetable_controller.dart +++ /dev/null @@ -1,171 +0,0 @@ -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> quotes = []; - List exams = []; - final ExamModelHelper _examDbHelper = ExamModelHelper(); - final CourseModelHelper _courseDbHelper = CourseModelHelper(); - - Future 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 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> fetchExams(List 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 data = json.decode(response.body); - - // Convert the JSON data into a list of Exam objects - List 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 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 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 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> courseData = await _courseDbHelper - .queryAll(); // Assuming _courseDbHelper is your SQLite helper for courses - List courses = courseData.map((e) => Course.fromJson(e)).toList(); - - List courseTitles = courses - .map((e) => "${e.unit.replaceAll('-', '')}${e.section.split('-')[0]}") - .toList(); - - // 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(); - } -} diff --git a/lib/tools/exam_timetable/exam_timetable.dart b/lib/tools/exam_timetable/exam_timetable.dart deleted file mode 100644 index fe0699e..0000000 --- a/lib/tools/exam_timetable/exam_timetable.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'widgets/widgets.dart'; -export 'models/exam.dart'; -export 'controllers/exam_timetable_controller.dart'; -export 'pages/exam_timetable_page.dart'; \ No newline at end of file diff --git a/lib/tools/exam_timetable/models/exam.dart b/lib/tools/exam_timetable/models/exam.dart deleted file mode 100644 index e3b039d..0000000 --- a/lib/tools/exam_timetable/models/exam.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'exam_helper.dart'; -export 'exam_model.dart'; \ No newline at end of file diff --git a/lib/tools/exam_timetable/models/exam_helper.dart b/lib/tools/exam_timetable/models/exam_helper.dart deleted file mode 100644 index 346f8b0..0000000 --- a/lib/tools/exam_timetable/models/exam_helper.dart +++ /dev/null @@ -1,57 +0,0 @@ -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 create(Map 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>> queryAll() async { - final db = await DatabaseHelper().database; - final exams = await db.query('exams'); - return exams; - } - - @override - Future delete(Map data) async { - final db = await DatabaseHelper().database; - return await db.delete('exams', - where: 'course_code = ?', whereArgs: [data['course_code']]); - } - - @override - Future update(Map data) async { - final db = await DatabaseHelper().database; - return await db.update( - 'exams', - data, - where: 'course_code = ?', - whereArgs: [data['course_code']], - ); - } - - @override - Future truncate() async { - final db = await DatabaseHelper().database; - await db.execute('DELETE FROM exams'); - } -} diff --git a/lib/tools/exam_timetable/models/exam_model.dart b/lib/tools/exam_timetable/models/exam_model.dart deleted file mode 100644 index c1d2c17..0000000 --- a/lib/tools/exam_timetable/models/exam_model.dart +++ /dev/null @@ -1,49 +0,0 @@ -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 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 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'], - ); - } -} diff --git a/lib/tools/exam_timetable/pages/exam_timetable_page.dart b/lib/tools/exam_timetable/pages/exam_timetable_page.dart deleted file mode 100644 index 7bf19b2..0000000 --- a/lib/tools/exam_timetable/pages/exam_timetable_page.dart +++ /dev/null @@ -1,259 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import 'package:intl/intl.dart'; - -class ExamTimeTablePage extends StatefulWidget { - const ExamTimeTablePage({super.key}); - - @override - State createState() => _ExamTimeTablePageState(); -} - -class _ExamTimeTablePageState extends State { - final userController = Get.find(); - final settingsController = Get.find(); - final controller = Get.put(ExamsTimeTableController()); - final _searchController = TextEditingController(); - bool _isSearching = false; - bool _searchComplete = false; - List searchedExams = []; - - @override - Widget build(BuildContext context) { - return Scaffold( - body: CustomScrollView( - slivers: [ - SliverAppBar( - floating: true, - snap: true, - pinned: true, - title: Text( - "Hi ${userController.user.value!.firstName.title()}", - ), - actions: [ - IconButton( - onPressed: () { - Get.defaultDialog( - title: "Academia Help", - content: const Text( - "Never miss an exam again with the intuitive exam timetable\nDouble tap an exam card to remove it", - ), - ); - }, - icon: const Icon(Ionicons.help), - ), - ], - expandedHeight: 250, - flexibleSpace: FlexibleSpaceBar( - background: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.tertiaryContainer, - ), - padding: const EdgeInsets.all(12), - child: GestureDetector( - onTap: () => controller.previousQuote(), - onDoubleTap: () => controller.nextQuote(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox(height: 60), - const Spacer(), - Obx( - () => Text( - controller.index > -1 - ? controller.quotes[controller.index.value]["q"] - : "Fool me once shame on you; fool me twice, shame on me.", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleLarge), - ), - Obx( - () => Text( - controller.index > -1 - ? controller.quotes[controller.index.value]["a"] - : "Chinese Proverb", - textAlign: TextAlign.end, - ), - ), - ], - ), - ), - ), - ), - ), - SliverToBoxAdapter( - child: Obx( - () => controller.hasExams.value - ? Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: ExamCountDownWidget( - examCount: controller.exams.length, - exam: controller.exams[0], - endtime: getExamDate(controller.exams[0]), - ), - ) - : const SizedBox(), - ), - ), - SliverFillRemaining( - hasScrollBody: true, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Obx( - () => controller.hasExams.value - ? ListView.builder( - itemCount: controller.exams.length, - itemBuilder: (context, index) => GestureDetector( - onDoubleTap: () async { - await controller - .removeExamFromStorage(controller.exams[index]); - - controller.hasExams.refresh(); - }, - child: ExamCard(exam: controller.exams[index]), - ), - ) - : Center( - child: Text( - "Who dares summon me?🧞", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleLarge, - ), - ), - ), - ), - ) - ], - ), - floatingActionButton: FloatingActionButton( - onPressed: () { - showModalBottomSheet( - context: context, - builder: (context) => StatefulBuilder( - builder: (context, StateSetter setState) { - return Container( - padding: const EdgeInsets.all(16), - width: MediaQuery.of(context).size.width, - child: Column( - children: [ - TextField( - controller: _searchController, - decoration: InputDecoration( - hintText: "BIL 111K, ENG 112, ...", - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(4), - ), - suffixIcon: IconButton( - onPressed: () async { - if (_searchController.text.trim().isEmpty) { - Get.snackbar( - "Error", - "Please provide units to search", - backgroundColor: Theme.of(context) - .colorScheme - .errorContainer, - colorText: - Theme.of(context).colorScheme.onSurface, - ); - return; - } - if (mounted) { - setState(() { - _isSearching = true; - _searchComplete = false; - }); - searchedExams = await controller.fetchExams( - _searchController.text.split(","), - ); - - if (searchedExams.isNotEmpty) { - setState(() { - _searchComplete = true; - }); - } - - // await examtimetableController.fetchExams(); - if (!mounted) { - return; - } - setState(() { - _isSearching = false; - }); - } - }, - icon: const Icon(Ionicons.search), - ), - ), - ), - const SizedBox(height: 12), - _isSearching - ? Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: Theme.of(context) - .colorScheme - .tertiaryContainer, - ), - child: Text( - "Your wish is my command. Performing forbidden magic 🧞\n Double Tap a course to add it", - textAlign: TextAlign.center, - style: - Theme.of(context).textTheme.titleLarge), - ) - : _searchComplete - ? SizedBox( - height: - MediaQuery.of(context).size.height * 0.4, - child: ListView.builder( - itemCount: searchedExams.length, - itemBuilder: (context, index) => - GestureDetector( - onDoubleTap: () async { - // Add exam - await controller.addExamToStorage( - searchedExams[index]); - searchedExams - .remove(searchedExams[index]); - - controller.hasExams.refresh(); - setState(() {}); - }, - child: ExamCard( - exam: searchedExams[index], - ), - )), - ) - : Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: Theme.of(context) - .colorScheme - .tertiaryContainer, - ), - child: Text( - "Please input your units seperated with commas and let the genie work his forbidden magic!", - textAlign: TextAlign.center, - style: Theme.of(context) - .textTheme - .titleLarge!), - ), - ], - ), - ); - }, - ), - ); - }, - child: const Icon( - Ionicons.search, - ), - ), - ); - } - - DateTime getExamDate(Exam exam) { - final formatter = DateFormat('EEEE dd/MM/yy'); - return formatter.parse(exam.day.title()); - } -} diff --git a/lib/tools/exam_timetable/widgets/count_down_widget.dart b/lib/tools/exam_timetable/widgets/count_down_widget.dart deleted file mode 100644 index d0cfe79..0000000 --- a/lib/tools/exam_timetable/widgets/count_down_widget.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:academia/exports/barrel.dart'; - -class ExamCountDownWidget extends StatefulWidget { - const ExamCountDownWidget({ - super.key, - required this.endtime, - required this.examCount, - this.exam, - }); - final DateTime endtime; - final int examCount; - final Exam? exam; - - @override - State createState() => _ExamCountDownWidgetState(); -} - -class _ExamCountDownWidgetState extends State { - var isCritical = false; - @override - Widget build(BuildContext context) { - return Container( - // items: [ - // Container( - // decoration: BoxDecoration( - // color: isCritical - // ? Theme.of(context).colorScheme.errorContainer - // : Theme.of(context).colorScheme.primaryContainer, - // borderRadius: BorderRadius.circular(8)), - // width: MediaQuery.of(context).size.width * 0.8, - // child: Center( - // child: TimerCountdown( - // timeTextStyle: - // Theme.of(context).textTheme.displayMedium!.copyWith( - // fontFamily: GoogleFonts.figtree().fontFamily, - // ), - // format: CountDownTimerFormat.daysHoursMinutesSeconds, - // endTime: widget.endtime, - // onTick: ((remainingTime) { - // if (remainingTime.inMinutes < 5) { - // setState(() { - // isCritical = true; - // }); - // } - // }), - // ), - // ), - // ), - // widget.exam == null - // ? Container( - // decoration: BoxDecoration( - // color: Theme.of(context).colorScheme.primaryContainer, - // borderRadius: BorderRadius.circular(8)), - // width: MediaQuery.of(context).size.width * 0.8, - // child: Center( - // child: Text( - // "Aah sh*t here we go again", - // textAlign: TextAlign.center, - // style: Theme.of(context).textTheme.titleLarge!.copyWith( - // fontFamily: GoogleFonts.figtree().fontFamily, - // ), - // ), - // ), - // ) - // : ExamCard(exam: widget.exam!), - // Container( - // decoration: BoxDecoration( - // color: Theme.of(context).colorScheme.tertiaryContainer, - // borderRadius: BorderRadius.circular(8)), - // width: MediaQuery.of(context).size.width * 0.8, - // child: Center( - // child: Text( - // "${widget.examCount} exams left to go 🫡", - // textAlign: TextAlign.center, - // style: Theme.of(context).textTheme.titleLarge!.copyWith( - // fontFamily: GoogleFonts.figtree().fontFamily, - // ), - // ), - // ), - // ) - // ], - // options: CarouselOptions( - // height: 200, - // showIndicator: true, - // autoPlay: true, - // slideIndicator: const CircularSlideIndicator(slideIndicatorOptions: SlideIndicatorOptions(itemSpacing: 14))), - ); - } -} diff --git a/lib/tools/exam_timetable/widgets/exam_card.dart b/lib/tools/exam_timetable/widgets/exam_card.dart deleted file mode 100644 index b603e7e..0000000 --- a/lib/tools/exam_timetable/widgets/exam_card.dart +++ /dev/null @@ -1,77 +0,0 @@ -import 'package:academia/exports/barrel.dart'; - -class ExamCard extends StatelessWidget { - const ExamCard({super.key, required this.exam, this.ispast = false}); - final Exam exam; - final bool ispast; - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(top: 8), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: Theme.of(context).colorScheme.secondaryContainer, - ), - child: ListTile( - enabled: !ispast, - leading: const Icon(Ionicons.ribbon), - title: Text( - exam.courseCode.toString(), - ), - subtitle: Column( - children: [ - const SizedBox(height: 12), - Row( - children: [ - const Icon(Ionicons.calendar), - const SizedBox(width: 4), - Text( - exam.day, - ) - ], - ), - const SizedBox(height: 12), - Row( - children: [ - const Icon(Ionicons.location), - const SizedBox(width: 4), - Text( - exam.venue, - ), - const Spacer(), - const Icon(Ionicons.time), - const SizedBox(width: 2), - Text( - exam.time, - ), - ], - ), - const SizedBox(height: 12), - exam.coordinator != null && exam.invigilator != null - ? Column(children: [ - Row( - children: [ - Text( - "Coordiator: ${exam.coordinator.toString().title()}", - ), - ], - ), - const SizedBox(height: 12), - Row( - children: [ - Text( - "Invigilator ${exam.invigilator.toString().title()}", - overflow: TextOverflow.ellipsis, - ) - ], - ), - ]) - : const SizedBox() - ], - ), - ), - ), - ); - } -} diff --git a/lib/tools/exam_timetable/widgets/widgets.dart b/lib/tools/exam_timetable/widgets/widgets.dart deleted file mode 100644 index 643d9ba..0000000 --- a/lib/tools/exam_timetable/widgets/widgets.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'count_down_widget.dart'; -export 'exam_card.dart'; \ No newline at end of file diff --git a/lib/tools/fees/fees.dart b/lib/tools/fees/fees.dart deleted file mode 100644 index 9171956..0000000 --- a/lib/tools/fees/fees.dart +++ /dev/null @@ -1 +0,0 @@ -export 'pages/pages.dart'; diff --git a/lib/tools/fees/pages/fees_page.dart b/lib/tools/fees/pages/fees_page.dart deleted file mode 100644 index e0187ac..0000000 --- a/lib/tools/fees/pages/fees_page.dart +++ /dev/null @@ -1,149 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import 'package:lottie/lottie.dart'; -import '../widgets/fees_card.dart'; - -class FeesPage extends StatelessWidget { - const FeesPage({super.key}); - - @override - Widget build(BuildContext context) { - final SettingsController settingsController = - Get.find(); - return Scaffold( - body: CustomScrollView( - slivers: [ - SliverAppBar( - title: const Text( - "Fees Statements", - ), - centerTitle: true, - leading: IconButton( - onPressed: () { - Navigator.pop(context); - }, - icon: const Icon(Ionicons.close_outline), - ), - actions: [ - IconButton( - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Information"), - content: const Text( - "Here you can view your fees statements - data is provided as is", - ), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Oh ok")), - ], - ), - ); - }, - icon: const Icon(Ionicons.information), - ) - ], - ), - SliverFillRemaining( - child: Obx( - () => settingsController.settings.value.showFeeStatistics - ? FutureBuilder( - future: magnet.fetchFeeStatement(), - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Lottie.asset("assets/lotties/fetching.json"), - const SizedBox(height: 22), - Text( - "Loading fees statements, we are. Powerful, the Force flows.", - textAlign: TextAlign.center, - style: - Theme.of(context).textTheme.headlineSmall, - ), - ], - ); - } - - if (snapshot.hasError) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Lottie.asset("assets/lotties/error.json"), - const SizedBox(height: 22), - Text( - "Please try again if the problem persists try after an hour", - textAlign: TextAlign.center, - style: - Theme.of(context).textTheme.headlineSmall, - ), - ], - ); - } - - return snapshot.data!.fold( - (l) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Lottie.asset("assets/lotties/error.json"), - const SizedBox(height: 22), - Text( - "Lost, your records are. Found it, we could not.", - textAlign: TextAlign.center, - style: - Theme.of(context).textTheme.headlineSmall, - ), - ], - ); - }, - (r) { - r = r.reversed.toList(); - return Padding( - padding: const EdgeInsets.all(8), - child: ListView.separated( - separatorBuilder: (context, index) => - const SizedBox(height: 4), - itemBuilder: (context, index) { - final data = r[index]; - - return FeesCard( - data: data, - index: index, - ); - }, - itemCount: r.length, - ), - ); - }, - ); - }, - ) - : Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset( - "assets/images/sketchbook-woman-and-a-man-analyze-data-2.png", - ), - const SizedBox(height: 22), - Text( - "You have disabled showing fee statistics from settings, please enable the feature to show your statistics here", - style: Theme.of(context).textTheme.headlineSmall, - textAlign: TextAlign.center, - ), - ], - ), - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/tools/fees/pages/pages.dart b/lib/tools/fees/pages/pages.dart deleted file mode 100644 index a1828b8..0000000 --- a/lib/tools/fees/pages/pages.dart +++ /dev/null @@ -1 +0,0 @@ -export 'fees_page.dart'; diff --git a/lib/tools/fees/widgets/fees_card.dart b/lib/tools/fees/widgets/fees_card.dart deleted file mode 100644 index 99c1b8f..0000000 --- a/lib/tools/fees/widgets/fees_card.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'package:academia/exports/barrel.dart'; - -class FeesCard extends StatelessWidget { - const FeesCard({ - super.key, - required this.data, - this.index = 0, - }); - final Map data; - final int index; - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - border: Border.all( - color: Theme.of(context).colorScheme.primaryContainer, - ), - borderRadius: BorderRadius.circular(8), - ), - child: ListTile( - title: Text(data["ref"].toString().title()), - leading: CircleAvatar( - child: Text("${index + 1}"), - ), - contentPadding: EdgeInsets.zero, - subtitle: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - data["description"].toString().trim(), - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.primary, - ), - ), - Row( - children: [ - Text( - "Posting Date: ", - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.tertiary, - ), - ), - Text( - data["posting_date"].toString().trim(), - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - - // Currencies - Row( - children: [ - Text( - "Credit: ", - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.tertiary, - ), - ), - Text( - data["credit"].toString().trim(), - style: Theme.of(context).textTheme.bodySmall, - ), - const Spacer(), - Text( - "Debit: ", - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.tertiary, - ), - ), - Text( - data["debit"].toString().trim(), - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - const SizedBox(height: 4), - Text( - "Balance: ${data["running_balance"].toString().trim()}", - style: Theme.of(context).textTheme.bodySmall?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/tools/gpa_calculator/controllers/gpacalculator_controller.dart b/lib/tools/gpa_calculator/controllers/gpacalculator_controller.dart deleted file mode 100644 index a8617ee..0000000 --- a/lib/tools/gpa_calculator/controllers/gpacalculator_controller.dart +++ /dev/null @@ -1,134 +0,0 @@ -import 'package:academia/models/core/course/course_model.dart'; -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/gpa_calculator/models/unit.dart'; -import 'package:flutter/widgets.dart'; -import 'package:get/get.dart'; - -class GPACalculatorController extends GetxController { - RxList _registeredCourses = [].obs; - final RxList _courses = [].obs; - - List get courseList => _courses; - - List get registeredCoursesList => _registeredCourses; - double get gpa => _calculateGPA(); - - set registeredCourses(List courses) { - _registeredCourses = courses.obs; - } - - void addCourse(String name, String creditHours, String grade) { - double tcreditHours = 0; - try { - tcreditHours = double.parse(creditHours); - } catch (e) { - Get.defaultDialog( - title: "Incorrect credit Hours", - content: const Text("Credit hours must be a number")); - throw Exception('Credit hours must be a number'); - } - final course = Unit(name: name, creditHours: tcreditHours, grade: grade); - _courses.add(course); - } - - void removeCourse(int index) { - _courses.removeAt(index); - _courses.refresh(); - } - - void updateCourse( - {required int index, - required String newName, - // required String newCode, - required String newCreditHours, - required String newGrade}) { - _courses[index].name = newName; - // _courses[index].code = newCode; - try { - _courses[index].creditHours = double.parse(newCreditHours); - } catch (e) { - Get.defaultDialog( - title: "Incorrect credit Hours", - content: const Text("Credit hours must be a number")); - throw Exception('Credit hours must be a number'); - } - _courses[index].grade = newGrade; - _courses.refresh(); - } - - double _calculateGPA() { - double totalPoints = 0; - double totalCreditHours = 0; - for (var course in _courses) { - totalPoints += getGradePoints(course.grade) * (course.creditHours); - totalCreditHours += course.creditHours; - } - return (totalPoints / totalCreditHours).toPrecision(2); - } - - double getGradePoints(String grade) { - switch (grade) { - case 'A': - return 4.0; - case 'A-': - return 3.7; - case 'B+': - return 3.3; - case 'B': - return 3.0; - case 'B-': - return 2.7; - case 'C+': - return 2.3; - case 'C': - return 2.0; - case 'C-': - return 1.7; - case 'D+': - return 1.3; - case 'D': - return 1.0; - case 'D-': - return 0.7; - case 'E': - return 0.0; - case 'F': - return 0.0; - default: - return 0.0; - } - } - - bool testValidGrade(var grade) { - switch (grade) { - case 'A': - return true; - case 'A-': - return true; - case 'B+': - return true; - case 'B': - return true; - case 'B-': - return true; - case 'C+': - return true; - case 'C': - return true; - case 'C-': - return true; - case 'D+': - return true; - case 'D': - return true; - case 'D-': - return true; - case 'E': - return true; - case 'F': - return true; - default: - return false; - } - } -} diff --git a/lib/tools/gpa_calculator/gpacalculator.dart b/lib/tools/gpa_calculator/gpacalculator.dart deleted file mode 100644 index 26ff50e..0000000 --- a/lib/tools/gpa_calculator/gpacalculator.dart +++ /dev/null @@ -1 +0,0 @@ -export 'pages/gpacalculator_page.dart'; diff --git a/lib/tools/gpa_calculator/models/unit.dart b/lib/tools/gpa_calculator/models/unit.dart deleted file mode 100644 index a070935..0000000 --- a/lib/tools/gpa_calculator/models/unit.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Unit { - String name; - // String code; - double creditHours; - String grade; - - Unit({ - required this.name, - // required this.code, - required this.creditHours, - required this.grade, - }); -} diff --git a/lib/tools/gpa_calculator/pages/gpacalculator_page.dart b/lib/tools/gpa_calculator/pages/gpacalculator_page.dart deleted file mode 100644 index 1512b26..0000000 --- a/lib/tools/gpa_calculator/pages/gpacalculator_page.dart +++ /dev/null @@ -1,166 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import '../controllers/gpacalculator_controller.dart'; -import '../widgets/widgets.dart'; - -class GpaCalculator extends StatelessWidget { - GpaCalculator({super.key}); - final unitController = Get.put(GPACalculatorController()); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: CustomScrollView( - slivers: [ - SliverAppBar( - actions: [ - IconButton( - onPressed: () { - unitController.courseList.clear(); - }, - icon: const Icon(Ionicons.close_circle_outline), - ), - IconButton( - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Information"), - content: const Text( - "The GPA calculator helps you to set realistic and attainable targets and also confirm your grades", - ), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Ok"), - ) - ], - ), - ); - }, - icon: const Icon(Ionicons.information_circle_outline), - ), - ], - expandedHeight: 250, - snap: true, - pinned: true, - floating: true, - flexibleSpace: FlexibleSpaceBar( - background: Container( - color: Theme.of(context).colorScheme.tertiaryContainer, - ), - title: const Text("GPA Calculator"), - ), - ), - const SliverPadding( - padding: EdgeInsets.all(12), - sliver: SliverToBoxAdapter( - child: Text( - "For accurate results, kindly ensure the credit hours loaded are correct for each of the units you are taking.", - textAlign: TextAlign.center, - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverToBoxAdapter( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - FilledButton( - onPressed: () { - loadRegisteredUnits(); - }, - child: const Text("Load Registed Courses"), - ), - FilledButton( - onPressed: () { - if (unitController.courseList.isEmpty) { - Get.defaultDialog( - title: "List empty", - content: const Text("Input Grades!"), - ); - return; - } - Get.defaultDialog( - title: "Your GPA", - content: Text( - "Your GPA is ${unitController.gpa.toString()}"), - ); - }, - child: const Text("Calculate"), - ), - ], - ), - ), - ), - Obx( - () => SliverList.builder( - itemBuilder: (context, index) { - final data = unitController.courseList.elementAt(index); - return ListTile( - leading: CircleAvatar( - child: Text((index + 1).toString()), - ), - title: Text(data.name), - subtitle: Text(data.creditHours.toString()), - trailing: Text(data.grade), - onTap: () { - showModalBottomSheet( - context: context, - builder: (context) => UnitForm( - unit: data, - index: index, - ), - ); - }, - ); - }, - itemCount: unitController.courseList.length, - ), - ), - ], - ), - floatingActionButton: FloatingActionButton( - onPressed: () { - showModalBottomSheet( - context: context, - builder: (context) => const UnitForm(), - ); - }, - child: const Icon(Ionicons.add), - ), - ); - } - - void loadRegisteredUnits() { - unitController.courseList.clear(); - var courseController = Get.find(); - unitController.registeredCourses = courseController.courses; - final registeredCourses = unitController.registeredCoursesList; - debugPrint("Courses: $registeredCourses"); - for (final course in registeredCourses) { - final period = course.period.split('-'); - final startPeriod = period[0].trim().split(':'); - final endPeriod = period[1].trim().split(':'); - - final startHour = int.parse(startPeriod[0]); - final startMinute = int.parse(startPeriod[1].substring(0, 2)); - final startAmPm = startPeriod[1].substring(2).trim(); - - final endHour = int.parse(endPeriod[0]); - final endMinute = int.parse(endPeriod[1].substring(0, 2)); - final endAmPm = endPeriod[1].substring(2).trim(); - - final startTime = DateTime( - 0, 0, 0, startHour + (startAmPm == 'PM' ? 12 : 0), startMinute); - final endTime = - DateTime(0, 0, 0, endHour + (endAmPm == 'PM' ? 12 : 0), endMinute); - - final difference = endTime.difference(startTime); - unitController.addCourse(course.unit, difference.inHours.toString(), "A"); - } - } -} diff --git a/lib/tools/gpa_calculator/widgets/unit_form.dart b/lib/tools/gpa_calculator/widgets/unit_form.dart deleted file mode 100644 index 35ce057..0000000 --- a/lib/tools/gpa_calculator/widgets/unit_form.dart +++ /dev/null @@ -1,159 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import '../models/unit.dart'; -import '../controllers/gpacalculator_controller.dart'; - -class UnitForm extends StatelessWidget { - const UnitForm({ - super.key, - this.unit, - this.index, - }); - final Unit? unit; - final int? index; - @override - Widget build(BuildContext context) { - final TextEditingController nameController = - TextEditingController(text: unit != null ? unit!.name : ""); - final TextEditingController creditHoursController = TextEditingController( - text: unit != null ? unit!.creditHours.toString() : "3.0", - ); - final TextEditingController gradeController = TextEditingController( - text: unit != null ? unit!.grade.toString() : "A", - ); - final GPACalculatorController gpaCalculatorController = - Get.find(); - - return Container( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom, - ), - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - const SizedBox( - height: 20, - ), - const Text( - "Enter the unit details", - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox( - height: 5, - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextField( - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Unit Name', - ), - controller: nameController, - ), - ), - const SizedBox( - height: 5, - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextField( - keyboardType: TextInputType.number, - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Credit Hours', - ), - controller: creditHoursController, - ), - ), - const SizedBox( - height: 5, - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: TextField( - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Grade', - ), - controller: gradeController, - ), - ), - const SizedBox( - height: 5, - ), - unit == null - ? FilledButton( - onPressed: () { - final name = nameController.text; - final creditHours = creditHoursController.text; - final grade = gradeController.text; - if (name.isNotEmpty && - creditHours.isNotEmpty && - grade.isNotEmpty && - gpaCalculatorController.testValidGrade(grade)) { - gpaCalculatorController.addCourse( - name, creditHours, grade); - Navigator.pop(context); - } else { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Invalide input"), - content: const Text( - "Please ensure all fields are filled and a valid grade is entered", - ), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Ok"), - ) - ], - ), - ); - } - }, - child: const Text("Add unit"), - ) - : FilledButton( - onPressed: () { - final name = nameController.text; - final creditHours = creditHoursController.text; - final grade = gradeController.text; - if (name.isNotEmpty && - creditHours.isNotEmpty && - grade.isNotEmpty && - gpaCalculatorController.testValidGrade(grade)) { - gpaCalculatorController.updateCourse( - index: index!, - newName: nameController.text, - newCreditHours: creditHoursController.text, - newGrade: gradeController.text); - Navigator.pop(context); - } else { - Get.defaultDialog( - title: "Invalid input", - content: const Padding( - padding: EdgeInsets.all(8.0), - child: Text( - "Please ensure all fields are filled and a valid grade is entered!"), - ), - ); - } - }, - child: const Text("Update Unit"), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/tools/gpa_calculator/widgets/widgets.dart b/lib/tools/gpa_calculator/widgets/widgets.dart deleted file mode 100644 index a3574d9..0000000 --- a/lib/tools/gpa_calculator/widgets/widgets.dart +++ /dev/null @@ -1 +0,0 @@ -export 'unit_form.dart'; diff --git a/lib/tools/leaderboard/achievements_page.dart b/lib/tools/leaderboard/achievements_page.dart deleted file mode 100644 index ed00408..0000000 --- a/lib/tools/leaderboard/achievements_page.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/models/core/reward/reward.dart'; -import 'package:get/get.dart'; -import 'package:intl/intl.dart'; -import 'package:lottie/lottie.dart'; - -class AchievementPage extends StatefulWidget { - const AchievementPage({super.key}); - - @override - State createState() => _AchievementPageState(); -} - -class _AchievementPageState extends State - with AutomaticKeepAliveClientMixin { - final rewardsController = Get.find(); - @override - bool get wantKeepAlive => true; - - String dateFormat(DateTime date) { - return DateFormat('EEEE, MMMM d, y').format(date); - } - - @override - Widget build(BuildContext context) { - super.build(context); - return RefreshIndicator( - onRefresh: () async { - final result = await rewardsController.fetchCurrentUserRewards(); - result.fold((l) => null, (r) async { - await RewardModelHelper().truncate(); - }); - setState(() {}); - }, - child: FutureBuilder( - future: rewardsController.loadRewards(), - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return Column( - children: [ - Lottie.asset( - "assets/lotties/fetching.json", - ), - const SizedBox(height: 18), - Text( - "Fetching your vibe ...", - style: Theme.of(context).textTheme.headlineLarge, - textAlign: TextAlign.center, - ), - ], - ); - } - return snapshot.data!.fold((l) { - return Column( - children: [ - Lottie.asset( - "assets/lotties/error.json", - ), - const SizedBox(height: 18), - Text( - "Argh snap, $l", - style: Theme.of(context).textTheme.headlineLarge, - textAlign: TextAlign.center, - ), - ], - ); - }, (r) { - return ListView.builder( - itemCount: r.length, - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: ListTile( - leading: CircleAvatar( - backgroundColor: - Theme.of(context).colorScheme.tertiaryContainer, - child: Text( - (index + 1).toString(), - ), - ), - title: Text( - r[index].reason, - ), - subtitle: Text( - dateFormat(r[index].awardedAt), - ), - trailing: Text(" + ${r[index].points.toString()}"), - ), - ); - }, - ); - }); - }, - ), - ); - } -} diff --git a/lib/tools/leaderboard/hall_of_fame_page.dart b/lib/tools/leaderboard/hall_of_fame_page.dart deleted file mode 100644 index cdcb538..0000000 --- a/lib/tools/leaderboard/hall_of_fame_page.dart +++ /dev/null @@ -1,188 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/leaderboard/widgets/top_three_widget.dart'; -import 'package:get/get.dart'; -import 'package:lottie/lottie.dart'; - -class HallOfFamePage extends StatefulWidget { - const HallOfFamePage({super.key}); - - @override - State createState() => _HallOfFamePageState(); -} - -class _HallOfFamePageState extends State - with AutomaticKeepAliveClientMixin { - @override - bool get wantKeepAlive => true; - - @override - Widget build(BuildContext context) { - super.build(context); - final rewardsController = Get.find(); - Future leaderBoardResult = rewardsController.fetchLeaderBoard(); - - return FutureBuilder( - future: leaderBoardResult, - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return Column( - children: [ - Lottie.asset( - "assets/lotties/fetching.json", - ), - const SizedBox(height: 18), - Text( - "Fetching awesome vibes students", - style: Theme.of(context).textTheme.headlineLarge, - textAlign: TextAlign.center, - ), - ], - ); - } - return RefreshIndicator( - onRefresh: () async { - Future.delayed(const Duration(seconds: 3)); - setState(() {}); - }, - child: snapshot.data?.fold((l) { - return Column( - children: [ - Lottie.asset( - "assets/lotties/error.json", - ), - const SizedBox(height: 18), - Text( - "Argh snap, $l", - style: Theme.of(context).textTheme.headlineLarge, - textAlign: TextAlign.center, - ), - ], - ); - }, - - // Please ensure that the api will atleast have 3 users - (r) { - final moreUsers = r.sublist(3); - return CustomScrollView( - slivers: [ - SliverPadding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 8, - ), - sliver: SliverToBoxAdapter( - child: Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: Theme.of(context) - .colorScheme - .primaryContainer, - ), - height: 200, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - LeaderBoardProfileWidget( - position: 2, - username: r[1].username, - points: r[1].vibePoints.toString(), - profileUrl: r[1].profileUrl, - gender: r[1].gender, - ), - LeaderBoardProfileWidget( - position: 1, - username: r[0].username, - points: r[0].vibePoints.toString(), - profileUrl: r[0].profileUrl, - gender: r[0].gender, - ), - LeaderBoardProfileWidget( - position: 3, - username: r[2].username, - points: r[2].vibePoints.toString(), - profileUrl: r[2].profileUrl, - gender: r[2].gender, - ), - ], - ), - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.only(top: 16), - sliver: SliverFillRemaining( - child: moreUsers.isEmpty - ? Center( - child: Text( - "Seems there's no content to display here yet.", - style: Theme.of(context) - .textTheme - .headlineLarge, - textAlign: TextAlign.center, - ), - ) - : ListView.builder( - itemCount: moreUsers.length, - itemBuilder: (context, index) { - return Container( - padding: const EdgeInsets.only(bottom: 8), - child: ListTile( - leading: CircleAvatar( - radius: 30, - child: moreUsers[index] - .profileUrl - .startsWith("http") - ? ClipRRect( - borderRadius: - BorderRadius.circular( - 60), - child: CachedNetworkImage( - imageUrl: moreUsers[index] - .profileUrl, - fit: BoxFit.fill, - ), - ) - : moreUsers[index].gender == - "male" - ? Image.asset( - "assets/images/male_student.png") - : Image.asset( - "assets/images/female_student.png")), - title: Text( - "@${moreUsers[index].username}", - ), - trailing: Icon( - moreUsers[index].gender == "male" - ? Ionicons.male - : Ionicons.female, - ), - subtitle: Text( - "${moreUsers[index].firstName} has ${moreUsers[index].vibePoints} vibe points", - ), - ), - ); - }, - ), - ), - ), - ], - ); - }) ?? - Column( - children: [ - Lottie.asset( - "assets/lotties/fetching.json", - ), - const SizedBox(height: 18), - Text( - "Fetching awesome vibes students", - style: Theme.of(context).textTheme.headlineLarge, - textAlign: TextAlign.center, - ), - ], - ), - ); - }); - } -} diff --git a/lib/tools/leaderboard/leaderboard.dart b/lib/tools/leaderboard/leaderboard.dart deleted file mode 100644 index b8f5e78..0000000 --- a/lib/tools/leaderboard/leaderboard.dart +++ /dev/null @@ -1 +0,0 @@ -export 'leaderboard_page.dart'; diff --git a/lib/tools/leaderboard/leaderboard_page.dart b/lib/tools/leaderboard/leaderboard_page.dart deleted file mode 100644 index dcddf41..0000000 --- a/lib/tools/leaderboard/leaderboard_page.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/leaderboard/hall_of_fame_page.dart'; -import 'achievements_page.dart'; - -class LeaderBoardPage extends StatelessWidget { - const LeaderBoardPage({super.key}); - - @override - Widget build(BuildContext context) { - return const Scaffold( - body: DefaultTabController( - length: 2, - child: CustomScrollView( - slivers: [ - SliverAppBar( - title: Text("Academia LeaderBoard"), - bottom: TabBar( - tabs: [ - Tab(text: "Hall of Fame"), - Tab(text: "Personal Achievements"), - ], - ), - ), - SliverFillRemaining( - child: TabBarView(children: [ - HallOfFamePage(), - AchievementPage(), - ]), - ) - ], - ), - ), - ); - } -} diff --git a/lib/tools/leaderboard/widgets/personal_rating_widget.dart b/lib/tools/leaderboard/widgets/personal_rating_widget.dart deleted file mode 100644 index 67b4f82..0000000 --- a/lib/tools/leaderboard/widgets/personal_rating_widget.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:flutter/material.dart'; - -class PersonalRatingWidget extends StatelessWidget { - const PersonalRatingWidget({super.key}); - - @override - Widget build(BuildContext context) { - return SizedBox( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Image.asset( - "assets/images/coin.png", - height: 80, - ), - const SizedBox(height: 8), - Text( - "1000 vibe points", - style: Theme.of(context) - .textTheme - .titleMedium - ?.copyWith(fontWeight: FontWeight.bold), - ), - const Text("earned"), - const Spacer(), - Text( - "Keep the streak burning; earn more points enjoy exclusive features and rewards", - style: Theme.of(context).textTheme.bodySmall, - textAlign: TextAlign.center, - ), - ], - ), - ); - } -} diff --git a/lib/tools/leaderboard/widgets/top_three_widget.dart b/lib/tools/leaderboard/widgets/top_three_widget.dart deleted file mode 100644 index 5510b74..0000000 --- a/lib/tools/leaderboard/widgets/top_three_widget.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:academia/exports/barrel.dart'; - -class LeaderBoardProfileWidget extends StatelessWidget { - const LeaderBoardProfileWidget({ - super.key, - required this.position, - required this.username, - required this.points, - required this.profileUrl, - this.gender = "male", - }); - final int position; - final String username; - final String points; - final String profileUrl; - final String gender; - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Text( - position == 1 ? Emojis.clothing_crown : "$position", - style: Theme.of(context).textTheme.headlineMedium, - ), - const Spacer(), - CircleAvatar( - radius: position == 1 ? 40 : 30, - child: profileUrl.startsWith("http") - ? ClipRRect( - borderRadius: BorderRadius.circular(60), - child: CachedNetworkImage( - imageUrl: profileUrl, - fit: BoxFit.fill, - ), - ) - : gender == "male" - ? Image.asset("assets/images/male_student.png") - : Image.asset("assets/images/female_student.png")), - const SizedBox(height: 4), - Text( - "@$username", - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 2), - Text( - "$points points", - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ); - } -} diff --git a/lib/tools/organizations/controllers/organization_controller.dart b/lib/tools/organizations/controllers/organization_controller.dart deleted file mode 100644 index 47d72f1..0000000 --- a/lib/tools/organizations/controllers/organization_controller.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:dartz/dartz.dart'; -import 'package:get/get.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -class OrganizationController extends GetxController { - // For sending requests - final OrganizationService _organizationService = OrganizationService(); - final StoryService _storyService = StoryService(); - final RxMap> stories = - RxMap>(); - RxList organizations = RxList(); - - late SharedPreferences prefs; - - @override - void onInit() async { - super.onInit(); - prefs = await SharedPreferences.getInstance(); - - final rawStoredOrganizations = prefs.getString("organizations"); - - if (rawStoredOrganizations != null) { - organizations.value = _decodeOrganizations(rawStoredOrganizations); - - for (final org in organizations) { - stories[org] = List.empty(growable: true); - } - return; - } - } - - Future> markStoryAsViewed( - Map authCreds, String storyID) async { - return _storyService.markStoryAsViewed( - authCreds, - storyID, - ); - } - - String _encodeOrganizatons(List organizations) { - List> jsonList = - organizations.map((org) => org.toJson()).toList(); - return jsonEncode(jsonList); - } - - List _decodeOrganizations(String rawOrganizations) { - final List raw = jsonDecode(rawOrganizations); - - return raw.map((e) { - return Organization.fromJson(e as Map); - }).toList(); - } - - Future fetchStories(Map authCreds) async { - final result = await _storyService.fetchStories(authCreds); - - return result.fold((l) { - return false; - }, (r) { - for (final story in r) { - // print(story); - final orgwithStory = stories.entries - .firstWhere((elem) => elem.key.id == story.keys.first) - .key; - stories[orgwithStory] = story.values.first; - } - return true; - }); - } - - Future>> fetchOrganizations( - Map authCreds) async { - final result = await _organizationService.fetchOrganizations(authCreds); - return result.fold((l) { - return left(l); - }, (r) async { - prefs.setString( - "organizations", - _encodeOrganizatons(r), - ); - - organizations.value = r; - - return right(r); - }); - } -} diff --git a/lib/tools/organizations/models/core/membership.dart b/lib/tools/organizations/models/core/membership.dart deleted file mode 100644 index 1be2b4b..0000000 --- a/lib/tools/organizations/models/core/membership.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:academia/exports/barrel.dart'; - -class Membership { - final int id; - final ChirpUser user; - final Organization organization; - final String role; - - Membership({ - required this.id, - required this.user, - required this.organization, - required this.role, - }); - - // Factory method to create an instance from JSON - factory Membership.fromJson(Map json) { - return Membership( - id: json['id'], - user: ChirpUser.fromJson(json['user']), - organization: Organization.fromJson(json['organization']), - role: json['role'], - ); - } - - // Method to convert an instance to JSON - Map toJson() { - return { - 'id': id, - 'user': user.toJson(), - 'organization': organization.toJson(), - 'role': role, - }; - } -} diff --git a/lib/tools/organizations/models/core/organization.dart b/lib/tools/organizations/models/core/organization.dart deleted file mode 100644 index 3f933f7..0000000 --- a/lib/tools/organizations/models/core/organization.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'organization.freezed.dart'; -part 'organization.g.dart'; - -@freezed -class Organization with _$Organization { - const factory Organization({ - required String id, - required String name, - required String email, - required bool active, - String? description, - String? logo, - String? banner, - @JsonKey(name: 'organization_page') String? organizationPage, - @JsonKey(name: 'created_at') DateTime? createdAt, - @JsonKey(name: 'updated_at') DateTime? updatedAt, - required String owner, - required String location, - required String phone, - }) = _Organization; - - factory Organization.fromJson(Map json) => - _$OrganizationFromJson(json); -} diff --git a/lib/tools/organizations/models/core/organization.freezed.dart b/lib/tools/organizations/models/core/organization.freezed.dart deleted file mode 100644 index 0f8bf8e..0000000 --- a/lib/tools/organizations/models/core/organization.freezed.dart +++ /dev/null @@ -1,436 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'organization.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -Organization _$OrganizationFromJson(Map json) { - return _Organization.fromJson(json); -} - -/// @nodoc -mixin _$Organization { - String get id => throw _privateConstructorUsedError; - String get name => throw _privateConstructorUsedError; - String get email => throw _privateConstructorUsedError; - bool get active => throw _privateConstructorUsedError; - String? get description => throw _privateConstructorUsedError; - String? get logo => throw _privateConstructorUsedError; - String? get banner => throw _privateConstructorUsedError; - @JsonKey(name: 'organization_page') - String? get organizationPage => throw _privateConstructorUsedError; - @JsonKey(name: 'created_at') - DateTime? get createdAt => throw _privateConstructorUsedError; - @JsonKey(name: 'updated_at') - DateTime? get updatedAt => throw _privateConstructorUsedError; - String get owner => throw _privateConstructorUsedError; - String get location => throw _privateConstructorUsedError; - String get phone => throw _privateConstructorUsedError; - - /// Serializes this Organization to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of Organization - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $OrganizationCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $OrganizationCopyWith<$Res> { - factory $OrganizationCopyWith( - Organization value, $Res Function(Organization) then) = - _$OrganizationCopyWithImpl<$Res, Organization>; - @useResult - $Res call( - {String id, - String name, - String email, - bool active, - String? description, - String? logo, - String? banner, - @JsonKey(name: 'organization_page') String? organizationPage, - @JsonKey(name: 'created_at') DateTime? createdAt, - @JsonKey(name: 'updated_at') DateTime? updatedAt, - String owner, - String location, - String phone}); -} - -/// @nodoc -class _$OrganizationCopyWithImpl<$Res, $Val extends Organization> - implements $OrganizationCopyWith<$Res> { - _$OrganizationCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of Organization - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = null, - Object? name = null, - Object? email = null, - Object? active = null, - Object? description = freezed, - Object? logo = freezed, - Object? banner = freezed, - Object? organizationPage = freezed, - Object? createdAt = freezed, - Object? updatedAt = freezed, - Object? owner = null, - Object? location = null, - Object? phone = null, - }) { - return _then(_value.copyWith( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - email: null == email - ? _value.email - : email // ignore: cast_nullable_to_non_nullable - as String, - active: null == active - ? _value.active - : active // ignore: cast_nullable_to_non_nullable - as bool, - description: freezed == description - ? _value.description - : description // ignore: cast_nullable_to_non_nullable - as String?, - logo: freezed == logo - ? _value.logo - : logo // ignore: cast_nullable_to_non_nullable - as String?, - banner: freezed == banner - ? _value.banner - : banner // ignore: cast_nullable_to_non_nullable - as String?, - organizationPage: freezed == organizationPage - ? _value.organizationPage - : organizationPage // ignore: cast_nullable_to_non_nullable - as String?, - createdAt: freezed == createdAt - ? _value.createdAt - : createdAt // ignore: cast_nullable_to_non_nullable - as DateTime?, - updatedAt: freezed == updatedAt - ? _value.updatedAt - : updatedAt // ignore: cast_nullable_to_non_nullable - as DateTime?, - owner: null == owner - ? _value.owner - : owner // ignore: cast_nullable_to_non_nullable - as String, - location: null == location - ? _value.location - : location // ignore: cast_nullable_to_non_nullable - as String, - phone: null == phone - ? _value.phone - : phone // ignore: cast_nullable_to_non_nullable - as String, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$OrganizationImplCopyWith<$Res> - implements $OrganizationCopyWith<$Res> { - factory _$$OrganizationImplCopyWith( - _$OrganizationImpl value, $Res Function(_$OrganizationImpl) then) = - __$$OrganizationImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String id, - String name, - String email, - bool active, - String? description, - String? logo, - String? banner, - @JsonKey(name: 'organization_page') String? organizationPage, - @JsonKey(name: 'created_at') DateTime? createdAt, - @JsonKey(name: 'updated_at') DateTime? updatedAt, - String owner, - String location, - String phone}); -} - -/// @nodoc -class __$$OrganizationImplCopyWithImpl<$Res> - extends _$OrganizationCopyWithImpl<$Res, _$OrganizationImpl> - implements _$$OrganizationImplCopyWith<$Res> { - __$$OrganizationImplCopyWithImpl( - _$OrganizationImpl _value, $Res Function(_$OrganizationImpl) _then) - : super(_value, _then); - - /// Create a copy of Organization - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = null, - Object? name = null, - Object? email = null, - Object? active = null, - Object? description = freezed, - Object? logo = freezed, - Object? banner = freezed, - Object? organizationPage = freezed, - Object? createdAt = freezed, - Object? updatedAt = freezed, - Object? owner = null, - Object? location = null, - Object? phone = null, - }) { - return _then(_$OrganizationImpl( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - email: null == email - ? _value.email - : email // ignore: cast_nullable_to_non_nullable - as String, - active: null == active - ? _value.active - : active // ignore: cast_nullable_to_non_nullable - as bool, - description: freezed == description - ? _value.description - : description // ignore: cast_nullable_to_non_nullable - as String?, - logo: freezed == logo - ? _value.logo - : logo // ignore: cast_nullable_to_non_nullable - as String?, - banner: freezed == banner - ? _value.banner - : banner // ignore: cast_nullable_to_non_nullable - as String?, - organizationPage: freezed == organizationPage - ? _value.organizationPage - : organizationPage // ignore: cast_nullable_to_non_nullable - as String?, - createdAt: freezed == createdAt - ? _value.createdAt - : createdAt // ignore: cast_nullable_to_non_nullable - as DateTime?, - updatedAt: freezed == updatedAt - ? _value.updatedAt - : updatedAt // ignore: cast_nullable_to_non_nullable - as DateTime?, - owner: null == owner - ? _value.owner - : owner // ignore: cast_nullable_to_non_nullable - as String, - location: null == location - ? _value.location - : location // ignore: cast_nullable_to_non_nullable - as String, - phone: null == phone - ? _value.phone - : phone // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$OrganizationImpl implements _Organization { - const _$OrganizationImpl( - {required this.id, - required this.name, - required this.email, - required this.active, - this.description, - this.logo, - this.banner, - @JsonKey(name: 'organization_page') this.organizationPage, - @JsonKey(name: 'created_at') this.createdAt, - @JsonKey(name: 'updated_at') this.updatedAt, - required this.owner, - required this.location, - required this.phone}); - - factory _$OrganizationImpl.fromJson(Map json) => - _$$OrganizationImplFromJson(json); - - @override - final String id; - @override - final String name; - @override - final String email; - @override - final bool active; - @override - final String? description; - @override - final String? logo; - @override - final String? banner; - @override - @JsonKey(name: 'organization_page') - final String? organizationPage; - @override - @JsonKey(name: 'created_at') - final DateTime? createdAt; - @override - @JsonKey(name: 'updated_at') - final DateTime? updatedAt; - @override - final String owner; - @override - final String location; - @override - final String phone; - - @override - String toString() { - return 'Organization(id: $id, name: $name, email: $email, active: $active, description: $description, logo: $logo, banner: $banner, organizationPage: $organizationPage, createdAt: $createdAt, updatedAt: $updatedAt, owner: $owner, location: $location, phone: $phone)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$OrganizationImpl && - (identical(other.id, id) || other.id == id) && - (identical(other.name, name) || other.name == name) && - (identical(other.email, email) || other.email == email) && - (identical(other.active, active) || other.active == active) && - (identical(other.description, description) || - other.description == description) && - (identical(other.logo, logo) || other.logo == logo) && - (identical(other.banner, banner) || other.banner == banner) && - (identical(other.organizationPage, organizationPage) || - other.organizationPage == organizationPage) && - (identical(other.createdAt, createdAt) || - other.createdAt == createdAt) && - (identical(other.updatedAt, updatedAt) || - other.updatedAt == updatedAt) && - (identical(other.owner, owner) || other.owner == owner) && - (identical(other.location, location) || - other.location == location) && - (identical(other.phone, phone) || other.phone == phone)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash( - runtimeType, - id, - name, - email, - active, - description, - logo, - banner, - organizationPage, - createdAt, - updatedAt, - owner, - location, - phone); - - /// Create a copy of Organization - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$OrganizationImplCopyWith<_$OrganizationImpl> get copyWith => - __$$OrganizationImplCopyWithImpl<_$OrganizationImpl>(this, _$identity); - - @override - Map toJson() { - return _$$OrganizationImplToJson( - this, - ); - } -} - -abstract class _Organization implements Organization { - const factory _Organization( - {required final String id, - required final String name, - required final String email, - required final bool active, - final String? description, - final String? logo, - final String? banner, - @JsonKey(name: 'organization_page') final String? organizationPage, - @JsonKey(name: 'created_at') final DateTime? createdAt, - @JsonKey(name: 'updated_at') final DateTime? updatedAt, - required final String owner, - required final String location, - required final String phone}) = _$OrganizationImpl; - - factory _Organization.fromJson(Map json) = - _$OrganizationImpl.fromJson; - - @override - String get id; - @override - String get name; - @override - String get email; - @override - bool get active; - @override - String? get description; - @override - String? get logo; - @override - String? get banner; - @override - @JsonKey(name: 'organization_page') - String? get organizationPage; - @override - @JsonKey(name: 'created_at') - DateTime? get createdAt; - @override - @JsonKey(name: 'updated_at') - DateTime? get updatedAt; - @override - String get owner; - @override - String get location; - @override - String get phone; - - /// Create a copy of Organization - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$OrganizationImplCopyWith<_$OrganizationImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/tools/organizations/models/core/organization.g.dart b/lib/tools/organizations/models/core/organization.g.dart deleted file mode 100644 index 479c377..0000000 --- a/lib/tools/organizations/models/core/organization.g.dart +++ /dev/null @@ -1,45 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'organization.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$OrganizationImpl _$$OrganizationImplFromJson(Map json) => - _$OrganizationImpl( - id: json['id'] as String, - name: json['name'] as String, - email: json['email'] as String, - active: json['active'] as bool, - description: json['description'] as String?, - logo: json['logo'] as String?, - banner: json['banner'] as String?, - organizationPage: json['organization_page'] as String?, - createdAt: json['created_at'] == null - ? null - : DateTime.parse(json['created_at'] as String), - updatedAt: json['updated_at'] == null - ? null - : DateTime.parse(json['updated_at'] as String), - owner: json['owner'] as String, - location: json['location'] as String, - phone: json['phone'] as String, - ); - -Map _$$OrganizationImplToJson(_$OrganizationImpl instance) => - { - 'id': instance.id, - 'name': instance.name, - 'email': instance.email, - 'active': instance.active, - 'description': instance.description, - 'logo': instance.logo, - 'banner': instance.banner, - 'organization_page': instance.organizationPage, - 'created_at': instance.createdAt?.toIso8601String(), - 'updated_at': instance.updatedAt?.toIso8601String(), - 'owner': instance.owner, - 'location': instance.location, - 'phone': instance.phone, - }; diff --git a/lib/tools/organizations/models/core/story.dart b/lib/tools/organizations/models/core/story.dart deleted file mode 100644 index 6e2e2af..0000000 --- a/lib/tools/organizations/models/core/story.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'story.freezed.dart'; -part 'story.g.dart'; - -@freezed -class Story with _$Story { - const factory Story({ - required String id, - required String description, - String? media, // Nullable media field - @JsonKey(name: 'file_type') required String fileType, - @JsonKey(name: 'number_of_views') required int numberOfViews, - String? url, // Nullable URL field - @JsonKey(name: 'created_at') required DateTime createdAt, - @JsonKey(name: 'updated_at') required DateTime updatedAt, - @JsonKey(name: 'viewed', defaultValue: false) required bool viewed, - required String organization, - }) = _Story; - - // Factory constructor for creating a new Story instance from a Map (JSON) - factory Story.fromJson(Map json) => _$StoryFromJson(json); -} diff --git a/lib/tools/organizations/models/core/story.freezed.dart b/lib/tools/organizations/models/core/story.freezed.dart deleted file mode 100644 index 31bc539..0000000 --- a/lib/tools/organizations/models/core/story.freezed.dart +++ /dev/null @@ -1,369 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'story.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -Story _$StoryFromJson(Map json) { - return _Story.fromJson(json); -} - -/// @nodoc -mixin _$Story { - String get id => throw _privateConstructorUsedError; - String get description => throw _privateConstructorUsedError; - String? get media => - throw _privateConstructorUsedError; // Nullable media field - @JsonKey(name: 'file_type') - String get fileType => throw _privateConstructorUsedError; - @JsonKey(name: 'number_of_views') - int get numberOfViews => throw _privateConstructorUsedError; - String? get url => throw _privateConstructorUsedError; // Nullable URL field - @JsonKey(name: 'created_at') - DateTime get createdAt => throw _privateConstructorUsedError; - @JsonKey(name: 'updated_at') - DateTime get updatedAt => throw _privateConstructorUsedError; - @JsonKey(name: 'viewed', defaultValue: false) - bool get viewed => throw _privateConstructorUsedError; - String get organization => throw _privateConstructorUsedError; - - /// Serializes this Story to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of Story - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $StoryCopyWith get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $StoryCopyWith<$Res> { - factory $StoryCopyWith(Story value, $Res Function(Story) then) = - _$StoryCopyWithImpl<$Res, Story>; - @useResult - $Res call( - {String id, - String description, - String? media, - @JsonKey(name: 'file_type') String fileType, - @JsonKey(name: 'number_of_views') int numberOfViews, - String? url, - @JsonKey(name: 'created_at') DateTime createdAt, - @JsonKey(name: 'updated_at') DateTime updatedAt, - @JsonKey(name: 'viewed', defaultValue: false) bool viewed, - String organization}); -} - -/// @nodoc -class _$StoryCopyWithImpl<$Res, $Val extends Story> - implements $StoryCopyWith<$Res> { - _$StoryCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of Story - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = null, - Object? description = null, - Object? media = freezed, - Object? fileType = null, - Object? numberOfViews = null, - Object? url = freezed, - Object? createdAt = null, - Object? updatedAt = null, - Object? viewed = null, - Object? organization = null, - }) { - return _then(_value.copyWith( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String, - description: null == description - ? _value.description - : description // ignore: cast_nullable_to_non_nullable - as String, - media: freezed == media - ? _value.media - : media // ignore: cast_nullable_to_non_nullable - as String?, - fileType: null == fileType - ? _value.fileType - : fileType // ignore: cast_nullable_to_non_nullable - as String, - numberOfViews: null == numberOfViews - ? _value.numberOfViews - : numberOfViews // ignore: cast_nullable_to_non_nullable - as int, - url: freezed == url - ? _value.url - : url // ignore: cast_nullable_to_non_nullable - as String?, - createdAt: null == createdAt - ? _value.createdAt - : createdAt // ignore: cast_nullable_to_non_nullable - as DateTime, - updatedAt: null == updatedAt - ? _value.updatedAt - : updatedAt // ignore: cast_nullable_to_non_nullable - as DateTime, - viewed: null == viewed - ? _value.viewed - : viewed // ignore: cast_nullable_to_non_nullable - as bool, - organization: null == organization - ? _value.organization - : organization // ignore: cast_nullable_to_non_nullable - as String, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$StoryImplCopyWith<$Res> implements $StoryCopyWith<$Res> { - factory _$$StoryImplCopyWith( - _$StoryImpl value, $Res Function(_$StoryImpl) then) = - __$$StoryImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String id, - String description, - String? media, - @JsonKey(name: 'file_type') String fileType, - @JsonKey(name: 'number_of_views') int numberOfViews, - String? url, - @JsonKey(name: 'created_at') DateTime createdAt, - @JsonKey(name: 'updated_at') DateTime updatedAt, - @JsonKey(name: 'viewed', defaultValue: false) bool viewed, - String organization}); -} - -/// @nodoc -class __$$StoryImplCopyWithImpl<$Res> - extends _$StoryCopyWithImpl<$Res, _$StoryImpl> - implements _$$StoryImplCopyWith<$Res> { - __$$StoryImplCopyWithImpl( - _$StoryImpl _value, $Res Function(_$StoryImpl) _then) - : super(_value, _then); - - /// Create a copy of Story - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = null, - Object? description = null, - Object? media = freezed, - Object? fileType = null, - Object? numberOfViews = null, - Object? url = freezed, - Object? createdAt = null, - Object? updatedAt = null, - Object? viewed = null, - Object? organization = null, - }) { - return _then(_$StoryImpl( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String, - description: null == description - ? _value.description - : description // ignore: cast_nullable_to_non_nullable - as String, - media: freezed == media - ? _value.media - : media // ignore: cast_nullable_to_non_nullable - as String?, - fileType: null == fileType - ? _value.fileType - : fileType // ignore: cast_nullable_to_non_nullable - as String, - numberOfViews: null == numberOfViews - ? _value.numberOfViews - : numberOfViews // ignore: cast_nullable_to_non_nullable - as int, - url: freezed == url - ? _value.url - : url // ignore: cast_nullable_to_non_nullable - as String?, - createdAt: null == createdAt - ? _value.createdAt - : createdAt // ignore: cast_nullable_to_non_nullable - as DateTime, - updatedAt: null == updatedAt - ? _value.updatedAt - : updatedAt // ignore: cast_nullable_to_non_nullable - as DateTime, - viewed: null == viewed - ? _value.viewed - : viewed // ignore: cast_nullable_to_non_nullable - as bool, - organization: null == organization - ? _value.organization - : organization // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$StoryImpl implements _Story { - const _$StoryImpl( - {required this.id, - required this.description, - this.media, - @JsonKey(name: 'file_type') required this.fileType, - @JsonKey(name: 'number_of_views') required this.numberOfViews, - this.url, - @JsonKey(name: 'created_at') required this.createdAt, - @JsonKey(name: 'updated_at') required this.updatedAt, - @JsonKey(name: 'viewed', defaultValue: false) required this.viewed, - required this.organization}); - - factory _$StoryImpl.fromJson(Map json) => - _$$StoryImplFromJson(json); - - @override - final String id; - @override - final String description; - @override - final String? media; -// Nullable media field - @override - @JsonKey(name: 'file_type') - final String fileType; - @override - @JsonKey(name: 'number_of_views') - final int numberOfViews; - @override - final String? url; -// Nullable URL field - @override - @JsonKey(name: 'created_at') - final DateTime createdAt; - @override - @JsonKey(name: 'updated_at') - final DateTime updatedAt; - @override - @JsonKey(name: 'viewed', defaultValue: false) - final bool viewed; - @override - final String organization; - - @override - String toString() { - return 'Story(id: $id, description: $description, media: $media, fileType: $fileType, numberOfViews: $numberOfViews, url: $url, createdAt: $createdAt, updatedAt: $updatedAt, viewed: $viewed, organization: $organization)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$StoryImpl && - (identical(other.id, id) || other.id == id) && - (identical(other.description, description) || - other.description == description) && - (identical(other.media, media) || other.media == media) && - (identical(other.fileType, fileType) || - other.fileType == fileType) && - (identical(other.numberOfViews, numberOfViews) || - other.numberOfViews == numberOfViews) && - (identical(other.url, url) || other.url == url) && - (identical(other.createdAt, createdAt) || - other.createdAt == createdAt) && - (identical(other.updatedAt, updatedAt) || - other.updatedAt == updatedAt) && - (identical(other.viewed, viewed) || other.viewed == viewed) && - (identical(other.organization, organization) || - other.organization == organization)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, id, description, media, fileType, - numberOfViews, url, createdAt, updatedAt, viewed, organization); - - /// Create a copy of Story - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$StoryImplCopyWith<_$StoryImpl> get copyWith => - __$$StoryImplCopyWithImpl<_$StoryImpl>(this, _$identity); - - @override - Map toJson() { - return _$$StoryImplToJson( - this, - ); - } -} - -abstract class _Story implements Story { - const factory _Story( - {required final String id, - required final String description, - final String? media, - @JsonKey(name: 'file_type') required final String fileType, - @JsonKey(name: 'number_of_views') required final int numberOfViews, - final String? url, - @JsonKey(name: 'created_at') required final DateTime createdAt, - @JsonKey(name: 'updated_at') required final DateTime updatedAt, - @JsonKey(name: 'viewed', defaultValue: false) required final bool viewed, - required final String organization}) = _$StoryImpl; - - factory _Story.fromJson(Map json) = _$StoryImpl.fromJson; - - @override - String get id; - @override - String get description; - @override - String? get media; // Nullable media field - @override - @JsonKey(name: 'file_type') - String get fileType; - @override - @JsonKey(name: 'number_of_views') - int get numberOfViews; - @override - String? get url; // Nullable URL field - @override - @JsonKey(name: 'created_at') - DateTime get createdAt; - @override - @JsonKey(name: 'updated_at') - DateTime get updatedAt; - @override - @JsonKey(name: 'viewed', defaultValue: false) - bool get viewed; - @override - String get organization; - - /// Create a copy of Story - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$StoryImplCopyWith<_$StoryImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/tools/organizations/models/core/story.g.dart b/lib/tools/organizations/models/core/story.g.dart deleted file mode 100644 index de86995..0000000 --- a/lib/tools/organizations/models/core/story.g.dart +++ /dev/null @@ -1,34 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'story.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$StoryImpl _$$StoryImplFromJson(Map json) => _$StoryImpl( - id: json['id'] as String, - description: json['description'] as String, - media: json['media'] as String?, - fileType: json['file_type'] as String, - numberOfViews: (json['number_of_views'] as num).toInt(), - url: json['url'] as String?, - createdAt: DateTime.parse(json['created_at'] as String), - updatedAt: DateTime.parse(json['updated_at'] as String), - viewed: json['viewed'] as bool? ?? false, - organization: json['organization'] as String, - ); - -Map _$$StoryImplToJson(_$StoryImpl instance) => - { - 'id': instance.id, - 'description': instance.description, - 'media': instance.media, - 'file_type': instance.fileType, - 'number_of_views': instance.numberOfViews, - 'url': instance.url, - 'created_at': instance.createdAt.toIso8601String(), - 'updated_at': instance.updatedAt.toIso8601String(), - 'viewed': instance.viewed, - 'organization': instance.organization, - }; diff --git a/lib/tools/organizations/models/models.dart b/lib/tools/organizations/models/models.dart deleted file mode 100644 index af499ac..0000000 --- a/lib/tools/organizations/models/models.dart +++ /dev/null @@ -1,5 +0,0 @@ -export '../models/core/organization.dart'; -export '../models/core/membership.dart'; -export '../models/services/organization_service.dart'; -export 'services/story_service.dart'; -export 'core/story.dart'; diff --git a/lib/tools/organizations/models/services/organization_service.dart b/lib/tools/organizations/models/services/organization_service.dart deleted file mode 100644 index 13568a6..0000000 --- a/lib/tools/organizations/models/services/organization_service.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:dartz/dartz.dart'; -import 'package:http/http.dart' as http; - -class OrganizationService with ChirpService { - Future>> fetchOrganizations( - Map authHeaders) async { - try { - // fetch organizations - final response = await http.get( - Uri.parse("${ChirpService.urlPrefix}/organizations/all"), - headers: authHeaders, - ); - - if (response.statusCode == 200) { - List> rawOrganizations = - json.decode(response.body).cast>(); - - return Right( - rawOrganizations.map((e) => Organization.fromJson(e)).toList(), - ); - } - - return const Left( - "Something went wrong white attempting to fetch organizations", - ); - } catch (e) { - return const Left( - "Please check your internet connection and try that again", - ); - } - } - - Future>> fetchOrganization( - Map authHeaders, String id) async { - try { - // fetch organizations - final response = await http.get( - Uri.parse("${ChirpService.urlPrefix}/organizations/find/$id"), - headers: authHeaders, - ); - - if (response.statusCode == 200) { - List> rawOrganizations = - json.decode(response.body).cast>(); - - return Right( - rawOrganizations.map((e) => Organization.fromJson(e)).toList(), - ); - } - - return const Left( - "Something went wrong white attempting to fetch organizations", - ); - } catch (e) { - return const Left( - "Please check your internet connection and try that again", - ); - } - } - - Future>> fetchUserMemberships( - String userID, Map authHeaders) async { - try { - // fetch organizations - final response = await http.get( - Uri.parse("${ChirpService.urlPrefix}/organizations/user/$userID"), - headers: authHeaders, - ); - - if (response.statusCode == 200) { - List> rawMemberships = - json.decode(response.body).cast>(); - - return Right( - rawMemberships.map((e) => Membership.fromJson(e)).toList(), - ); - } - - return const Left( - "Something went wrong white attempting to fetch organizations", - ); - } catch (e) { - return const Left( - "Please check your internet connection and try that again", - ); - } - } -} diff --git a/lib/tools/organizations/models/services/story_service.dart b/lib/tools/organizations/models/services/story_service.dart deleted file mode 100644 index 06b06a3..0000000 --- a/lib/tools/organizations/models/services/story_service.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:dartz/dartz.dart'; -import 'package:http/http.dart' as http; - -class StoryService with ChirpService { - Future>>>> fetchStories( - Map authHeaders) async { - try { - final response = await http.get( - Uri.parse("${ChirpService.urlPrefix}/stories/"), - headers: authHeaders, - ); - - if (response.statusCode == 200) { - Map rawData = json.decode(response.body); - - List>> parsedStories = - rawData.entries.map((entry) { - // Organization ID is the key - String organizationId = entry.key; - - // The value is a List of story JSON objects - List storiesJson = entry.value; - - // Parse each story JSON into a Story model - List stories = storiesJson - .map((storyJson) => - Story.fromJson(storyJson as Map)) - .toList(); - - // Return a map with organization ID and corresponding list of Story objects - return {organizationId: stories}; - }).toList(); - - // Return the parsed stories wrapped in a Right - return Right(parsedStories); - } - - return const Left( - "Something went wrong white attempting to fetch organizations", - ); - } catch (e) { - return const Left( - "Please check your internet connection and try that again", - ); - } - } - - Future> markStoryAsViewed( - Map authHeaders, - String storyID, - ) async { - try { - final response = await http.get( - Uri.parse("${ChirpService.urlPrefix}/stories/view/$storyID/"), - headers: authHeaders, - ); - - if (response.statusCode == 200) { - return right(true); - } - - return const Left( - "Something went wrong white attempting to mark story as viewed", - ); - } catch (e) { - return const Left( - "Please check your internet connection and try that again", - ); - } - } -} diff --git a/lib/tools/organizations/organizations.dart b/lib/tools/organizations/organizations.dart deleted file mode 100644 index 5f639e3..0000000 --- a/lib/tools/organizations/organizations.dart +++ /dev/null @@ -1,4 +0,0 @@ -export 'pages/organizations_page.dart'; -export 'models/models.dart'; -export 'pages/membership_pages.dart'; -export 'controllers/organization_controller.dart'; diff --git a/lib/tools/organizations/pages/membership_pages.dart b/lib/tools/organizations/pages/membership_pages.dart deleted file mode 100644 index 8e10394..0000000 --- a/lib/tools/organizations/pages/membership_pages.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/organizations/widgets/membership_card.dart'; -import 'package:dartz/dartz.dart' as dartz; -import 'package:get/get.dart'; - -class MembershipPage extends StatefulWidget { - const MembershipPage({super.key}); - - @override - State createState() => _MembershipPageState(); -} - -class _MembershipPageState extends State { - final UserController userController = Get.find(); - late Future>> _loader; - final OrganizationService _service = OrganizationService(); - - @override - void initState() { - super.initState(); - _loader = _service.fetchUserMemberships( - userController.user.value?.id ?? '', userController.authHeaders); - - setState(() {}); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: RefreshIndicator( - onRefresh: () async { - _loader = _service.fetchUserMemberships( - userController.user.value?.id ?? '', userController.authHeaders); - setState(() {}); - }, - child: CustomScrollView( - slivers: [ - SliverAppBar( - expandedHeight: 250, - pinned: true, - floating: true, - flexibleSpace: FlexibleSpaceBar( - title: const Text("Your memberships"), - background: Container( - color: Theme.of(context).colorScheme.primaryContainer, - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverToBoxAdapter( - child: SchoolIDCard( - user: userController.user.value!, - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverFillRemaining( - hasScrollBody: true, - child: FutureBuilder( - future: _loader, - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return const Text("Loading memberships"); - } - - return snapshot.data!.fold((l) { - return Center(child: Text("Arg .. $l")); - }, (r) { - return ListView.separated( - itemBuilder: (context, index) => MembershipCard( - membership: r[index], - ), - separatorBuilder: (context, index) => - const SizedBox(height: 2), - itemCount: r.length); - }); - }, - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/tools/organizations/pages/organization_view.dart b/lib/tools/organizations/pages/organization_view.dart deleted file mode 100644 index e7e1ed0..0000000 --- a/lib/tools/organizations/pages/organization_view.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:flutter_html/flutter_html.dart'; - -class OrganizationView extends StatelessWidget { - const OrganizationView({ - super.key, - required this.organization, - }); - final Organization organization; - - @override - Widget build(BuildContext context) { - return Scaffold( - body: CustomScrollView( - slivers: [ - SliverAppBar( - expandedHeight: 250, - pinned: true, - floating: true, - flexibleSpace: FlexibleSpaceBar( - title: Text(organization.name), - background: organization.banner == null - ? Container( - color: Theme.of(context).colorScheme.primaryContainer, - ) - : CachedNetworkImage( - imageUrl: organization.banner!, - fit: BoxFit.fill, - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverToBoxAdapter( - child: CircleAvatar( - radius: 60, - child: ClipRRect( - borderRadius: BorderRadius.circular(60), - child: CachedNetworkImage( - imageUrl: organization.logo!, - fit: BoxFit.fill, - ), - ), - ), - ), - ), - SliverToBoxAdapter( - child: ListTile( - leading: const Icon(Ionicons.mail_outline), - title: Text(organization.email), - ), - ), - SliverToBoxAdapter( - child: ListTile( - leading: const Icon(Ionicons.phone_portrait_outline), - title: Text(organization.phone), - ), - ), - SliverToBoxAdapter( - child: ListTile( - leading: const Icon(Ionicons.locate), - title: Text(organization.location), - ), - ), - SliverVisibility( - visible: !(organization.organizationPage == null), - sliver: SliverToBoxAdapter( - child: ListTile( - onTap: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => WebviewPage( - title: "Organization", - url: organization.organizationPage!))); - }, - title: const Text("Visit organization's site"), - leading: const Icon(Ionicons.link_outline), - ), - ), - ), - SliverToBoxAdapter( - child: ListTile( - title: Text( - "Organization Description", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleMedium, - ), - subtitle: Html(data: organization.description ?? ""), - ), - ), - ], - ), - ); - } -} diff --git a/lib/tools/organizations/pages/organizations_page.dart b/lib/tools/organizations/pages/organizations_page.dart deleted file mode 100644 index d0cef61..0000000 --- a/lib/tools/organizations/pages/organizations_page.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import '../widgets/widgets.dart'; -import 'package:dartz/dartz.dart' as dartz; -import 'package:get/get.dart'; - -class OrganizationsPage extends StatefulWidget { - const OrganizationsPage({super.key}); - - @override - State createState() => _OrganizationsPageState(); -} - -class _OrganizationsPageState extends State - with AutomaticKeepAliveClientMixin { - final UserController userController = Get.find(); - final OrganizationController organizationController = - Get.find(); - @override - bool get wantKeepAlive => true; - - // TH loading state - late Future>> _loader; - - @override - void initState() { - super.initState(); - _loader = - organizationController.fetchOrganizations(userController.authHeaders); - } - - @override - Widget build(BuildContext context) { - super.build(context); - return RefreshIndicator( - onRefresh: () async { - _loader = organizationController - .fetchOrganizations(userController.authHeaders); - setState(() {}); - }, - child: FutureBuilder( - future: _loader, - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.done) { - return ListView.builder( - itemBuilder: (context, index) => const ListTile( - leading: CircleAvatar(), - trailing: CircleAvatar(radius: 5), - ), - ); - } - // - // if (snapshot.hasError) { - // return const Center( - // child: Text("Error"), - // ); - // } - - return snapshot.data!.fold((l) { - return Center( - child: Text( - "Holly .. $l", - style: Theme.of(context).textTheme.headlineSmall, - textAlign: TextAlign.center, - ), - ); - }, (r) { - return ListView.separated( - itemBuilder: (context, index) => OrganizationCard( - organization: r[index], - ), - separatorBuilder: (context, index) => const SizedBox(height: 2), - itemCount: r.length, - ); - }); - }, - ), - ); - } -} diff --git a/lib/tools/organizations/widgets/membership_card.dart b/lib/tools/organizations/widgets/membership_card.dart deleted file mode 100644 index 48c054b..0000000 --- a/lib/tools/organizations/widgets/membership_card.dart +++ /dev/null @@ -1,154 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:google_fonts/google_fonts.dart'; - -class MembershipCard extends StatelessWidget { - const MembershipCard({ - super.key, - required this.membership, - }); - final Membership membership; - - Color? _cardColor(Membership membership) { - switch (membership.role) { - case "member": - return Colors.transparent; - case "premium": - return Colors.teal[100]; - case "administrator": - return Colors.amber; - default: - } - return Colors.transparent; - } - - @override - Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - color: _cardColor(membership), - borderRadius: BorderRadius.circular(4), - border: Border.all( - color: Theme.of(context).colorScheme.shadow, - width: 2, - ), - ), - padding: const EdgeInsets.all(12), - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width, - maxHeight: 232, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - CachedNetworkImage( - imageUrl: membership.organization.logo!, - height: 40, - ), - const Spacer(), - Text( - membership.role.title(), - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: Colors.blue, - fontWeight: FontWeight.w700, - ), - ) - ], - ), - const SizedBox(height: 4), - Text( - "${membership.user.firstName.title()} ${membership.user.lastName.title()}", - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontFamily: GoogleFonts.jetBrainsMono().fontFamily, - ), - textAlign: TextAlign.left, - ), - const SizedBox(height: 8), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Flexible( - flex: 1, - child: ProfilePictureWidget( - profileSize: 30, - ), - ), - const SizedBox(width: 12), - Flexible( - flex: 2, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - Text( - "Membership ID: ", - style: - Theme.of(context).textTheme.bodySmall?.copyWith( - fontWeight: FontWeight.bold, - fontFamily: - GoogleFonts.jetBrainsMono().fontFamily, - ), - ), - Text( - membership.id.toString(), - style: - Theme.of(context).textTheme.bodySmall?.copyWith( - fontFamily: - GoogleFonts.jetBrainsMono().fontFamily, - ), - ), - ], - ), - const SizedBox(height: 8), - Row( - children: [ - Text( - "Organization: ", - style: - Theme.of(context).textTheme.bodySmall?.copyWith( - fontWeight: FontWeight.bold, - fontFamily: - GoogleFonts.jetBrainsMono().fontFamily, - ), - ), - Text( - membership.organization.name, - style: - Theme.of(context).textTheme.bodySmall?.copyWith( - fontFamily: - GoogleFonts.jetBrainsMono().fontFamily, - ), - ), - ], - ), - const SizedBox(height: 8), - Text( - membership.id.toString(), - textAlign: TextAlign.left, - style: Theme.of(context).textTheme.displayLarge?.copyWith( - fontWeight: FontWeight.bold, - fontFamily: - GoogleFonts.libreBarcode128().fontFamily, - ), - ), - ], - ), - ) - ], - ), - const Spacer(), - Text( - "The above is a member of the ${membership.organization.name} group", - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: Colors.red, overflow: TextOverflow.fade), - ), - ], - ), - ); - } -} diff --git a/lib/tools/organizations/widgets/organization_card.dart b/lib/tools/organizations/widgets/organization_card.dart deleted file mode 100644 index 12c7b09..0000000 --- a/lib/tools/organizations/widgets/organization_card.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import '../pages/organization_view.dart'; - -class OrganizationCard extends StatelessWidget { - const OrganizationCard({ - super.key, - required this.organization, - }); - final Organization organization; - - @override - Widget build(BuildContext context) { - return ListTile( - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => OrganizationView( - organization: organization, - ), - ), - ); - }, - leading: CircleAvatar( - backgroundImage: CachedNetworkImageProvider( - organization.logo ?? "", - ), - ), - title: Text(organization.name), - subtitle: Text(organization.email), - trailing: const Icon(Ionicons.arrow_forward_outline), - ); - } -} diff --git a/lib/tools/organizations/widgets/widgets.dart b/lib/tools/organizations/widgets/widgets.dart deleted file mode 100644 index a83db12..0000000 --- a/lib/tools/organizations/widgets/widgets.dart +++ /dev/null @@ -1 +0,0 @@ -export 'organization_card.dart'; diff --git a/lib/tools/student_performance/pages/student_performance_page.dart b/lib/tools/student_performance/pages/student_performance_page.dart deleted file mode 100644 index eec114f..0000000 --- a/lib/tools/student_performance/pages/student_performance_page.dart +++ /dev/null @@ -1,136 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import 'package:http/http.dart' as http; - -enum StudentPerfomanceParameter { audit, transcript } - -class StudentPerformancePage extends StatefulWidget { - const StudentPerformancePage({ - super.key, - required this.parameter, - }); - final StudentPerfomanceParameter parameter; - - @override - State createState() => _StudentPerformancePageState(); -} - -class _StudentPerformancePageState extends State { - final SettingsController _settingsController = Get.find(); - final UserController _userController = Get.find(); - bool documentLocked = true; - int currentPage = 0, totalPages = 0; - bool isReady = false; - String errorMessage = ''; - - Future _loadPdf() async { - try { - setState(() { - isReady = false; - }); - final response = await http.get(Uri.parse(widget.parameter == - StudentPerfomanceParameter.transcript - ? "https://student.daystar.ac.ke/Downloads/STDAUDIT-${_userController.user.value!.admissionNumber}.pdf" - : "https://student.daystar.ac.ke/Downloads/PROVISIONAL%20RESULTS-${_userController.user.value!.admissionNumber}.pdf")); - if (response.statusCode != 200) { - throw "Error while fetching content"; - } - - setState(() { - isReady = true; - }); - return response.bodyBytes; - } catch (e) { - setState(() { - errorMessage = e.toString(); - }); - } - return Uint8List(0); - } - - @override - void initState() { - super.initState(); - - if (_settingsController.settings.value.showAudit && - widget.parameter == StudentPerfomanceParameter.audit) { - documentLocked = false; - return; - } - - if (_settingsController.settings.value.showTranscript && - widget.parameter == StudentPerfomanceParameter.transcript) { - documentLocked = false; - return; - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text( - widget.parameter == StudentPerfomanceParameter.audit - ? "Student Audit" - : "Student Transcript", - ), - ), - body: documentLocked - ? Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "The requested document cannot be loaded because the document is locked from the settings page", - style: Theme.of(context).textTheme.headlineSmall, - textAlign: TextAlign.center, - ), - ], - ) - : FutureBuilder( - future: _loadPdf(), - builder: (context, snapshot) { - return snapshot.hasData - ? snapshot.hasError - ? Center( - child: Column( - children: [ - Image.asset("assets/images/bot_sad.png"), - Text( - snapshot.error.toString(), - ), - ], - ), - ) - : Stack( - children: [ - PDFView( - fitPolicy: FitPolicy.BOTH, - pdfData: snapshot.data, - pageFling: true, - defaultPage: currentPage, - onPageChanged: (page, total) { - setState(() { - currentPage = page!; - }); - }, - onRender: (pages) { - setState(() {}); - }, - onError: (error) { - debugPrint("Error"); - }, - ), - ], - ) - : const Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text("Loading .."), - ], - ), - ); - }), - ); - } -} diff --git a/lib/tools/student_performance/student_performance.dart b/lib/tools/student_performance/student_performance.dart deleted file mode 100644 index 945d87e..0000000 --- a/lib/tools/student_performance/student_performance.dart +++ /dev/null @@ -1 +0,0 @@ -export 'pages/student_performance_page.dart'; diff --git a/lib/tools/todo/controllers/todo_controller.dart b/lib/tools/todo/controllers/todo_controller.dart deleted file mode 100644 index 315260b..0000000 --- a/lib/tools/todo/controllers/todo_controller.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; - -class TodoController extends GetxController { - final RxList allTodos = [].obs; - - @override - void onInit() { - super.onInit(); - getAllTodos().then((value) { - debugPrint("[+] Todos Loaded"); - }); - } - - List filterTodosByDate(String filterOption) { - final todos = allTodos; - final today = DateTime.now(); - final tomorrow = DateTime.now().add(const Duration(days: 1)); - switch (filterOption.toLowerCase().trim()) { - case "today": - return todos - .where((element) => - element.due.day == today.day && - element.due.month == today.month && - element.due.year == today.year) - .toList(growable: false); - - case "tommorrow": - return todos - .where((element) => - element.due.day == tomorrow.day && - element.due.month == tomorrow.month && - element.due.year == tomorrow.year) - .toList(growable: false); - - case "week": - return todos - .where((element) => - element.due.isAfter(today) && - element.due.difference(today).inDays < 7) - .toList(growable: false); - - case "month": - return todos - .where((element) => - element.due.day < 31 && - element.due.month == today.month && - element.due.year == today.year) - .toList(growable: false); - - default: - return todos; - } - } - - Future addTask(Todo t) async { - int value = await TodoModelHelper().create(t.toJson()); - t.id = value; - allTodos.add(t); - return value == 0 ? false : true; - } - - Future updateTodo(Todo t) async { - allTodos.removeWhere((todo) => todo.id == t.id); - int value = await TodoModelHelper().update(t.toJson()); - allTodos.add(t); - return value == 0 ? false : true; - } - - Future> getAllTodos() async { - TodoModelHelper().queryAll().then((value) { - allTodos.clear(); - for (final val in value) { - allTodos.add(Todo.fromJson(val)); - } - }); - return allTodos; - } - - Future deleteTodo(Todo t) async { - allTodos.removeWhere((value) => t.id == value.id); - int value = await TodoModelHelper().delete(t.toJson()); - return value == 0 ? false : true; - } -} diff --git a/lib/tools/todo/models/models.dart b/lib/tools/todo/models/models.dart deleted file mode 100644 index 4c91d40..0000000 --- a/lib/tools/todo/models/models.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'todo_model.dart'; -export 'todo_model_helper.dart'; -export 'services/todo_background_service.dart'; diff --git a/lib/tools/todo/models/services/todo_background_service.dart b/lib/tools/todo/models/services/todo_background_service.dart deleted file mode 100644 index e7aaf83..0000000 --- a/lib/tools/todo/models/services/todo_background_service.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/notifier/local_notification_channel.dart'; -import 'package:academia/notifier/local_notification_status_manager.dart'; -import 'package:academia/notifier/local_notifier_service.dart'; - -class TodoBackgroundService { - static final TodoBackgroundService _instance = - TodoBackgroundService._internal(); - - factory TodoBackgroundService() { - return _instance; - } - - /// Private named constructor that prevents external instantiation. - TodoBackgroundService._internal(); - - void notifyPendingTasks() { - TodoModelHelper().queryAll().then((value) { - value.map((e) { - final todo = Todo.fromJson(e); - - if ((!todo.complete) && todo.notify == true) { - LocalNotifierService().showNotification( - id: LocalNotificationStatusManager().getNextId(), - title: "Todos ${Emojis.smile_skull}", - body: "Todo ${todo.name} requires your attention", - channelKey: LocalNotificationChannelType.reminders.channelKey, - ); - } - }); - }); - } -} diff --git a/lib/tools/todo/models/todo_model.dart b/lib/tools/todo/models/todo_model.dart deleted file mode 100644 index bd49192..0000000 --- a/lib/tools/todo/models/todo_model.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'dart:convert'; - -class Todo { - int? id; - String name; - bool complete; - bool notify; - String description; - Map? subTasks; - DateTime due; - DateTime dateAdded; - DateTime? dateCompleted; - - Todo({ - this.id, - required this.name, - this.subTasks, - required this.complete, - required this.description, - required this.due, - required this.dateAdded, - this.dateCompleted, - this.notify = false, - }); - // Method to convert Todo object to JSON Map - Map toJson() { - return { - 'id': id, - 'name': name, - 'sub_tasks': json.encode(subTasks), - 'complete': complete ? 1 : 0, - 'notify': notify ? 1 : 0, - 'description': description, - 'due': due.toIso8601String(), - 'dateAdded': dateAdded.toIso8601String(), - 'dateCompleted': dateCompleted?.toIso8601String(), - }; - } - - // Factory method to create a Todo object from a JSON Map - factory Todo.fromJson(Map data) { - return Todo( - id: data['id'], - name: data['name'], - subTasks: json.decode(data["sub_tasks"]).cast(), - complete: data['complete'] == 1 ? true : false, - notify: data['notify'] == 1 ? true : false, - description: data['description'], - due: DateTime.parse(data['due']), - dateAdded: DateTime.parse(data['dateAdded']), - dateCompleted: data['dateCompleted'] != null - ? DateTime.parse(data['dateCompleted']) - : null, - ); - } -} diff --git a/lib/tools/todo/models/todo_model_helper.dart b/lib/tools/todo/models/todo_model_helper.dart deleted file mode 100644 index 39798f5..0000000 --- a/lib/tools/todo/models/todo_model_helper.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:academia/storage/storage.dart'; -import 'package:academia/exports/barrel.dart'; -import 'package:sqflite/sqflite.dart'; - -class TodoModelHelper implements DatabaseOperations { - static final TodoModelHelper _instance = TodoModelHelper._internal(); - - factory TodoModelHelper() { - return _instance; - } - - TodoModelHelper._internal(); - - @override - Future create(Map data) async { - final db = await DatabaseHelper().database; - final id = await db.insert( - 'todos', - data, - conflictAlgorithm: ConflictAlgorithm.replace, - ); - - debugPrint("[+] Todo written successfully"); - return id; - } - - @override - Future>> queryAll() async { - final db = await DatabaseHelper().database; - final todos = await db.query('todos'); - return todos; - } - - @override - Future delete(Map data) async { - final db = await DatabaseHelper().database; - return await db.delete('todos', where: 'id = ?', whereArgs: [data["id"]]); - } - - @override - Future update(Map data) async { - final db = await DatabaseHelper().database; - return await db - .update('todos', data, where: 'id = ?', whereArgs: [data['id']]); - } - - @override - Future truncate() async { - final db = await DatabaseHelper().database; - await db.execute('DELETE FROM todos'); - } -} diff --git a/lib/tools/todo/todo.dart b/lib/tools/todo/todo.dart deleted file mode 100644 index bead6fe..0000000 --- a/lib/tools/todo/todo.dart +++ /dev/null @@ -1,18 +0,0 @@ -export 'todo_home_screen.dart'; -export 'models/models.dart'; -export 'controllers/todo_controller.dart'; -export 'widgets/widgets.dart'; -import "package:intl/intl.dart"; - -// Helper functions -String trimAndEllipsis(String input, int maxLength) { - if (input.length <= maxLength) { - return input; - } else { - return "${input.substring(0, maxLength)}..."; - } -} - -String formatDateTime(DateTime dateTime) { - return DateFormat('EEEE, d MMM y').format(dateTime); -} diff --git a/lib/tools/todo/todo_home_screen.dart b/lib/tools/todo/todo_home_screen.dart deleted file mode 100644 index 1f83672..0000000 --- a/lib/tools/todo/todo_home_screen.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/tools/todo/todo_view_page.dart'; -import 'package:academia/tools/todo/widgets/filled_todo_homescreen.dart'; -import 'package:get/get.dart'; - -class TodoHomeScreen extends StatelessWidget { - const TodoHomeScreen({super.key}); - - @override - Widget build(BuildContext context) { - final todoController = Get.find(); - return Scaffold( - body: Obx( - () => todoController.allTodos.isEmpty - ? const EmptyTodoHomeScreen() - : const FilledTodoHomeScreen(), - ), - floatingActionButton: FloatingActionButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const TodoViewPage(), - ), - ); - }, - child: const Icon(Ionicons.add), - ), - ); - } -} diff --git a/lib/tools/todo/todo_view_page.dart b/lib/tools/todo/todo_view_page.dart deleted file mode 100644 index 0c37cb8..0000000 --- a/lib/tools/todo/todo_view_page.dart +++ /dev/null @@ -1,341 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/notifier/local_notification_channel.dart'; -import 'package:academia/notifier/notifier.dart'; -import 'package:get/get.dart'; - -class TodoViewPage extends StatefulWidget { - const TodoViewPage({super.key, this.todo}); - final Todo? todo; - - @override - State createState() => _TodoViewPageState(); -} - -class _TodoViewPageState extends State { - final TodoController todoController = Get.find(); - final NotificationsController notificationsController = - Get.find(); - final Map subtasks = {}; - final TextEditingController titleController = TextEditingController(); - final TextEditingController duedateController = TextEditingController(); - final TextEditingController descriptionController = TextEditingController(); - final TextEditingController subTaskController = TextEditingController(); - bool notify = false; - late DateTime dueDate; - final GlobalKey formState = GlobalKey(); - - Future pickDueDate() async { - final date = await showDatePicker( - context: context, - firstDate: DateTime.now(), - lastDate: DateTime.now().add( - const Duration(days: 365), - ), - ); - - if (date != null) { - setState(() { - dueDate = date; - duedateController.text = (formatDateTime(date)); - }); - } - } - - @override - void initState() { - super.initState(); - if (widget.todo != null) { - // subtasks = widget.todo.subTasks ?? {}; - titleController.text = widget.todo!.name; - descriptionController.text = widget.todo!.description; - duedateController.text = formatDateTime(widget.todo!.due); - dueDate = widget.todo!.due; - notify = widget.todo!.notify; - subtasks.addAll(widget.todo!.subTasks ?? {}); - } - setState(() {}); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Form( - key: formState, - child: CustomScrollView( - slivers: [ - SliverAppBar( - pinned: true, - floating: true, - snap: true, - expandedHeight: 250, - flexibleSpace: FlexibleSpaceBar( - title: Text( - widget.todo == null ? "Create a todo" : "Update todo", - ), - ), - ), - - // body - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverToBoxAdapter( - child: TextFormField( - controller: titleController, - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (value) => value!.length < 5 - ? "Please provide a valuable title" - : null, - style: Theme.of(context).textTheme.titleMedium, - decoration: const InputDecoration( - hintText: "Your awesome task name", - ), - ), - ), - ), - - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverToBoxAdapter( - child: TextFormField( - onTap: () async => await pickDueDate(), - controller: duedateController, - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (value) => - value!.length < 5 ? "Please pick a date" : null, - style: Theme.of(context).textTheme.titleMedium, - decoration: const InputDecoration( - hintText: "Due Date", - ), - ), - ), - ), - - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverToBoxAdapter( - child: Row( - children: [ - Checkbox( - value: notify, - onChanged: (value) { - setState(() { - notify = value!; - }); - }, - ), - const Text("Notify me everyday on this task"), - ], - ), - ), - ), - - // Task description - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverToBoxAdapter( - child: TextFormField( - controller: descriptionController, - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (value) => value!.length < 5 - ? "Please provide a description for your task" - : null, - maxLines: 3, - decoration: InputDecoration( - hintText: "Describe your task", - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(2), - ), - ), - ), - ), - ), - - // Split them to subtasks - SliverPadding( - padding: const EdgeInsets.all(12), - sliver: SliverToBoxAdapter( - child: Text( - "Split your tasks into subtasks", - style: Theme.of(context).textTheme.titleSmall, - ), - ), - ), - - // Now for the sub taks - SliverPadding( - padding: const EdgeInsets.all(16), - sliver: SliverList.separated( - itemBuilder: (context, index) { - return index == subtasks.length - ? TextButton.icon( - icon: const Icon(Ionicons.add), - onPressed: () { - showModalBottomSheet( - context: context, - builder: (context) => Container( - padding: const EdgeInsets.all(12), - child: Column( - children: [ - TextFormField( - controller: subTaskController, - autovalidateMode: AutovalidateMode - .onUserInteraction, - validator: (value) => value! - .length < - 5 - ? "Please provide a description for your task" - : null, - decoration: const InputDecoration( - hintText: "Sub tasks name", - ), - ), - const SizedBox(height: 22), - FilledButton( - onPressed: () { - if (subTaskController - .text.isNotEmpty) { - setState(() { - subtasks.addAll({ - subTaskController.text: - false - }); - subTaskController.text = ""; - }); - Navigator.pop(context); - } - }, - child: const Text("Add")) - ], - ), - )); - }, - label: const Text("Add Subtask"), - ) - : ListTile( - title: Text( - subtasks.keys.elementAt(index).title(), - ), - leading: Checkbox( - value: subtasks.values.elementAt(index), - onChanged: (val) { - setState(() { - subtasks[subtasks.keys.elementAt(index)] = - val!; - }); - }), - trailing: IconButton( - onPressed: () { - setState(() { - subtasks.remove(subtasks.keys.elementAt(index)); - }); - }, - icon: const Icon(Ionicons.trash), - ), - ); - }, - separatorBuilder: (context, index) => const SizedBox(height: 2), - itemCount: subtasks.length + 1, - ), - ) - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: widget.todo == null - ? () async { - if (!formState.currentState!.validate()) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text("Please ensure you fill the form"), - )); - return; - } - - // Add the data to the controller - final ok = await todoController.addTask( - Todo( - subTasks: subtasks, - name: titleController.text, - description: descriptionController.text, - dateAdded: DateTime.now(), - due: dueDate, - notify: notify, - complete: false), - ); - - if (ok) { - if (!context.mounted) return; - if (notify) { - // notification logic - LocalNotifierService().showNotification( - id: LocalNotificationStatusManager().getNextId(), - title: "Todos", - body: - "Todo ${titleController.text} has been scheduled successfully ${Emojis.smile_clown_face}", - channelKey: - LocalNotificationChannelType.reminders.channelKey, - ); - } - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text( - notify - ? "Your task has been saved and you will be notified daily" - : "Your task has been sucessfully saved", - ), - )); - return; - } - if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text( - "Something went wrong while trying to save your todo"), - )); - return; - } - : () async { - if (!formState.currentState!.validate()) { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text("Please ensure you fill the form"), - )); - return; - } - - widget.todo?.name = titleController.text; - widget.todo?.description = descriptionController.text; - widget.todo?.subTasks = subtasks; - widget.todo?.due = dueDate; - widget.todo?.notify = notify; - - final ok = await todoController.updateTodo(widget.todo!); - if (ok) { - if (!context.mounted) return; - if (notify) { - // notification logic - LocalNotifierService().showNotification( - id: LocalNotificationStatusManager().getNextId(), - title: "Todos", - body: - "Todo ${titleController.text} has been scheduled successfully ${Emojis.smile_clown_face}", - channelKey: - LocalNotificationChannelType.reminders.channelKey, - ); - } - - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text( - "Your task has been sucessfully updated", - ), - )); - return; - } - if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text( - "Something went wrong while attempting to update todo", - ), - )); - }, - child: const Icon(Ionicons.checkmark), - ), - ); - } -} diff --git a/lib/tools/todo/widgets/empty_todo_state.dart b/lib/tools/todo/widgets/empty_todo_state.dart deleted file mode 100644 index c441ada..0000000 --- a/lib/tools/todo/widgets/empty_todo_state.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/material.dart'; - -class EmptyTodoHomeScreen extends StatelessWidget { - const EmptyTodoHomeScreen({super.key}); - - @override - Widget build(BuildContext context) { - return CustomScrollView( - slivers: [ - const SliverAppBar( - snap: true, - floating: true, - pinned: true, - expandedHeight: 250, - flexibleSpace: FlexibleSpaceBar( - title: Text("Todos"), - ), - ), - SliverFillRemaining( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset( - "assets/images/man-and-woman-searching.png", - ), - Text( - "You have no todo items yet, create one to get started", - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineSmall, - ), - ], - ), - ), - ], - ); - } -} diff --git a/lib/tools/todo/widgets/filled_todo_homescreen.dart b/lib/tools/todo/widgets/filled_todo_homescreen.dart deleted file mode 100644 index 495a45e..0000000 --- a/lib/tools/todo/widgets/filled_todo_homescreen.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; - -class FilledTodoHomeScreen extends StatelessWidget { - const FilledTodoHomeScreen({super.key}); - - Widget _buildFilteredTodos(String filter, List todos) { - return todos.isEmpty - ? const Center( - child: Text( - "Oops! Seems you have nothing in this category create a todo to get started", - textAlign: TextAlign.center, - ), - ) - : ListView.separated( - itemBuilder: (context, index) { - final data = todos[index]; - return TodoTile(todo: data); - }, - separatorBuilder: (context, index) => const SizedBox(height: 2), - itemCount: todos.length); - } - - @override - Widget build(BuildContext context) { - final TodoController todoController = Get.find(); - return DefaultTabController( - length: 4, - child: CustomScrollView( - slivers: [ - SliverAppBar( - floating: true, - snap: true, - pinned: true, - title: const Text("Todos"), - actions: [ - IconButton( - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text("Feature"), - content: const Text( - "Todos helps you keep track of assignments and todo stuff", - ), - actions: [ - FilledButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text("Ok cool"), - ) - ], - ), - ); - }, - icon: const Icon(Ionicons.information_circle_outline), - ), - ], - bottom: const TabBar( - tabs: [ - Tab(text: "Today"), - Tab(text: "Tommorrow"), - Tab(text: "Week"), - Tab(text: "Month"), - ], - ), - ), - SliverFillRemaining( - child: Obx( - () => TabBarView(children: [ - _buildFilteredTodos( - "today", todoController.filterTodosByDate("today")), - _buildFilteredTodos( - "tommorrow", todoController.filterTodosByDate("tommorrow")), - _buildFilteredTodos( - "week", todoController.filterTodosByDate("week")), - _buildFilteredTodos( - "month", todoController.filterTodosByDate("month")), - ]), - ), - ) - ], - ), - ); - } -} diff --git a/lib/tools/todo/widgets/todo_tile.dart b/lib/tools/todo/widgets/todo_tile.dart deleted file mode 100644 index dc7e833..0000000 --- a/lib/tools/todo/widgets/todo_tile.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; -import '../todo_view_page.dart'; - -class TodoTile extends StatefulWidget { - const TodoTile({super.key, required this.todo}); - final Todo todo; - - @override - State createState() => _TodoTileState(); -} - -class _TodoTileState extends State { - final TodoController todoController = Get.find(); - double calculatePercent() { - final subtasksCount = widget.todo.subTasks?.values.length ?? 0; - if (subtasksCount == 0) { - return 0; - } - final done = widget.todo.subTasks!.values - .where((val) => val == true) - .toList() - .length; - return (done / subtasksCount); - } - - @override - Widget build(BuildContext context) { - return ListTile( - onTap: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => TodoViewPage( - todo: widget.todo, - ))); - }, - leading: Checkbox( - value: widget.todo.complete, - onChanged: (value) { - setState(() { - widget.todo.complete = value!; - todoController.updateTodo( - widget.todo..dateCompleted = DateTime.now(), - ); - }); - }), - title: Text( - widget.todo.name, - style: widget.todo.complete - ? const TextStyle(decoration: TextDecoration.lineThrough) - : null, - ), - subtitle: LinearProgressIndicator( - value: calculatePercent(), - ), - trailing: IconButton( - onPressed: () async { - final deleted = await todoController.deleteTodo(widget.todo); - if (deleted && context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text("Todo has been sucessfully deleted")), - ); - return; - } - if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - "Something went wrong while trying to delete todo try again")), - ); - }, - icon: const Icon(Ionicons.trash), - ), - ); - } -} diff --git a/lib/tools/todo/widgets/widgets.dart b/lib/tools/todo/widgets/widgets.dart deleted file mode 100644 index f444fc1..0000000 --- a/lib/tools/todo/widgets/widgets.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'empty_todo_state.dart'; -export 'todo_tile.dart'; diff --git a/lib/tools/tools.dart b/lib/tools/tools.dart deleted file mode 100644 index 4332b99..0000000 --- a/lib/tools/tools.dart +++ /dev/null @@ -1,12 +0,0 @@ -export 'leaderboard/leaderboard.dart'; -export 'anki/anki.dart'; -export 'student_performance/student_performance.dart'; -export 'events/events.dart'; -export 'fees/fees.dart'; -export 'gpa_calculator/gpacalculator.dart'; -export 'birthday/birthday.dart'; -export 'chirp/chirp.dart'; -export 'ask_me/ask_me.dart'; -export 'exam_timetable/exam_timetable.dart'; -export 'todo/todo.dart'; -export 'organizations/organizations.dart'; diff --git a/lib/utils/network/auth_interceptor.dart b/lib/utils/network/auth_interceptor.dart new file mode 100644 index 0000000..28f1154 --- /dev/null +++ b/lib/utils/network/auth_interceptor.dart @@ -0,0 +1,33 @@ +import 'package:academia/database/database.dart'; +import 'package:dio/dio.dart'; + +class AuthInterceptor extends Interceptor { + final Dio dio; + final UserCredentialData creds; + final AppDatabase database; + + AuthInterceptor( + {required this.dio, required this.creds, required this.database}); + + @override + void onRequest( + RequestOptions options, RequestInterceptorHandler handler) async { + // Add your API key & other stuff to the headers. + options.headers.addAll({"Authorization": "Bearer ${creds.accessToken!}"}); + handler.next(options); + } + + @override + void onError(DioException err, ErrorInterceptorHandler handler) async { + // TODO Add automatic token refreshing + if (err.response?.statusCode == 401) { + return handler + .resolve(await dio.fetch(err.requestOptions)); // Repeat the request. + } + + return handler.reject(DioException( + requestOptions: err.requestOptions, + error: err.response, + )); + } +} diff --git a/lib/utils/network/dio_client.dart b/lib/utils/network/dio_client.dart new file mode 100644 index 0000000..1a372f2 --- /dev/null +++ b/lib/utils/network/dio_client.dart @@ -0,0 +1,44 @@ +import 'package:academia/database/database.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import './auth_interceptor.dart'; + +class DioClient { + final UserCredentialData creds; + final AppDatabase database; + static const String _baseUrl = "http://192.168.2.115:8000/v2"; + // static const String _baseUrl = "http://192.168.26.183:8000/v2"; + + DioClient({ + required this.creds, + required this.database, + }) { + dio.interceptors.add(LogInterceptor( + error: true, + responseBody: true, + request: true, + requestBody: true, + requestHeader: true, + responseHeader: true, + logPrint: (o) => debugPrint(o.toString()), + )); + } + + final Dio dio = Dio( + BaseOptions(baseUrl: _baseUrl), + ); + + void addAuthInterceptor() { + dio.interceptors.add( + AuthInterceptor( + dio: dio, + creds: creds, + database: database, + ), + ); + } + + void addInterceptor(Interceptor interceptor) { + dio.interceptors.add(interceptor); + } +} diff --git a/lib/utils/network/network.dart b/lib/utils/network/network.dart new file mode 100644 index 0000000..90ecd02 --- /dev/null +++ b/lib/utils/network/network.dart @@ -0,0 +1 @@ +export 'dio_client.dart'; diff --git a/lib/utils/responsive/responsive.dart b/lib/utils/responsive/responsive.dart new file mode 100644 index 0000000..975cfd4 --- /dev/null +++ b/lib/utils/responsive/responsive.dart @@ -0,0 +1,4 @@ +class ScreenDimension { + static const int mobileWidth = 600; + static const int desktopWidth = 1200; +} diff --git a/lib/utils/router/router.dart b/lib/utils/router/router.dart new file mode 100644 index 0000000..e7a7ee1 --- /dev/null +++ b/lib/utils/router/router.dart @@ -0,0 +1,67 @@ +import 'package:academia/features/auth/cubit/auth_cubit.dart'; +import 'package:academia/features/auth/cubit/auth_states.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:academia/features/features.dart'; + +class AcademiaRouter { + static GoRouter get router => _router; + static GlobalKey get globalNavigatorKey => + GlobalKey(); + + static const String registerRoute = "/register"; + static const String auth = "/auth"; + static const String profile = "profile"; + + static final GoRouter _router = GoRouter( + initialLocation: "/", + navigatorKey: globalNavigatorKey, + routes: [ + GoRoute( + path: "/", + name: "/", + builder: (context, state) => const DefaultRoute(), + ), + GoRoute( + path: "/auth", + name: "/auth", + builder: (context, state) => const LoginPage(), + ), + GoRoute( + path: "/register", + name: "/register", + builder: (context, state) => const SignUpPage(), + ), + GoRoute( + path: "/home", + name: "/home", + builder: (context, state) => const Layout(), + ), + GoRoute( + path: profile, + name: "/$profile", + builder: (context, state) => const ProfilePage(), + ), + ], + ); +} + +class DefaultRoute extends StatelessWidget { + const DefaultRoute({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder(builder: (context, state) { + switch (state.runtimeType) { + case AuthErrorState: + return Center( + child: Text((state as AuthErrorState).message), + ); + case AuthCachedUsersRetrieved: + return const UserSelectionPage(); + } + return const OnboardingPage(); + }); + } +} diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart new file mode 100644 index 0000000..ac1e339 --- /dev/null +++ b/lib/utils/utils.dart @@ -0,0 +1 @@ +export 'responsive/responsive.dart'; diff --git a/lib/utils/validator/validator.dart b/lib/utils/validator/validator.dart new file mode 100644 index 0000000..229bc2c --- /dev/null +++ b/lib/utils/validator/validator.dart @@ -0,0 +1,79 @@ +import 'package:flutter/services.dart'; + +String? validateAdmissionNumberInput(String? value) { + // Define the pattern: two digits, a dash, and four digits + final pattern = RegExp(r'^\d{2}-\d{4}$'); + + if (value == null || value.isEmpty) { + return 'Input is required'; + } else if (!pattern.hasMatch(value)) { + return 'Invalid format (use: xx-xxxx)'; + } + return null; // Valid input +} + +class AdmnoDashFormatter extends TextInputFormatter { + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, TextEditingValue newValue) { + final newText = newValue.text; + + // Automatically insert a dash after the first two digits + if (newText.length == 2 && !newText.contains('-')) { + return TextEditingValue( + text: '$newText-', + selection: const TextSelection.collapsed(offset: 3), + ); + } + + // Prevent additional dashes or incorrect input + return newValue; + } +} + +class KenyanPhoneNumberFormatter extends TextInputFormatter { + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, TextEditingValue newValue) { + // Remove any non-digit characters except the '+' sign + String sanitized = newValue.text.replaceAll(RegExp(r'[^0-9+]'), ''); + + // If input starts with +254, keep it + if (sanitized.startsWith('+254')) { + // Keep only the first 13 characters (+254 followed by 9 digits) + if (sanitized.length > 13) { + sanitized = sanitized.substring(0, 13); + } + } else { + // If it doesn't start with +254, reset to empty + sanitized = ''; + } + + return TextEditingValue( + text: sanitized, + selection: TextSelection.collapsed(offset: sanitized.length), + ); + } +} + +class EmailInputFormatter extends TextInputFormatter { + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, TextEditingValue newValue) { + // Define a regex for a basic email validation that ends with @daystar.ac.ke + final emailRegExp = RegExp(r'^[a-zA-Z0-9._%+-]+@daystar\.ac\.ke$'); + + // If the new value is empty, return it + if (newValue.text.isEmpty) { + return newValue; + } + + // Check if the input matches the email regex + if (emailRegExp.hasMatch(newValue.text)) { + return newValue; // Input is valid, allow it + } + + // If input is invalid, return the old value + return oldValue; + } +} diff --git a/lib/widgets/course_attendance_widget.dart b/lib/widgets/course_attendance_widget.dart deleted file mode 100644 index 9191801..0000000 --- a/lib/widgets/course_attendance_widget.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:academia/exports/barrel.dart'; - -class CourseAttendanceCard extends StatelessWidget { - const CourseAttendanceCard({ - super.key, - required this.course, - required this.percent, - }); - final String course; - final double percent; - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - border: Border.all( - color: Theme.of(context).primaryColor, - ), - ), - constraints: BoxConstraints( - minWidth: MediaQuery.of(context).size.width * 0.38, - maxWidth: MediaQuery.of(context).size.width * 0.4, - ), - child: CircularPercentIndicator( - animation: true, - animationDuration: 1000, - radius: 50, - percent: percent / 100, - lineWidth: 20, - progressColor: Theme.of(context).primaryColor, - circularStrokeCap: CircularStrokeCap.round, - // center: Text( - // "$percent%", - // style: h6, - // ), - // footer: Text( - // course, - // style: h6.copyWith( - // fontSize: 10, - // ), - // overflow: TextOverflow.ellipsis, - // ), - ), - ); - } -} diff --git a/lib/widgets/info_card.dart b/lib/widgets/info_card.dart deleted file mode 100644 index fc35686..0000000 --- a/lib/widgets/info_card.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:academia/exports/barrel.dart'; - -class InfoCard extends StatelessWidget { - const InfoCard({ - super.key, - required this.title, - required this.content, - required this.icon, - }); - final String title; - final String content; - final IconData icon; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric( - vertical: 12, - horizontal: 12, - ), - child: Container( - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(color: Theme.of(context).colorScheme.outline), - ), - ), - padding: const EdgeInsets.all(6), - child: Row( - children: [ - Icon(icon), - const SizedBox(width: 12), - Text( - "$title:", - style: Theme.of(context) - .textTheme - .titleSmall - ?.copyWith(fontWeight: FontWeight.bold), - ), - const SizedBox(width: 8), - Expanded( - child: Text( - content, - style: const TextStyle( - overflow: TextOverflow.ellipsis, - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/widgets/profile_picture.dart b/lib/widgets/profile_picture.dart deleted file mode 100644 index c986570..0000000 --- a/lib/widgets/profile_picture.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:get/get.dart'; - -/// The ProfilePictureWidget displays a user's profile picture -/// accordingly representing the user's settings on whether to show -/// their picture or not -/// -/// The [profileSize] parameter controls the size of the image - -class ProfilePictureWidget extends StatelessWidget { - const ProfilePictureWidget({ - super.key, - this.profileSize = 18, - }); - - final double profileSize; - - @override - Widget build(BuildContext context) { - final settingsController = Get.find(); - final userController = Get.find(); - - return CircleAvatar( - radius: profileSize, - child: ClipRRect( - borderRadius: const BorderRadius.all( - Radius.circular(800), - ), - child: Obx( - () => settingsController.settings.value.showProfilePicture - ? userController.user.value == null - ? Image.asset( - "assets/icons/academia.png", - height: 400, - width: 400, - fit: BoxFit.fill, - ) - : userController.user.value!.profileUrl.startsWith("http") - ? CachedNetworkImage( - placeholder: (context, progress) => - const CircularProgressIndicator(), - imageUrl: userController.user.value!.profileUrl, - errorWidget: (context, error, url) => Image.asset( - userController.user.value!.gender == "male" - ? "assets/images/male_student.png" - : "assets/images/female_student.png", - ), - fit: BoxFit.cover, - height: 300, - ) - : Image.memory( - Uint8List.fromList( - base64Decode( - userController.user.value!.schoolProfile - .replaceFirst("data:image/gif;base64,", ""), - ), - ), - fit: BoxFit.cover, - ) - : Image.asset( - userController.user.value!.gender == "male" - ? "assets/images/male_student.png" - : "assets/images/female_student.png", - ), - ), - ), - ); - } -} diff --git a/lib/widgets/school_id_card_widget.dart b/lib/widgets/school_id_card_widget.dart deleted file mode 100644 index 9764154..0000000 --- a/lib/widgets/school_id_card_widget.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'package:academia/exports/barrel.dart'; -import 'package:academia/models/models.dart'; -import 'package:get/get.dart'; -import 'package:google_fonts/google_fonts.dart'; - -class SchoolIDCard extends StatelessWidget { - const SchoolIDCard({super.key, required this.user}); - final User user; - - @override - Widget build(BuildContext context) { - final UserController userController = Get.find(); - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: Theme.of(context).colorScheme.shadow, - ), - ), - padding: const EdgeInsets.all(12), - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width, - maxHeight: 250, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - Image.asset( - "assets/logos/du-logo.png", - width: 80, - ), - const Spacer(), - Text( - "STUDENT", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: Colors.red, - fontWeight: FontWeight.w700, - ), - ) - ], - ), - const SizedBox(height: 4), - Text( - "${user.firstName.toUpperCase()} ${user.lastName.toUpperCase()}", - style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontFamily: GoogleFonts.jetBrainsMono().fontFamily, - ), - textAlign: TextAlign.left, - ), - const SizedBox(height: 8), - Row( - children: [ - Flexible( - flex: 1, - child: CircleAvatar( - radius: 60, - child: ClipRRect( - borderRadius: BorderRadius.circular(800), - child: Image.memory( - Uint8List.fromList( - base64Decode( - userController.user.value!.schoolProfile - .replaceFirst("data:image/gif;base64,", ""), - ), - ), - fit: BoxFit.cover, - ), - ), - )), - const SizedBox(width: 12), - Flexible( - flex: 2, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( - children: [ - Text( - "ID/ Passport: ", - style: - Theme.of(context).textTheme.bodySmall?.copyWith( - fontWeight: FontWeight.bold, - fontFamily: - GoogleFonts.jetBrainsMono().fontFamily, - ), - ), - Text( - user.nationalId, - style: - Theme.of(context).textTheme.bodySmall?.copyWith( - fontFamily: - GoogleFonts.jetBrainsMono().fontFamily, - ), - ), - ], - ), - const SizedBox(height: 8), - Row( - children: [ - Text( - "Admission: ", - style: - Theme.of(context).textTheme.bodySmall?.copyWith( - fontWeight: FontWeight.bold, - fontFamily: - GoogleFonts.jetBrainsMono().fontFamily, - ), - ), - Text( - user.admissionNumber, - style: - Theme.of(context).textTheme.bodySmall?.copyWith( - fontFamily: - GoogleFonts.jetBrainsMono().fontFamily, - ), - ), - ], - ), - const SizedBox(height: 8), - Text( - user.admissionNumber, - textAlign: TextAlign.left, - style: Theme.of(context).textTheme.displayLarge?.copyWith( - fontWeight: FontWeight.bold, - fontFamily: - GoogleFonts.libreBarcode128().fontFamily, - ), - ), - ], - ), - ) - ], - ), - const Spacer(), - Text( - "*Note that this is a duplicate, data provided here is as is", - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: Colors.red, overflow: TextOverflow.fade), - ), - ], - ), - ); - } -} diff --git a/lib/widgets/stat.dart b/lib/widgets/stat.dart deleted file mode 100644 index 962a1e1..0000000 --- a/lib/widgets/stat.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:academia/exports/barrel.dart'; - -class Stat extends StatelessWidget { - const Stat({ - super.key, - required this.title, - required this.percentage, - }); - final String title; - final double percentage; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: CircularPercentIndicator( - footer: Text(title), - center: Text("${(percentage * 100).toStringAsFixed(1)}%"), - curve: Curves.elasticIn, - lineWidth: 18, - animation: true, - percent: percentage, - startAngle: 40, - backgroundColor: Theme.of(context).colorScheme.primaryContainer, - progressColor: Theme.of(context).colorScheme.primary, - animationDuration: 2000, - animateFromLastPercent: true, - radius: 40, - ), - ); - } -} diff --git a/lib/widgets/tool_card.dart b/lib/widgets/tool_card.dart deleted file mode 100644 index e60ac99..0000000 --- a/lib/widgets/tool_card.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:academia/exports/barrel.dart'; - -class ToolCard extends StatefulWidget { - const ToolCard({ - super.key, - required this.heading, - required this.image, - required this.description, - required this.ontap, - required this.action, - }); - final String heading; - final String image; - final String description; - final Function ontap; - final String action; - - @override - State createState() => _ToolCardState(); -} - -class _ToolCardState extends State { - bool _isLoading = false; - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: () async { - setState(() { - _isLoading = true; - }); - await widget.ontap.call(); - setState(() { - _isLoading = false; - }); - }, - child: Card( - child: Padding( - padding: const EdgeInsets.only(bottom: 12, right: 12), - child: Column( - children: [ - ListTile( - title: Text(widget.heading), - // subtitle: Text(subheading), - // trailing: const Icon(Icons.favorite_outline), - ), - SizedBox( - height: 200.0, - child: _isLoading - ? const Text("Loading") - : Image.asset( - widget.image, - fit: BoxFit.fitWidth, - ), - ), - Container( - padding: const EdgeInsets.all(16.0), - alignment: Alignment.centerLeft, - child: Text(widget.description), - ), - OverflowBar( - alignment: MainAxisAlignment.end, - children: [ - FilledButton.tonal( - child: Text(widget.action), - onPressed: () async { - setState(() { - _isLoading = true; - }); - await widget.ontap.call(); - setState(() { - _isLoading = false; - }); - }, - ), - ], - ) - ], - ), - ), - ), - ); - } -} diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart deleted file mode 100644 index 2b48acc..0000000 --- a/lib/widgets/widgets.dart +++ /dev/null @@ -1,5 +0,0 @@ -export 'stat.dart'; -export 'profile_picture.dart'; -export 'school_id_card_widget.dart'; -export 'info_card.dart'; -export 'tool_card.dart'; diff --git a/lib/workers/background_worker.dart b/lib/workers/background_worker.dart deleted file mode 100644 index ab22f7e..0000000 --- a/lib/workers/background_worker.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:academia/exports/barrel.dart'; - -// background services -@pragma('vm:entry-point') -void callbackDispatcher() { - Workmanager().executeTask((task, inputData) { - switch (task) { - case BackgroundConfig.todosIDentifier: - TodoBackgroundService().notifyPendingTasks(); - break; - case BackgroundConfig.coursesIDentifier: - CoursesBackgroundService().notifyTodaysCourses(); - break; - default: - } - return Future.value(true); - }); -} - -Duration timeUntilNextTarget(DateTime targetTime) { - DateTime now = DateTime.now(); - Duration difference = targetTime.difference(now); - return difference; -} - -class BackgroundWorker { - static final BackgroundWorker _instance = BackgroundWorker._internal(); - - factory BackgroundWorker() { - return _instance; - } - - /// Private named constructor that prevents external instantiation. - BackgroundWorker._internal(); - - Future initialize() async { - if (Platform.isAndroid || Platform.isIOS) { - await Workmanager().initialize( - callbackDispatcher, - isInDebugMode: false, - ); - - DateTime now = DateTime.now(); - - /// Courses background service - Workmanager().registerPeriodicTask( - BackgroundConfig.coursesIDentifier, - BackgroundConfig.coursesIDentifier, - initialDelay: timeUntilNextTarget( - DateTime(now.year, now.month, now.day, 7, 0, 0), - ), - frequency: const Duration(hours: 8), - ); - - /// Todos task that is suppossed to run after every 24 hours at 8 am - Workmanager().registerPeriodicTask( - BackgroundConfig.todosIDentifier, - BackgroundConfig.todosIDentifier, - initialDelay: const Duration(seconds: 10), - frequency: const Duration(hours: 8), - ); - } - } -} diff --git a/lib/workers/workers.dart b/lib/workers/workers.dart deleted file mode 100644 index 7ab23a1..0000000 --- a/lib/workers/workers.dart +++ /dev/null @@ -1,8 +0,0 @@ -export 'background_worker.dart'; - -// Tasks Define - -class BackgroundConfig { - static const String todosIDentifier = "com.dita.academia.todos"; - static const String coursesIDentifier = "com.dita.academia.courses"; -} diff --git a/pubspec.yaml b/pubspec.yaml index e455eb0..37482b6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 2.0.0+2 environment: - sdk: '>=2.19.6 <3.0.0' + sdk: ^3.6.0-216.1.beta # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -35,54 +35,33 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 flutter_native_splash: ^2.0.5 - http: ^1.0.0 - html: ^0.15.3 intl: ^0.18.1 - build_runner: ^2.3.3 - get: ^4.6.5 flutter_launcher_icons: ^0.13.1 - webview_flutter: ^4.2.2 magnet: git: url: https://github.com/IamMuuo/magnet ref: master cached_network_image: ^3.2.3 - percent_indicator: ^4.2.3 - flutter_pdfview: ^1.3.2 - path_provider: ^2.1.4 - timezone: ^0.9.2 - story_view: ^0.16.0 - ionicons: ^0.2.2 google_fonts: ^6.2.1 - flutter_timer_countdown: ^1.0.7 awesome_notifications_core: ^0.9.3 awesome_notifications: ^0.8.2 - sqflite: ^2.3.3 - path: ^1.9.0 - flex_color_picker: ^3.4.1 - sqflite_common_ffi: ^2.3.3 - connectivity_plus: ^6.0.3 dartz: ^0.10.1 lottie: ^3.1.2 - shared_preferences: ^2.2.3 - flutter_heatmap_calendar: ^1.0.5 - image_picker: ^1.1.2 - image_cropper: ^8.0.1 - workmanager: ^0.5.2 timeago: ^3.7.0 - swipe_to: ^1.0.6 - file_picker: ^8.0.5 - appinio_swiper: ^2.1.1 - flutter_html: ^3.0.0-beta.2 - freezed: ^2.5.7 - freezed_annotation: ^2.4.4 - json_serializable: ^6.8.0 - flutter_colorpicker: ^1.1.0 local_auth: ^2.3.0 + go_router: ^14.3.0 + icons_plus: ^5.0.0 + rive: ^0.13.15 + drift: ^2.21.0 + drift_flutter: ^0.2.1 + flutter_bloc: ^8.1.6 + sliver_tools: ^0.2.12 + dynamic_color: ^1.7.0 + intl_phone_field: ^3.2.0 + flutter_svg: ^2.0.14 + dio: ^5.7.0 + skeletonizer: ^1.4.2 - - - dev_dependencies: flutter_test: sdk: flutter @@ -93,6 +72,8 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^2.0.0 + drift_dev: ^2.21.2 + build_runner: ^2.4.13 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -117,6 +98,7 @@ flutter: - assets/images/ - assets/logos/ - assets/lotties/ + - assets/rive/ # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see