diff --git a/refilc/lib/api/providers/self_note_provider.dart b/refilc/lib/api/providers/self_note_provider.dart index 3a095b7..4be70a5 100644 --- a/refilc/lib/api/providers/self_note_provider.dart +++ b/refilc/lib/api/providers/self_note_provider.dart @@ -7,17 +7,23 @@ import 'package:provider/provider.dart'; class SelfNoteProvider with ChangeNotifier { late List _notes; + late List _todoItems; late BuildContext _context; + List get notes => _notes; + List get todos => _todoItems; SelfNoteProvider({ List initialNotes = const [], + List initialTodoItems = const [], required BuildContext context, }) { _notes = List.castFrom(initialNotes); + _todoItems = List.castFrom(initialTodoItems); _context = context; if (_notes.isEmpty) restore(); + if (_todoItems.isEmpty) restoreTodo(); } // restore self notes from db @@ -38,6 +44,24 @@ class SelfNoteProvider with ChangeNotifier { } } + // restore todo items from db + Future restoreTodo() async { + String? userId = Provider.of(_context, listen: false).id; + + // await Provider.of(_context, listen: false) + // .userStore + // .storeSelfNotes([], userId: userId!); + + // load self notes from db + if (userId != null) { + var dbTodo = await Provider.of(_context, listen: false) + .userQuery + .getTodoItems(userId: userId); + _todoItems = dbTodo; + notifyListeners(); + } + } + // fetches fresh data from api (not needed, cuz no api for that) // Future fetch() async { // } @@ -54,4 +78,17 @@ class SelfNoteProvider with ChangeNotifier { _notes = notes; notifyListeners(); } + + // store todo items in db + Future storeTodo(List todos) async { + User? user = Provider.of(_context, listen: false).user; + if (user == null) throw "Cannot store Self Notes for User null"; + String userId = user.id; + + await Provider.of(_context, listen: false) + .userStore + .storeSelfTodoItems(todos, userId: userId); + _todoItems = todos; + notifyListeners(); + } } diff --git a/refilc/lib/database/init.dart b/refilc/lib/database/init.dart index 23960a9..0d2822a 100644 --- a/refilc/lib/database/init.dart +++ b/refilc/lib/database/init.dart @@ -84,7 +84,7 @@ const userDataDB = DatabaseStruct("user_data", { "goal_befores": String, "goal_pin_dates": String, // todo and notes - "todo_items": String, "self_notes": String, + "todo_items": String, "self_notes": String, "self_todo": String, // v5 shit "roundings": String, "grade_rarities": String, @@ -152,7 +152,7 @@ Future initDB(DatabaseProvider database) async { "goal_befores": "{}", "goal_pin_dates": "{}", // todo and notes - "todo_items": "{}", "self_notes": "[]", + "todo_items": "{}", "self_notes": "[]", "self_todo": "[]", // v5 shit "roundings": "{}", "grade_rarities": "{}", diff --git a/refilc/lib/database/query.dart b/refilc/lib/database/query.dart index e899e6d..741a253 100644 --- a/refilc/lib/database/query.dart +++ b/refilc/lib/database/query.dart @@ -317,6 +317,18 @@ class UserDatabaseQuery { return selfNotes; } + Future> getTodoItems({required String userId}) async { + List userData = + await db.query("user_data", where: "id = ?", whereArgs: [userId]); + if (userData.isEmpty) return []; + String? todoItemsJson = userData.elementAt(0)["self_todo"] as String?; + if (todoItemsJson == null) return []; + List todoItems = (jsonDecode(todoItemsJson) as List) + .map((e) => TodoItem.fromJson(e)) + .toList(); + return todoItems; + } + // v5 Future> getRoundings({required String userId}) async { List userData = diff --git a/refilc/lib/database/store.dart b/refilc/lib/database/store.dart index 6132780..46929c0 100644 --- a/refilc/lib/database/store.dart +++ b/refilc/lib/database/store.dart @@ -196,6 +196,13 @@ class UserDatabaseStore { where: "id = ?", whereArgs: [userId]); } + Future storeSelfTodoItems(List todoItems, + {required String userId}) async { + String todoItemsJson = jsonEncode(todoItems.map((e) => e.json).toList()); + await db.update("user_data", {"self_todo": todoItemsJson}, + where: "id = ?", whereArgs: [userId]); + } + // v5 Future storeRoundings(Map roundings, {required String userId}) async { diff --git a/refilc/lib/models/self_note.dart b/refilc/lib/models/self_note.dart index bd86a1d..cbaf42b 100644 --- a/refilc/lib/models/self_note.dart +++ b/refilc/lib/models/self_note.dart @@ -33,3 +33,37 @@ class SelfNote { 'note_type': noteType == NoteType.image ? 'image' : 'text', }; } + +class TodoItem { + String id; + String title; + String content; + bool done; + + Map? json; + + TodoItem({ + required this.id, + required this.title, + required this.content, + required this.done, + this.json, + }); + + factory TodoItem.fromJson(Map json) { + return TodoItem( + id: json['id'], + title: json['title'], + content: json['content'], + done: json['done'], + json: json, + ); + } + + get toJson => { + 'id': id, + 'title': title, + 'content': content, + 'done': done, + }; +} diff --git a/refilc_mobile_ui/lib/pages/grades/grades_page.dart b/refilc_mobile_ui/lib/pages/grades/grades_page.dart index 6f8d084..9cb4742 100644 --- a/refilc_mobile_ui/lib/pages/grades/grades_page.dart +++ b/refilc_mobile_ui/lib/pages/grades/grades_page.dart @@ -667,6 +667,8 @@ class GradesPageState extends State { // SoonAlert.show(context: context); gradeCalcTotal(context); + + Navigator.of(context, rootNavigator: true).pop(); }, ), ), diff --git a/refilc_mobile_ui/lib/pages/notes/notes_page.dart b/refilc_mobile_ui/lib/pages/notes/notes_page.dart index b9550b0..47c509f 100644 --- a/refilc_mobile_ui/lib/pages/notes/notes_page.dart +++ b/refilc_mobile_ui/lib/pages/notes/notes_page.dart @@ -35,6 +35,7 @@ import 'package:refilc_plus/models/premium_scopes.dart'; import 'package:refilc_plus/providers/plus_provider.dart'; import 'package:refilc_plus/ui/mobile/plus/premium_inline.dart'; import 'package:refilc_plus/ui/mobile/plus/upsell.dart'; +import 'package:uuid/uuid.dart'; import 'notes_page.i18n.dart'; enum AbsenceFilter { absences, delays, misses } @@ -65,9 +66,14 @@ class NotesPageState extends State with TickerProviderStateMixin { Map doneItems = {}; List noteTiles = []; + List todoItems = []; + + final TextEditingController _taskName = TextEditingController(); + final TextEditingController _taskContent = TextEditingController(); void generateTiles() async { doneItems = await databaseProvider.userQuery.toDoItems(userId: user.id!); + todoItems = await databaseProvider.userQuery.getTodoItems(userId: user.id!); List tiles = []; @@ -82,7 +88,7 @@ class NotesPageState extends State with TickerProviderStateMixin { List toDoTiles = []; if (hw.isNotEmpty && - !Provider.of(context, listen: false) + Provider.of(context, listen: false) .hasScope(PremiumScopes.unlimitedSelfNotes)) { toDoTiles.addAll(hw.map((e) => TickTile( padding: EdgeInsets.zero, @@ -102,6 +108,21 @@ class NotesPageState extends State with TickerProviderStateMixin { ))); } + if (selfNoteProvider.todos.isNotEmpty) { + toDoTiles.addAll(selfNoteProvider.todos.map((e) => TickTile( + padding: EdgeInsets.zero, + title: e.title, + description: e.content, + isTicked: e.done, + onTap: (p0) async { + todoItems.firstWhere((element) => element.id == e.id).done = p0; + + await databaseProvider.userStore + .storeSelfTodoItems(todoItems, userId: user.id!); + }, + ))); + } + if (toDoTiles.isNotEmpty) { tiles.add(const SizedBox( height: 10.0, @@ -313,6 +334,8 @@ class NotesPageState extends State with TickerProviderStateMixin { .fetch( from: DateTime.now().subtract(const Duration(days: 30))); Provider.of(context, listen: false).restore(); + Provider.of(context, listen: false) + .restoreTodo(); generateTiles(); @@ -410,7 +433,159 @@ class NotesPageState extends State with TickerProviderStateMixin { }, ), ), + const SizedBox( + height: 10.0, + ), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.0), + color: Theme.of(context).colorScheme.background), + child: ListTile( + title: Row( + children: [ + const Icon(Icons.task_outlined), + const SizedBox( + width: 10.0, + ), + Text('new_task'.i18n), + ], + ), + onTap: () { + if (!Provider.of(context, listen: false) + .hasScope(PremiumScopes.unlimitedSelfNotes)) { + PlusLockedFeaturePopup.show( + context: context, feature: PremiumFeature.selfNotes); + + return; + } + + showTaskCreation(context); + }, + ), + ), ]), ); } + + void showTaskCreation(context) { + showDialog( + context: context, + builder: (context) => AlertDialog( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(14.0))), + contentPadding: const EdgeInsets.only(top: 10.0), + title: Text("new_task".i18n), + content: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 10.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: _taskName, + decoration: InputDecoration( + border: OutlineInputBorder( + borderSide: + const BorderSide(color: Colors.grey, width: 1.5), + borderRadius: BorderRadius.circular(12.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: + const BorderSide(color: Colors.grey, width: 1.5), + borderRadius: BorderRadius.circular(12.0), + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), + hintText: "task_name".i18n, + suffixIcon: IconButton( + icon: const Icon( + FeatherIcons.x, + color: Colors.grey, + ), + onPressed: () { + setState(() { + _taskName.text = ""; + }); + }, + ), + ), + ), + const SizedBox( + height: 10.0, + ), + TextField( + controller: _taskContent, + decoration: InputDecoration( + border: OutlineInputBorder( + borderSide: + const BorderSide(color: Colors.grey, width: 1.5), + borderRadius: BorderRadius.circular(12.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: + const BorderSide(color: Colors.grey, width: 1.5), + borderRadius: BorderRadius.circular(12.0), + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), + hintText: "task_content".i18n, + suffixIcon: IconButton( + icon: const Icon( + FeatherIcons.x, + color: Colors.grey, + ), + onPressed: () { + setState(() { + _taskContent.text = ""; + }); + }, + ), + ), + ), + ], + ), + ), + actions: [ + TextButton( + child: Text( + "cancel".i18n, + style: const TextStyle(fontWeight: FontWeight.w500), + ), + onPressed: () { + Navigator.of(context).maybePop(); + }, + ), + TextButton( + child: Text( + "next".i18n, + style: const TextStyle(fontWeight: FontWeight.w500), + ), + onPressed: () async { + todoItems.add(TodoItem.fromJson({ + 'id': const Uuid().v4(), + 'title': _taskName.text.replaceAll(' ', '') != "" + ? _taskName.text + : 'no_title'.i18n, + 'content': _taskContent.text, + 'done': false, + })); + + await databaseProvider.userStore + .storeSelfTodoItems(todoItems, userId: user.id!); + + setState(() { + _taskName.text = ""; + _taskContent.text = ""; + }); + + Provider.of(context, listen: false).restore(); + Provider.of(context, listen: false) + .restoreTodo(); + + generateTiles(); + + Navigator.of(context).pop(true); + }, + ), + ], + ), + ); + } } diff --git a/refilc_mobile_ui/lib/pages/notes/notes_page.i18n.dart b/refilc_mobile_ui/lib/pages/notes/notes_page.i18n.dart index f452a9e..28bc703 100644 --- a/refilc_mobile_ui/lib/pages/notes/notes_page.i18n.dart +++ b/refilc_mobile_ui/lib/pages/notes/notes_page.i18n.dart @@ -14,6 +14,10 @@ extension ScreensLocalization on String { "hint_t": "Note title...", "your_notes": "Your Notes", "new_image": "New Image", + "no_title": "No title", + "task_content": "Task content...", + "task_name": "Task title...", + "new_task": "New Task", }, "hu_hu": { "notes": "Füzet", @@ -26,6 +30,10 @@ extension ScreensLocalization on String { "hint_t": "Jegyzet címe...", "your_notes": "Jegyzeteid", "new_image": "Új kép", + "no_title": "Nincs cím", + "task_content": "Feladat tartalma...", + "task_name": "Feladat címe...", + "new_task": "Új feladat", }, "de_de": { "notes": "Broschüre", @@ -38,6 +46,10 @@ extension ScreensLocalization on String { "hint_t": "Titel notieren...", "your_notes": "Deine Noten", "new_image": "Neues Bild", + "no_title": "Kein Titel", + "task_content": "Aufgabeninhalt...", + "task_name": "Aufgabentitel...", + "new_task": "Neue Aufgabe", }, }; diff --git a/refilc_mobile_ui/lib/pages/notes/submenu/create_image_note.dart b/refilc_mobile_ui/lib/pages/notes/submenu/create_image_note.dart index c81faf7..9da532b 100644 --- a/refilc_mobile_ui/lib/pages/notes/submenu/create_image_note.dart +++ b/refilc_mobile_ui/lib/pages/notes/submenu/create_image_note.dart @@ -143,6 +143,7 @@ class _ImageNoteEditorState extends State { .storeSelfNotes(selfNotes, userId: widget.u.id); Provider.of(context, listen: false).restore(); + Provider.of(context, listen: false).restoreTodo(); debugPrint('$file'); } diff --git a/refilc_mobile_ui/lib/pages/notes/submenu/notes_screen.dart b/refilc_mobile_ui/lib/pages/notes/submenu/notes_screen.dart index 8f2795a..df9b01b 100644 --- a/refilc_mobile_ui/lib/pages/notes/submenu/notes_screen.dart +++ b/refilc_mobile_ui/lib/pages/notes/submenu/notes_screen.dart @@ -268,6 +268,7 @@ class NotesScreenState extends State { Provider.of(context, listen: false) .fetch(from: DateTime.now().subtract(const Duration(days: 30))); Provider.of(context, listen: false).restore(); + Provider.of(context, listen: false).restoreTodo(); return Future(() => null); },