From fb2e52dd663b9734d13aa682806fd8272bf299a2 Mon Sep 17 00:00:00 2001 From: Erick Date: Sat, 21 Dec 2024 15:34:00 +0300 Subject: [PATCH 01/12] fix: central content profile cards being rounded --- lib/features/profile/views/profile_page_mobile.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/features/profile/views/profile_page_mobile.dart b/lib/features/profile/views/profile_page_mobile.dart index bdd775c..145a7d9 100644 --- a/lib/features/profile/views/profile_page_mobile.dart +++ b/lib/features/profile/views/profile_page_mobile.dart @@ -195,7 +195,7 @@ class _ProfilePageMobileState extends State { elevation: 0, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( - top: Radius.circular(12), + top: Radius.zero, ), ), child: ListTile( @@ -208,7 +208,7 @@ class _ProfilePageMobileState extends State { elevation: 0, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( - top: Radius.circular(12), + top: Radius.zero, ), ), child: ListTile( From 5f63a9e116ca4457465fde248b51ad12b1975a90 Mon Sep 17 00:00:00 2001 From: Erick Date: Sun, 22 Dec 2024 14:04:00 +0300 Subject: [PATCH 02/12] feat: added multiple accounts sign in --- lib/features/profile/views/profile_page_mobile.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/features/profile/views/profile_page_mobile.dart b/lib/features/profile/views/profile_page_mobile.dart index 145a7d9..259489d 100644 --- a/lib/features/profile/views/profile_page_mobile.dart +++ b/lib/features/profile/views/profile_page_mobile.dart @@ -1,8 +1,11 @@ import 'package:academia/database/database.dart'; import 'package:academia/features/features.dart'; +import 'package:academia/utils/router/router.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; import 'package:icons_plus/icons_plus.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:sliver_tools/sliver_tools.dart'; @@ -65,7 +68,12 @@ class _ProfilePageMobileState extends State { icon: const Icon(Bootstrap.pencil), ), IconButton( - onPressed: () {}, + onPressed: () { + HapticFeedback.heavyImpact().then((val) { + if (!context.mounted) return; + GoRouter.of(context).pushNamed(AcademiaRouter.auth); + }); + }, icon: const Icon(Bootstrap.person_add), ) ], From 160ce2108403bf61326f653a6ed4122e9a87179a Mon Sep 17 00:00:00 2001 From: Erick Date: Mon, 23 Dec 2024 12:12:00 +0300 Subject: [PATCH 03/12] chore: essentials page ui design --- lib/features/essentials/essentials.dart | 1 + .../views/essentials_mobile_page.dart | 154 ++++++++++++++++++ .../essentials/views/essentials_page.dart | 19 +++ lib/features/home/views/layout.dart | 9 +- 4 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 lib/features/essentials/essentials.dart create mode 100644 lib/features/essentials/views/essentials_mobile_page.dart create mode 100644 lib/features/essentials/views/essentials_page.dart diff --git a/lib/features/essentials/essentials.dart b/lib/features/essentials/essentials.dart new file mode 100644 index 0000000..8c408fb --- /dev/null +++ b/lib/features/essentials/essentials.dart @@ -0,0 +1 @@ +export 'views/essentials_page.dart'; diff --git a/lib/features/essentials/views/essentials_mobile_page.dart b/lib/features/essentials/views/essentials_mobile_page.dart new file mode 100644 index 0000000..191cf95 --- /dev/null +++ b/lib/features/essentials/views/essentials_mobile_page.dart @@ -0,0 +1,154 @@ +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 EssentialsMobilePage extends StatefulWidget { + const EssentialsMobilePage({super.key}); + + @override + State createState() => _EssentialsMobilePageState(); +} + +class _EssentialsMobilePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( + expandedHeight: 250, + flexibleSpace: FlexibleSpaceBar( + title: Text( + "Essentials", + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + fontFamily: GoogleFonts.libreBaskerville().fontFamily, + ), + ), + ), + ), + SliverPadding( + padding: const EdgeInsets.all(12), + sliver: MultiSliver( + children: const [ + Card( + elevation: 0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12), + ), + ), + child: ListTile( + leading: const Icon(Bootstrap.bell), + title: const Text("Todos & Assigments"), + subtitle: Text("Keep track of your assignments and todos"), + ), + ), + Card( + elevation: 0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.zero, + ), + ), + child: ListTile( + leading: const Icon(Bootstrap.clock), + title: const Text("Exam timetable"), + subtitle: Text("Psst.. Never miss an exam"), + ), + ), + Card( + elevation: 0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.zero, + ), + ), + child: ListTile( + leading: const Icon(Bootstrap.filetype_pdf), + title: const Text("Past Revision Papers"), + subtitle: Text("You want them? You get them.."), + ), + ), + + Card( + elevation: 0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + bottom: Radius.zero, + ), + ), + child: ListTile( + leading: const Icon(Bootstrap.file_ppt), + title: const Text("Ask Me"), + subtitle: Text("Boring notes? We'll help you revise"), + ), + ), + Card( + elevation: 0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + bottom: Radius.circular(12), + ), + ), + child: ListTile( + leading: const Icon(Bootstrap.play), + title: const Text("Flash Cards"), + subtitle: Text( + "Curious if you really understood? Try our flashcards", + ), + ), + ), + + //Page Break for student performance + SizedBox(height: 18), + Card( + elevation: 0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12), + ), + ), + child: ListTile( + leading: const Icon(Bootstrap.bell), + title: const Text("Student Audit"), + subtitle: Text("Keep track of your assignments and todos"), + ), + ), + Card( + elevation: 0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + bottom: Radius.zero, + ), + ), + child: ListTile( + leading: const Icon(Bootstrap.activity), + title: const Text("GPA Calculator"), + subtitle: Text("Watch out for those grades!"), + ), + ), + + Card( + elevation: 0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + bottom: Radius.circular(12), + ), + ), + child: ListTile( + leading: const Icon(Bootstrap.play), + title: const Text("Flash Cards"), + subtitle: Text( + "Curious if you really understood? Try our flashcards", + ), + ), + ), + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/features/essentials/views/essentials_page.dart b/lib/features/essentials/views/essentials_page.dart new file mode 100644 index 0000000..51774cf --- /dev/null +++ b/lib/features/essentials/views/essentials_page.dart @@ -0,0 +1,19 @@ +import 'package:academia/features/essentials/views/essentials_mobile_page.dart'; +import 'package:academia/utils/responsive/responsive.dart'; +import 'package:flutter/material.dart'; + +class EssentialsPage extends StatelessWidget { + const EssentialsPage({super.key}); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) => + constraints.maxWidth < ScreenDimension.mobileWidth + ? const EssentialsMobilePage() + : const Center( + child: Text("Essentials coming soon"), + ), + ); + } +} diff --git a/lib/features/home/views/layout.dart b/lib/features/home/views/layout.dart index 310ae91..3658369 100644 --- a/lib/features/home/views/layout.dart +++ b/lib/features/home/views/layout.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:academia/features/essentials/essentials.dart'; import 'package:academia/features/features.dart'; import 'package:academia/utils/responsive/responsive.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; @@ -84,9 +85,7 @@ class _LayoutState extends State { Center( child: Text("Social"), ), - Center( - child: Text("Statistics"), - ), + EssentialsPage(), ProfilePage() ], ) @@ -143,9 +142,7 @@ class _LayoutState extends State { Center( child: Text("Social"), ), - Center( - child: Text("Statistics"), - ), + EssentialsPage(), ProfilePage() ], ), From 88a56a8aca1dbfc0f0a6186c7ff78fd24a11b8b5 Mon Sep 17 00:00:00 2001 From: Erick Date: Tue, 24 Dec 2024 01:51:23 +0300 Subject: [PATCH 04/12] chore: added header background color --- lib/features/essentials/views/essentials_mobile_page.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/features/essentials/views/essentials_mobile_page.dart b/lib/features/essentials/views/essentials_mobile_page.dart index 191cf95..9bca1a5 100644 --- a/lib/features/essentials/views/essentials_mobile_page.dart +++ b/lib/features/essentials/views/essentials_mobile_page.dart @@ -18,6 +18,7 @@ class _EssentialsMobilePageState extends State { slivers: [ SliverAppBar( expandedHeight: 250, + backgroundColor: Theme.of(context).colorScheme.errorContainer, flexibleSpace: FlexibleSpaceBar( title: Text( "Essentials", From 66abdd5233c5a45eb1a5875e53186fdde3dbb160 Mon Sep 17 00:00:00 2001 From: Erick Date: Tue, 24 Dec 2024 02:03:51 +0300 Subject: [PATCH 05/12] chore: introduced courses to the application --- lib/features/courses/courses.dart | 1 + lib/features/courses/views/courses_page.dart | 18 ++++++++++ .../courses/views/courses_page_desktop.dart | 12 +++++++ .../courses/views/courses_page_mobile.dart | 35 +++++++++++++++++++ lib/features/features.dart | 1 + lib/features/home/views/layout.dart | 8 ++--- 6 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 lib/features/courses/courses.dart create mode 100644 lib/features/courses/views/courses_page.dart create mode 100644 lib/features/courses/views/courses_page_desktop.dart create mode 100644 lib/features/courses/views/courses_page_mobile.dart diff --git a/lib/features/courses/courses.dart b/lib/features/courses/courses.dart new file mode 100644 index 0000000..f9e167c --- /dev/null +++ b/lib/features/courses/courses.dart @@ -0,0 +1 @@ +export 'views/courses_page.dart'; diff --git a/lib/features/courses/views/courses_page.dart b/lib/features/courses/views/courses_page.dart new file mode 100644 index 0000000..29b9616 --- /dev/null +++ b/lib/features/courses/views/courses_page.dart @@ -0,0 +1,18 @@ +import 'package:academia/features/courses/views/courses_page_desktop.dart'; +import 'package:academia/features/courses/views/courses_page_mobile.dart'; +import 'package:academia/utils/utils.dart'; +import 'package:flutter/material.dart'; + +class CoursesPage extends StatelessWidget { + const CoursesPage({super.key}); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) => + constraints.maxWidth < ScreenDimension.mobileWidth + ? const CoursesPageMobile() + : const CoursesPageDesktop(), + ); + } +} diff --git a/lib/features/courses/views/courses_page_desktop.dart b/lib/features/courses/views/courses_page_desktop.dart new file mode 100644 index 0000000..bb6c95f --- /dev/null +++ b/lib/features/courses/views/courses_page_desktop.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class CoursesPageDesktop extends StatelessWidget { + const CoursesPageDesktop({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Text("Course page desktop"), + ); + } +} diff --git a/lib/features/courses/views/courses_page_mobile.dart b/lib/features/courses/views/courses_page_mobile.dart new file mode 100644 index 0000000..e7118c3 --- /dev/null +++ b/lib/features/courses/views/courses_page_mobile.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class CoursesPageMobile extends StatefulWidget { + const CoursesPageMobile({super.key}); + + @override + State createState() => _CoursesPageMobileState(); +} + +class _CoursesPageMobileState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: RefreshIndicator( + onRefresh: () async {}, + child: CustomScrollView( + slivers: [ + SliverAppBar( + expandedHeight: 250, + flexibleSpace: FlexibleSpaceBar( + title: Text( + "Courses", + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + fontFamily: GoogleFonts.libreBaskerville().fontFamily, + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/features.dart b/lib/features/features.dart index 6a4ff23..5c34560 100644 --- a/lib/features/features.dart +++ b/lib/features/features.dart @@ -2,3 +2,4 @@ export 'onboarding/onboarding.dart'; export 'auth/auth.dart'; export 'home/home.dart'; export 'profile/profile.dart'; +export 'courses/courses.dart'; diff --git a/lib/features/home/views/layout.dart b/lib/features/home/views/layout.dart index 3658369..859655e 100644 --- a/lib/features/home/views/layout.dart +++ b/lib/features/home/views/layout.dart @@ -79,9 +79,7 @@ class _LayoutState extends State { Center( child: Text("Statistics"), ), - Center( - child: Text("Courses"), - ), + CoursesPage(), Center( child: Text("Social"), ), @@ -136,9 +134,7 @@ class _LayoutState extends State { Center( child: Text("Statistics"), ), - Center( - child: Text("Courses"), - ), + CoursesPage(), Center( child: Text("Social"), ), From ddabd3d57c3ceffb16138eba54e468cd1701b616 Mon Sep 17 00:00:00 2001 From: Erick Date: Wed, 25 Dec 2024 08:34:10 +0300 Subject: [PATCH 06/12] chore: courses page ui --- lib/features/courses/views/courses_page_mobile.dart | 13 +++++++++++++ .../essentials/views/essentials_mobile_page.dart | 9 +++++++++ lib/utils/network/dio_client.dart | 4 ++-- pubspec.yaml | 1 + 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/features/courses/views/courses_page_mobile.dart b/lib/features/courses/views/courses_page_mobile.dart index e7118c3..8aca866 100644 --- a/lib/features/courses/views/courses_page_mobile.dart +++ b/lib/features/courses/views/courses_page_mobile.dart @@ -18,6 +18,9 @@ class _CoursesPageMobileState extends State { slivers: [ SliverAppBar( expandedHeight: 250, + pinned: true, + floating: true, + snap: true, flexibleSpace: FlexibleSpaceBar( title: Text( "Courses", @@ -27,6 +30,16 @@ class _CoursesPageMobileState extends State { ), ), ), + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 12), + sliver: SliverList.separated( + itemBuilder: (context, index) => ListTile( + leading: CircleAvatar(), + title: Text("ACS 200"), + subtitle: Text("PLAB * 10:00 - 13:00 * Fredrick Ogore")), + separatorBuilder: (context, index) => SizedBox(), + ), + ), ], ), ), diff --git a/lib/features/essentials/views/essentials_mobile_page.dart b/lib/features/essentials/views/essentials_mobile_page.dart index 9bca1a5..b31056a 100644 --- a/lib/features/essentials/views/essentials_mobile_page.dart +++ b/lib/features/essentials/views/essentials_mobile_page.dart @@ -17,6 +17,9 @@ class _EssentialsMobilePageState extends State { body: CustomScrollView( slivers: [ SliverAppBar( + pinned: true, + snap: true, + floating: true, expandedHeight: 250, backgroundColor: Theme.of(context).colorScheme.errorContainer, flexibleSpace: FlexibleSpaceBar( @@ -27,6 +30,12 @@ class _EssentialsMobilePageState extends State { ), ), ), + actions: [ + IconButton( + onPressed: () {}, + icon: const Icon(Bootstrap.qr_code_scan), + ), + ], ), SliverPadding( padding: const EdgeInsets.all(12), diff --git a/lib/utils/network/dio_client.dart b/lib/utils/network/dio_client.dart index 96c191b..b8c39c6 100644 --- a/lib/utils/network/dio_client.dart +++ b/lib/utils/network/dio_client.dart @@ -4,8 +4,8 @@ import './auth_interceptor.dart'; import 'package:pretty_dio_logger/pretty_dio_logger.dart'; class DioClient { - // static const String _baseUrl = "http://192.168.2.115:8000/v2"; - static const String _baseUrl = "http://127.0.0.1:8000/v2"; + static const String _baseUrl = "http://192.168.167.218:8000/v2"; + // static const String _baseUrl = "http://127.0.0.1:8000/v2"; DioClient() { dio.interceptors.add( diff --git a/pubspec.yaml b/pubspec.yaml index 606a5ba..7d55ba8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,6 +64,7 @@ dependencies: get_it: ^8.0.2 pretty_dio_logger: ^1.4.0 connectivity_plus: ^6.1.1 + flex_color_picker: ^3.6.0 dev_dependencies: flutter_test: From fc523f7ae37225263430e7f05eca0416f327bae9 Mon Sep 17 00:00:00 2001 From: Erick Date: Wed, 25 Dec 2024 08:43:06 +0300 Subject: [PATCH 07/12] fix: homescreen having option to go back to login screen # FIX - Popped all the previous routes --- lib/features/auth/views/login_page.dart | 9 ++++++--- .../auth/views/user_selection_page.dart | 20 ------------------- .../views/widgets/user_selection_tile.dart | 16 +++++++++------ 3 files changed, 16 insertions(+), 29 deletions(-) diff --git a/lib/features/auth/views/login_page.dart b/lib/features/auth/views/login_page.dart index 78eb3f8..299aa7f 100644 --- a/lib/features/auth/views/login_page.dart +++ b/lib/features/auth/views/login_page.dart @@ -1,6 +1,7 @@ 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/features/features.dart'; import 'package:academia/utils/router/router.dart'; import 'package:academia/utils/validator/validator.dart'; import 'package:flutter/material.dart'; @@ -179,9 +180,11 @@ class _LoginPageState extends State { ); }, (r) { HapticFeedback.heavyImpact(); - GoRouter.of(context).pushReplacementNamed( - AcademiaRouter.home, - ); + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (context) => const Layout(), + ), + (Route route) => false); }); }, child: state is AuthLoadingState diff --git a/lib/features/auth/views/user_selection_page.dart b/lib/features/auth/views/user_selection_page.dart index d8a0713..6c6866e 100644 --- a/lib/features/auth/views/user_selection_page.dart +++ b/lib/features/auth/views/user_selection_page.dart @@ -7,7 +7,6 @@ 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:skeletonizer/skeletonizer.dart'; import 'package:sliver_tools/sliver_tools.dart'; class UserSelectionPage extends StatefulWidget { @@ -20,25 +19,6 @@ class UserSelectionPage extends StatefulWidget { class _UserSelectionPageState extends State { late AuthCubit authCubit = BlocProvider.of(context); - /// 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( diff --git a/lib/features/auth/views/widgets/user_selection_tile.dart b/lib/features/auth/views/widgets/user_selection_tile.dart index a46abda..27ee103 100644 --- a/lib/features/auth/views/widgets/user_selection_tile.dart +++ b/lib/features/auth/views/widgets/user_selection_tile.dart @@ -1,6 +1,7 @@ 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/features/home/views/layout.dart'; import 'package:academia/utils/router/router.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; @@ -22,7 +23,7 @@ class UserSelectionTile extends StatefulWidget { } class _UserSelectionTileState extends State { - late AuthCubit _authCubit = BlocProvider.of(context); + late AuthCubit authCubit = BlocProvider.of(context); UserProfileData? profile; UserCredentialData? creds; @@ -46,11 +47,11 @@ class _UserSelectionTileState extends State { } Future _fetchUserProfile(UserData user) async { - return await _authCubit.fetchUserProfile(user); + return await authCubit.fetchUserProfile(user); } Future _fetchUserCredentials(UserData user) async { - final result = await _authCubit.fetchUserCredsFromCache(user); + final result = await authCubit.fetchUserCredsFromCache(user); return result.fold((l) { _showMessageDialog("Error", l); return null; @@ -73,15 +74,18 @@ class _UserSelectionTileState extends State { enabled: snapshot.connectionState != ConnectionState.done, child: ListTile( onTap: () async { - final result = await _authCubit.authenticate(creds!); + final result = await authCubit.authenticate(creds!); result.fold((l) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(l), )); }, (r) { if (!context.mounted) return; - GoRouter.of(context) - .pushReplacementNamed(AcademiaRouter.home); + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (context) => const Layout(), + ), + (Route route) => false); }); }, title: Text("@${widget.user.username}"), From 08c3be6c8639742deb785d1fff17149045acaa1b Mon Sep 17 00:00:00 2001 From: Erick Date: Wed, 25 Dec 2024 18:48:01 +0300 Subject: [PATCH 08/12] chore: added courses models --- lib/database/database.dart | 8 +- lib/database/database.g.dart | 683 +++++++++++++++++++++- lib/features/courses/models/course.dart | 17 + lib/features/courses/models/semester.dart | 18 + lib/utils/network/dio_client.dart | 2 +- 5 files changed, 725 insertions(+), 3 deletions(-) create mode 100644 lib/features/courses/models/course.dart create mode 100644 lib/features/courses/models/semester.dart diff --git a/lib/database/database.dart b/lib/database/database.dart index a0c858c..a63eed1 100644 --- a/lib/database/database.dart +++ b/lib/database/database.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:academia/features/auth/models/user.dart'; import 'package:academia/features/auth/models/user_credentials.dart'; import 'package:academia/features/auth/models/user_profile.dart'; +import 'package:academia/features/courses/models/course.dart'; import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:drift_flutter/drift_flutter.dart'; @@ -43,7 +44,12 @@ Future _getDatabaseDirectory() async { } } -@DriftDatabase(tables: [User, UserProfile, UserCredential]) +@DriftDatabase(tables: [ + User, + UserProfile, + UserCredential, + Course, +]) 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. diff --git a/lib/database/database.g.dart b/lib/database/database.g.dart index c566a91..7d38e02 100644 --- a/lib/database/database.g.dart +++ b/lib/database/database.g.dart @@ -1496,18 +1496,472 @@ class UserCredentialCompanion extends UpdateCompanion { } } +class $CourseTable extends Course with TableInfo<$CourseTable, CourseData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $CourseTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _unitMeta = const VerificationMeta('unit'); + @override + late final GeneratedColumn unit = GeneratedColumn( + 'unit', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE')); + static const VerificationMeta _sectionMeta = + const VerificationMeta('section'); + @override + late final GeneratedColumn section = GeneratedColumn( + 'section', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _weekDayMeta = + const VerificationMeta('weekDay'); + @override + late final GeneratedColumn weekDay = GeneratedColumn( + 'week_day', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _campusMeta = const VerificationMeta('campus'); + @override + late final GeneratedColumn campus = GeneratedColumn( + 'campus', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _roomMeta = const VerificationMeta('room'); + @override + late final GeneratedColumn room = GeneratedColumn( + 'room', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _lecturerMeta = + const VerificationMeta('lecturer'); + @override + late final GeneratedColumn lecturer = GeneratedColumn( + 'lecturer', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _periodMeta = const VerificationMeta('period'); + @override + late final GeneratedColumn period = GeneratedColumn( + 'period', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _colorMeta = const VerificationMeta('color'); + @override + late final GeneratedColumn color = GeneratedColumn( + 'color', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + @override + List get $columns => + [id, unit, section, weekDay, campus, room, lecturer, period, color]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'course'; + @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('unit')) { + context.handle( + _unitMeta, unit.isAcceptableOrUnknown(data['unit']!, _unitMeta)); + } else if (isInserting) { + context.missing(_unitMeta); + } + if (data.containsKey('section')) { + context.handle(_sectionMeta, + section.isAcceptableOrUnknown(data['section']!, _sectionMeta)); + } else if (isInserting) { + context.missing(_sectionMeta); + } + if (data.containsKey('week_day')) { + context.handle(_weekDayMeta, + weekDay.isAcceptableOrUnknown(data['week_day']!, _weekDayMeta)); + } else if (isInserting) { + context.missing(_weekDayMeta); + } + if (data.containsKey('campus')) { + context.handle(_campusMeta, + campus.isAcceptableOrUnknown(data['campus']!, _campusMeta)); + } else if (isInserting) { + context.missing(_campusMeta); + } + if (data.containsKey('room')) { + context.handle( + _roomMeta, room.isAcceptableOrUnknown(data['room']!, _roomMeta)); + } else if (isInserting) { + context.missing(_roomMeta); + } + if (data.containsKey('lecturer')) { + context.handle(_lecturerMeta, + lecturer.isAcceptableOrUnknown(data['lecturer']!, _lecturerMeta)); + } else if (isInserting) { + context.missing(_lecturerMeta); + } + if (data.containsKey('period')) { + context.handle(_periodMeta, + period.isAcceptableOrUnknown(data['period']!, _periodMeta)); + } else if (isInserting) { + context.missing(_periodMeta); + } + if (data.containsKey('color')) { + context.handle( + _colorMeta, color.isAcceptableOrUnknown(data['color']!, _colorMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + CourseData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return CourseData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}id']), + unit: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}unit'])!, + section: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}section'])!, + weekDay: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}week_day'])!, + campus: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}campus'])!, + room: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}room'])!, + lecturer: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}lecturer'])!, + period: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}period'])!, + color: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}color']), + ); + } + + @override + $CourseTable createAlias(String alias) { + return $CourseTable(attachedDatabase, alias); + } +} + +class CourseData extends DataClass implements Insertable { + final String? id; + final String unit; + final String section; + final String weekDay; + final String campus; + final String room; + final String lecturer; + final String period; + final int? color; + const CourseData( + {this.id, + required this.unit, + required this.section, + required this.weekDay, + required this.campus, + required this.room, + required this.lecturer, + required this.period, + this.color}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (!nullToAbsent || id != null) { + map['id'] = Variable(id); + } + map['unit'] = Variable(unit); + map['section'] = Variable(section); + map['week_day'] = Variable(weekDay); + map['campus'] = Variable(campus); + map['room'] = Variable(room); + map['lecturer'] = Variable(lecturer); + map['period'] = Variable(period); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + return map; + } + + CourseCompanion toCompanion(bool nullToAbsent) { + return CourseCompanion( + id: id == null && nullToAbsent ? const Value.absent() : Value(id), + unit: Value(unit), + section: Value(section), + weekDay: Value(weekDay), + campus: Value(campus), + room: Value(room), + lecturer: Value(lecturer), + period: Value(period), + color: + color == null && nullToAbsent ? const Value.absent() : Value(color), + ); + } + + factory CourseData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return CourseData( + id: serializer.fromJson(json['id']), + unit: serializer.fromJson(json['unit']), + section: serializer.fromJson(json['section']), + weekDay: serializer.fromJson(json['day_of_the_week']), + campus: serializer.fromJson(json['campus']), + room: serializer.fromJson(json['room']), + lecturer: serializer.fromJson(json['lecturer']), + period: serializer.fromJson(json['period']), + color: serializer.fromJson(json['color']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'unit': serializer.toJson(unit), + 'section': serializer.toJson(section), + 'day_of_the_week': serializer.toJson(weekDay), + 'campus': serializer.toJson(campus), + 'room': serializer.toJson(room), + 'lecturer': serializer.toJson(lecturer), + 'period': serializer.toJson(period), + 'color': serializer.toJson(color), + }; + } + + CourseData copyWith( + {Value id = const Value.absent(), + String? unit, + String? section, + String? weekDay, + String? campus, + String? room, + String? lecturer, + String? period, + Value color = const Value.absent()}) => + CourseData( + id: id.present ? id.value : this.id, + unit: unit ?? this.unit, + section: section ?? this.section, + weekDay: weekDay ?? this.weekDay, + campus: campus ?? this.campus, + room: room ?? this.room, + lecturer: lecturer ?? this.lecturer, + period: period ?? this.period, + color: color.present ? color.value : this.color, + ); + CourseData copyWithCompanion(CourseCompanion data) { + return CourseData( + id: data.id.present ? data.id.value : this.id, + unit: data.unit.present ? data.unit.value : this.unit, + section: data.section.present ? data.section.value : this.section, + weekDay: data.weekDay.present ? data.weekDay.value : this.weekDay, + campus: data.campus.present ? data.campus.value : this.campus, + room: data.room.present ? data.room.value : this.room, + lecturer: data.lecturer.present ? data.lecturer.value : this.lecturer, + period: data.period.present ? data.period.value : this.period, + color: data.color.present ? data.color.value : this.color, + ); + } + + @override + String toString() { + return (StringBuffer('CourseData(') + ..write('id: $id, ') + ..write('unit: $unit, ') + ..write('section: $section, ') + ..write('weekDay: $weekDay, ') + ..write('campus: $campus, ') + ..write('room: $room, ') + ..write('lecturer: $lecturer, ') + ..write('period: $period, ') + ..write('color: $color') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, unit, section, weekDay, campus, room, lecturer, period, color); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is CourseData && + other.id == this.id && + other.unit == this.unit && + other.section == this.section && + other.weekDay == this.weekDay && + other.campus == this.campus && + other.room == this.room && + other.lecturer == this.lecturer && + other.period == this.period && + other.color == this.color); +} + +class CourseCompanion extends UpdateCompanion { + final Value id; + final Value unit; + final Value section; + final Value weekDay; + final Value campus; + final Value room; + final Value lecturer; + final Value period; + final Value color; + final Value rowid; + const CourseCompanion({ + this.id = const Value.absent(), + this.unit = const Value.absent(), + this.section = const Value.absent(), + this.weekDay = const Value.absent(), + this.campus = const Value.absent(), + this.room = const Value.absent(), + this.lecturer = const Value.absent(), + this.period = const Value.absent(), + this.color = const Value.absent(), + this.rowid = const Value.absent(), + }); + CourseCompanion.insert({ + this.id = const Value.absent(), + required String unit, + required String section, + required String weekDay, + required String campus, + required String room, + required String lecturer, + required String period, + this.color = const Value.absent(), + this.rowid = const Value.absent(), + }) : unit = Value(unit), + section = Value(section), + weekDay = Value(weekDay), + campus = Value(campus), + room = Value(room), + lecturer = Value(lecturer), + period = Value(period); + static Insertable custom({ + Expression? id, + Expression? unit, + Expression? section, + Expression? weekDay, + Expression? campus, + Expression? room, + Expression? lecturer, + Expression? period, + Expression? color, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (unit != null) 'unit': unit, + if (section != null) 'section': section, + if (weekDay != null) 'week_day': weekDay, + if (campus != null) 'campus': campus, + if (room != null) 'room': room, + if (lecturer != null) 'lecturer': lecturer, + if (period != null) 'period': period, + if (color != null) 'color': color, + if (rowid != null) 'rowid': rowid, + }); + } + + CourseCompanion copyWith( + {Value? id, + Value? unit, + Value? section, + Value? weekDay, + Value? campus, + Value? room, + Value? lecturer, + Value? period, + Value? color, + Value? rowid}) { + return CourseCompanion( + id: id ?? this.id, + unit: unit ?? this.unit, + section: section ?? this.section, + weekDay: weekDay ?? this.weekDay, + campus: campus ?? this.campus, + room: room ?? this.room, + lecturer: lecturer ?? this.lecturer, + period: period ?? this.period, + color: color ?? this.color, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (unit.present) { + map['unit'] = Variable(unit.value); + } + if (section.present) { + map['section'] = Variable(section.value); + } + if (weekDay.present) { + map['week_day'] = Variable(weekDay.value); + } + if (campus.present) { + map['campus'] = Variable(campus.value); + } + if (room.present) { + map['room'] = Variable(room.value); + } + if (lecturer.present) { + map['lecturer'] = Variable(lecturer.value); + } + if (period.present) { + map['period'] = Variable(period.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CourseCompanion(') + ..write('id: $id, ') + ..write('unit: $unit, ') + ..write('section: $section, ') + ..write('weekDay: $weekDay, ') + ..write('campus: $campus, ') + ..write('room: $room, ') + ..write('lecturer: $lecturer, ') + ..write('period: $period, ') + ..write('color: $color, ') + ..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); + late final $CourseTable course = $CourseTable(this); @override Iterable> get allTables => allSchemaEntities.whereType>(); @override List get allSchemaEntities => - [user, userProfile, userCredential]; + [user, userProfile, userCredential, course]; @override DriftDatabaseOptions get options => const DriftDatabaseOptions(storeDateTimeAsText: true); @@ -2657,6 +3111,231 @@ typedef $$UserCredentialTableProcessedTableManager = ProcessedTableManager< (UserCredentialData, $$UserCredentialTableReferences), UserCredentialData, PrefetchHooks Function({bool userId, bool username, bool email})>; +typedef $$CourseTableCreateCompanionBuilder = CourseCompanion Function({ + Value id, + required String unit, + required String section, + required String weekDay, + required String campus, + required String room, + required String lecturer, + required String period, + Value color, + Value rowid, +}); +typedef $$CourseTableUpdateCompanionBuilder = CourseCompanion Function({ + Value id, + Value unit, + Value section, + Value weekDay, + Value campus, + Value room, + Value lecturer, + Value period, + Value color, + Value rowid, +}); + +class $$CourseTableFilterComposer + extends Composer<_$AppDatabase, $CourseTable> { + $$CourseTableFilterComposer({ + 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 unit => $composableBuilder( + column: $table.unit, builder: (column) => ColumnFilters(column)); + + ColumnFilters get section => $composableBuilder( + column: $table.section, builder: (column) => ColumnFilters(column)); + + ColumnFilters get weekDay => $composableBuilder( + column: $table.weekDay, builder: (column) => ColumnFilters(column)); + + ColumnFilters get campus => $composableBuilder( + column: $table.campus, builder: (column) => ColumnFilters(column)); + + ColumnFilters get room => $composableBuilder( + column: $table.room, builder: (column) => ColumnFilters(column)); + + ColumnFilters get lecturer => $composableBuilder( + column: $table.lecturer, builder: (column) => ColumnFilters(column)); + + ColumnFilters get period => $composableBuilder( + column: $table.period, builder: (column) => ColumnFilters(column)); + + ColumnFilters get color => $composableBuilder( + column: $table.color, builder: (column) => ColumnFilters(column)); +} + +class $$CourseTableOrderingComposer + extends Composer<_$AppDatabase, $CourseTable> { + $$CourseTableOrderingComposer({ + 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 unit => $composableBuilder( + column: $table.unit, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get section => $composableBuilder( + column: $table.section, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get weekDay => $composableBuilder( + column: $table.weekDay, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get campus => $composableBuilder( + column: $table.campus, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get room => $composableBuilder( + column: $table.room, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get lecturer => $composableBuilder( + column: $table.lecturer, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get period => $composableBuilder( + column: $table.period, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get color => $composableBuilder( + column: $table.color, builder: (column) => ColumnOrderings(column)); +} + +class $$CourseTableAnnotationComposer + extends Composer<_$AppDatabase, $CourseTable> { + $$CourseTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get unit => + $composableBuilder(column: $table.unit, builder: (column) => column); + + GeneratedColumn get section => + $composableBuilder(column: $table.section, builder: (column) => column); + + GeneratedColumn get weekDay => + $composableBuilder(column: $table.weekDay, builder: (column) => column); + + GeneratedColumn get campus => + $composableBuilder(column: $table.campus, builder: (column) => column); + + GeneratedColumn get room => + $composableBuilder(column: $table.room, builder: (column) => column); + + GeneratedColumn get lecturer => + $composableBuilder(column: $table.lecturer, builder: (column) => column); + + GeneratedColumn get period => + $composableBuilder(column: $table.period, builder: (column) => column); + + GeneratedColumn get color => + $composableBuilder(column: $table.color, builder: (column) => column); +} + +class $$CourseTableTableManager extends RootTableManager< + _$AppDatabase, + $CourseTable, + CourseData, + $$CourseTableFilterComposer, + $$CourseTableOrderingComposer, + $$CourseTableAnnotationComposer, + $$CourseTableCreateCompanionBuilder, + $$CourseTableUpdateCompanionBuilder, + (CourseData, BaseReferences<_$AppDatabase, $CourseTable, CourseData>), + CourseData, + PrefetchHooks Function()> { + $$CourseTableTableManager(_$AppDatabase db, $CourseTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$CourseTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$CourseTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$CourseTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value id = const Value.absent(), + Value unit = const Value.absent(), + Value section = const Value.absent(), + Value weekDay = const Value.absent(), + Value campus = const Value.absent(), + Value room = const Value.absent(), + Value lecturer = const Value.absent(), + Value period = const Value.absent(), + Value color = const Value.absent(), + Value rowid = const Value.absent(), + }) => + CourseCompanion( + id: id, + unit: unit, + section: section, + weekDay: weekDay, + campus: campus, + room: room, + lecturer: lecturer, + period: period, + color: color, + rowid: rowid, + ), + createCompanionCallback: ({ + Value id = const Value.absent(), + required String unit, + required String section, + required String weekDay, + required String campus, + required String room, + required String lecturer, + required String period, + Value color = const Value.absent(), + Value rowid = const Value.absent(), + }) => + CourseCompanion.insert( + id: id, + unit: unit, + section: section, + weekDay: weekDay, + campus: campus, + room: room, + lecturer: lecturer, + period: period, + color: color, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$CourseTableProcessedTableManager = ProcessedTableManager< + _$AppDatabase, + $CourseTable, + CourseData, + $$CourseTableFilterComposer, + $$CourseTableOrderingComposer, + $$CourseTableAnnotationComposer, + $$CourseTableCreateCompanionBuilder, + $$CourseTableUpdateCompanionBuilder, + (CourseData, BaseReferences<_$AppDatabase, $CourseTable, CourseData>), + CourseData, + PrefetchHooks Function()>; class $AppDatabaseManager { final _$AppDatabase _db; @@ -2666,4 +3345,6 @@ class $AppDatabaseManager { $$UserProfileTableTableManager(_db, _db.userProfile); $$UserCredentialTableTableManager get userCredential => $$UserCredentialTableTableManager(_db, _db.userCredential); + $$CourseTableTableManager get course => + $$CourseTableTableManager(_db, _db.course); } diff --git a/lib/features/courses/models/course.dart b/lib/features/courses/models/course.dart new file mode 100644 index 0000000..76de6e0 --- /dev/null +++ b/lib/features/courses/models/course.dart @@ -0,0 +1,17 @@ +import 'package:drift/drift.dart'; + +class Course extends Table { + TextColumn get id => text().nullable()(); + TextColumn get unit => text().unique()(); + TextColumn get section => text()(); + @JsonKey("day_of_the_week") + TextColumn get weekDay => text()(); + TextColumn get campus => text()(); + TextColumn get room => text()(); + TextColumn get lecturer => text()(); + TextColumn get period => text()(); + IntColumn get color => integer().nullable()(); + + @override + Set>? get primaryKey => {id}; +} diff --git a/lib/features/courses/models/semester.dart b/lib/features/courses/models/semester.dart new file mode 100644 index 0000000..2e96da6 --- /dev/null +++ b/lib/features/courses/models/semester.dart @@ -0,0 +1,18 @@ +import 'package:drift/drift.dart'; + +class Semester extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + TextColumn get description => text()(); + @JsonKey("begins_at") + DateTimeColumn get beginsAt => dateTime()(); + @JsonKey("ends_at") + DateTimeColumn get endsAt => dateTime()(); + @JsonKey("created_at") + DateTimeColumn get createdAt => dateTime()(); + @JsonKey("modified_at") + DateTimeColumn get modifiedAt => dateTime()(); + + @override + Set>? get primaryKey => {id}; +} diff --git a/lib/utils/network/dio_client.dart b/lib/utils/network/dio_client.dart index b8c39c6..1fb3318 100644 --- a/lib/utils/network/dio_client.dart +++ b/lib/utils/network/dio_client.dart @@ -4,7 +4,7 @@ import './auth_interceptor.dart'; import 'package:pretty_dio_logger/pretty_dio_logger.dart'; class DioClient { - static const String _baseUrl = "http://192.168.167.218:8000/v2"; + static const String _baseUrl = "http://192.168.43.218:8000/v2"; // static const String _baseUrl = "http://127.0.0.1:8000/v2"; DioClient() { From 796629a3710fc9d13de7ec16e27cd9b3af62ef80 Mon Sep 17 00:00:00 2001 From: Erick Date: Thu, 26 Dec 2024 08:00:43 +0300 Subject: [PATCH 09/12] fix: profile page card padding # FIXES - Reduced padding size on the profile content cards - Removed dio content fetch - Auth screens now navigate the user without an option to o back --- .../auth/repository/user_repository.dart | 6 ++++-- lib/features/auth/views/login_page.dart | 11 ++++------- .../views/widgets/user_selection_tile.dart | 9 ++++----- .../profile/views/profile_page_mobile.dart | 18 +++++++++++++----- lib/utils/network/auth_interceptor.dart | 12 +++++++----- lib/utils/network/dio_client.dart | 4 ++-- 6 files changed, 34 insertions(+), 26 deletions(-) diff --git a/lib/features/auth/repository/user_repository.dart b/lib/features/auth/repository/user_repository.dart index df203b2..5e1d10c 100644 --- a/lib/features/auth/repository/user_repository.dart +++ b/lib/features/auth/repository/user_repository.dart @@ -53,7 +53,7 @@ final class UserRepository { // TODO: (erick) enable auth with magnet // authenticate with magnet - const magnetResult = Right(Object()); + const magnetResult = Right(Object()); // await (GetIt.instance.get(instanceName: "magnet").login()); // return magnetResult.fold((error) { @@ -88,7 +88,9 @@ final class UserRepository { if (localResult.isRight()) { final profile = (localResult as Right).value; if (profile == null) { - return await refreshUserProfile(user); + return left( + "Failed to fetch your profile from cache please connect to the internet and refresh", + ); } return right((localResult as Right).value); } diff --git a/lib/features/auth/views/login_page.dart b/lib/features/auth/views/login_page.dart index 299aa7f..1046520 100644 --- a/lib/features/auth/views/login_page.dart +++ b/lib/features/auth/views/login_page.dart @@ -1,6 +1,4 @@ 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/features/features.dart'; import 'package:academia/utils/router/router.dart'; import 'package:academia/utils/validator/validator.dart'; @@ -180,11 +178,10 @@ class _LoginPageState extends State { ); }, (r) { HapticFeedback.heavyImpact(); - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (context) => const Layout(), - ), - (Route route) => false); + context.pop(); + GoRouter.of(context).pushReplacementNamed( + AcademiaRouter.home, + ); }); }, child: state is AuthLoadingState diff --git a/lib/features/auth/views/widgets/user_selection_tile.dart b/lib/features/auth/views/widgets/user_selection_tile.dart index 27ee103..a5f793d 100644 --- a/lib/features/auth/views/widgets/user_selection_tile.dart +++ b/lib/features/auth/views/widgets/user_selection_tile.dart @@ -81,11 +81,10 @@ class _UserSelectionTileState extends State { )); }, (r) { if (!context.mounted) return; - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (context) => const Layout(), - ), - (Route route) => false); + context.pop(); + GoRouter.of(context).pushReplacementNamed( + AcademiaRouter.home, + ); }); }, title: Text("@${widget.user.username}"), diff --git a/lib/features/profile/views/profile_page_mobile.dart b/lib/features/profile/views/profile_page_mobile.dart index 259489d..cf77b76 100644 --- a/lib/features/profile/views/profile_page_mobile.dart +++ b/lib/features/profile/views/profile_page_mobile.dart @@ -188,6 +188,7 @@ class _ProfilePageMobileState extends State { ), Card( elevation: 0, + margin: const EdgeInsets.only(bottom: 2), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(12), @@ -201,6 +202,7 @@ class _ProfilePageMobileState extends State { ), Card( elevation: 0, + margin: const EdgeInsets.only(bottom: 2), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.zero, @@ -214,6 +216,7 @@ class _ProfilePageMobileState extends State { ), Card( elevation: 0, + margin: const EdgeInsets.only(bottom: 2), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.zero, @@ -230,6 +233,7 @@ class _ProfilePageMobileState extends State { ), Card( elevation: 0, + margin: const EdgeInsets.only(bottom: 2), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.zero)), child: ListTile( @@ -240,6 +244,7 @@ class _ProfilePageMobileState extends State { ), Card( elevation: 0, + margin: const EdgeInsets.only(bottom: 2), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.zero)), child: ListTile( @@ -250,6 +255,7 @@ class _ProfilePageMobileState extends State { ), Card( elevation: 0, + margin: const EdgeInsets.only(bottom: 2), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.zero)), child: ListTile( @@ -260,6 +266,7 @@ class _ProfilePageMobileState extends State { ), Card( elevation: 0, + margin: const EdgeInsets.only(bottom: 2), shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.zero)), child: ListTile( @@ -268,17 +275,18 @@ class _ProfilePageMobileState extends State { subtitle: Text(user.email ?? "unknown"), ), ), - const Card( + Card( + margin: EdgeInsets.zero, elevation: 0, - shape: RoundedRectangleBorder( + shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( bottom: Radius.circular(12), ), ), child: ListTile( - leading: Icon(Bootstrap.hash), - title: Text("Admission Number"), - subtitle: Text("21-1000"), + leading: const Icon(Bootstrap.house_heart), + title: const Text("Campus"), + subtitle: Text(profile.campus), ), ), ], diff --git a/lib/utils/network/auth_interceptor.dart b/lib/utils/network/auth_interceptor.dart index c143e47..b06dc49 100644 --- a/lib/utils/network/auth_interceptor.dart +++ b/lib/utils/network/auth_interceptor.dart @@ -35,11 +35,13 @@ class AuthInterceptor extends Interceptor { @override void onError(DioException err, ErrorInterceptorHandler handler) async { - // TODO: erick Add automatic token refreshing - if (err.response?.statusCode == 401) { - return handler - .resolve(await dio.fetch(err.requestOptions)); // Repeat the request. - } + // TODO: erick Add automatic token refreshing and code retrial + // if (err.response?.statusCode == 401) { + // print("Some really bad error"); + // + // return handler + // .resolve(await dio.fetch(err.requestOptions)); // Repeat the request. + // } return handler.reject(DioException( requestOptions: err.requestOptions, diff --git a/lib/utils/network/dio_client.dart b/lib/utils/network/dio_client.dart index 96c191b..1fb3318 100644 --- a/lib/utils/network/dio_client.dart +++ b/lib/utils/network/dio_client.dart @@ -4,8 +4,8 @@ import './auth_interceptor.dart'; import 'package:pretty_dio_logger/pretty_dio_logger.dart'; class DioClient { - // static const String _baseUrl = "http://192.168.2.115:8000/v2"; - static const String _baseUrl = "http://127.0.0.1:8000/v2"; + static const String _baseUrl = "http://192.168.43.218:8000/v2"; + // static const String _baseUrl = "http://127.0.0.1:8000/v2"; DioClient() { dio.interceptors.add( From 7dc9f9f7c1585330e2a8964e4284ba2d8b0e0eec Mon Sep 17 00:00:00 2001 From: Erick Date: Thu, 26 Dec 2024 08:08:53 +0300 Subject: [PATCH 10/12] fix: reduced content padding on essentials page cards --- .../views/essentials_mobile_page.dart | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/lib/features/essentials/views/essentials_mobile_page.dart b/lib/features/essentials/views/essentials_mobile_page.dart index 9bca1a5..eb20658 100644 --- a/lib/features/essentials/views/essentials_mobile_page.dart +++ b/lib/features/essentials/views/essentials_mobile_page.dart @@ -34,67 +34,72 @@ class _EssentialsMobilePageState extends State { children: const [ Card( elevation: 0, - shape: const RoundedRectangleBorder( + margin: EdgeInsets.only(bottom: 2), + shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(12), ), ), child: ListTile( - leading: const Icon(Bootstrap.bell), - title: const Text("Todos & Assigments"), + leading: Icon(Bootstrap.bell), + title: Text("Todos & Assigments"), subtitle: Text("Keep track of your assignments and todos"), ), ), Card( elevation: 0, - shape: const RoundedRectangleBorder( + margin: EdgeInsets.only(bottom: 2), + shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.zero, ), ), child: ListTile( - leading: const Icon(Bootstrap.clock), - title: const Text("Exam timetable"), + leading: Icon(Bootstrap.clock), + title: Text("Exam timetable"), subtitle: Text("Psst.. Never miss an exam"), ), ), Card( elevation: 0, - shape: const RoundedRectangleBorder( + margin: EdgeInsets.only(bottom: 2), + shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.zero, ), ), child: ListTile( - leading: const Icon(Bootstrap.filetype_pdf), - title: const Text("Past Revision Papers"), + leading: Icon(Bootstrap.filetype_pdf), + title: Text("Past Revision Papers"), subtitle: Text("You want them? You get them.."), ), ), Card( elevation: 0, - shape: const RoundedRectangleBorder( + margin: EdgeInsets.only(bottom: 2), + shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( bottom: Radius.zero, ), ), child: ListTile( - leading: const Icon(Bootstrap.file_ppt), - title: const Text("Ask Me"), + leading: Icon(Bootstrap.file_ppt), + title: Text("Ask Me"), subtitle: Text("Boring notes? We'll help you revise"), ), ), Card( elevation: 0, - shape: const RoundedRectangleBorder( + margin: EdgeInsets.only(bottom: 2), + shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( bottom: Radius.circular(12), ), ), child: ListTile( - leading: const Icon(Bootstrap.play), - title: const Text("Flash Cards"), + leading: Icon(Bootstrap.play), + title: Text("Flash Cards"), subtitle: Text( "Curious if you really understood? Try our flashcards", ), @@ -104,42 +109,45 @@ class _EssentialsMobilePageState extends State { //Page Break for student performance SizedBox(height: 18), Card( + margin: EdgeInsets.only(bottom: 2), elevation: 0, - shape: const RoundedRectangleBorder( + shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(12), ), ), child: ListTile( - leading: const Icon(Bootstrap.bell), - title: const Text("Student Audit"), + leading: Icon(Bootstrap.bell), + title: Text("Student Audit"), subtitle: Text("Keep track of your assignments and todos"), ), ), Card( elevation: 0, - shape: const RoundedRectangleBorder( + margin: EdgeInsets.only(bottom: 2), + shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( bottom: Radius.zero, ), ), child: ListTile( - leading: const Icon(Bootstrap.activity), - title: const Text("GPA Calculator"), + leading: Icon(Bootstrap.activity), + title: Text("GPA Calculator"), subtitle: Text("Watch out for those grades!"), ), ), Card( elevation: 0, - shape: const RoundedRectangleBorder( + margin: EdgeInsets.only(bottom: 2), + shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical( bottom: Radius.circular(12), ), ), child: ListTile( - leading: const Icon(Bootstrap.play), - title: const Text("Flash Cards"), + leading: Icon(Bootstrap.play), + title: Text("Flash Cards"), subtitle: Text( "Curious if you really understood? Try our flashcards", ), From 0d08636229c0d7cc2e7744ab177243a4656e1384 Mon Sep 17 00:00:00 2001 From: Erick Date: Thu, 26 Dec 2024 11:13:51 +0300 Subject: [PATCH 11/12] feat: got courses to display on the courses page --- lib/app.dart | 1 + lib/database/database.g.dart | 85 ++++++++++++++++--- .../repository/user_remote_repository.dart | 1 - .../auth/repository/user_repository.dart | 9 +- lib/features/courses/courses.dart | 2 + lib/features/courses/cubit/course_cubit.dart | 28 ++++++ lib/features/courses/cubit/course_state.dart | 37 ++++++++ lib/features/courses/models/course.dart | 5 +- .../repository/course_local_repository.dart | 71 ++++++++++++++++ .../repository/course_remote_repository.dart | 17 ++++ .../courses/repository/course_repository.dart | 35 ++++++++ .../courses/views/courses_page_mobile.dart | 73 +++++++++++++--- 12 files changed, 336 insertions(+), 28 deletions(-) create mode 100644 lib/features/courses/cubit/course_cubit.dart create mode 100644 lib/features/courses/cubit/course_state.dart create mode 100644 lib/features/courses/repository/course_local_repository.dart create mode 100644 lib/features/courses/repository/course_remote_repository.dart create mode 100644 lib/features/courses/repository/course_repository.dart diff --git a/lib/app.dart b/lib/app.dart index da48d7a..f270c3f 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -25,6 +25,7 @@ class Academia extends StatelessWidget { providers: [ BlocProvider(create: (_) => AuthCubit()), BlocProvider(create: (_) => ProfileCubit()), + BlocProvider(create: (_) => CourseCubit()), ], child: DynamicColorBuilder( builder: (lightscheme, darkscheme) => MaterialApp.router( diff --git a/lib/database/database.g.dart b/lib/database/database.g.dart index 7d38e02..092b45e 100644 --- a/lib/database/database.g.dart +++ b/lib/database/database.g.dart @@ -1510,9 +1510,7 @@ class $CourseTable extends Course with TableInfo<$CourseTable, CourseData> { @override late final GeneratedColumn unit = GeneratedColumn( 'unit', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE')); + type: DriftSqlType.string, requiredDuringInsert: true); static const VerificationMeta _sectionMeta = const VerificationMeta('section'); @override @@ -1551,9 +1549,27 @@ class $CourseTable extends Course with TableInfo<$CourseTable, CourseData> { late final GeneratedColumn color = GeneratedColumn( 'color', aliasedName, true, type: DriftSqlType.int, requiredDuringInsert: false); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); @override - List get $columns => - [id, unit, section, weekDay, campus, room, lecturer, period, color]; + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: Constant(DateTime.now())); + @override + List get $columns => [ + id, + unit, + section, + weekDay, + campus, + room, + lecturer, + period, + color, + createdAt + ]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -1613,6 +1629,10 @@ class $CourseTable extends Course with TableInfo<$CourseTable, CourseData> { context.handle( _colorMeta, color.isAcceptableOrUnknown(data['color']!, _colorMeta)); } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } return context; } @@ -1640,6 +1660,8 @@ class $CourseTable extends Course with TableInfo<$CourseTable, CourseData> { .read(DriftSqlType.string, data['${effectivePrefix}period'])!, color: attachedDatabase.typeMapping .read(DriftSqlType.int, data['${effectivePrefix}color']), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at']), ); } @@ -1659,6 +1681,7 @@ class CourseData extends DataClass implements Insertable { final String lecturer; final String period; final int? color; + final DateTime? createdAt; const CourseData( {this.id, required this.unit, @@ -1668,7 +1691,8 @@ class CourseData extends DataClass implements Insertable { required this.room, required this.lecturer, required this.period, - this.color}); + this.color, + this.createdAt}); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -1685,6 +1709,9 @@ class CourseData extends DataClass implements Insertable { if (!nullToAbsent || color != null) { map['color'] = Variable(color); } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } return map; } @@ -1700,6 +1727,9 @@ class CourseData extends DataClass implements Insertable { period: Value(period), color: color == null && nullToAbsent ? const Value.absent() : Value(color), + createdAt: createdAt == null && nullToAbsent + ? const Value.absent() + : Value(createdAt), ); } @@ -1716,6 +1746,7 @@ class CourseData extends DataClass implements Insertable { lecturer: serializer.fromJson(json['lecturer']), period: serializer.fromJson(json['period']), color: serializer.fromJson(json['color']), + createdAt: serializer.fromJson(json['created_at']), ); } @override @@ -1731,6 +1762,7 @@ class CourseData extends DataClass implements Insertable { 'lecturer': serializer.toJson(lecturer), 'period': serializer.toJson(period), 'color': serializer.toJson(color), + 'created_at': serializer.toJson(createdAt), }; } @@ -1743,7 +1775,8 @@ class CourseData extends DataClass implements Insertable { String? room, String? lecturer, String? period, - Value color = const Value.absent()}) => + Value color = const Value.absent(), + Value createdAt = const Value.absent()}) => CourseData( id: id.present ? id.value : this.id, unit: unit ?? this.unit, @@ -1754,6 +1787,7 @@ class CourseData extends DataClass implements Insertable { lecturer: lecturer ?? this.lecturer, period: period ?? this.period, color: color.present ? color.value : this.color, + createdAt: createdAt.present ? createdAt.value : this.createdAt, ); CourseData copyWithCompanion(CourseCompanion data) { return CourseData( @@ -1766,6 +1800,7 @@ class CourseData extends DataClass implements Insertable { lecturer: data.lecturer.present ? data.lecturer.value : this.lecturer, period: data.period.present ? data.period.value : this.period, color: data.color.present ? data.color.value : this.color, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, ); } @@ -1780,14 +1815,15 @@ class CourseData extends DataClass implements Insertable { ..write('room: $room, ') ..write('lecturer: $lecturer, ') ..write('period: $period, ') - ..write('color: $color') + ..write('color: $color, ') + ..write('createdAt: $createdAt') ..write(')')) .toString(); } @override - int get hashCode => Object.hash( - id, unit, section, weekDay, campus, room, lecturer, period, color); + int get hashCode => Object.hash(id, unit, section, weekDay, campus, room, + lecturer, period, color, createdAt); @override bool operator ==(Object other) => identical(this, other) || @@ -1800,7 +1836,8 @@ class CourseData extends DataClass implements Insertable { other.room == this.room && other.lecturer == this.lecturer && other.period == this.period && - other.color == this.color); + other.color == this.color && + other.createdAt == this.createdAt); } class CourseCompanion extends UpdateCompanion { @@ -1813,6 +1850,7 @@ class CourseCompanion extends UpdateCompanion { final Value lecturer; final Value period; final Value color; + final Value createdAt; final Value rowid; const CourseCompanion({ this.id = const Value.absent(), @@ -1824,6 +1862,7 @@ class CourseCompanion extends UpdateCompanion { this.lecturer = const Value.absent(), this.period = const Value.absent(), this.color = const Value.absent(), + this.createdAt = const Value.absent(), this.rowid = const Value.absent(), }); CourseCompanion.insert({ @@ -1836,6 +1875,7 @@ class CourseCompanion extends UpdateCompanion { required String lecturer, required String period, this.color = const Value.absent(), + this.createdAt = const Value.absent(), this.rowid = const Value.absent(), }) : unit = Value(unit), section = Value(section), @@ -1854,6 +1894,7 @@ class CourseCompanion extends UpdateCompanion { Expression? lecturer, Expression? period, Expression? color, + Expression? createdAt, Expression? rowid, }) { return RawValuesInsertable({ @@ -1866,6 +1907,7 @@ class CourseCompanion extends UpdateCompanion { if (lecturer != null) 'lecturer': lecturer, if (period != null) 'period': period, if (color != null) 'color': color, + if (createdAt != null) 'created_at': createdAt, if (rowid != null) 'rowid': rowid, }); } @@ -1880,6 +1922,7 @@ class CourseCompanion extends UpdateCompanion { Value? lecturer, Value? period, Value? color, + Value? createdAt, Value? rowid}) { return CourseCompanion( id: id ?? this.id, @@ -1891,6 +1934,7 @@ class CourseCompanion extends UpdateCompanion { lecturer: lecturer ?? this.lecturer, period: period ?? this.period, color: color ?? this.color, + createdAt: createdAt ?? this.createdAt, rowid: rowid ?? this.rowid, ); } @@ -1925,6 +1969,9 @@ class CourseCompanion extends UpdateCompanion { if (color.present) { map['color'] = Variable(color.value); } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } if (rowid.present) { map['rowid'] = Variable(rowid.value); } @@ -1943,6 +1990,7 @@ class CourseCompanion extends UpdateCompanion { ..write('lecturer: $lecturer, ') ..write('period: $period, ') ..write('color: $color, ') + ..write('createdAt: $createdAt, ') ..write('rowid: $rowid') ..write(')')) .toString(); @@ -3121,6 +3169,7 @@ typedef $$CourseTableCreateCompanionBuilder = CourseCompanion Function({ required String lecturer, required String period, Value color, + Value createdAt, Value rowid, }); typedef $$CourseTableUpdateCompanionBuilder = CourseCompanion Function({ @@ -3133,6 +3182,7 @@ typedef $$CourseTableUpdateCompanionBuilder = CourseCompanion Function({ Value lecturer, Value period, Value color, + Value createdAt, Value rowid, }); @@ -3171,6 +3221,9 @@ class $$CourseTableFilterComposer ColumnFilters get color => $composableBuilder( column: $table.color, builder: (column) => ColumnFilters(column)); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, builder: (column) => ColumnFilters(column)); } class $$CourseTableOrderingComposer @@ -3208,6 +3261,9 @@ class $$CourseTableOrderingComposer ColumnOrderings get color => $composableBuilder( column: $table.color, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, builder: (column) => ColumnOrderings(column)); } class $$CourseTableAnnotationComposer @@ -3245,6 +3301,9 @@ class $$CourseTableAnnotationComposer GeneratedColumn get color => $composableBuilder(column: $table.color, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); } class $$CourseTableTableManager extends RootTableManager< @@ -3279,6 +3338,7 @@ class $$CourseTableTableManager extends RootTableManager< Value lecturer = const Value.absent(), Value period = const Value.absent(), Value color = const Value.absent(), + Value createdAt = const Value.absent(), Value rowid = const Value.absent(), }) => CourseCompanion( @@ -3291,6 +3351,7 @@ class $$CourseTableTableManager extends RootTableManager< lecturer: lecturer, period: period, color: color, + createdAt: createdAt, rowid: rowid, ), createCompanionCallback: ({ @@ -3303,6 +3364,7 @@ class $$CourseTableTableManager extends RootTableManager< required String lecturer, required String period, Value color = const Value.absent(), + Value createdAt = const Value.absent(), Value rowid = const Value.absent(), }) => CourseCompanion.insert( @@ -3315,6 +3377,7 @@ class $$CourseTableTableManager extends RootTableManager< lecturer: lecturer, period: period, color: color, + createdAt: createdAt, rowid: rowid, ), withReferenceMapper: (p0) => p0 diff --git a/lib/features/auth/repository/user_remote_repository.dart b/lib/features/auth/repository/user_remote_repository.dart index 6b072c3..59a6821 100644 --- a/lib/features/auth/repository/user_remote_repository.dart +++ b/lib/features/auth/repository/user_remote_repository.dart @@ -45,7 +45,6 @@ final class UserRemoteRepository with DioErrorHandler { } on DioException catch (de) { return handleDioError(de); } catch (e) { - rethrow; return left("Something went terribly wrong please try that later"); } } diff --git a/lib/features/auth/repository/user_repository.dart b/lib/features/auth/repository/user_repository.dart index 5e1d10c..4598a17 100644 --- a/lib/features/auth/repository/user_repository.dart +++ b/lib/features/auth/repository/user_repository.dart @@ -46,16 +46,17 @@ final class UserRepository { Future> authenticateRemotely( UserCredentialData credentials) async { // Register a magnet singleton instance + + // TODO: (erick) enable auth with magnet // GetIt.instance.registerSingletonIfAbsent( // () => Magnet(credentials.admno, credentials.password), // instanceName: "magnet", // ); - // TODO: (erick) enable auth with magnet // authenticate with magnet - const magnetResult = Right(Object()); - // await (GetIt.instance.get(instanceName: "magnet").login()); - // + const magnetResult = + // await (GetIt.instance.get(instanceName: "magnet").login()); + Right(Object()); return magnetResult.fold((error) { return left(error.toString()); }, (session) async { diff --git a/lib/features/courses/courses.dart b/lib/features/courses/courses.dart index f9e167c..08a758e 100644 --- a/lib/features/courses/courses.dart +++ b/lib/features/courses/courses.dart @@ -1 +1,3 @@ export 'views/courses_page.dart'; +export 'cubit/course_cubit.dart'; +export 'cubit/course_state.dart'; diff --git a/lib/features/courses/cubit/course_cubit.dart b/lib/features/courses/cubit/course_cubit.dart new file mode 100644 index 0000000..9d0c84e --- /dev/null +++ b/lib/features/courses/cubit/course_cubit.dart @@ -0,0 +1,28 @@ +import 'package:academia/features/courses/cubit/course_state.dart'; +import 'package:academia/features/courses/repository/course_repository.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CourseCubit extends Cubit { + final CourseRepository _courseRepository = CourseRepository(); + CourseCubit() : super(CourseStateInitial()) { + fetchCachedCourses().then((value) {}); + } + + Future syncCourses() async { + final res = await _courseRepository.syncCoursesWithMagnet(); + if (res.isLeft()) { + emit(CourseStateError(error: (res as Left).value)); + } + emit(CourseStateLoaded(courses: (res as Right).value)); + } + + Future fetchCachedCourses() async { + final result = await _courseRepository.fetchAllCachedCourses(); + result.fold((error) { + emit(CourseStateError(error: error)); + }, (courses) { + emit(CourseStateLoaded(courses: courses)); + }); + } +} diff --git a/lib/features/courses/cubit/course_state.dart b/lib/features/courses/cubit/course_state.dart new file mode 100644 index 0000000..9251005 --- /dev/null +++ b/lib/features/courses/cubit/course_state.dart @@ -0,0 +1,37 @@ +import 'package:academia/database/database.dart'; + +/// The base course state +/// note that the [busy] flag will be inherited by all +/// states to indicate that the state is busy and might +/// change +abstract class CourseState { + CourseState({this.busy = false}); + bool busy; +} + +/// The [CourseStateInitial] represents the first state +/// of courses +final class CourseStateInitial extends CourseState {} + +/// The [CourseStateLoading] indicates that the courses +/// are loading either from remote or cache and they're +/// bound to change +final class CourseStateLoading extends CourseState {} + +/// [CourseStateLoaded] indicates that the courses are loaded +/// The [courses] member contains a list of all courses +final class CourseStateLoaded extends CourseState { + final List courses; + CourseStateLoaded({required this.courses}); +} + +/// The [CourseStateError] represents the courses error state +/// It should be emitted if for example fetching was an error +/// or parsing the course data was an error +/// +/// The [error] member contains a [String] message of +/// what went wrong +final class CourseStateError extends CourseState { + final String error; + CourseStateError({required this.error}); +} diff --git a/lib/features/courses/models/course.dart b/lib/features/courses/models/course.dart index 76de6e0..2884d2e 100644 --- a/lib/features/courses/models/course.dart +++ b/lib/features/courses/models/course.dart @@ -2,7 +2,7 @@ import 'package:drift/drift.dart'; class Course extends Table { TextColumn get id => text().nullable()(); - TextColumn get unit => text().unique()(); + TextColumn get unit => text()(); TextColumn get section => text()(); @JsonKey("day_of_the_week") TextColumn get weekDay => text()(); @@ -11,6 +11,9 @@ class Course extends Table { TextColumn get lecturer => text()(); TextColumn get period => text()(); IntColumn get color => integer().nullable()(); + @JsonKey("created_at") + DateTimeColumn get createdAt => + dateTime().nullable().withDefault(Constant(DateTime.now()))(); @override Set>? get primaryKey => {id}; diff --git a/lib/features/courses/repository/course_local_repository.dart b/lib/features/courses/repository/course_local_repository.dart new file mode 100644 index 0000000..8d774d7 --- /dev/null +++ b/lib/features/courses/repository/course_local_repository.dart @@ -0,0 +1,71 @@ +import 'package:academia/database/database.dart'; +import 'package:dartz/dartz.dart'; +import 'package:drift/drift.dart'; +import 'package:get_it/get_it.dart'; + +/// [CourseLocalRepository] +/// A Helper class to manipulate course related information +/// on the device local storage +final class CourseLocalRepository { + // the db's instance + final AppDatabase _localDb = GetIt.instance.get(instanceName: "cacheDB"); + + /// Fetches all cached courses + /// Incase of an error a message of type [String] is returned + /// On success, a [List] of [CourseData] is returned + Future>> fetchAllCachedCourses() async { + try { + final users = await (_localDb.course.select() + ..orderBy([ + (c) => OrderingTerm( + expression: c.createdAt, + mode: OrderingMode.desc, + ), + ])) + .get(); + return right(users); + } catch (e) { + return left("Failed to retrieve users with message ${e.toString()}"); + } + } + + /// Adds a [CourseData] specified by [course] to [_localDb] cache + /// This method can also be used to update courses since it also updates the + /// information on conflict + Future> addCourseToCache(CourseData course) async { + try { + final ok = await _localDb.into(_localDb.course).insertOnConflictUpdate( + course.toCompanion(true), + ); + if (ok != 0) { + return right(true); + } + return left( + "The specified course data was not inserted since it exists and conflicted", + ); + } catch (e) { + return left( + "Failed to append course to cache with error description ${e.toString()}", + ); + } + } + + /// Delete the [CourseData] specified by [course] from local cache + /// It wil return an instance of [String] describing the error that it might have + /// encountered or a boolean [true] incase it was a success + Future> deleteCourseFromCache(CourseData course) async { + try { + final ok = await _localDb.delete(_localDb.course).delete(course); + if (ok != 0) { + return right(true); + } + return left( + "The specified course was not deleted because it does not exist", + ); + } catch (e) { + return left( + "Failed to delete course from cache with error description ${e.toString()}", + ); + } + } +} diff --git a/lib/features/courses/repository/course_remote_repository.dart b/lib/features/courses/repository/course_remote_repository.dart new file mode 100644 index 0000000..67d8379 --- /dev/null +++ b/lib/features/courses/repository/course_remote_repository.dart @@ -0,0 +1,17 @@ +import 'package:academia/database/database.dart'; +import 'package:dartz/dartz.dart'; +import 'package:get_it/get_it.dart'; +import 'package:magnet/magnet.dart'; + +final class CourseRemoteRepository { + /// Fetches courses from magnet. + Future>> fetchCoursesFromMagnet() async { + final magnetInstance = GetIt.instance.get(instanceName: "magnet"); + final magnetResult = await magnetInstance.fetchUserTimeTable(); + return magnetResult.fold((error) { + return left(error.toString()); + }, (courses) { + return right(courses.map((c) => CourseData.fromJson(c)).toList()); + }); + } +} diff --git a/lib/features/courses/repository/course_repository.dart b/lib/features/courses/repository/course_repository.dart new file mode 100644 index 0000000..38da255 --- /dev/null +++ b/lib/features/courses/repository/course_repository.dart @@ -0,0 +1,35 @@ +import 'package:academia/database/database.dart'; +import 'package:dartz/dartz.dart'; + +import 'course_local_repository.dart'; +import 'course_remote_repository.dart'; + +final class CourseRepository { + final CourseLocalRepository _localRepository = CourseLocalRepository(); + final CourseRemoteRepository _courseRemoteRepository = + CourseRemoteRepository(); + + /// Fetches all cached courses + /// Incase of an error a message of type [String] is returned + /// On success, a [List] of [CourseData] is returned + Future>> fetchAllCachedCourses() async { + return await _localRepository.fetchAllCachedCourses(); + } + + Future>> syncCoursesWithMagnet() async { + final result = await _courseRemoteRepository.fetchCoursesFromMagnet(); + + return result.fold((error) { + return left(error); + }, (courses) async { + // Cache them to local db + for (final course in courses) { + final res = await _localRepository.addCourseToCache(course); + if (res.isLeft()) { + return left((res as Left).value); + } + } + return right(courses); + }); + } +} diff --git a/lib/features/courses/views/courses_page_mobile.dart b/lib/features/courses/views/courses_page_mobile.dart index 8aca866..b0b6781 100644 --- a/lib/features/courses/views/courses_page_mobile.dart +++ b/lib/features/courses/views/courses_page_mobile.dart @@ -1,5 +1,8 @@ +import 'package:academia/features/features.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:skeletonizer/skeletonizer.dart'; class CoursesPageMobile extends StatefulWidget { const CoursesPageMobile({super.key}); @@ -9,11 +12,20 @@ class CoursesPageMobile extends StatefulWidget { } class _CoursesPageMobileState extends State { + late CourseCubit courseCubit = BlocProvider.of(context); + + @override + void initState() { + super.initState(); + } + @override Widget build(BuildContext context) { return Scaffold( body: RefreshIndicator( - onRefresh: () async {}, + onRefresh: () async { + await courseCubit.syncCourses(); + }, child: CustomScrollView( slivers: [ SliverAppBar( @@ -30,16 +42,55 @@ class _CoursesPageMobileState extends State { ), ), ), - SliverPadding( - padding: const EdgeInsets.symmetric(horizontal: 12), - sliver: SliverList.separated( - itemBuilder: (context, index) => ListTile( - leading: CircleAvatar(), - title: Text("ACS 200"), - subtitle: Text("PLAB * 10:00 - 13:00 * Fredrick Ogore")), - separatorBuilder: (context, index) => SizedBox(), - ), - ), + BlocBuilder(builder: (context, state) { + if (state is CourseStateLoaded) { + if (state.courses.isNotEmpty) { + return SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 12), + sliver: SliverList.separated( + itemBuilder: (context, index) { + final course = state.courses[index]; + return ListTile( + leading: const CircleAvatar(), + title: Text("${course.unit} ${course.section}"), + subtitle: Text( + "${course.room} * ${course.period} * ${course.lecturer}", + ), + ); + }, + separatorBuilder: (context, index) => const SizedBox(), + itemCount: state.courses.length, + ), + ); + } + + return const SliverFillRemaining( + // TODO: erick add an illustration or animation + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text("You have no courses yet, please pull to refresh"), + ], + ), + ); + } + return SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 12), + sliver: SliverList.separated( + itemCount: 8, + itemBuilder: (context, index) => const Skeletonizer( + enabled: true, + child: ListTile( + leading: CircleAvatar(), + title: Text("Some Couse"), + subtitle: Text("PLAB * 10:00 - 13:00 * Awesome Lecturer"), + ), + ), + separatorBuilder: (context, index) => const SizedBox(), + ), + ); + }), ], ), ), From c9736cb738d9c35f3d2c6064d0fb0e5c51649975 Mon Sep 17 00:00:00 2001 From: Erick Date: Thu, 26 Dec 2024 11:52:30 +0300 Subject: [PATCH 12/12] fix: courses page stuck on loading state on app restart --- lib/database/database.g.dart | 307 ++++++++++++++---- .../auth/repository/user_repository.dart | 14 +- lib/features/courses/cubit/course_cubit.dart | 15 +- lib/features/courses/models/course.dart | 5 +- .../repository/course_local_repository.dart | 4 +- .../courses/repository/course_repository.dart | 13 +- .../courses/views/courses_page_mobile.dart | 8 +- 7 files changed, 279 insertions(+), 87 deletions(-) diff --git a/lib/database/database.g.dart b/lib/database/database.g.dart index 092b45e..dd699cb 100644 --- a/lib/database/database.g.dart +++ b/lib/database/database.g.dart @@ -1501,16 +1501,19 @@ class $CourseTable extends Course with TableInfo<$CourseTable, CourseData> { final GeneratedDatabase attachedDatabase; final String? _alias; $CourseTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _idMeta = const VerificationMeta('id'); - @override - late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); static const VerificationMeta _unitMeta = const VerificationMeta('unit'); @override late final GeneratedColumn unit = GeneratedColumn( 'unit', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _userMeta = const VerificationMeta('user'); + @override + late final GeneratedColumn user = GeneratedColumn( + 'user', aliasedName, true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('REFERENCES user (id)')); static const VerificationMeta _sectionMeta = const VerificationMeta('section'); @override @@ -1559,8 +1562,8 @@ class $CourseTable extends Course with TableInfo<$CourseTable, CourseData> { defaultValue: Constant(DateTime.now())); @override List get $columns => [ - id, unit, + user, section, weekDay, campus, @@ -1580,15 +1583,16 @@ class $CourseTable extends Course with TableInfo<$CourseTable, CourseData> { {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('unit')) { context.handle( _unitMeta, unit.isAcceptableOrUnknown(data['unit']!, _unitMeta)); } else if (isInserting) { context.missing(_unitMeta); } + if (data.containsKey('user')) { + context.handle( + _userMeta, user.isAcceptableOrUnknown(data['user']!, _userMeta)); + } if (data.containsKey('section')) { context.handle(_sectionMeta, section.isAcceptableOrUnknown(data['section']!, _sectionMeta)); @@ -1637,15 +1641,15 @@ class $CourseTable extends Course with TableInfo<$CourseTable, CourseData> { } @override - Set get $primaryKey => {id}; + Set get $primaryKey => {unit}; @override CourseData map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; return CourseData( - id: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}id']), unit: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}unit'])!, + user: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}user']), section: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}section'])!, weekDay: attachedDatabase.typeMapping @@ -1672,8 +1676,8 @@ class $CourseTable extends Course with TableInfo<$CourseTable, CourseData> { } class CourseData extends DataClass implements Insertable { - final String? id; final String unit; + final String? user; final String section; final String weekDay; final String campus; @@ -1683,8 +1687,8 @@ class CourseData extends DataClass implements Insertable { final int? color; final DateTime? createdAt; const CourseData( - {this.id, - required this.unit, + {required this.unit, + this.user, required this.section, required this.weekDay, required this.campus, @@ -1696,10 +1700,10 @@ class CourseData extends DataClass implements Insertable { @override Map toColumns(bool nullToAbsent) { final map = {}; - if (!nullToAbsent || id != null) { - map['id'] = Variable(id); - } map['unit'] = Variable(unit); + if (!nullToAbsent || user != null) { + map['user'] = Variable(user); + } map['section'] = Variable(section); map['week_day'] = Variable(weekDay); map['campus'] = Variable(campus); @@ -1717,8 +1721,8 @@ class CourseData extends DataClass implements Insertable { CourseCompanion toCompanion(bool nullToAbsent) { return CourseCompanion( - id: id == null && nullToAbsent ? const Value.absent() : Value(id), unit: Value(unit), + user: user == null && nullToAbsent ? const Value.absent() : Value(user), section: Value(section), weekDay: Value(weekDay), campus: Value(campus), @@ -1737,8 +1741,8 @@ class CourseData extends DataClass implements Insertable { {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return CourseData( - id: serializer.fromJson(json['id']), unit: serializer.fromJson(json['unit']), + user: serializer.fromJson(json['user']), section: serializer.fromJson(json['section']), weekDay: serializer.fromJson(json['day_of_the_week']), campus: serializer.fromJson(json['campus']), @@ -1753,8 +1757,8 @@ class CourseData extends DataClass implements Insertable { Map toJson({ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return { - 'id': serializer.toJson(id), 'unit': serializer.toJson(unit), + 'user': serializer.toJson(user), 'section': serializer.toJson(section), 'day_of_the_week': serializer.toJson(weekDay), 'campus': serializer.toJson(campus), @@ -1767,8 +1771,8 @@ class CourseData extends DataClass implements Insertable { } CourseData copyWith( - {Value id = const Value.absent(), - String? unit, + {String? unit, + Value user = const Value.absent(), String? section, String? weekDay, String? campus, @@ -1778,8 +1782,8 @@ class CourseData extends DataClass implements Insertable { Value color = const Value.absent(), Value createdAt = const Value.absent()}) => CourseData( - id: id.present ? id.value : this.id, unit: unit ?? this.unit, + user: user.present ? user.value : this.user, section: section ?? this.section, weekDay: weekDay ?? this.weekDay, campus: campus ?? this.campus, @@ -1791,8 +1795,8 @@ class CourseData extends DataClass implements Insertable { ); CourseData copyWithCompanion(CourseCompanion data) { return CourseData( - id: data.id.present ? data.id.value : this.id, unit: data.unit.present ? data.unit.value : this.unit, + user: data.user.present ? data.user.value : this.user, section: data.section.present ? data.section.value : this.section, weekDay: data.weekDay.present ? data.weekDay.value : this.weekDay, campus: data.campus.present ? data.campus.value : this.campus, @@ -1807,8 +1811,8 @@ class CourseData extends DataClass implements Insertable { @override String toString() { return (StringBuffer('CourseData(') - ..write('id: $id, ') ..write('unit: $unit, ') + ..write('user: $user, ') ..write('section: $section, ') ..write('weekDay: $weekDay, ') ..write('campus: $campus, ') @@ -1822,14 +1826,14 @@ class CourseData extends DataClass implements Insertable { } @override - int get hashCode => Object.hash(id, unit, section, weekDay, campus, room, + int get hashCode => Object.hash(unit, user, section, weekDay, campus, room, lecturer, period, color, createdAt); @override bool operator ==(Object other) => identical(this, other) || (other is CourseData && - other.id == this.id && other.unit == this.unit && + other.user == this.user && other.section == this.section && other.weekDay == this.weekDay && other.campus == this.campus && @@ -1841,8 +1845,8 @@ class CourseData extends DataClass implements Insertable { } class CourseCompanion extends UpdateCompanion { - final Value id; final Value unit; + final Value user; final Value section; final Value weekDay; final Value campus; @@ -1853,8 +1857,8 @@ class CourseCompanion extends UpdateCompanion { final Value createdAt; final Value rowid; const CourseCompanion({ - this.id = const Value.absent(), this.unit = const Value.absent(), + this.user = const Value.absent(), this.section = const Value.absent(), this.weekDay = const Value.absent(), this.campus = const Value.absent(), @@ -1866,8 +1870,8 @@ class CourseCompanion extends UpdateCompanion { this.rowid = const Value.absent(), }); CourseCompanion.insert({ - this.id = const Value.absent(), required String unit, + this.user = const Value.absent(), required String section, required String weekDay, required String campus, @@ -1885,8 +1889,8 @@ class CourseCompanion extends UpdateCompanion { lecturer = Value(lecturer), period = Value(period); static Insertable custom({ - Expression? id, Expression? unit, + Expression? user, Expression? section, Expression? weekDay, Expression? campus, @@ -1898,8 +1902,8 @@ class CourseCompanion extends UpdateCompanion { Expression? rowid, }) { return RawValuesInsertable({ - if (id != null) 'id': id, if (unit != null) 'unit': unit, + if (user != null) 'user': user, if (section != null) 'section': section, if (weekDay != null) 'week_day': weekDay, if (campus != null) 'campus': campus, @@ -1913,8 +1917,8 @@ class CourseCompanion extends UpdateCompanion { } CourseCompanion copyWith( - {Value? id, - Value? unit, + {Value? unit, + Value? user, Value? section, Value? weekDay, Value? campus, @@ -1925,8 +1929,8 @@ class CourseCompanion extends UpdateCompanion { Value? createdAt, Value? rowid}) { return CourseCompanion( - id: id ?? this.id, unit: unit ?? this.unit, + user: user ?? this.user, section: section ?? this.section, weekDay: weekDay ?? this.weekDay, campus: campus ?? this.campus, @@ -1942,12 +1946,12 @@ class CourseCompanion extends UpdateCompanion { @override Map toColumns(bool nullToAbsent) { final map = {}; - if (id.present) { - map['id'] = Variable(id.value); - } if (unit.present) { map['unit'] = Variable(unit.value); } + if (user.present) { + map['user'] = Variable(user.value); + } if (section.present) { map['section'] = Variable(section.value); } @@ -1981,8 +1985,8 @@ class CourseCompanion extends UpdateCompanion { @override String toString() { return (StringBuffer('CourseCompanion(') - ..write('id: $id, ') ..write('unit: $unit, ') + ..write('user: $user, ') ..write('section: $section, ') ..write('weekDay: $weekDay, ') ..write('campus: $campus, ') @@ -2061,6 +2065,20 @@ final class $$UserTableReferences return ProcessedTableManager( manager.$state.copyWith(prefetchedData: cache)); } + + static MultiTypedResultKey<$CourseTable, List> _courseRefsTable( + _$AppDatabase db) => + MultiTypedResultKey.fromTable(db.course, + aliasName: $_aliasNameGenerator(db.user.id, db.course.user)); + + $$CourseTableProcessedTableManager get courseRefs { + final manager = $$CourseTableTableManager($_db, $_db.course) + .filter((f) => f.user.id($_item.id)); + + final cache = $_typedResult.readTableOrNull(_courseRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache)); + } } class $$UserTableFilterComposer extends Composer<_$AppDatabase, $UserTable> { @@ -2124,6 +2142,27 @@ class $$UserTableFilterComposer extends Composer<_$AppDatabase, $UserTable> { )); return f(composer); } + + Expression courseRefs( + Expression Function($$CourseTableFilterComposer f) f) { + final $$CourseTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.course, + getReferencedColumn: (t) => t.user, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$CourseTableFilterComposer( + $db: $db, + $table: $db.course, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } } class $$UserTableOrderingComposer extends Composer<_$AppDatabase, $UserTable> { @@ -2230,6 +2269,27 @@ class $$UserTableAnnotationComposer )); return f(composer); } + + Expression courseRefs( + Expression Function($$CourseTableAnnotationComposer a) f) { + final $$CourseTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.course, + getReferencedColumn: (t) => t.user, + builder: (joinBuilder, + {$addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer}) => + $$CourseTableAnnotationComposer( + $db: $db, + $table: $db.course, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + )); + return f(composer); + } } class $$UserTableTableManager extends RootTableManager< @@ -2243,7 +2303,7 @@ class $$UserTableTableManager extends RootTableManager< $$UserTableUpdateCompanionBuilder, (UserData, $$UserTableReferences), UserData, - PrefetchHooks Function({bool userProfileRefs})> { + PrefetchHooks Function({bool userProfileRefs, bool courseRefs})> { $$UserTableTableManager(_$AppDatabase db, $UserTable table) : super(TableManagerState( db: db, @@ -2314,10 +2374,14 @@ class $$UserTableTableManager extends RootTableManager< .map((e) => (e.readTable(table), $$UserTableReferences(db, table, e))) .toList(), - prefetchHooksCallback: ({userProfileRefs = false}) { + prefetchHooksCallback: ( + {userProfileRefs = false, courseRefs = false}) { return PrefetchHooks( db: db, - explicitlyWatchedTables: [if (userProfileRefs) db.userProfile], + explicitlyWatchedTables: [ + if (userProfileRefs) db.userProfile, + if (courseRefs) db.course + ], addJoins: null, getPrefetchedDataCallback: (items) async { return [ @@ -2332,6 +2396,17 @@ class $$UserTableTableManager extends RootTableManager< referencedItemsForCurrentItem: (item, referencedItems) => referencedItems.where((e) => e.userId == item.id), + typedResults: items), + if (courseRefs) + await $_getPrefetchedData( + currentTable: table, + referencedTable: + $$UserTableReferences._courseRefsTable(db), + managerFromTypedResult: (p0) => + $$UserTableReferences(db, table, p0).courseRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => + referencedItems.where((e) => e.user == item.id), typedResults: items) ]; }, @@ -2351,7 +2426,7 @@ typedef $$UserTableProcessedTableManager = ProcessedTableManager< $$UserTableUpdateCompanionBuilder, (UserData, $$UserTableReferences), UserData, - PrefetchHooks Function({bool userProfileRefs})>; + PrefetchHooks Function({bool userProfileRefs, bool courseRefs})>; typedef $$UserProfileTableCreateCompanionBuilder = UserProfileCompanion Function({ required String userId, @@ -3160,8 +3235,8 @@ typedef $$UserCredentialTableProcessedTableManager = ProcessedTableManager< UserCredentialData, PrefetchHooks Function({bool userId, bool username, bool email})>; typedef $$CourseTableCreateCompanionBuilder = CourseCompanion Function({ - Value id, required String unit, + Value user, required String section, required String weekDay, required String campus, @@ -3173,8 +3248,8 @@ typedef $$CourseTableCreateCompanionBuilder = CourseCompanion Function({ Value rowid, }); typedef $$CourseTableUpdateCompanionBuilder = CourseCompanion Function({ - Value id, Value unit, + Value user, Value section, Value weekDay, Value campus, @@ -3186,6 +3261,24 @@ typedef $$CourseTableUpdateCompanionBuilder = CourseCompanion Function({ Value rowid, }); +final class $$CourseTableReferences + extends BaseReferences<_$AppDatabase, $CourseTable, CourseData> { + $$CourseTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static $UserTable _userTable(_$AppDatabase db) => + db.user.createAlias($_aliasNameGenerator(db.course.user, db.user.id)); + + $$UserTableProcessedTableManager? get user { + if ($_item.user == null) return null; + final manager = $$UserTableTableManager($_db, $_db.user) + .filter((f) => f.id($_item.user!)); + final item = $_typedResult.readTableOrNull(_userTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item])); + } +} + class $$CourseTableFilterComposer extends Composer<_$AppDatabase, $CourseTable> { $$CourseTableFilterComposer({ @@ -3195,9 +3288,6 @@ class $$CourseTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnFilters get unit => $composableBuilder( column: $table.unit, builder: (column) => ColumnFilters(column)); @@ -3224,6 +3314,26 @@ class $$CourseTableFilterComposer ColumnFilters get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnFilters(column)); + + $$UserTableFilterComposer get user { + final $$UserTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.user, + 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 $$CourseTableOrderingComposer @@ -3235,9 +3345,6 @@ class $$CourseTableOrderingComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get unit => $composableBuilder( column: $table.unit, builder: (column) => ColumnOrderings(column)); @@ -3264,6 +3371,26 @@ class $$CourseTableOrderingComposer ColumnOrderings get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnOrderings(column)); + + $$UserTableOrderingComposer get user { + final $$UserTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.user, + 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 $$CourseTableAnnotationComposer @@ -3275,9 +3402,6 @@ class $$CourseTableAnnotationComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumn get unit => $composableBuilder(column: $table.unit, builder: (column) => column); @@ -3304,6 +3428,26 @@ class $$CourseTableAnnotationComposer GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); + + $$UserTableAnnotationComposer get user { + final $$UserTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.user, + 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 $$CourseTableTableManager extends RootTableManager< @@ -3315,9 +3459,9 @@ class $$CourseTableTableManager extends RootTableManager< $$CourseTableAnnotationComposer, $$CourseTableCreateCompanionBuilder, $$CourseTableUpdateCompanionBuilder, - (CourseData, BaseReferences<_$AppDatabase, $CourseTable, CourseData>), + (CourseData, $$CourseTableReferences), CourseData, - PrefetchHooks Function()> { + PrefetchHooks Function({bool user})> { $$CourseTableTableManager(_$AppDatabase db, $CourseTable table) : super(TableManagerState( db: db, @@ -3329,8 +3473,8 @@ class $$CourseTableTableManager extends RootTableManager< createComputedFieldComposer: () => $$CourseTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ - Value id = const Value.absent(), Value unit = const Value.absent(), + Value user = const Value.absent(), Value section = const Value.absent(), Value weekDay = const Value.absent(), Value campus = const Value.absent(), @@ -3342,8 +3486,8 @@ class $$CourseTableTableManager extends RootTableManager< Value rowid = const Value.absent(), }) => CourseCompanion( - id: id, unit: unit, + user: user, section: section, weekDay: weekDay, campus: campus, @@ -3355,8 +3499,8 @@ class $$CourseTableTableManager extends RootTableManager< rowid: rowid, ), createCompanionCallback: ({ - Value id = const Value.absent(), required String unit, + Value user = const Value.absent(), required String section, required String weekDay, required String campus, @@ -3368,8 +3512,8 @@ class $$CourseTableTableManager extends RootTableManager< Value rowid = const Value.absent(), }) => CourseCompanion.insert( - id: id, unit: unit, + user: user, section: section, weekDay: weekDay, campus: campus, @@ -3381,9 +3525,42 @@ class $$CourseTableTableManager extends RootTableManager< rowid: rowid, ), withReferenceMapper: (p0) => p0 - .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .map((e) => + (e.readTable(table), $$CourseTableReferences(db, table, e))) .toList(), - prefetchHooksCallback: null, + prefetchHooksCallback: ({user = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic>>(state) { + if (user) { + state = state.withJoin( + currentTable: table, + currentColumn: table.user, + referencedTable: $$CourseTableReferences._userTable(db), + referencedColumn: $$CourseTableReferences._userTable(db).id, + ) as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, )); } @@ -3396,9 +3573,9 @@ typedef $$CourseTableProcessedTableManager = ProcessedTableManager< $$CourseTableAnnotationComposer, $$CourseTableCreateCompanionBuilder, $$CourseTableUpdateCompanionBuilder, - (CourseData, BaseReferences<_$AppDatabase, $CourseTable, CourseData>), + (CourseData, $$CourseTableReferences), CourseData, - PrefetchHooks Function()>; + PrefetchHooks Function({bool user})>; class $AppDatabaseManager { final _$AppDatabase _db; diff --git a/lib/features/auth/repository/user_repository.dart b/lib/features/auth/repository/user_repository.dart index 4598a17..d974027 100644 --- a/lib/features/auth/repository/user_repository.dart +++ b/lib/features/auth/repository/user_repository.dart @@ -48,15 +48,15 @@ final class UserRepository { // Register a magnet singleton instance // TODO: (erick) enable auth with magnet - // GetIt.instance.registerSingletonIfAbsent( - // () => Magnet(credentials.admno, credentials.password), - // instanceName: "magnet", - // ); + GetIt.instance.registerSingletonIfAbsent( + () => Magnet(credentials.admno, credentials.password), + instanceName: "magnet", + ); // authenticate with magnet - const magnetResult = - // await (GetIt.instance.get(instanceName: "magnet").login()); - Right(Object()); + final magnetResult = + await (GetIt.instance.get(instanceName: "magnet").login()); + // Right(Object()); return magnetResult.fold((error) { return left(error.toString()); }, (session) async { diff --git a/lib/features/courses/cubit/course_cubit.dart b/lib/features/courses/cubit/course_cubit.dart index 9d0c84e..5b83ed2 100644 --- a/lib/features/courses/cubit/course_cubit.dart +++ b/lib/features/courses/cubit/course_cubit.dart @@ -1,3 +1,4 @@ +import 'package:academia/database/database.dart'; import 'package:academia/features/courses/cubit/course_state.dart'; import 'package:academia/features/courses/repository/course_repository.dart'; import 'package:dartz/dartz.dart'; @@ -5,20 +6,20 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class CourseCubit extends Cubit { final CourseRepository _courseRepository = CourseRepository(); - CourseCubit() : super(CourseStateInitial()) { - fetchCachedCourses().then((value) {}); - } + CourseCubit() : super(CourseStateInitial()); - Future syncCourses() async { - final res = await _courseRepository.syncCoursesWithMagnet(); + Future syncCourses(UserData user) async { + emit(CourseStateLoading()); + final res = await _courseRepository.syncCoursesWithMagnet(user); if (res.isLeft()) { emit(CourseStateError(error: (res as Left).value)); } emit(CourseStateLoaded(courses: (res as Right).value)); } - Future fetchCachedCourses() async { - final result = await _courseRepository.fetchAllCachedCourses(); + Future fetchCachedCourses(UserData user) async { + emit(CourseStateLoading()); + final result = await _courseRepository.fetchAllCachedCourses(user); result.fold((error) { emit(CourseStateError(error: error)); }, (courses) { diff --git a/lib/features/courses/models/course.dart b/lib/features/courses/models/course.dart index 2884d2e..91d9ac3 100644 --- a/lib/features/courses/models/course.dart +++ b/lib/features/courses/models/course.dart @@ -1,8 +1,9 @@ +import 'package:academia/features/auth/models/user.dart'; import 'package:drift/drift.dart'; class Course extends Table { - TextColumn get id => text().nullable()(); TextColumn get unit => text()(); + TextColumn get user => text().references(User, #id).nullable()(); TextColumn get section => text()(); @JsonKey("day_of_the_week") TextColumn get weekDay => text()(); @@ -16,5 +17,5 @@ class Course extends Table { dateTime().nullable().withDefault(Constant(DateTime.now()))(); @override - Set>? get primaryKey => {id}; + Set>? get primaryKey => {unit}; } diff --git a/lib/features/courses/repository/course_local_repository.dart b/lib/features/courses/repository/course_local_repository.dart index 8d774d7..7cd596a 100644 --- a/lib/features/courses/repository/course_local_repository.dart +++ b/lib/features/courses/repository/course_local_repository.dart @@ -13,7 +13,8 @@ final class CourseLocalRepository { /// Fetches all cached courses /// Incase of an error a message of type [String] is returned /// On success, a [List] of [CourseData] is returned - Future>> fetchAllCachedCourses() async { + Future>> fetchAllCachedCourses( + UserData user) async { try { final users = await (_localDb.course.select() ..orderBy([ @@ -23,6 +24,7 @@ final class CourseLocalRepository { ), ])) .get(); + users.removeWhere((course) => course.user == user.id); return right(users); } catch (e) { return left("Failed to retrieve users with message ${e.toString()}"); diff --git a/lib/features/courses/repository/course_repository.dart b/lib/features/courses/repository/course_repository.dart index 38da255..f1eb9a1 100644 --- a/lib/features/courses/repository/course_repository.dart +++ b/lib/features/courses/repository/course_repository.dart @@ -1,5 +1,6 @@ import 'package:academia/database/database.dart'; import 'package:dartz/dartz.dart'; +import 'package:drift/drift.dart'; import 'course_local_repository.dart'; import 'course_remote_repository.dart'; @@ -12,11 +13,13 @@ final class CourseRepository { /// Fetches all cached courses /// Incase of an error a message of type [String] is returned /// On success, a [List] of [CourseData] is returned - Future>> fetchAllCachedCourses() async { - return await _localRepository.fetchAllCachedCourses(); + Future>> fetchAllCachedCourses( + UserData user) async { + return await _localRepository.fetchAllCachedCourses(user); } - Future>> syncCoursesWithMagnet() async { + Future>> syncCoursesWithMagnet( + UserData user) async { final result = await _courseRemoteRepository.fetchCoursesFromMagnet(); return result.fold((error) { @@ -24,7 +27,9 @@ final class CourseRepository { }, (courses) async { // Cache them to local db for (final course in courses) { - final res = await _localRepository.addCourseToCache(course); + final res = await _localRepository.addCourseToCache( + course.copyWith(user: Value(user.id)), + ); if (res.isLeft()) { return left((res as Left).value); } diff --git a/lib/features/courses/views/courses_page_mobile.dart b/lib/features/courses/views/courses_page_mobile.dart index b0b6781..e9f8ebd 100644 --- a/lib/features/courses/views/courses_page_mobile.dart +++ b/lib/features/courses/views/courses_page_mobile.dart @@ -13,9 +13,13 @@ class CoursesPageMobile extends StatefulWidget { class _CoursesPageMobileState extends State { late CourseCubit courseCubit = BlocProvider.of(context); + late AuthCubit authCubit = BlocProvider.of(context); @override void initState() { + courseCubit.fetchCachedCourses( + (authCubit.state as AuthenticatedState).user, + ); super.initState(); } @@ -24,7 +28,9 @@ class _CoursesPageMobileState extends State { return Scaffold( body: RefreshIndicator( onRefresh: () async { - await courseCubit.syncCourses(); + await courseCubit.syncCourses( + (authCubit.state as AuthenticatedState).user, + ); }, child: CustomScrollView( slivers: [