From 48842724534fefd87f2ea5bfce9bd09325ca27ca Mon Sep 17 00:00:00 2001 From: Jiseeeh Date: Fri, 29 Mar 2024 20:21:21 +0800 Subject: [PATCH 01/13] chore: remove todo --- .../note_taking/presentation/pages/note_taking_screen.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/features/note_taking/presentation/pages/note_taking_screen.dart b/lib/features/note_taking/presentation/pages/note_taking_screen.dart index 9a6d6c8b..30718d9f 100644 --- a/lib/features/note_taking/presentation/pages/note_taking_screen.dart +++ b/lib/features/note_taking/presentation/pages/note_taking_screen.dart @@ -107,7 +107,6 @@ class _NoteTakingScreenState extends ConsumerState { } AppBar _buildAppBar() { - // TODO: add editing of note title return AppBar( title: Text(widget.note.title), ); From 3145f510fbbcb00f860718c05bdd6fb150a7d547 Mon Sep 17 00:00:00 2001 From: Jiseeeh Date: Fri, 29 Mar 2024 21:27:34 +0800 Subject: [PATCH 02/13] style: fix floating btn blocking content --- .../note_taking/presentation/pages/notebook_pages_screen.dart | 2 +- .../note_taking/presentation/pages/notebooks_screen.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/features/note_taking/presentation/pages/notebook_pages_screen.dart b/lib/features/note_taking/presentation/pages/notebook_pages_screen.dart index ad7d415f..573796ab 100644 --- a/lib/features/note_taking/presentation/pages/notebook_pages_screen.dart +++ b/lib/features/note_taking/presentation/pages/notebook_pages_screen.dart @@ -59,7 +59,7 @@ class NotebookPagesScreen extends ConsumerWidget { crossAxisSpacing: 10, mainAxisSpacing: 10, childAspectRatio: (1 / 1.2), - padding: const EdgeInsets.all(10), + padding: const EdgeInsets.fromLTRB(10, 10, 10, 50), children: [ for (var note in notebook.notes) _buildNoteCard(context, ref, note) ], diff --git a/lib/features/note_taking/presentation/pages/notebooks_screen.dart b/lib/features/note_taking/presentation/pages/notebooks_screen.dart index 14ea7a12..b1fda954 100644 --- a/lib/features/note_taking/presentation/pages/notebooks_screen.dart +++ b/lib/features/note_taking/presentation/pages/notebooks_screen.dart @@ -48,7 +48,7 @@ class NotebooksScreen extends ConsumerWidget { crossAxisSpacing: 10, mainAxisSpacing: 10, childAspectRatio: (1 / 1.5), - padding: const EdgeInsets.all(10), + padding: const EdgeInsets.fromLTRB(10, 10, 10, 50), children: [for (var notebook in value) NotebookCard(notebook)], ), AsyncError(:final error) => Center( From 08b87434fe72bfca2fa3397dc6e1525349dc6314 Mon Sep 17 00:00:00 2001 From: Jiseeeh Date: Sat, 30 Mar 2024 22:41:49 +0800 Subject: [PATCH 03/13] refactor: make create notebook take the file instead --- .../repositories/note_repository_impl.dart | 12 +++++------ .../domain/repositories/note_repository.dart | 5 +++-- .../domain/usecases/create_notebook.dart | 5 +++-- .../providers/notes_provider.dart | 11 ++++------ .../providers/notes_provider.g.dart | 2 +- .../widgets/add_notebook_dialog.dart | 21 ++++--------------- 6 files changed, 21 insertions(+), 35 deletions(-) diff --git a/lib/features/note_taking/data/repositories/note_repository_impl.dart b/lib/features/note_taking/data/repositories/note_repository_impl.dart index a64c844b..689ec8e5 100644 --- a/lib/features/note_taking/data/repositories/note_repository_impl.dart +++ b/lib/features/note_taking/data/repositories/note_repository_impl.dart @@ -30,10 +30,9 @@ class NoteRepositoryImpl implements NoteRepository { @override Future> createNotebook( - String name, String coverImgUrl, String coverImgFileName) async { + String name, XFile? coverImg) async { try { - var nbModel = await _noteRemoteDataSource.createNotebook( - name, coverImgUrl, coverImgFileName); + var nbModel = await _noteRemoteDataSource.createNotebook(name, coverImg); return Right(nbModel); } catch (e) { @@ -85,12 +84,13 @@ class NoteRepositoryImpl implements NoteRepository { } @override - Future> uploadNotebookCover(XFile coverImg) async { + Future>> uploadNotebookCover( + XFile coverImg) async { try { - var downloadUrl = + var downloadUrls = await _noteRemoteDataSource.uploadNotebookCover(coverImg); - return Right(downloadUrl); + return Right(downloadUrls); } catch (e) { return Left(GenericFailure(message: e.toString())); } diff --git a/lib/features/note_taking/domain/repositories/note_repository.dart b/lib/features/note_taking/domain/repositories/note_repository.dart index 43e7955e..8c609ae8 100644 --- a/lib/features/note_taking/domain/repositories/note_repository.dart +++ b/lib/features/note_taking/domain/repositories/note_repository.dart @@ -8,9 +8,10 @@ abstract class NoteRepository { Future> createNote( {required String notebookId, required String title}); Future> createNotebook( - String name, String coverImgUrl, String coverImgFileName); + String name, XFile? coverImg); Future>> getNotebooks(); - Future> uploadNotebookCover(XFile coverImg); + + Future>> uploadNotebookCover(XFile coverImg); Future> updateNote( {required String notebookId, required NoteModel note}); Future> updateNotebook( diff --git a/lib/features/note_taking/domain/usecases/create_notebook.dart b/lib/features/note_taking/domain/usecases/create_notebook.dart index ab5fbede..afaecb86 100644 --- a/lib/features/note_taking/domain/usecases/create_notebook.dart +++ b/lib/features/note_taking/domain/usecases/create_notebook.dart @@ -1,4 +1,5 @@ import 'package:dartz/dartz.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:u_do_note/core/error/failures.dart'; import 'package:u_do_note/features/note_taking/data/models/notebook.dart'; @@ -9,7 +10,7 @@ class CreateNotebook { CreateNotebook(this._noteRepository); - Future> call(String name, String coverImgUrl, String coverImgFileName) async { - return await _noteRepository.createNotebook(name, coverImgUrl, coverImgFileName); + Future> call(String name, XFile? coverImg) async { + return await _noteRepository.createNotebook(name, coverImg); } } diff --git a/lib/features/note_taking/presentation/providers/notes_provider.dart b/lib/features/note_taking/presentation/providers/notes_provider.dart index a019f7b1..85f65c12 100644 --- a/lib/features/note_taking/presentation/providers/notes_provider.dart +++ b/lib/features/note_taking/presentation/providers/notes_provider.dart @@ -162,13 +162,10 @@ class Notebooks extends _$Notebooks { } /// Creates a notebook from the given [name] - Future createNotebook( - {required String name, - required String coverImgUrl, - required String coverImgFileName}) async { + Future createNotebook({required String name, XFile? coverImg}) async { final createNotebook = ref.read(createNotebookProvider); - var result = await createNotebook(name, coverImgUrl, coverImgFileName); + var result = await createNotebook(name, coverImg); return result.fold((failure) => failure.message, (nbModel) { List notebookEntities = @@ -244,12 +241,12 @@ class Notebooks extends _$Notebooks { }); } - Future uploadNotebookCover({required XFile coverImg}) async { + Future> uploadNotebookCover({required XFile coverImg}) async { final uploadNotebookCover = ref.read(uploadNotebookCoverProvider); var failureOrCoverImgUrl = await uploadNotebookCover(coverImg); return failureOrCoverImgUrl.fold( - (failure) => '', (coverImgUrl) => coverImgUrl); + (failure) => [failure.message], (coverImgUrl) => coverImgUrl); } } diff --git a/lib/features/note_taking/presentation/providers/notes_provider.g.dart b/lib/features/note_taking/presentation/providers/notes_provider.g.dart index 52d646aa..775e303e 100644 --- a/lib/features/note_taking/presentation/providers/notes_provider.g.dart +++ b/lib/features/note_taking/presentation/providers/notes_provider.g.dart @@ -156,7 +156,7 @@ final deleteNotebookProvider = AutoDisposeProvider.internal( ); typedef DeleteNotebookRef = AutoDisposeProviderRef; -String _$notebooksHash() => r'7a7caad2b2ff18f564ac149c6b6615a8c4f213c0'; +String _$notebooksHash() => r'dce33caed0700c1c029c29665f4482040ba525ff'; /// See also [Notebooks]. @ProviderFor(Notebooks) diff --git a/lib/features/note_taking/presentation/widgets/add_notebook_dialog.dart b/lib/features/note_taking/presentation/widgets/add_notebook_dialog.dart index a9fec589..789ab1c3 100644 --- a/lib/features/note_taking/presentation/widgets/add_notebook_dialog.dart +++ b/lib/features/note_taking/presentation/widgets/add_notebook_dialog.dart @@ -4,8 +4,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:u_do_note/features/note_taking/data/models/notebook.dart'; +import 'package:u_do_note/features/note_taking/data/models/notebook.dart'; import 'package:u_do_note/features/note_taking/domain/entities/notebook.dart'; import 'package:u_do_note/features/note_taking/presentation/providers/notes_provider.dart'; @@ -24,7 +24,7 @@ class AddNotebookDialogState extends ConsumerState { final _nameController = TextEditingController(); final _formKey = GlobalKey(); var _notebookCoverLocalPath = ""; - var _notebookCoverFileName = ""; + // var _notebookCoverFileName = ""; var _notebookCoverUrl = ""; XFile? _notebookCoverImg; @@ -35,7 +35,7 @@ class AddNotebookDialogState extends ConsumerState { if (widget.notebookEntity != null) { _nameController.text = widget.notebookEntity!.subject; _notebookCoverUrl = widget.notebookEntity!.coverUrl; - _notebookCoverFileName = widget.notebookEntity!.coverFileName; + // _notebookCoverFileName = widget.notebookEntity!.coverFileName; } } @@ -53,17 +53,6 @@ class AddNotebookDialogState extends ConsumerState { VoidCallback _onCreate(BuildContext context) { return () async { if (_formKey.currentState!.validate()) { - if (_notebookCoverImg != null) { - var coverDownloadUrl = await ref - .read(notebooksProvider.notifier) - .uploadNotebookCover(coverImg: _notebookCoverImg!); - - setState(() { - _notebookCoverUrl = coverDownloadUrl; - _notebookCoverFileName = _notebookCoverImg!.name; - }); - } - EasyLoading.show( status: 'Creating Notebook...', maskType: EasyLoadingMaskType.black, @@ -72,9 +61,7 @@ class AddNotebookDialogState extends ConsumerState { String result = await ref .read(notebooksProvider.notifier) .createNotebook( - name: _nameController.text, - coverImgUrl: _notebookCoverUrl, - coverImgFileName: _notebookCoverFileName); + name: _nameController.text, coverImg: _notebookCoverImg); EasyLoading.dismiss(); From 3c3db122a6ba641e97b1514b5a42e81300e48838 Mon Sep 17 00:00:00 2001 From: Jiseeeh Date: Sat, 30 Mar 2024 22:42:20 +0800 Subject: [PATCH 04/13] refactor: make return type a list of string --- .../note_taking/domain/usecases/upload_notebook_cover.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/features/note_taking/domain/usecases/upload_notebook_cover.dart b/lib/features/note_taking/domain/usecases/upload_notebook_cover.dart index 1db5873c..fd01b80c 100644 --- a/lib/features/note_taking/domain/usecases/upload_notebook_cover.dart +++ b/lib/features/note_taking/domain/usecases/upload_notebook_cover.dart @@ -9,7 +9,7 @@ class UploadNotebookCover { UploadNotebookCover(this.noteRepository); - Future> call(XFile coverImg) async { + Future>> call(XFile coverImg) async { return await noteRepository.uploadNotebookCover(coverImg); } } From 7560396eb849668776caa6cbfa2a9f607e6778f9 Mon Sep 17 00:00:00 2001 From: Jiseeeh Date: Sat, 30 Mar 2024 22:42:53 +0800 Subject: [PATCH 05/13] fix: fix bug on adding of existing subject title --- .../datasources/note_remote_datasource.dart | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/features/note_taking/data/datasources/note_remote_datasource.dart b/lib/features/note_taking/data/datasources/note_remote_datasource.dart index 5fa7deaf..50e3e08d 100644 --- a/lib/features/note_taking/data/datasources/note_remote_datasource.dart +++ b/lib/features/note_taking/data/datasources/note_remote_datasource.dart @@ -4,8 +4,8 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:u_do_note/core/firestore_collection_enum.dart'; +import 'package:u_do_note/core/firestore_collection_enum.dart'; import 'package:u_do_note/core/logger/logger.dart'; import 'package:u_do_note/core/shared/data/models/note.dart'; import 'package:u_do_note/features/note_taking/data/models/notebook.dart'; @@ -16,8 +16,7 @@ class NoteRemoteDataSource { const NoteRemoteDataSource(this._firestore, this._auth); - Future createNotebook( - String name, String coverImgUrl, String coverImgFileName) async { + Future createNotebook(String name, XFile? coverImg) async { logger.i('Creating notebook...'); var userId = _auth.currentUser!.uid; @@ -33,9 +32,18 @@ class NoteRemoteDataSource { .get(); if (notebook.docs.isNotEmpty) { - response = "Notebook with name $name already exists."; - return NotebookModel.fromFirestore( - notebook.docs.first.id, notebook.docs.first.data()); + response = "Notebook with the name [$name] already exists."; + throw response; + } + + var coverImgUrl = ''; + var coverImgFileName = ''; + + if (coverImg != null) { + var urls = await uploadNotebookCover(coverImg); + + coverImgUrl = urls[0]; + coverImgFileName = urls[1]; } var createdAt = Timestamp.now(); @@ -45,7 +53,7 @@ class NoteRemoteDataSource { .collection('user_notes') .add({ 'subject': name.toLowerCase(), - 'cover_url': coverImgUrl, + 'cover_url': coverImgUrl.isEmpty ? '' : coverImgUrl, 'cover_file_name': coverImgFileName, 'created_at': FieldValue.serverTimestamp(), }); @@ -187,15 +195,15 @@ class NoteRemoteDataSource { var userId = _auth.currentUser!.uid; if (coverImg != null) { - var coverDownloadUrl = await uploadNotebookCover(coverImg); + var urls = await uploadNotebookCover(coverImg); if (notebook.coverFileName.isNotEmpty) { await deleteNotebookCover(notebook.coverFileName); } var updatedModel = notebook.copyWith( - coverUrl: coverDownloadUrl, - coverFileName: coverImg.name, + coverUrl: urls[0], + coverFileName: urls[1], ); await _firestore @@ -307,10 +315,13 @@ class NoteRemoteDataSource { return true; } - Future uploadNotebookCover(XFile image) async { + Future> uploadNotebookCover(XFile image) async { FirebaseStorage storage = FirebaseStorage.instance; - final fileName = image.name; + final fileNameArr = image.name.split('.'); + final fileName = + "${DateTime.now().millisecondsSinceEpoch.toString()}_${fileNameArr[0]}.${fileNameArr[1]}"; + logger.i('Uploading notebook cover with name: $fileName...'); var fileReference = storage.ref().child('notebook_covers/$fileName'); @@ -320,6 +331,6 @@ class NoteRemoteDataSource { var downloadUrl = await snapshot.ref.getDownloadURL(); logger.i('Notebook cover uploaded successfully with url: $downloadUrl'); - return downloadUrl; + return [downloadUrl, fileName]; } } From c77dcd1cc1ff948673ec0350ed7507bf69ecda7e Mon Sep 17 00:00:00 2001 From: Jiseeeh Date: Sat, 6 Apr 2024 20:01:29 +0800 Subject: [PATCH 06/13] style: replace tabs order --- lib/core/shared/presentation/pages/homepage_screen.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/core/shared/presentation/pages/homepage_screen.dart b/lib/core/shared/presentation/pages/homepage_screen.dart index 61721542..40547d73 100644 --- a/lib/core/shared/presentation/pages/homepage_screen.dart +++ b/lib/core/shared/presentation/pages/homepage_screen.dart @@ -10,7 +10,7 @@ class HomepageScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return AutoTabsScaffold( - routes: const [ReviewRoute(), NotebooksRoute()], + routes: const [NotebooksRoute(), ReviewRoute()], extendBody: true, bottomNavigationBuilder: (_, tabsRouter) { // TODO: make this sht look like the bottom nav bar in the figma @@ -20,9 +20,9 @@ class HomepageScreen extends ConsumerWidget { fixedColor: Colors.grey, unselectedItemColor: Colors.black, items: const [ + BottomNavigationBarItem(label: 'Notes', icon: Icon(Icons.home)), BottomNavigationBarItem( - label: 'Review Page', icon: Icon(Icons.home)), - BottomNavigationBarItem(label: 'Notes', icon: Icon(Icons.folder)), + label: 'Review Methods', icon: Icon(Icons.folder)), BottomNavigationBarItem( label: 'Analytics', icon: Icon(Icons.bar_chart)), BottomNavigationBarItem( From 1169933f3a5ce09b5211a060eea391334136ef2e Mon Sep 17 00:00:00 2001 From: Jiseeeh Date: Sat, 6 Apr 2024 20:01:40 +0800 Subject: [PATCH 07/13] feat: replace initial text --- .../note_taking/data/datasources/note_remote_datasource.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/features/note_taking/data/datasources/note_remote_datasource.dart b/lib/features/note_taking/data/datasources/note_remote_datasource.dart index 50e3e08d..93c58e55 100644 --- a/lib/features/note_taking/data/datasources/note_remote_datasource.dart +++ b/lib/features/note_taking/data/datasources/note_remote_datasource.dart @@ -98,7 +98,7 @@ class NoteRemoteDataSource { } // ? r treats the string as a raw string - const defaultContent = r'[{"insert":"Start taking notes\n"}]'; + var defaultContent = r'[{"insert":"' '$title' r'\n"}]'; var newNote = NoteModel( id: DateTime.now().millisecondsSinceEpoch.toString(), title: title, From 53a6c5c0df14544f869b5b1b17038d1e036eaa4e Mon Sep 17 00:00:00 2001 From: Jiseeeh Date: Sat, 6 Apr 2024 20:01:56 +0800 Subject: [PATCH 08/13] style: remove shadow on scroll --- .../note_taking/presentation/pages/note_taking_screen.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/features/note_taking/presentation/pages/note_taking_screen.dart b/lib/features/note_taking/presentation/pages/note_taking_screen.dart index 30718d9f..e19f339b 100644 --- a/lib/features/note_taking/presentation/pages/note_taking_screen.dart +++ b/lib/features/note_taking/presentation/pages/note_taking_screen.dart @@ -109,6 +109,7 @@ class _NoteTakingScreenState extends ConsumerState { AppBar _buildAppBar() { return AppBar( title: Text(widget.note.title), + scrolledUnderElevation: 0, ); } } From fb69ea479aa5e24c195a2e57bbba62afd1938b6b Mon Sep 17 00:00:00 2001 From: Jiseeeh Date: Sat, 6 Apr 2024 20:02:18 +0800 Subject: [PATCH 09/13] feat: replace tabs order --- lib/routes/app_route.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/routes/app_route.dart b/lib/routes/app_route.dart index 9389c71d..ae0602b7 100644 --- a/lib/routes/app_route.dart +++ b/lib/routes/app_route.dart @@ -33,8 +33,8 @@ class AppRouter extends _$AppRouter { AutoRoute(page: SignUpRoute.page, path: '/sign-up'), AutoRoute(page: LoginRoute.page, path: '/login'), AutoRoute(page: HomepageRoute.page, path: '/home', children: [ - AutoRoute(page: ReviewRoute.page, path: ''), - AutoRoute(page: NotebooksRoute.page, path: 'notebooks'), + AutoRoute(page: NotebooksRoute.page, path: ''), + AutoRoute(page: ReviewRoute.page, path: 'review'), AutoRoute(page: NoteTakingRoute.page, path: 'note-taking') ]), AutoRoute( From e7e9a232e6d31d44de9a013c2ba3d635a3d41350 Mon Sep 17 00:00:00 2001 From: Jiseeeh Date: Sat, 6 Apr 2024 20:02:28 +0800 Subject: [PATCH 10/13] feat: replace gpt model --- .../review_page/data/datasources/feynman_remote_datasource.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/features/review_page/data/datasources/feynman_remote_datasource.dart b/lib/features/review_page/data/datasources/feynman_remote_datasource.dart index 1830326c..3b2aee3f 100644 --- a/lib/features/review_page/data/datasources/feynman_remote_datasource.dart +++ b/lib/features/review_page/data/datasources/feynman_remote_datasource.dart @@ -50,7 +50,7 @@ class FeynmanRemoteDataSource { // the actual request. OpenAIChatCompletionModel chatCompletion = await OpenAI.instance.chat.create( - model: "gpt-3.5-turbo-1106", + model: "gpt-3.5-turbo-0125", seed: 6, messages: requestMessages, temperature: 0.2, From ee9f275187399dda2b6094cb5122407a44fdd207 Mon Sep 17 00:00:00 2001 From: Jiseeeh Date: Sat, 6 Apr 2024 22:29:56 +0800 Subject: [PATCH 11/13] style: remove shadow on scroll --- .../note_taking/presentation/pages/notebook_pages_screen.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/features/note_taking/presentation/pages/notebook_pages_screen.dart b/lib/features/note_taking/presentation/pages/notebook_pages_screen.dart index 573796ab..7658d4e2 100644 --- a/lib/features/note_taking/presentation/pages/notebook_pages_screen.dart +++ b/lib/features/note_taking/presentation/pages/notebook_pages_screen.dart @@ -25,6 +25,7 @@ class NotebookPagesScreen extends ConsumerWidget { return Scaffold( appBar: AppBar( + scrolledUnderElevation: 0, title: Text( notebooks!.firstWhere((nb) => nb.id == notebookId).subject, style: const TextStyle(fontWeight: FontWeight.bold), From 120b7fc37fefc56728b642913ec8c91e8211d499 Mon Sep 17 00:00:00 2001 From: Jiseeeh Date: Sun, 7 Apr 2024 14:58:35 +0800 Subject: [PATCH 12/13] feat: add floating speed dial --- .../pages/note_taking_screen.dart | 60 +++++++++++----- .../pages/notebook_pages_screen.dart | 71 ++++++++++++++----- .../presentation/pages/notebooks_screen.dart | 61 +++++++++++++--- pubspec.lock | 8 +++ pubspec.yaml | 1 + 5 files changed, 158 insertions(+), 43 deletions(-) diff --git a/lib/features/note_taking/presentation/pages/note_taking_screen.dart b/lib/features/note_taking/presentation/pages/note_taking_screen.dart index e19f339b..983151ae 100644 --- a/lib/features/note_taking/presentation/pages/note_taking_screen.dart +++ b/lib/features/note_taking/presentation/pages/note_taking_screen.dart @@ -6,6 +6,7 @@ import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:auto_route/auto_route.dart'; +import 'package:flutter_speed_dial/flutter_speed_dial.dart'; import 'package:u_do_note/core/shared/data/models/note.dart'; import 'package:u_do_note/core/shared/domain/entities/note.dart'; @@ -27,6 +28,7 @@ class NoteTakingScreen extends ConsumerStatefulWidget { class _NoteTakingScreenState extends ConsumerState { final _controller = QuillController.basic(); + var readOnly = false; @override void initState() { @@ -69,27 +71,51 @@ class _NoteTakingScreenState extends ConsumerState { @override Widget build(BuildContext context) { return SafeArea( - child: Scaffold( - appBar: _buildAppBar(), - body: _buildBody(), - floatingActionButton: FloatingActionButton( - onPressed: onSave(ref), - child: const Icon(Icons.save), - )), - ); + child: Scaffold( + appBar: _buildAppBar(), + body: _buildBody(), + floatingActionButton: SpeedDial( + activeIcon: Icons.close, + buttonSize: const Size(50, 50), + curve: Curves.bounceIn, + children: [ + SpeedDialChild( + elevation: 0, + child: const Icon(Icons.save_rounded), + labelWidget: const Text('Save Note'), + onTap: onSave(ref)), + SpeedDialChild( + elevation: 0, + child: const Icon(Icons.psychology_rounded), + labelWidget: const Text('Analyze Note'), + onTap: () {}), + SpeedDialChild( + elevation: 0, + child: const Icon(Icons.preview_rounded), + labelWidget: const Text('Read only'), + onTap: () { + setState(() { + readOnly = !readOnly; + }); + }), + ], + child: const Icon(Icons.add_rounded), + ))); } Widget _buildBody() { return Column( children: [ - // TODO: add to toolbar: ocr, preview mode - QuillToolbar.simple( - configurations: QuillSimpleToolbarConfigurations( - controller: _controller, - multiRowsDisplay: false, - toolbarSize: 40, - ), - ), + // TODO: add to toolbar: ocr, + (!readOnly) + ? QuillToolbar.simple( + configurations: QuillSimpleToolbarConfigurations( + controller: _controller, + multiRowsDisplay: false, + toolbarSize: 40, + ), + ) + : const SizedBox(), const Divider( color: Colors.grey, ), @@ -98,7 +124,7 @@ class _NoteTakingScreenState extends ConsumerState { configurations: QuillEditorConfigurations( padding: const EdgeInsets.all(8), controller: _controller, - readOnly: false, + readOnly: readOnly, ), ), ), diff --git a/lib/features/note_taking/presentation/pages/notebook_pages_screen.dart b/lib/features/note_taking/presentation/pages/notebook_pages_screen.dart index 7658d4e2..f0b80bf1 100644 --- a/lib/features/note_taking/presentation/pages/notebook_pages_screen.dart +++ b/lib/features/note_taking/presentation/pages/notebook_pages_screen.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_speed_dial/flutter_speed_dial.dart'; import 'package:u_do_note/core/shared/domain/entities/note.dart'; import 'package:u_do_note/features/note_taking/domain/entities/notebook.dart'; @@ -13,41 +14,78 @@ import 'package:u_do_note/features/note_taking/presentation/widgets/add_note_dia import 'package:u_do_note/routes/app_route.dart'; @RoutePage() -class NotebookPagesScreen extends ConsumerWidget { +class NotebookPagesScreen extends ConsumerStatefulWidget { final String notebookId; const NotebookPagesScreen(@PathParam('notebookId') this.notebookId, {Key? key}) : super(key: key); @override - Widget build(BuildContext context, WidgetRef ref) { + ConsumerState createState() => + _NotebookPagesScreenState(); +} + +class _NotebookPagesScreenState extends ConsumerState { + var gridCols = 2; + @override + Widget build(BuildContext context) { var notebooks = ref.watch(notebooksProvider).value; return Scaffold( appBar: AppBar( scrolledUnderElevation: 0, title: Text( - notebooks!.firstWhere((nb) => nb.id == notebookId).subject, + notebooks!.firstWhere((nb) => nb.id == widget.notebookId).subject, style: const TextStyle(fontWeight: FontWeight.bold), ), ), body: _buildBody(context, ref, notebooks), - floatingActionButton: FloatingActionButton( - onPressed: () async { - showDialog( - context: context, - builder: ((context) => AddNoteDialog(notebookId))); - }, - child: const Icon(Icons.add), + floatingActionButton: SpeedDial( + activeIcon: Icons.close, + buttonSize: const Size(50, 50), + curve: Curves.bounceIn, + children: [ + SpeedDialChild( + elevation: 0, + child: const Icon(Icons.note_add), + labelWidget: const Text('Add Note'), + onTap: () { + showDialog( + context: context, + builder: ((context) => AddNoteDialog(widget.notebookId))); + }), + SpeedDialChild( + elevation: 0, + child: const Icon(Icons.looks_two_rounded), + labelWidget: const Text('Two Columns'), + onTap: () { + setState(() { + if (gridCols != 2) { + gridCols = 2; + } + }); + }), + SpeedDialChild( + elevation: 0, + child: const Icon(Icons.looks_3_rounded), + labelWidget: const Text('Three Columns'), + onTap: () { + setState(() { + if (gridCols != 3) { + gridCols = 3; + } + }); + }), + ], + child: const Icon(Icons.add_rounded), ), ); } - // TODO: deleting and updating a notebook, e.g, changing the subject name // and if applicable, also allow changing of background image Widget _buildBody( BuildContext context, WidgetRef ref, List? notebooks) { - var notebook = notebooks!.firstWhere((nb) => nb.id == notebookId); + var notebook = notebooks!.firstWhere((nb) => nb.id == widget.notebookId); if (notebook.notes.isEmpty) { return const Center( @@ -56,7 +94,7 @@ class NotebookPagesScreen extends ConsumerWidget { } return GridView.count( - crossAxisCount: 2, + crossAxisCount: gridCols, crossAxisSpacing: 10, mainAxisSpacing: 10, childAspectRatio: (1 / 1.2), @@ -100,8 +138,8 @@ class NotebookPagesScreen extends ConsumerWidget { children: [ IconButton( onPressed: () { - context.router.push( - NoteTakingRoute(notebookId: notebookId, note: note)); + context.router.push(NoteTakingRoute( + notebookId: widget.notebookId, note: note)); }, icon: const Icon(Icons.edit)), IconButton( @@ -140,7 +178,8 @@ class NotebookPagesScreen extends ConsumerWidget { var res = await ref .read(notebooksProvider.notifier) - .deleteNote(notebookId: notebookId, noteId: note.id); + .deleteNote( + notebookId: widget.notebookId, noteId: note.id); EasyLoading.dismiss(); EasyLoading.showInfo(res); diff --git a/lib/features/note_taking/presentation/pages/notebooks_screen.dart b/lib/features/note_taking/presentation/pages/notebooks_screen.dart index b1fda954..74136cc6 100644 --- a/lib/features/note_taking/presentation/pages/notebooks_screen.dart +++ b/lib/features/note_taking/presentation/pages/notebooks_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:auto_route/auto_route.dart'; +import 'package:flutter_speed_dial/flutter_speed_dial.dart'; import 'package:u_do_note/features/note_taking/domain/entities/notebook.dart'; import 'package:u_do_note/features/note_taking/presentation/providers/notes_provider.dart'; @@ -8,24 +9,64 @@ import 'package:u_do_note/features/note_taking/presentation/widgets/add_notebook import 'package:u_do_note/features/note_taking/presentation/widgets/notebook_card.dart'; @RoutePage() -class NotebooksScreen extends ConsumerWidget { +class NotebooksScreen extends ConsumerStatefulWidget { const NotebooksScreen({super.key}); @override - Widget build(BuildContext context, WidgetRef ref) { + ConsumerState createState() => + _NotebooksScreenState(); +} + +class _NotebooksScreenState extends ConsumerState { + var gridCols = 2; + + @override + Widget build(BuildContext context) { final asyncNotes = ref.watch(notebooksProvider); return SafeArea( child: Scaffold( appBar: _buildAppBar(), body: _buildBody(userNotes: asyncNotes), - floatingActionButton: FloatingActionButton( - onPressed: () { - showDialog( - context: context, - builder: (context) => const AddNotebookDialog()); - }, - child: const Icon(Icons.add)), + floatingActionButton: SpeedDial( + activeIcon: Icons.close, + buttonSize: const Size(50, 50), + curve: Curves.bounceIn, + children: [ + SpeedDialChild( + elevation: 0, + child: const Icon(Icons.note_add), + labelWidget: const Text('Add Notebook'), + onTap: () { + showDialog( + context: context, + builder: ((context) => const AddNotebookDialog())); + }), + SpeedDialChild( + elevation: 0, + child: const Icon(Icons.looks_two_rounded), + labelWidget: const Text('Two Columns'), + onTap: () { + setState(() { + if (gridCols != 2) { + gridCols = 2; + } + }); + }), + SpeedDialChild( + elevation: 0, + child: const Icon(Icons.looks_3_rounded), + labelWidget: const Text('Three Columns'), + onTap: () { + setState(() { + if (gridCols != 3) { + gridCols = 3; + } + }); + }), + ], + child: const Icon(Icons.add_rounded), + ), ), ); } @@ -44,7 +85,7 @@ class NotebooksScreen extends ConsumerWidget { Widget _buildBody({required AsyncValue> userNotes}) { return switch (userNotes) { AsyncData(:final value) => GridView.count( - crossAxisCount: 2, + crossAxisCount: gridCols, crossAxisSpacing: 10, mainAxisSpacing: 10, childAspectRatio: (1 / 1.5), diff --git a/pubspec.lock b/pubspec.lock index 4d7858fa..2e335472 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -675,6 +675,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.10" + flutter_speed_dial: + dependency: "direct main" + description: + name: flutter_speed_dial + sha256: "698a037274a66dbae8697c265440e6acb6ab6cae9ac5f95c749e7944d8f28d41" + url: "https://pub.dev" + source: hosted + version: "7.0.0" flutter_spinkit: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 31153f88..9fd88976 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,6 +22,7 @@ dependencies: flutter_easyloading: ^3.0.5 flutter_quill: ^9.3.1 flutter_riverpod: ^2.4.10 + flutter_speed_dial: ^7.0.0 hive: ^2.2.3 hive_flutter: ^1.1.0 image_picker: ^1.0.7 From 7d04d3503b7f4f15a1a6d7bee548945c884d9613 Mon Sep 17 00:00:00 2001 From: Jiseeeh Date: Sun, 7 Apr 2024 21:50:43 +0800 Subject: [PATCH 13/13] feat: add note analyzer --- .../pages/note_taking_screen.dart | 171 +++++++++++++++++- 1 file changed, 170 insertions(+), 1 deletion(-) diff --git a/lib/features/note_taking/presentation/pages/note_taking_screen.dart b/lib/features/note_taking/presentation/pages/note_taking_screen.dart index 983151ae..863219a8 100644 --- a/lib/features/note_taking/presentation/pages/note_taking_screen.dart +++ b/lib/features/note_taking/presentation/pages/note_taking_screen.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:dart_openai/dart_openai.dart'; import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_quill/flutter_quill.dart'; @@ -8,8 +9,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:auto_route/auto_route.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart'; +import 'package:u_do_note/core/logger/logger.dart'; import 'package:u_do_note/core/shared/data/models/note.dart'; import 'package:u_do_note/core/shared/domain/entities/note.dart'; +import 'package:u_do_note/core/shared/theme/colors.dart'; import 'package:u_do_note/features/note_taking/presentation/providers/notes_provider.dart'; @RoutePage() @@ -68,6 +71,172 @@ class _NoteTakingScreenState extends ConsumerState { }; } + VoidCallback onAnalyzeNote(BuildContext context, WidgetRef ref) { + return () async { + EasyLoading.show( + status: 'Analyzing note...', + maskType: EasyLoadingMaskType.black, + dismissOnTap: false); + + final systemMessage = OpenAIChatCompletionChoiceMessageModel( + role: OpenAIChatMessageRole.system, + content: [ + OpenAIChatCompletionChoiceMessageContentItemModel.text( + "You are a helpful assistant that wants to help students to analyze their notes and determine what learning technique is best for their notes. Respond in JSON format with the properties learningTechnique, and reason. The reason should be in 2nd person perspective.", + ), + ]); + + final sampleUserMessage = OpenAIChatCompletionChoiceMessageModel( + role: OpenAIChatMessageRole.user, + content: [ + OpenAIChatCompletionChoiceMessageContentItemModel.text( + """ + Given my note below, identify which learning technique suits it, the available techniques are, Leitner System, Feynman Technique, Acronym Mnemonics, and Pomodoro Technique. Your response should be in json format, with the props learningTechnique, and reason. + + By all means, marry. If you get a good wife, you'll become happy; if you get a bad one, you'll become a philosopher. + This quote playfully explores the dual nature of marriage and its potential outcomes. It suggests that embarking on marriage can lead to two distinct paths. Firstly, if one is fortunate enough to marry a good spouse, their life is likely to be filled with happiness and contentment. A loving and supportive partner can bring immense joy and fulfillment, enriching every aspect of life. However, the quote also humorously acknowledges the possibility of marrying a less-than-ideal spouse. In such a scenario, the challenges and difficulties of the relationship may compel one to introspect deeply, pondering the complexities of human nature and the intricacies of relationships. This reflective process, born out of adversity, can lead to a philosophical outlook on life, prompting the individual to seek wisdom and understanding amidst the trials of marriage. Thus, whether one's marriage brings happiness or adversity, the quote suggests that it has the potential to profoundly shape one's perspective and journey through life. + + He is richest who is content with the least, for content is the wealth of nature. + This quote attributed to Socrates underscores the notion that true wealth lies not in material possessions, but in the state of contentment. It suggests that the person who finds contentment with the simplest aspects of life is, in fact, the wealthiest. In this view, material wealth and possessions are secondary to the inner satisfaction derived from being content with what one has. Contentment is depicted as a natural form of wealth, inherent to human existence. By emphasizing the value of contentment, the quote encourages a shift in perspective away from the pursuit of material accumulation towards finding fulfillment in the present moment and in the simple pleasures of life. It reflects Socrates' philosophical emphasis on virtues such as moderation, self-awareness, and inner harmony as essential components of a fulfilling life. + """, + ), + ]); + + final assistantMessage = OpenAIChatCompletionChoiceMessageModel( + role: OpenAIChatMessageRole.assistant, + content: [ + OpenAIChatCompletionChoiceMessageContentItemModel.text( + '{"learningTechnique": "Feynman Technique", "reason": "Your note has an extensive explanation of two quotes, demonstrating understanding by breaking down complex concepts into simpler terms. The Feynman Technique involves explaining concepts in simple terms as if teaching them to someone else." }', + ), + ]); + + final userMessage = OpenAIChatCompletionChoiceMessageModel( + role: OpenAIChatMessageRole.user, + content: [ + OpenAIChatCompletionChoiceMessageContentItemModel.text( + """ + Given my note below, identify which learning technique suits it, the available techniques are, Leitner System, Feynman Technique, Acronym Mnemonics, and Pomodoro Technique. Your response should be in json format, with the props learningTechnique, and reason. + + ${_controller.document.toPlainText()} + """, + ), + ]); + + final requestMessages = [ + systemMessage, + sampleUserMessage, + assistantMessage, + userMessage, + ]; + + OpenAIChatCompletionModel chatCompletion = + await OpenAI.instance.chat.create( + model: "gpt-3.5-turbo-0125", + responseFormat: {"type": "json_object"}, + messages: requestMessages, + temperature: 0.2, + maxTokens: 600, + ); + + String? response = + chatCompletion.choices.first.message.content!.first.text; + + var decodedJson = json.decode(response!); + + logger.d('learning technique: ${decodedJson['learningTechnique']}'); + logger.d('reason: ${decodedJson['reason']}'); + + if (!context.mounted) return; + + EasyLoading.dismiss(); + + var isGoingToReview = await showDialog( + context: context, + builder: (context) => AlertDialog( + scrollable: true, + title: const Text('Analysis result'), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RichText( + text: TextSpan( + children: [ + const TextSpan( + text: 'Learning Technique: ', + style: TextStyle( + fontWeight: FontWeight.bold, + color: AppColors.jetBlack, + fontSize: 20)), + TextSpan( + text: decodedJson['learningTechnique'], + style: + const TextStyle(color: AppColors.jetBlack)), + ], + ), + ), + const SizedBox(height: 10), + RichText( + text: TextSpan( + children: [ + const TextSpan( + text: 'Reason: ', + style: TextStyle( + fontWeight: FontWeight.bold, + color: AppColors.jetBlack, + fontSize: 20)), + TextSpan( + text: decodedJson['reason'], + style: + const TextStyle(color: AppColors.jetBlack)), + ], + ), + ), + const SizedBox(height: 10), + RichText( + text: const TextSpan( + children: [ + TextSpan( + text: 'Notice: ', + style: TextStyle( + fontWeight: FontWeight.bold, + color: AppColors.jetBlack, + fontSize: 20)), + TextSpan( + text: + 'If you want to get started with the suggested learning technique, click ', + style: TextStyle(color: AppColors.jetBlack)), + TextSpan( + text: 'Go button below', + style: TextStyle( + fontWeight: FontWeight.bold, + color: AppColors.jetBlack)), + ], + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: const Text('Close'), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + child: const Text('Go'), + ), + ], + )); + + if (isGoingToReview) { + // TODO: implement + } + }; + } + @override Widget build(BuildContext context) { return SafeArea( @@ -88,7 +257,7 @@ class _NoteTakingScreenState extends ConsumerState { elevation: 0, child: const Icon(Icons.psychology_rounded), labelWidget: const Text('Analyze Note'), - onTap: () {}), + onTap: onAnalyzeNote(context, ref)), SpeedDialChild( elevation: 0, child: const Icon(Icons.preview_rounded),