diff --git a/App/README.md b/App/README.md index 0ecffcb..c87b8f8 100644 --- a/App/README.md +++ b/App/README.md @@ -2,9 +2,3 @@ Built on flutter ## Made by: 1)Nirbhay - 2)Shreyas - 3)Rohit - 4)Palash - - - ## Supervised by: Sidharth Bahl diff --git a/App/android/app/src/main/AndroidManifest.xml b/App/android/app/src/main/AndroidManifest.xml index 0014354..86370cc 100644 --- a/App/android/app/src/main/AndroidManifest.xml +++ b/App/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ + diff --git a/App/android/app/src/main/res/drawable-v21/launch_background.xml b/App/android/app/src/main/res/drawable-v21/launch_background.xml index f74085f..4068590 100644 --- a/App/android/app/src/main/res/drawable-v21/launch_background.xml +++ b/App/android/app/src/main/res/drawable-v21/launch_background.xml @@ -4,9 +4,9 @@ - + android:src="@mipmap/ic_launcher" /> + diff --git a/App/android/app/src/main/res/values/styles.xml b/App/android/app/src/main/res/values/styles.xml index d74aa35..15ed0f2 100644 --- a/App/android/app/src/main/res/values/styles.xml +++ b/App/android/app/src/main/res/values/styles.xml @@ -1,7 +1,7 @@ - diff --git a/App/lib/components/add_new_todo.dart b/App/lib/components/add_new_todo.dart new file mode 100644 index 0000000..07423d5 --- /dev/null +++ b/App/lib/components/add_new_todo.dart @@ -0,0 +1,128 @@ +// ignore_for_file: use_key_in_widget_constructors, prefer_const_constructors, prefer_const_literals_to_create_immutables, must_be_immutable, avoid_print + +import 'package:app/constants.dart'; +import 'package:app/models/auth.dart'; +import 'package:app/models/theme.dart'; +import 'package:app/models/todo.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class BottomModalSheetTodo extends StatelessWidget { + String text = ''; + final String title; + final String buttonText; + final String catId; + final bool isCategory; // true for category and false for item + BottomModalSheetTodo({ + required this.title, + required this.buttonText, + required this.isCategory, + required this.catId, + }); + @override + Widget build(BuildContext context) { + return Container( + color: Provider.of(context).isTheme + ? Color(0xFF000000) + : Color(0xFF757575), + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 30, + vertical: 20, + ), + decoration: BoxDecoration( + color: Provider.of(context).isTheme ? kyellow : kpink, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + title, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 30, + color: kbgcolor, + ), + ), + TextField( + autofocus: true, + textAlign: TextAlign.center, + onChanged: (value) { + text = value; + }, + onSubmitted: (value) { + print('adding new item to todo'); + String key = Provider.of(context, listen: false).key; + if (text == '') { + Navigator.pop(context); + } else if (isCategory) { + // creating a category + Provider.of(context, listen: false) + .createCategory(key, text); + Navigator.pop(context); + } else { + // creating an item + Provider.of(context, listen: false).createItem( + key, + catId, // get catID + text, + ); + Navigator.pop(context); + } + }, + style: TextStyle( + fontSize: 18, + ), + ), + SizedBox( + height: 15, + ), + GestureDetector( + onTap: () { + print('adding new item to todo'); + String key = Provider.of(context, listen: false).key; + if (text == '') { + Navigator.pop(context); + } else if (isCategory) { + // creating a category + Provider.of(context, listen: false) + .createCategory(key, text); + Navigator.pop(context); + } else { + // creating an item + Provider.of(context, listen: false).createItem( + key, + catId, // get catID + text, + ); + Navigator.pop(context); + } + }, + child: Container( + decoration: BoxDecoration( + color: kgreyblack, + borderRadius: BorderRadius.all( + Radius.circular(8), + ), + ), + child: Center( + child: Padding( + padding: EdgeInsets.all(10.0), + child: Text( + buttonText, + style: TextStyle(color: Colors.white, fontSize: 20), + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/App/lib/components/delete_dialog.dart b/App/lib/components/delete_dialog.dart index 2948624..b86e59f 100644 --- a/App/lib/components/delete_dialog.dart +++ b/App/lib/components/delete_dialog.dart @@ -1,16 +1,29 @@ -// ignore_for_file: prefer_const_constructors, use_key_in_widget_constructors +// ignore_for_file: prefer_const_constructors, use_key_in_widget_constructors, prefer_const_constructors_in_immutables, curly_braces_in_flow_control_structures import 'dart:ui'; import 'package:app/models/auth.dart'; import 'package:app/models/notes.dart'; import 'package:app/models/theme.dart'; +import 'package:app/models/todo.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../constants.dart'; class DeleteDialog extends StatelessWidget { + final String buttonTitle; + final String boxTitle; + final int toDelete; + final String catID; + final String itemId; + DeleteDialog({ + required this.boxTitle, + required this.buttonTitle, + required this.toDelete, + required this.catID, + required this.itemId, + }); @override Widget build(BuildContext context) { return BackdropFilter( @@ -28,7 +41,7 @@ class DeleteDialog extends StatelessWidget { ? kbgcolor : kpink, title: Text( - 'Do you want to delete all the tasks?', + boxTitle, style: TextStyle( color: Provider.of(context, listen: false).isTheme ? Colors.white @@ -72,14 +85,41 @@ class DeleteDialog extends StatelessWidget { color: Colors.red, onPressed: () { String key = Provider.of(context, listen: false).key; - Provider.of(context, listen: false).deleteAllNotes(key); + if (toDelete == 1) { + Provider.of(context, listen: false).deleteAllNotes(key); + } + if (toDelete == 2) { + Provider.of(context, listen: false) + .deleteCategory(key, catID); + } + if (toDelete == 3) { + Provider.of(context, listen: false) + .deleteToDoItem(key, itemId); + } + if (toDelete == 4) { + Provider.of(context, listen: false) + .deleteAllCategories(key); + } Navigator.pop(context); - ScaffoldMessenger.of(context).showSnackBar(snackBar); + if (toDelete == 1) + ScaffoldMessenger.of(context).showSnackBar(snackBarDeleteNotes); + if (toDelete == 2) { + ScaffoldMessenger.of(context) + .showSnackBar(snackBarDeleteCategory); + Navigator.pop(context); + } + if (toDelete == 3) { + ScaffoldMessenger.of(context).showSnackBar(snackBarDeleteItem); + } + if (toDelete == 4) { + ScaffoldMessenger.of(context) + .showSnackBar(snackBarDeleteAllCategories); + } }, child: Padding( padding: EdgeInsets.all(10.0), child: Text( - 'Delete All', + buttonTitle, style: TextStyle( color: Colors.white, fontSize: 18, diff --git a/App/lib/components/profile.dart b/App/lib/components/profile.dart index 49db2a0..6f1adf9 100644 --- a/App/lib/components/profile.dart +++ b/App/lib/components/profile.dart @@ -75,29 +75,69 @@ class _ProfileScreenState extends State { ), ), SizedBox(height: 50), - Container( - child: Text( - name, - style: TextStyle( - color: Provider.of(context).isTheme - ? Colors.white - : kbgcolor, - fontSize: 30, - fontFamily: 'Lobster', - ), + Padding( + padding: EdgeInsets.only( + left: 20, + right: 10, ), - ), - SizedBox(height: 20), - Container( - child: Text( - email, - style: TextStyle( - color: Provider.of(context).isTheme - ? Colors.white - : kbgcolor, - fontSize: 20, - fontFamily: 'roboto', - ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.account_circle, + color: + Provider.of(context, listen: false) + .isTheme + ? Colors.white + : Colors.black, + ), + SizedBox( + width: 7, + ), + Text( + name, + style: TextStyle( + color: Provider.of(context).isTheme + ? Colors.white + : kbgcolor, + fontSize: 20, + fontFamily: 'roboto', + ), + ), + ], + ), + SizedBox(height: 20), + Row( + children: [ + Icon( + Icons.email, + color: + Provider.of(context, listen: false) + .isTheme + ? Colors.white + : Colors.black, + ), + SizedBox( + width: 7, + ), + Flexible( + child: Text( + email, + style: TextStyle( + color: Provider.of(context).isTheme + ? Colors.white + : kbgcolor, + fontSize: 18, + fontFamily: 'roboto', + ), + ), + ), + ], + ), + ], ), ), ], diff --git a/App/lib/components/setting_tile.dart b/App/lib/components/setting_tile.dart index 08aa2a7..7438f54 100644 --- a/App/lib/components/setting_tile.dart +++ b/App/lib/components/setting_tile.dart @@ -1,4 +1,4 @@ -// ignore_for_file: use_key_in_widget_constructors, prefer_const_constructors_in_immutables +// ignore_for_file: use_key_in_widget_constructors, prefer_const_constructors_in_immutables, prefer_const_constructors import 'package:app/constants.dart'; import 'package:app/models/theme.dart'; @@ -7,34 +7,55 @@ import 'package:provider/provider.dart'; class SettingTile extends StatelessWidget { final String title; - SettingTile({required this.title}); + final IconData icon; + SettingTile({required this.icon, required this.title}); @override Widget build(BuildContext context) { return SizedBox( - height: 75, + height: 90, width: 400, child: Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( - 20, + 15, ), ), color: Provider.of(context).isTheme ? kgreyblack : kpink, - elevation: 5, + elevation: 2, child: Padding( - padding: const EdgeInsets.all(10.0), - child: Text( - title, - textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Poppins', - fontWeight: FontWeight.bold, - color: Provider.of(context).isTheme - ? Colors.white - : Colors.black, - fontSize: 25, - ), + padding: const EdgeInsets.only( + left: 30, + right: 20, + top: 10, + bottom: 10, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon( + icon, + color: Provider.of(context, listen: false).isTheme + ? Colors.white + : Colors.black, + size: 28, + ), + SizedBox( + width: 25, + ), + Text( + title, + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'Poppins', + fontWeight: FontWeight.bold, + color: Provider.of(context).isTheme + ? Colors.white + : Colors.black, + fontSize: 18, + ), + ), + ], ), ), ), diff --git a/App/lib/components/todo_category_tile.dart b/App/lib/components/todo_category_tile.dart new file mode 100644 index 0000000..ba3fa43 --- /dev/null +++ b/App/lib/components/todo_category_tile.dart @@ -0,0 +1,110 @@ +// ignore_for_file: prefer_const_constructors, must_be_immutable, use_key_in_widget_constructors + +import 'package:app/constants.dart'; +import 'package:app/models/theme.dart'; +import 'package:app/models/todo.dart'; +import 'package:app/models/todo_components.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class TodoCategoryTile extends StatelessWidget { + Category category; + Function onTap; + TodoCategoryTile({required this.category, required this.onTap}); + + @override + Widget build(BuildContext context) { + List items = + Provider.of(context).getItemListForCategory(category.id); + return Padding( + padding: EdgeInsets.only( + left: 20, + right: 20, + bottom: 10, + ), + child: GestureDetector( + onTap: () { + onTap(); + }, + child: SizedBox( + height: 125, + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 20, + ), + ), + color: Provider.of(context).isTheme + ? kgreyblack + : klightpink, + elevation: 5, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 15, + vertical: 15, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text( + category.category, + style: TextStyle( + color: Provider.of(context).isTheme + ? kNoteTitle + : Colors.black, + fontSize: 25, + ), + ), + SizedBox( + height: 2, + ), + Expanded( + child: (items.isNotEmpty) + ? ListView.builder( + physics: NeverScrollableScrollPhysics(), + itemCount: (items.length >= 2) ? 2 : items.length, + itemBuilder: (context, index) { + return Text( + items[index].item, + style: TextStyle( + color: + Provider.of(context).isTheme + ? kNoteBody + : Colors.black54, + decoration: items[index].isDone + ? TextDecoration.lineThrough + : null, + ), + ); + }, + ) + : Text( + 'No items yet', + style: TextStyle( + color: Provider.of(context).isTheme + ? kNoteBody + : Colors.black54, + ), + ), + ), + Container( + alignment: Alignment.bottomRight, + child: Text( + category.updatedAt, + style: TextStyle( + color: Provider.of(context).isTheme + ? kNotetime + : Colors.black38, + ), + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/App/lib/components/todo_item_tile.dart b/App/lib/components/todo_item_tile.dart new file mode 100644 index 0000000..ffc7fc6 --- /dev/null +++ b/App/lib/components/todo_item_tile.dart @@ -0,0 +1,222 @@ +// ignore_for_file: use_key_in_widget_constructors, prefer_const_constructors_in_immutables, must_be_immutable, prefer_const_constructors + +import 'package:app/constants.dart'; +import 'package:app/models/auth.dart'; +import 'package:app/models/theme.dart'; +import 'package:app/models/todo.dart'; +import 'package:app/models/todo_components.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class TodoItemListTile extends StatefulWidget { + late TodoItem item; + TodoItemListTile({ + required this.item, + }); + + @override + State createState() => _TodoItemListTileState(); +} + +class _TodoItemListTileState extends State { + String editedItem = ''; + @override + Widget build(BuildContext context) { + return ListTile( + title: Text( + widget.item.item, + style: TextStyle( + fontSize: 18, + color: Provider.of(context, listen: false).isTheme + ? Colors.white + : Colors.black, + decoration: widget.item.isDone ? TextDecoration.lineThrough : null, + ), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Theme( + data: ThemeData( + unselectedWidgetColor: + Provider.of(context, listen: false).isTheme + ? kyellow + : kbgcolor, + ), + child: Checkbox( + value: widget.item.isDone, + onChanged: (value) { + String key = Provider.of(context, listen: false).key; + + widget.item.isDone = !widget.item.isDone; + Provider.of(context, listen: false).updateToDoItem( + key, + widget.item.id, + widget.item.isDone, + widget.item.item, + ); + }, + activeColor: + Provider.of(context, listen: false).isTheme + ? kyellow + : kbgcolor, + checkColor: + Provider.of(context, listen: false).isTheme + ? kbgcolor + : Colors.white, + ), + ), + PopupMenuButton( + color: Provider.of(context, listen: false).isTheme + ? Colors.white + : kpink, + icon: Icon( + Icons.more_vert, + color: Provider.of(context, listen: false).isTheme + ? kyellow + : kbgcolor, + ), + itemBuilder: (context) { + return [ + PopupMenuItem( + child: Text('Edit'), + value: 0, + ), + PopupMenuItem( + child: Text('Delete'), + onTap: () { + String key = Provider.of(context, listen: false).key; + Provider.of(context, listen: false) + .deleteToDoItem(key, widget.item.id); + }, + ), + ]; + }, + onSelected: (value) { + if (value == 0) { + editedItem = widget.item.item; + showDialog( + context: context, + builder: (context) { + return EditItemDialog(editedItem, widget.item); + }); + } + }, + ), + ], + ), + ); + } +} + +class EditItemDialog extends StatelessWidget { + String editedItem; + final TodoItem itemObject; + EditItemDialog(this.editedItem, this.itemObject); + @override + Widget build(BuildContext context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular( + 10, + ), + ), + ), + backgroundColor: Provider.of(context, listen: false).isTheme + ? kbgcolor + : kpink, + content: TextFormField( + autofocus: true, + initialValue: itemObject.item, + onChanged: (value) { + editedItem = value; + }, + textInputAction: TextInputAction.newline, + keyboardType: TextInputType.multiline, + maxLines: null, + style: TextStyle( + color: Provider.of(context, listen: false).isTheme + ? Colors.white + : Colors.black, + fontSize: 20, + ), + decoration: InputDecoration( + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(20), + ), + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Provider.of(context, listen: false).isTheme + ? Colors.white + : Colors.black, + )), + ), + ), + actionsPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 10), + actions: [ + MaterialButton( + minWidth: 60, + height: 40, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + elevation: 10, + color: Provider.of(context, listen: false).isTheme + ? kyellow + : Color(0xFF8CD4CB), + onPressed: () { + Navigator.pop(context); + }, + child: Padding( + padding: EdgeInsets.all(10.0), + child: Text( + 'Cancel', + style: TextStyle( + color: Colors.black, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + MaterialButton( + minWidth: 60, + height: 40, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + elevation: 10, + color: Colors.green, + onPressed: () { + String key = Provider.of(context, listen: false).key; + if (editedItem == '') { + Provider.of(context, listen: false) + .deleteToDoItem(key, itemObject.id); + } else { + Provider.of(context, listen: false).updateToDoItem( + key, + itemObject.id, + itemObject.isDone, + editedItem, + ); + } + Navigator.pop(context); + }, + child: Padding( + padding: EdgeInsets.all(10.0), + child: Text( + 'Okay', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ); + } +} diff --git a/App/lib/constants.dart b/App/lib/constants.dart index 362b6f2..17f0f5a 100644 --- a/App/lib/constants.dart +++ b/App/lib/constants.dart @@ -22,7 +22,7 @@ const kNoteTitle = Colors.white; const kNoteBody = Colors.grey; const kNotetime = Colors.grey; -final snackBar = SnackBar( +final snackBarDeleteNotes = SnackBar( content: Text( 'Poof! All notes deleted', style: TextStyle( @@ -33,3 +33,36 @@ final snackBar = SnackBar( duration: Duration(seconds: 2), backgroundColor: Colors.black, ); +final snackBarDeleteCategory = SnackBar( + content: Text( + 'Poof! Category deleted', + style: TextStyle( + fontSize: 20, + fontFamily: 'roboto', + ), + ), + duration: Duration(seconds: 2), + backgroundColor: Colors.black, +); +final snackBarDeleteItem = SnackBar( + content: Text( + 'Item deleted', + style: TextStyle( + fontSize: 20, + fontFamily: 'roboto', + ), + ), + duration: Duration(seconds: 2), + backgroundColor: Colors.black, +); +final snackBarDeleteAllCategories = SnackBar( + content: Text( + 'Poof! All Categories deleted', + style: TextStyle( + fontSize: 20, + fontFamily: 'roboto', + ), + ), + duration: Duration(seconds: 2), + backgroundColor: Colors.black, +); diff --git a/App/lib/main.dart b/App/lib/main.dart index c056c11..8c471af 100644 --- a/App/lib/main.dart +++ b/App/lib/main.dart @@ -2,6 +2,7 @@ import 'package:app/models/notes.dart'; import 'package:app/models/theme.dart'; +import 'package:app/models/todo.dart'; import 'package:app/models/user.dart'; import 'package:app/routers/approutes.dart'; import 'package:app/routers/routenames.dart'; @@ -23,6 +24,7 @@ class MyApp extends StatelessWidget { ChangeNotifierProvider.value(value: Notes()), ChangeNotifierProvider.value(value: User()), ChangeNotifierProvider.value(value: CustomTheme()), + ChangeNotifierProvider.value(value: ToDo()), ], child: Homew(), ); diff --git a/App/lib/models/auth.dart b/App/lib/models/auth.dart index 3b9e68b..174c98e 100644 --- a/App/lib/models/auth.dart +++ b/App/lib/models/auth.dart @@ -2,6 +2,7 @@ import 'package:app/models/notes.dart'; import 'package:app/models/theme.dart'; +import 'package:app/models/todo.dart'; import 'package:app/models/user.dart'; import 'package:app/routers/routenames.dart'; import 'package:flutter/cupertino.dart'; @@ -106,6 +107,9 @@ class Auth with ChangeNotifier { Provider.of(context, listen: false).getIsTheme(); Provider.of(context, listen: false).clearList(); Provider.of(context, listen: false).getList(key); + Provider.of(context, listen: false).clearList(); + Provider.of(context, listen: false).getCategoriesList(key); + Provider.of(context, listen: false).listAllTodoItems(key); Navigator.pushReplacementNamed(context, RouteNames.dashboard); } } diff --git a/App/lib/models/notes.dart b/App/lib/models/notes.dart index 59a2271..8e069fa 100644 --- a/App/lib/models/notes.dart +++ b/App/lib/models/notes.dart @@ -12,10 +12,19 @@ import 'note.dart'; class Notes with ChangeNotifier { List _notes = []; List ids = []; - List get notesList { - return [..._notes]; + + List notesList(String target) { + if (target == '') { + return [..._notes]; + } else { + return _notes.where((element) => element.title.contains(target)).toList(); + } } + // List searchList(String target) { + // _notes.where((element) => element.title == target).toList(); + // } + void clearList() { _notes.clear(); } diff --git a/App/lib/models/todo.dart b/App/lib/models/todo.dart new file mode 100644 index 0000000..f67c6e4 --- /dev/null +++ b/App/lib/models/todo.dart @@ -0,0 +1,264 @@ +// ignore_for_file: avoid_print, prefer_final_fields + +import 'package:app/models/todo_components.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; + +class ToDo with ChangeNotifier { + List _categories = []; + List _items = []; + + List getCateList(String target) { + if (target == '') { + return [..._categories]; + } else { + return _categories + .where((element) => element.category.contains(target)) + .toList(); + } + } + + List getItemListForCategory(String catID) { + print('getting the list of all items in the todo list'); + List items = + _items.where((element) => element.categoryId == catID).toList(); + return items; + } + + List getAllItems() { + return [..._items]; + } + + Future createCategory(String key, String categoryName) async { + try { + print('creating new category'); + http.Response response = await http.post( + Uri.parse('https://notefyapi.servatom.com/api/todo/category/'), + headers: {'Authorization': 'Token $key'}, + body: {"category": categoryName}, + ); + final data = jsonDecode(response.body); + print(data); + if (response.statusCode == 200) { + _categories.add( + Category( + id: data["id"].toString(), + category: data["category"], + createdAt: data["created_at"], + updatedAt: data["updated_at"], + ), + ); + notifyListeners(); + } else { + throw 'Error in creating a category'; + } + } catch (e) { + print(e); + } + } + + Future getCategoriesList(String key) async { + try { + print('getting list of categories'); + http.Response response = await http.get( + Uri.parse('https://notefyapi.servatom.com/api/todo/category/'), + headers: {'Authorization': 'Token $key'}, + ); + final data = jsonDecode(response.body); + print(data); + if (response.statusCode == 200) { + List tempCategories = data; + for (int i = 0; i < tempCategories.length; i++) { + _categories.add( + Category( + id: tempCategories[i]["id"].toString(), + category: tempCategories[i]["category"], + createdAt: tempCategories[i]["created_at"], + updatedAt: tempCategories[i]["updated_at"], + ), + ); + } + notifyListeners(); + } else { + throw 'Error in getting list of categories'; + } + } catch (e) { + print(e); + } + } + + Future updateCategory(String key, String newName, String catId) async { + try { + print('updating a category'); + http.Response response = await http.put( + Uri.parse('https://notefyapi.servatom.com/api/todo/category/'), + headers: {'Authorization': 'Token $key'}, + body: {"cat_id": catId, "category": newName}, + ); + final data = jsonDecode(response.body); + print(data); + int index = _categories.indexWhere((element) => element.id == catId); + if (response.statusCode == 200) { + _categories[index].updatedAt = data["updated_at"]; + _categories[index].category = data["category"]; + notifyListeners(); + } else { + throw "Error in update category"; + } + } catch (e) { + print(e); + } + } + + Future deleteCategory(String key, String catId) async { + try { + print('deleting category'); + http.Response response = await http.delete( + Uri.parse('https://notefyapi.servatom.com/api/todo/category/'), + headers: {'Authorization': 'Token $key'}, + body: {"cat_id": catId}, + ); + if (response.statusCode == 200) { + print('delete succesfully'); + int index = _categories.indexWhere((element) => element.id == catId); + _categories.remove(_categories[index]); + notifyListeners(); + } else { + throw 'Error in deleting note'; + } + } catch (e) { + print(e); + } + } + + void deleteAllCategories(String key) { + for (int i = 0; i < _categories.length; i++) { + deleteCategory(key, _categories[i].id); + } + } + + Future createItem(String key, String catId, String item) async { + try { + print('creating new item = $catId = $item'); + http.Response response = await http.post( + Uri.parse('https://notefyapi.servatom.com/api/todo/item/'), + headers: {'Authorization': 'Token $key'}, + body: {"cat_id": catId, "item": item}, + ); + final data = jsonDecode(response.body); + print('data = $data'); + if (response.statusCode == 200) { + _items.add( + TodoItem( + id: data["id"].toString(), + categoryId: data["category_id"].toString(), + categoryName: data["category"], + createdAt: data["created_at"], + isDone: data["isDone"], + item: data["item"], + updatedAt: data["updated_at"], + ), + ); + print('addition succesful'); + notifyListeners(); + } else { + throw 'Error in creating a new item'; + } + } catch (e) { + print(e); + } + } + + Future listAllTodoItems(String key) async { + try { + print('getting all the items as a list'); + http.Response response = await http.get( + Uri.parse('https://notefyapi.servatom.com/api/todo/item/'), + headers: {'Authorization': 'Token $key'}, + ); + final data = jsonDecode(response.body); + print(data); + if (response.statusCode == 200) { + List tempItems = data; + for (int i = 0; i < tempItems.length; i++) { + _items.add( + TodoItem( + id: tempItems[i]["id"].toString(), + categoryId: tempItems[i]["category_id"].toString(), + categoryName: tempItems[i]["category"], + createdAt: tempItems[i]["created_at"], + isDone: tempItems[i]["isDone"], + item: tempItems[i]["item"], + updatedAt: tempItems[i]["updated_at"], + ), + ); + } + print('listing all the items'); + notifyListeners(); + } else { + throw 'Error in getting the list of all items'; + } + } catch (e) { + print(e); + } + } + + Future updateToDoItem( + String key, String itemId, bool isDone, String item) async { + try { + print('updating an item = $isDone'); + http.Response response = await http.put( + Uri.parse('https://notefyapi.servatom.com/api/todo/item/'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Token $key' + }, + body: jsonEncode( + {"todo_item_id": itemId, "isDone": isDone, "item": item}), + ); + print("update done"); + final data = jsonDecode(response.body); + print(data); + int index = _items.indexWhere((element) => element.id == itemId); + if (response.statusCode == 200) { + _items[index].item = data["item"]; + _items[index].isDone = data["isDone"]; + _items[index].updatedAt = data["updated_at"]; + notifyListeners(); + } else { + throw 'error in updating item'; + } + } catch (e) { + print(e); + } + } + + Future deleteToDoItem(String key, String itemId) async { + try { + print('deleting an item'); + http.Response response = await http.delete( + Uri.parse('https://notefyapi.servatom.com/api/todo/item/'), + headers: {'Authorization': 'Token $key'}, + body: {"todo_item_id": itemId}, + ); + final data = jsonDecode(response.body); + print(data); + int index = _items.indexWhere((element) => element.id == itemId); + if (response.statusCode == 200) { + print('deleted succesfully'); + _items.remove(_items[index]); + notifyListeners(); + } else { + throw 'error in deleting item'; + } + } catch (e) { + print(e); + } + } + + void clearList() { + _categories = []; + _items = []; + } +} diff --git a/App/lib/models/todo_components.dart b/App/lib/models/todo_components.dart new file mode 100644 index 0000000..a94d210 --- /dev/null +++ b/App/lib/models/todo_components.dart @@ -0,0 +1,31 @@ +class Category { + String id; + String category; + String createdAt; + String updatedAt; + Category({ + required this.id, + required this.category, + required this.createdAt, + required this.updatedAt, + }); +} + +class TodoItem { + String id; + String item; + String categoryName; + bool isDone; + String createdAt; + String updatedAt; + String categoryId; + TodoItem({ + required this.id, + required this.categoryId, + required this.categoryName, + required this.createdAt, + required this.isDone, + required this.item, + required this.updatedAt, + }); +} diff --git a/App/lib/routers/approutes.dart b/App/lib/routers/approutes.dart index 67ace5c..32a78f5 100644 --- a/App/lib/routers/approutes.dart +++ b/App/lib/routers/approutes.dart @@ -1,6 +1,8 @@ import 'package:app/models/note.dart'; +import 'package:app/models/todo_components.dart'; import 'package:app/routers/routenames.dart'; import 'package:app/screens/dashboard.dart'; +import 'package:app/screens/items_screen.dart'; import 'package:app/screens/loginscreen.dart'; import 'package:app/screens/mainscreen.dart'; import 'package:app/screens/notescreen.dart'; @@ -32,6 +34,12 @@ class AppRoutes { return MaterialPageRoute(builder: (_) => ResetPassScreen()); case RouteNames.serverdownpage: return MaterialPageRoute(builder: (_) => ServerDown()); + case RouteNames.itemscreen: + var cat = settings.arguments as Category; + return MaterialPageRoute( + builder: (_) => ItemsScreen( + category: cat, + )); case RouteNames.noterscreen: var notes = settings.arguments as Note; return MaterialPageRoute( diff --git a/App/lib/routers/routenames.dart b/App/lib/routers/routenames.dart index d9b58bb..9d90c71 100644 --- a/App/lib/routers/routenames.dart +++ b/App/lib/routers/routenames.dart @@ -9,4 +9,5 @@ class RouteNames { static const String splash = '/splash'; static const String resetpasswordscreen = '/resetpassword'; static const String serverdownpage = '/serverdown'; + static const String itemscreen = '/itemscreen'; } diff --git a/App/lib/screens/dashboard.dart b/App/lib/screens/dashboard.dart index 053feb9..d4fd6e5 100644 --- a/App/lib/screens/dashboard.dart +++ b/App/lib/screens/dashboard.dart @@ -1,6 +1,4 @@ -// ignore_for_file: prefer_const_constructors, use_key_in_widget_constructors, avoid_print, unused_import, curly_braces_in_flow_control_structures - - +// ignore_for_file: prefer_const_constructors, use_key_in_widget_constructors, avoid_print, unused_import, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables import 'package:app/models/auth.dart'; import 'package:app/components/drawer.dart'; @@ -8,9 +6,13 @@ import 'package:app/components/note_tile.dart'; import 'package:app/constants.dart'; import 'package:app/models/note.dart'; import 'package:app/models/notes.dart'; +import 'package:app/models/theme.dart'; import 'package:app/routers/routenames.dart'; +import 'package:app/screens/notesdashboard.dart'; +import 'package:app/screens/tododashboard.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; class DashBoard extends StatefulWidget { @@ -19,96 +21,70 @@ class DashBoard extends StatefulWidget { } class _DashBoardState extends State { - bool isVisible = true; - + bool search = false; + String toSearch = ''; @override Widget build(BuildContext context) { - List notesList = Provider.of(context, listen: true).notesList; - return Scaffold( - drawer: DashboardDrawer(), - body: NestedScrollView( - floatHeaderSlivers: true, - headerSliverBuilder: (context, innerBoxIsScrolled) => [ - SliverAppBar( - floating: true, - snap: true, - iconTheme: IconThemeData(color: kbgcolor), - title: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Notefy', - style: TextStyle( - color: kbgcolor, - fontFamily: 'roboto', - ), - ), - Image.asset( - 'images/logo.png', - scale: 10, + return DefaultTabController( + length: 2, + child: Scaffold( + drawer: DashboardDrawer(), + appBar: AppBar( + bottom: TabBar( + tabs: [ + Tab( + icon: Icon( + Icons.create, ), - ], - ), - centerTitle: true, - actions: [ - IconButton( - onPressed: () { - print('hello'); - }, + ), + Tab( icon: Icon( - Icons.search, + Icons.check_box, ), - ) + ), ], ), - ], - body: NotificationListener( - onNotification: (notification) { - if (notification.direction == ScrollDirection.forward) { - if (!isVisible) - setState(() { - isVisible = true; - }); - } else if (notification.direction == ScrollDirection.reverse) { - if (isVisible) - setState(() { - isVisible = false; - }); - } - return true; - }, - child: ListView.builder( - itemCount: notesList.length, - itemBuilder: (context, index) { - return NoteTile( - note: notesList[index], - ); - }, + iconTheme: IconThemeData(color: kbgcolor), + title: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Notefy', + style: TextStyle( + color: kbgcolor, + fontFamily: 'roboto', + ), + ), + Image.asset( + 'images/logo.png', + scale: 10, + ), + ], ), - ), - ), - floatingActionButton: isVisible - ? FloatingActionButton( + centerTitle: true, + actions: [ + IconButton( onPressed: () { - Navigator.pushNamed( - context, - RouteNames.noterscreen, - arguments: Note( - body: '', - title: '', - id: '', - createTime: '', - updateTime: '', - ), - ); + print('hello'); + setState(() { + search = !search; + toSearch = ''; + }); }, - child: Icon( - Icons.add, + icon: Icon( + search ? Icons.clear : Icons.search, color: kbgcolor, - size: 30, ), ) - : null, + ], + ), + body: TabBarView( + children: [ + NotesDashBoard(search, toSearch), + ToDoDashBoard(search, toSearch), + ], + ), + ), ); } } diff --git a/App/lib/screens/items_screen.dart b/App/lib/screens/items_screen.dart new file mode 100644 index 0000000..303e5a1 --- /dev/null +++ b/App/lib/screens/items_screen.dart @@ -0,0 +1,234 @@ +// ignore_for_file: use_key_in_widget_constructors, prefer_const_constructors, prefer_const_constructors_in_immutables, curly_braces_in_flow_control_structures, avoid_print + +import 'package:app/components/delete_dialog.dart'; +import 'package:app/components/todo_item_tile.dart'; +import 'package:app/constants.dart'; +import 'package:app/models/auth.dart'; +import 'package:app/models/theme.dart'; +import 'package:app/models/todo.dart'; +import 'package:app/models/todo_components.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:provider/provider.dart'; + +import '../components/add_new_todo.dart'; + +class ItemsScreen extends StatefulWidget { + final Category category; + ItemsScreen({required this.category}); + @override + State createState() => _ItemsScreenState(); +} + +class _ItemsScreenState extends State { + bool isVisible = true; + late String title; + void setVariable() { + print('item screen'); + print(widget.category); + title = widget.category.category; + } + + @override + void initState() { + setVariable(); + setState(() {}); + super.initState(); + } + + @override + Widget build(BuildContext context) { + List items = Provider.of(context) + .getItemListForCategory(widget.category.id.toString()); + return Scaffold( + backgroundColor: Provider.of(context, listen: false).isTheme + ? kbgcolor + : Colors.white, + floatingActionButton: isVisible + ? FloatingActionButton( + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => SingleChildScrollView( + child: Padding( + padding: MediaQuery.of(context).viewInsets, + child: BottomModalSheetTodo( + title: 'Add Item', + buttonText: 'Add', + isCategory: false, + catId: widget.category.id, + ), + ), + ), + ); + }, + child: Icon( + Icons.add, + color: kbgcolor, + size: 30, + ), + ) + : null, + appBar: AppBar( + backgroundColor: + Provider.of(context, listen: false).isTheme + ? kbgcolor + : Colors.white, + elevation: 0, + leading: IconButton( + onPressed: () { + String key = Provider.of(context, listen: false).key; + Provider.of(context, listen: false).updateCategory( + key, + title, + widget.category.id, + ); + Navigator.pop(context); + }, + icon: Icon( + Icons.keyboard_arrow_left, + color: Provider.of(context, listen: false).isTheme + ? kyellow + : kbgcolor, + size: 35, + ), + ), + automaticallyImplyLeading: false, + actions: [ + IconButton( + onPressed: () { + showDialog( + context: context, + builder: (context) { + return DeleteDialog( + boxTitle: 'Do you want to delete this category?', + buttonTitle: 'Delete', + toDelete: 2, + catID: widget.category.id, + itemId: '', + ); + }); + }, + icon: Icon(Icons.delete, color: Colors.red), + ), + ], + ), + body: WillPopScope( + onWillPop: () async { + String key = Provider.of(context, listen: false).key; + Provider.of(context, listen: false).updateCategory( + key, + title, + widget.category.id, + ); + Navigator.pop(context); + return true; + }, + child: Container( + padding: EdgeInsets.only( + bottom: 20, + ), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + ), + child: TextFormField( + initialValue: widget.category.category, + textAlign: TextAlign.center, + onChanged: (value) { + title = value; + }, + maxLines: 1, + style: TextStyle( + color: + Provider.of(context, listen: false).isTheme + ? Colors.white + : Colors.black, + fontSize: 30, + ), + decoration: InputDecoration( + hintText: 'Category Name', + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(20), + ), + hintStyle: TextStyle( + fontSize: 25, + color: Provider.of(context, listen: false) + .isTheme + ? Colors.grey + : Colors.black54, + ), + ), + onFieldSubmitted: (value) { + String key = Provider.of(context, listen: false).key; + Provider.of(context, listen: false).updateCategory( + key, + title, + widget.category.id, + ); + }, + ), + ), + NotificationListener( + onNotification: (notification) { + if (notification.direction == ScrollDirection.forward) { + if (!isVisible) + setState(() { + isVisible = true; + }); + } else if (notification.direction == + ScrollDirection.reverse) { + if (isVisible) + setState(() { + isVisible = false; + }); + } + return true; + }, + child: Expanded( + child: (items.isNotEmpty) + ? Padding( + padding: EdgeInsets.only( + top: 20, + bottom: 20, + left: 20, + right: 10, + ), + child: ListView.builder( + itemCount: items.length, + itemBuilder: (context, index) { + return TodoItemListTile( + item: items[index], + ); + }, + ), + ) + : Padding( + padding: const EdgeInsets.only( + bottom: 20, + right: 20, + left: 20, + ), + child: Text( + 'No items yet', + style: TextStyle( + color: Provider.of(context).isTheme + ? kNoteBody + : Colors.black54, + fontSize: 16, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/App/lib/screens/loginscreen.dart b/App/lib/screens/loginscreen.dart index 3dfa2b7..43c8251 100644 --- a/App/lib/screens/loginscreen.dart +++ b/App/lib/screens/loginscreen.dart @@ -5,6 +5,7 @@ import 'package:app/components/error_box.dart'; import 'package:app/components/inputfield.dart'; import 'package:app/constants.dart'; import 'package:app/models/notes.dart'; +import 'package:app/models/todo.dart'; import 'package:app/models/user.dart'; import 'package:app/routers/routenames.dart'; import 'package:flutter/material.dart'; @@ -95,14 +96,23 @@ class _LoginScreenState extends State { String key = Provider.of(context, listen: false).key; - Provider.of(context, listen: false) - .getUserdetail(key); + if (key != '') { + Provider.of(context, listen: false) + .getUserdetail(key); - Provider.of(context, listen: false) - .clearList(); - Provider.of(context, listen: false) - .getList(key); - Navigator.pushNamed(context, RouteNames.dashboard); + Provider.of(context, listen: false) + .clearList(); + Provider.of(context, listen: false) + .getList(key); + + Provider.of(context, listen: false) + .clearList(); + Provider.of(context, listen: false) + .getCategoriesList(key); + Provider.of(context, listen: false) + .listAllTodoItems(key); + Navigator.pushNamed(context, RouteNames.dashboard); + } } catch (e) { showDialog( context: context, diff --git a/App/lib/screens/notescreen.dart b/App/lib/screens/notescreen.dart index 98430fd..4af1549 100644 --- a/App/lib/screens/notescreen.dart +++ b/App/lib/screens/notescreen.dart @@ -22,6 +22,8 @@ class _NoteScreenState extends State { late String title; late String body; late String noteID; + var titleCopy = ''; + var bodyCopy = ''; void setVariable() { print(widget.note); if (widget.note != null) { @@ -34,6 +36,8 @@ class _NoteScreenState extends State { body = ''; noteID = ''; } + titleCopy = title; + bodyCopy = body; } @override @@ -57,7 +61,9 @@ class _NoteScreenState extends State { elevation: 0, leading: IconButton( onPressed: () { - if (title == '') { + if (title == '' && body == '' && noteID == '') { + Navigator.pop(context); + } else if (title == '') { showDialog( context: context, builder: (context) { @@ -83,17 +89,21 @@ class _NoteScreenState extends State { .createNote(key, title, body); Navigator.pop(context); } else if (noteID != '') { - String key = Provider.of(context, listen: false).key; - Provider.of(context, listen: false) - .updateNote(key, title, body, noteID); - Navigator.pop(context); + if (title == titleCopy && body == bodyCopy) { + Navigator.pop(context); + } else { + String key = Provider.of(context, listen: false).key; + Provider.of(context, listen: false) + .updateNote(key, title, body, noteID); + Navigator.pop(context); + } } }, icon: Icon( Icons.keyboard_arrow_left, color: Provider.of(context, listen: false).isTheme ? kyellow - : kpink, + : kbgcolor, size: 35, ), ), @@ -110,73 +120,118 @@ class _NoteScreenState extends State { ), ], ), - body: Container( - padding: EdgeInsets.only( - left: 20, - right: 20, - bottom: 20, - ), - child: Column( - children: [ - TextFormField( - initialValue: title == 'k' ? body.split(' ').first : title, - onChanged: (value) { - title = value; - }, - maxLines: 1, - style: TextStyle( - color: Provider.of(context, listen: false).isTheme - ? Colors.white - : Colors.black, - fontSize: 25, - ), - decoration: InputDecoration( - hintText: 'Title', - border: OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(20), - ), - hintStyle: TextStyle( - fontSize: 25, + body: WillPopScope( + onWillPop: () async { + if (title == '' && body == '' && noteID == '') { + Navigator.pop(context); + } else if (title == '') { + showDialog( + context: context, + builder: (context) { + return ErrorBox( + errorText: 'Title cannot be empty', + onpressed: () { + Navigator.pop(context); + }); + }); + } else if (body == '') { + showDialog( + context: context, + builder: (context) { + return ErrorBox( + errorText: 'Body cannot be empty', + onpressed: () { + Navigator.pop(context); + }); + }); + } else if (noteID == '') { + String key = Provider.of(context, listen: false).key; + Provider.of(context, listen: false) + .createNote(key, title, body); + Navigator.pop(context); + } else if (noteID != '') { + if (title == titleCopy && body == bodyCopy) { + Navigator.pop(context); + } else { + String key = Provider.of(context, listen: false).key; + Provider.of(context, listen: false) + .updateNote(key, title, body, noteID); + Navigator.pop(context); + } + } + + return true; + }, + child: Container( + padding: EdgeInsets.only( + left: 20, + right: 20, + bottom: 20, + ), + child: Column( + children: [ + TextFormField( + initialValue: title == 'k' ? body.split(' ').first : title, + onChanged: (value) { + title = value; + }, + maxLines: 1, + style: TextStyle( color: Provider.of(context, listen: false).isTheme - ? Colors.grey - : Colors.black54, + ? Colors.white + : Colors.black, + fontSize: 25, ), - ), - onFieldSubmitted: (value) {}, - ), - Expanded( - child: TextFormField( - initialValue: body, - onChanged: (value) { - body = value; - }, - textInputAction: TextInputAction.newline, - keyboardType: TextInputType.multiline, - maxLines: null, - style: TextStyle( - color: Provider.of(context, listen: false).isTheme - ? Colors.white - : Colors.black, - fontSize: 20, - ), - decoration: InputDecoration( - hintText: 'Body', - border: OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(20), + decoration: InputDecoration( + hintText: 'Title', + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(20), + ), + hintStyle: TextStyle( + fontSize: 25, + color: + Provider.of(context, listen: false).isTheme + ? Colors.grey + : Colors.black54, + ), ), - hintStyle: TextStyle( - fontSize: 20, + onFieldSubmitted: (value) {}, + ), + Expanded( + child: TextFormField( + initialValue: body, + onChanged: (value) { + body = value; + }, + textInputAction: TextInputAction.newline, + keyboardType: TextInputType.multiline, + maxLines: null, + style: TextStyle( color: Provider.of(context, listen: false).isTheme - ? Colors.grey - : Colors.black54, + ? Colors.white + : Colors.black, + fontSize: 20, ), - ), - )) - ], + decoration: InputDecoration( + hintText: 'Body', + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(20), + ), + hintStyle: TextStyle( + fontSize: 20, + color: + Provider.of(context, listen: false).isTheme + ? Colors.grey + : Colors.black54, + ), + ), + )) + ], + ), ), ), ); diff --git a/App/lib/screens/notesdashboard.dart b/App/lib/screens/notesdashboard.dart new file mode 100644 index 0000000..1a37b60 --- /dev/null +++ b/App/lib/screens/notesdashboard.dart @@ -0,0 +1,155 @@ +// ignore_for_file: prefer_const_constructors, curly_braces_in_flow_control_structures, use_key_in_widget_constructors, must_be_immutable + +import 'package:app/components/note_tile.dart'; +import 'package:app/models/note.dart'; +import 'package:app/models/notes.dart'; +import 'package:app/models/theme.dart'; +import 'package:app/routers/routenames.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:provider/provider.dart'; +import 'package:app/constants.dart'; + +class NotesDashBoard extends StatefulWidget { + bool search; + String toSearch; + NotesDashBoard(this.search, this.toSearch); + @override + State createState() => _NotesDashBoardState(); +} + +class _NotesDashBoardState extends State { + bool isVisible = true; + + @override + void initState() { + widget.toSearch = ''; + super.initState(); + } + + @override + Widget build(BuildContext context) { + List tempList = + Provider.of(context, listen: true).notesList(widget.toSearch); + tempList.sort((a, b) => a.updateTime.compareTo(b.updateTime)); + List notesList = tempList.reversed.toList(); + return Scaffold( + floatingActionButton: isVisible + ? FloatingActionButton( + heroTag: 'button2', + onPressed: () { + Navigator.pushNamed( + context, + RouteNames.noterscreen, + arguments: Note( + body: '', + title: '', + id: '', + createTime: '', + updateTime: '', + ), + ); + }, + child: Icon( + Icons.add, + color: kbgcolor, + size: 30, + ), + ) + : null, + body: NotificationListener( + onNotification: (notification) { + if (notification.direction == ScrollDirection.forward) { + if (!isVisible) + setState(() { + isVisible = true; + }); + } else if (notification.direction == ScrollDirection.reverse) { + if (isVisible) + setState(() { + isVisible = false; + }); + } + return true; + }, + child: Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Column( + children: [ + if (widget.search) + Padding( + padding: + const EdgeInsets.only(right: 20, left: 20, bottom: 10), + child: TextField( + autofocus: true, + textAlign: TextAlign.center, + onChanged: (value) { + setState(() { + widget.toSearch = value; + }); + }, + style: TextStyle( + color: Provider.of(context).isTheme + ? Colors.white + : Colors.black, + ), + decoration: InputDecoration( + hintText: 'Search by title', + hintStyle: TextStyle(color: Colors.grey), + filled: true, + fillColor: Provider.of(context).isTheme + ? Color(0xFF3D3D3D) + : Color(0xFFFFE7EF), + border: OutlineInputBorder( + borderSide: BorderSide( + color: Provider.of(context).isTheme + ? Colors.black + : Colors.white, + width: 1, + ), + borderRadius: BorderRadius.all( + Radius.circular(30), + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Provider.of(context).isTheme + ? Colors.black + : Colors.white, + width: 1, + ), + borderRadius: BorderRadius.all( + Radius.circular(30), + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Provider.of(context).isTheme + ? Colors.black + : Colors.white, + width: 1, + ), + borderRadius: BorderRadius.all( + Radius.circular(30), + ), + ), + ), + ), + ), + Expanded( + child: ListView.builder( + itemCount: notesList.length, + itemBuilder: (context, index) { + return NoteTile( + note: notesList[index], + ); + }, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/App/lib/screens/settingsreen.dart b/App/lib/screens/settingsreen.dart index 9d40f31..d62e0a4 100644 --- a/App/lib/screens/settingsreen.dart +++ b/App/lib/screens/settingsreen.dart @@ -46,35 +46,59 @@ class SettingsScreen extends StatelessWidget { padding: const EdgeInsets.all(10), child: Column( children: [ - InkWell( + GestureDetector( onTap: () { Navigator.pushNamed(context, RouteNames.resetpasswordscreen); }, - child: SettingTile(title: 'Reset Password'), + child: SettingTile(icon: Icons.lock, title: 'Reset Password'), ), - SizedBox(height: 15), + SizedBox(height: 10), GestureDetector( onTap: () { Provider.of(context, listen: false) .toggleTheme(); }, - child: SettingTile(title: 'Theme')), - SizedBox(height: 15), + child: SettingTile(icon: Icons.color_lens, title: 'Theme')), + SizedBox(height: 10), + GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (context) { + return DeleteDialog( + boxTitle: 'Do you want to delete all the notes?', + buttonTitle: 'Delete All', + toDelete: 1, + catID: '', + itemId: '', + ); + }); + }, + child: SettingTile( + icon: Icons.delete_sweep, title: 'Delete all notes')), + SizedBox(height: 10), GestureDetector( onTap: () { showDialog( context: context, builder: (context) { - return DeleteDialog(); + return DeleteDialog( + boxTitle: 'Do you want to delete all the categories?', + buttonTitle: 'Delete All', + toDelete: 4, + catID: '', + itemId: '', + ); }); }, - child: SettingTile(title: 'Delete all notes')), - SizedBox(height: 15), + child: SettingTile( + icon: Icons.delete, title: 'Delete all categories')), + SizedBox(height: 10), GestureDetector( onTap: () { Provider.of(context, listen: false).logoutUser(context); }, - child: SettingTile(title: 'Logout')), + child: SettingTile(icon: Icons.logout, title: 'Logout')), ], ), ), diff --git a/App/lib/screens/splash.dart b/App/lib/screens/splash.dart index 5e04f8e..c5845ac 100644 --- a/App/lib/screens/splash.dart +++ b/App/lib/screens/splash.dart @@ -33,6 +33,11 @@ class _SplashscreenState extends State { print('response code = ${response.statusCode}'); if (response.statusCode == 404) { + await Future.delayed( + Duration( + milliseconds: 1500, + ), + ); Provider.of(context, listen: false).isLoggedIn(context); } else if (response.statusCode == 523) { serverDownpage(); diff --git a/App/lib/screens/tododashboard.dart b/App/lib/screens/tododashboard.dart new file mode 100644 index 0000000..688a9b2 --- /dev/null +++ b/App/lib/screens/tododashboard.dart @@ -0,0 +1,169 @@ +// ignore_for_file: use_key_in_widget_constructors, avoid_unnecessary_containers, prefer_const_constructors, prefer_const_literals_to_create_immutables, avoid_print, must_be_immutable, curly_braces_in_flow_control_structures + +import 'package:app/components/todo_category_tile.dart'; +import 'package:app/constants.dart'; +import 'package:app/models/theme.dart'; +import 'package:app/models/todo.dart'; +import 'package:app/models/todo_components.dart'; +import 'package:app/routers/routenames.dart'; +import 'package:app/components/add_new_todo.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:provider/provider.dart'; + +class ToDoDashBoard extends StatefulWidget { + bool search; + String toSearch; + ToDoDashBoard(this.search, this.toSearch); + @override + State createState() => _ToDoDashBoardState(); +} + +class _ToDoDashBoardState extends State { + bool isVisible = true; + + @override + void initState() { + widget.toSearch = ''; + super.initState(); + } + + @override + Widget build(BuildContext context) { + List tempList = + Provider.of(context).getCateList(widget.toSearch); + tempList.sort((a, b) => a.updatedAt.compareTo(b.updatedAt)); + List categoriesList = tempList.reversed.toList(); + return Scaffold( + floatingActionButton: isVisible + ? FloatingActionButton( + heroTag: 'button1', + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => SingleChildScrollView( + child: Padding( + padding: MediaQuery.of(context).viewInsets, + child: Container( + child: BottomModalSheetTodo( + title: 'Add Category', + buttonText: 'Add', + isCategory: true, + catId: '', + ), + ), + ), + ), + ); + }, + child: Icon( + Icons.add, + color: kbgcolor, + size: 30, + ), + ) + : null, + body: NotificationListener( + onNotification: (notification) { + if (notification.direction == ScrollDirection.forward) { + if (!isVisible) + setState(() { + isVisible = true; + }); + } else if (notification.direction == ScrollDirection.reverse) { + if (isVisible) + setState(() { + isVisible = false; + }); + } + return true; + }, + child: Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Column( + children: [ + if (widget.search) + Padding( + padding: + const EdgeInsets.only(right: 20, left: 20, bottom: 10), + child: TextField( + autofocus: true, + textAlign: TextAlign.center, + onChanged: (value) { + setState(() { + widget.toSearch = value; + }); + }, + style: TextStyle( + color: Provider.of(context).isTheme + ? Colors.white + : Colors.black, + ), + decoration: InputDecoration( + hintText: 'Search by category', + hintStyle: TextStyle(color: Colors.grey), + filled: true, + fillColor: Provider.of(context).isTheme + ? Color(0xFF3D3D3D) + : Color(0xFFFFE7EF), + border: OutlineInputBorder( + borderSide: BorderSide( + color: Provider.of(context).isTheme + ? Colors.black + : Colors.white, + width: 1, + ), + borderRadius: BorderRadius.all( + Radius.circular(30), + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Provider.of(context).isTheme + ? Colors.black + : Colors.white, + width: 1, + ), + borderRadius: BorderRadius.all( + Radius.circular(30), + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Provider.of(context).isTheme + ? Colors.black + : Colors.white, + width: 1, + ), + borderRadius: BorderRadius.all( + Radius.circular(30), + ), + ), + ), + ), + ), + Expanded( + child: ListView.builder( + itemCount: categoriesList.length, + itemBuilder: (context, index) { + return TodoCategoryTile( + category: categoriesList[index], + onTap: () { + Navigator.pushNamed( + context, + RouteNames.itemscreen, + arguments: categoriesList[index], + ); + }, + ); + }, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/README.md b/README.md index a2e9758..01e0322 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,14 @@ Computer Science and Engineering, TIET @ @mannadamay12
+ + :boy: Nirbhay Makhija
+       Email: nmakhija_be20@thapar.edu
+       GitHub: @Nirbhay-nrb
+ + :boy: Rohit Kumar
+       Email: rkumar_be20@thapar.edu
+       GitHub: @krohitk17

## Contributions diff --git a/backend/Dockerfile b/backend/Dockerfile index 4bc7f8e..e13c665 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -14,6 +14,7 @@ RUN python -m pip install -r requirements.txt WORKDIR /app COPY . /app +RUN mkdir logs # Creates a non-root user with an explicit UID and adds permission to access the /app folder # For more info, please refer to https://aka.ms/vscode-docker-python-configure-containers diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index df9708a..d94ab73 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -10,4 +10,5 @@ services: - "6969:6969" volumes: - ./db.sqlite3:/app/db.sqlite3 + - logs:/app/logs restart: always \ No newline at end of file diff --git a/backend/run.sh b/backend/run.sh index 16ec178..ec95db8 100755 --- a/backend/run.sh +++ b/backend/run.sh @@ -1 +1,2 @@ -gunicorn --certfile=origin.pem --keyfile=key.pem --worker-class gevent --bind 0.0.0.0:6969 config.wsgi:application +#gunicorn --access-logfile /app/logs/access.log --error-logfile /app/logs/error.log --certfile=origin.pem --keyfile=key.pem --worker-class gevent --bind 0.0.0.0:6969 config.wsgi:application +gunicorn --worker-class gevent --certfile=origin.pem --keyfile=key.pem --bind 0.0.0.0:6969 config.wsgi:application \ No newline at end of file diff --git a/backend/todo/views.py b/backend/todo/views.py index 8d07eee..e2bebf9 100644 --- a/backend/todo/views.py +++ b/backend/todo/views.py @@ -40,12 +40,16 @@ def delete(self, request): category = ToDoCategory.objects.get( author=request.user, id=request.data["cat_id"] ) - category.delete() - - # delete all items of category - items = ToDoItem.objects.filter(category=category) - items.delete() - return Response({"message": "Successfully Deleted"}, status=204) + if category is not None: + # delete all items of category + items = ToDoItem.objects.filter(category=category) + items.delete() + # delete category + category.delete() + print("here") + return Response({"message": "Successfully Deleted"}) + else: + return Response({"message": "Category not found"}, status=404) except: return Response({"message": "Error"}) @@ -107,7 +111,7 @@ def delete(self, request): category__author=request.user, id=todo_item ) todo_item.delete() - return Response({"message": "successfully deleted"}, status=204) + return Response({"message": "successfully deleted"}) except: return Response({"message": "Error"}, status=400) diff --git a/frontend/.env.development b/frontend/.env.development new file mode 100644 index 0000000..e96244c --- /dev/null +++ b/frontend/.env.development @@ -0,0 +1 @@ +REACT_APP_API_ENDPOINT = https://notefy-test.herokuapp.com \ No newline at end of file diff --git a/frontend/.env.production b/frontend/.env.production new file mode 100644 index 0000000..3b28781 --- /dev/null +++ b/frontend/.env.production @@ -0,0 +1 @@ +REACT_APP_API_ENDPOINT = https://notefyapi.servatom.com \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md index 09cd66e..774ae6e 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -8,14 +8,14 @@ In the project directory, you can run: ### `npm start` -Runs the app in the development mode.\ +Runs the app in the development mode. Open [http://localhost:3000](http://localhost:3000) to view it in the browser. ### `npm test` -Launches the test runner in the interactive watch mode.\ +Launches the test runner in the interactive watch mode. ### `npm run build` -Builds the app for production to the `build` folder.\ \ No newline at end of file +Builds the app for production to the `build` folder. diff --git a/frontend/src/URL.js b/frontend/src/URL.js index ccf1eb7..18f70a4 100644 --- a/frontend/src/URL.js +++ b/frontend/src/URL.js @@ -1,4 +1,4 @@ -const URL = 'https://notefyapi.servatom.com'; +const URL = process.env.REACT_APP_API_ENDPOINT; export default URL; diff --git a/frontend/src/assets/css/Settings.css b/frontend/src/assets/css/Settings.css index 8d8c64d..2bcccab 100644 --- a/frontend/src/assets/css/Settings.css +++ b/frontend/src/assets/css/Settings.css @@ -36,9 +36,9 @@ .pfpContainer { - height: 150px; - width: 150px; - border-radius: 150px; + height: 100px; + width: 100px; + border-radius: 100px; margin: 0 auto; overflow: hidden; text-align: right; @@ -46,7 +46,7 @@ .pfp { width: 100%; - height: 150px; + height: 100px; object-fit: cover; object-position: center; } @@ -54,7 +54,7 @@ .changeAvatar { margin-top: 10px; - font-size: 0.9rem; + font-size: 0.8rem; cursor: pointer; } @@ -64,11 +64,36 @@ color: var(--yellowcolor); } +.controlContainer +{ + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + margin: 30px 0px 20px; + width: 75%; + padding: 10px 30px; + border: 2px dotted rgba(255, 255, 0, 0.589); + border-radius: 10px; +} +.option +{ + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-evenly; +} + +.option input +{ + margin: 0px 10px; +} + .settingsForm { padding: 0; margin: 0; - margin-top: 30px; + margin-top: 0px; } .settingsForm form diff --git a/frontend/src/components/AddNote.js b/frontend/src/components/AddNote.js deleted file mode 100644 index 185ee65..0000000 --- a/frontend/src/components/AddNote.js +++ /dev/null @@ -1,46 +0,0 @@ -import "../assets/css/Notes.css"; -import { useState } from "react"; -import {nanoid} from 'nanoid'; - -const AddNote=(props)=>{ - - const [noteText, setNoteText] = useState(""); - - const changeHandler =(event)=> - { - setNoteText(event.target.value); - } -const saveHandler=()=> -{ - if(noteText) - { - - let date= new Date; - const day = date.getDate(); - const month = date.getMonth() + 1; - const year = date.getFullYear(); - - let newnote={ - - text: noteText, - // date: day+"/"+month+"/"+year - }; - props.onSave(newnote); - setNoteText(""); - } -} - - return( -
-