diff --git a/assets/images/left_arrow.png b/assets/images/left_arrow.png new file mode 100644 index 0000000..481b22c Binary files /dev/null and b/assets/images/left_arrow.png differ diff --git a/assets/images/right_arrow.png b/assets/images/right_arrow.png new file mode 100644 index 0000000..a6e2c78 Binary files /dev/null and b/assets/images/right_arrow.png differ diff --git a/lib/constants/tools.dart b/lib/constants/tools.dart index ddd255a..38c4621 100644 --- a/lib/constants/tools.dart +++ b/lib/constants/tools.dart @@ -115,6 +115,17 @@ final List> allTools = [ }, { "id": 10, + "name": "Academia Anki", + "action": "Lets Up That Grade", + "image": "assets/images/tasks_manager.png", + "ontap": () { + Get.to(const AnkiHomePage()); + }, + "description": + "Ready to revolutionize your study habits?\nLet's help you master courses effortlessly", + }, + { + "id": 11, "name": "Ask Me", "action": "Ask Me", "image": "assets/images/think.jpeg", diff --git a/lib/storage/schemas.dart b/lib/storage/schemas.dart index d8db609..78a147c 100644 --- a/lib/storage/schemas.dart +++ b/lib/storage/schemas.dart @@ -124,7 +124,6 @@ const schemas = { end_date TEXT NOT NULL ); """, - "course_topics": """ CREATE TABLE IF NOT EXISTS course_topics ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -133,6 +132,28 @@ const schemas = { description TEXT NOT NULL ); """, + // Anki + // Topic + "ankiTopics": """ + CREATE TABLE IF NOT EXISTS ankiTopics ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT UNIQUE NOT NULL, + desc TEXT NOT NULL, + is_favourite INTEGER DEFAULT 0 NOT NULL, + num_cards INTERGER DEFAULT 0 NOT NULL + ); + """, + // AnkiCard + "ankiCards": """ + CREATE TABLE IF NOT EXISTS ankiCards ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + topic_id INTERGER NOT NULL, + question TEXT NOT NULL, + answer TEXT NOT NULL, + FOREIGN KEY(topic_id) REFERENCES ankiTopics(id) + ); + """, + //Ask Me //Files from Ask Me from which questions are being generated "askme_files": """ @@ -143,7 +164,6 @@ const schemas = { avgScore INTEGER NOT NULL ); """, - //AskMe Scores "askme_scores": """ CREATE TABLE IF NOT EXISTS askme_scores ( diff --git a/lib/tools/anki/anki.dart b/lib/tools/anki/anki.dart new file mode 100644 index 0000000..44fd4d1 --- /dev/null +++ b/lib/tools/anki/anki.dart @@ -0,0 +1 @@ +export 'pages/anki_home.dart'; diff --git a/lib/tools/anki/controllers/ankicard_controller.dart b/lib/tools/anki/controllers/ankicard_controller.dart new file mode 100644 index 0000000..2efe9b5 --- /dev/null +++ b/lib/tools/anki/controllers/ankicard_controller.dart @@ -0,0 +1,52 @@ +import 'package:academia/exports/barrel.dart'; +import 'package:academia/tools/anki/models/models.dart'; +import 'package:get/get.dart'; + +class AnkiCardController extends GetxController { + AnkiCardController({ + required this.topicId, + }); + + final RxList allCards = [].obs; + final int topicId; + + @override + void onInit() { + super.onInit(); + getAllTopicCards().then((value) { + debugPrint("[+] Anki Topic Cards Loaded"); + }); + } + + // returns the number of topics + int numAnkiCards() { + return allCards.length; + } + + Future addAnkiCard(AnkiCard ankiCard) async { + int value = await AnkiCardModelHelper().create(ankiCard.toJson()); + return value == 0 ? false : true; + } + + Future updateAnkiCard(AnkiCard ankiCard) async { + int value = await AnkiCardModelHelper().update(ankiCard.toJson()); + return value == 0 ? false : true; + } + + Future> getAllTopicCards() async { + AnkiCardModelHelper().queryAnkiCardsByTopic(topicId).then((values) { + allCards.clear(); + values = values.reversed.toList(); + for (final val in values) { + allCards.add(AnkiCard.fromJson(val)); + } + }); + return allCards; + } + + Future deleteCard(AnkiCard ankiCard) async { + int value = await AnkiCardModelHelper().delete(ankiCard.toJson()); + getAllTopicCards(); + return value == 0 ? false : true; + } +} diff --git a/lib/tools/anki/controllers/answer_controller.dart b/lib/tools/anki/controllers/answer_controller.dart new file mode 100644 index 0000000..d905c5b --- /dev/null +++ b/lib/tools/anki/controllers/answer_controller.dart @@ -0,0 +1,7 @@ +// import 'package:academia/exports/barrel.dart'; +import 'package:get/get.dart'; + +class AnswerCardController extends GetxController { + var ansCard = "".obs; + var ansSwitch = true.obs; +} diff --git a/lib/tools/anki/controllers/controllers.dart b/lib/tools/anki/controllers/controllers.dart new file mode 100644 index 0000000..f6acfdc --- /dev/null +++ b/lib/tools/anki/controllers/controllers.dart @@ -0,0 +1,3 @@ +export 'answer_controller.dart'; +export 'topic_controller.dart'; +export 'ankicard_controller.dart'; diff --git a/lib/tools/anki/controllers/topic_controller.dart b/lib/tools/anki/controllers/topic_controller.dart new file mode 100644 index 0000000..2f7ee5d --- /dev/null +++ b/lib/tools/anki/controllers/topic_controller.dart @@ -0,0 +1,96 @@ +import 'package:academia/exports/barrel.dart'; +import 'package:academia/tools/anki/models/models.dart'; +import 'package:get/get.dart'; + +class TopicController extends GetxController { + final RxList allTopics = [].obs; + final RxList allFavourites = [].obs; + + @override + void onInit() { + super.onInit(); + getAllTopics().then((value) { + debugPrint("[+] Topics Loaded"); + }); + getAllFavourites().then((value) { + debugPrint("[+] Favourite Topics Loaded"); + }); + } + + // returns the number of topics + int numTopics() { + return allTopics.length; + } + + Future addTopic(AnkiTopic topic) async { + int value = await TopicModelHelper().create(topic.toJson()); + return value == 0 ? false : true; + } + + // Marking topic as favourite and vice versa + Future favouriteTopic(AnkiTopic topic) async { + topic.isFavourite = !topic.isFavourite; + // update topic list + if (allFavourites.length >= 5) { + updateFavourites(); + } + int value = await TopicModelHelper().update(topic.toJson()); + return value == 0 ? false : true; + } + + // updating favourite lists by popping one + void updateFavourites() async { + // pops the last favourited topic when more than five + AnkiTopic removedTopic = allFavourites.removeAt(0); + removedTopic.isFavourite = false; + // update the topic in db + await updateTopic(removedTopic); + } + + // increments topics number of cards by one + Future incTopicNumCards(AnkiTopic topic) async { + topic.numCards++; + int value = await TopicModelHelper().update(topic.toJson()); + return value == 0 ? false : true; + } + + // decrements topics number of cards by one + Future descTopicNumCards(AnkiTopic topic) async { + topic.numCards--; + int value = await TopicModelHelper().update(topic.toJson()); + return value == 0 ? false : true; + } + + Future updateTopic(AnkiTopic topic) async { + int value = await TopicModelHelper().update(topic.toJson()); + return value == 0 ? false : true; + } + + Future> getAllFavourites() async { + TopicModelHelper().getFavourites().then((values) { + allFavourites.clear(); + values = values.reversed.toList(); + for (final val in values) { + allFavourites.add(AnkiTopic.fromJson(val)); + } + }); + return allFavourites.reversed.toList(); + } + + Future> getAllTopics() async { + TopicModelHelper().queryAll().then((values) { + allTopics.clear(); + values = values.reversed.toList(); + for (final val in values) { + allTopics.add(AnkiTopic.fromJson(val)); + } + }); + return allTopics; + } + + Future deleteTopic(AnkiTopic topic) async { + int value = await TopicModelHelper().delete(topic.toJson()); + getAllTopics(); + return value == 0 ? false : true; + } +} diff --git a/lib/tools/anki/models/ankicard_model.dart b/lib/tools/anki/models/ankicard_model.dart new file mode 100644 index 0000000..de2613a --- /dev/null +++ b/lib/tools/anki/models/ankicard_model.dart @@ -0,0 +1,28 @@ +// ankiTCard model + +class AnkiCard { + int? id; + int topicId; + String question; + String answer; + + AnkiCard({ + this.id, + required this.topicId, + required this.question, + required this.answer, + }); + + AnkiCard.fromJson(Map json) + : id = json['id'], + topicId = json['topic_id'], + question = json['question'], + answer = json['answer']; + + Map toJson() => { + 'id': id, + 'topic_id': topicId, + 'question': question, + 'answer': answer, + }; +} diff --git a/lib/tools/anki/models/ankicard_model_helper.dart b/lib/tools/anki/models/ankicard_model_helper.dart new file mode 100644 index 0000000..f8ac4a8 --- /dev/null +++ b/lib/tools/anki/models/ankicard_model_helper.dart @@ -0,0 +1,63 @@ +import 'package:academia/storage/storage.dart'; +import 'package:academia/exports/barrel.dart'; +import 'package:sqflite/sqflite.dart'; + +class AnkiCardModelHelper implements DatabaseOperations { + static final AnkiCardModelHelper _instance = AnkiCardModelHelper._internal(); + + factory AnkiCardModelHelper() { + return _instance; + } + + AnkiCardModelHelper._internal(); + + @override + Future create(Map data) async { + final db = await DatabaseHelper().database; + final id = await db.insert( + 'ankiCards', + data, + conflictAlgorithm: ConflictAlgorithm.replace, + ); + + debugPrint("[+] Anki Card Created Succcessfuly"); + return id; + } + + @override + Future>> queryAll() async { + final db = await DatabaseHelper().database; + final ankiCards = await db.query('ankiCards'); + return ankiCards; + } + + @override + Future delete(Map data) async { + final db = await DatabaseHelper().database; + return await db + .delete('ankiCards', where: 'id = ?', whereArgs: [data["id"]]); + } + + @override + Future update(Map data) async { + final db = await DatabaseHelper().database; + return await db + .update('ankiCards', data, where: 'id = ?', whereArgs: [data['id']]); + } + + Future>> queryAnkiCardsByTopic(int topicId) async { + final db = await DatabaseHelper().database; + return await db.query( + 'ankiCards', + where: 'topic_id = ?', + whereArgs: [topicId], + ); + } + + // make sure to call in the logout function + @override + Future truncate() async { + final db = await DatabaseHelper().database; + await db.execute('DELETE FROM ankiCards'); + } +} diff --git a/lib/tools/anki/models/models.dart b/lib/tools/anki/models/models.dart new file mode 100644 index 0000000..6e88551 --- /dev/null +++ b/lib/tools/anki/models/models.dart @@ -0,0 +1,4 @@ +export 'topic_model.dart'; +export 'topic_model_helper.dart'; +export 'ankicard_model.dart'; +export 'ankicard_model_helper.dart'; diff --git a/lib/tools/anki/models/topic_model.dart b/lib/tools/anki/models/topic_model.dart new file mode 100644 index 0000000..df5a037 --- /dev/null +++ b/lib/tools/anki/models/topic_model.dart @@ -0,0 +1,32 @@ +// ankiTopic model + +class AnkiTopic { + int? id; + String name; + String desc; + bool isFavourite; + int numCards; + + AnkiTopic({ + this.id, + required this.name, + required this.desc, + this.isFavourite = false, + this.numCards = 0, + }); + + AnkiTopic.fromJson(Map json) + : id = json['id'], + name = json['name'], + desc = json['desc'], + isFavourite = json['is_favourite'] == 0 ? false : true, + numCards = json['num_cards']; + + Map toJson() => { + 'id': id, + 'name': name, + 'desc': desc, + 'is_favourite': isFavourite ? 1 : 0, + 'num_cards': numCards, + }; +} diff --git a/lib/tools/anki/models/topic_model_helper.dart b/lib/tools/anki/models/topic_model_helper.dart new file mode 100644 index 0000000..1698f3d --- /dev/null +++ b/lib/tools/anki/models/topic_model_helper.dart @@ -0,0 +1,63 @@ +import 'package:academia/storage/storage.dart'; +import 'package:academia/exports/barrel.dart'; +import 'package:sqflite/sqflite.dart'; + +class TopicModelHelper implements DatabaseOperations { + static final TopicModelHelper _instance = TopicModelHelper._internal(); + + factory TopicModelHelper() { + return _instance; + } + + TopicModelHelper._internal(); + + @override + Future create(Map data) async { + final db = await DatabaseHelper().database; + final id = await db.insert( + 'ankiTopics', + data, + conflictAlgorithm: ConflictAlgorithm.replace, + ); + + debugPrint("[+] Anki Topic Created Succcessfuly"); + return id; + } + + @override + Future>> queryAll() async { + final db = await DatabaseHelper().database; + final todos = await db.query('ankiTopics'); + return todos; + } + + @override + Future delete(Map data) async { + final db = await DatabaseHelper().database; + return await db + .delete('ankiTopics', where: 'id = ?', whereArgs: [data["id"]]); + } + + @override + Future update(Map data) async { + final db = await DatabaseHelper().database; + return await db + .update('ankiTopics', data, where: 'id = ?', whereArgs: [data['id']]); + } + + Future>> getFavourites() async { + final db = await DatabaseHelper().database; + return await db.query( + 'ankiTopics', + where: 'is_favourite = ?', + whereArgs: [1], + ); + } + + // make sure to call in the logout function + @override + Future truncate() async { + final db = await DatabaseHelper().database; + await db.execute('DELETE FROM ankiTopics'); + } +} diff --git a/lib/tools/anki/pages/anki_home.dart b/lib/tools/anki/pages/anki_home.dart new file mode 100644 index 0000000..eec317e --- /dev/null +++ b/lib/tools/anki/pages/anki_home.dart @@ -0,0 +1,313 @@ +import 'package:academia/exports/barrel.dart'; +import 'package:academia/tools/anki/models/models.dart'; +import 'package:academia/tools/anki/widgets/widgets.dart'; +import 'package:academia/tools/anki/controllers/controllers.dart'; +import 'package:get/get.dart'; +import 'package:lottie/lottie.dart'; + +class AnkiHomePage extends StatelessWidget { + const AnkiHomePage({super.key}); + + @override + Widget build(BuildContext context) { + // topic controller + final TopicController topicController = Get.put(TopicController()); + + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Obx( + () => topicController.allTopics.isEmpty + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Lottie.asset( + "assets/lotties/study.json", + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "Your anki space is a clean slate! 📝 Create your first topic and let Academia Anki help you ace those grades!", + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center, + ), + ), + CreateTopicWidget( + topicController: topicController, + ) + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.all(16.0), + child: Text( + "Starred Topics", + style: TextStyle( + fontWeight: FontWeight.bold, fontSize: 15), + ), + ), + topicController.allFavourites.isEmpty + ? SizedBox( + height: MediaQuery.of(context).size.height * 0.27, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: + MediaQuery.of(context).size.height * 0.18, + child: Lottie.asset( + "assets/lotties/empty.json", + fit: BoxFit.fitHeight, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "You Have No Favourite Topics, Favourite A Topic To Find It Easily", + style: + Theme.of(context).textTheme.titleSmall, + textAlign: TextAlign.center, + ), + ), + ], + ), + ) + : Align( + alignment: Alignment.center, + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.27, + width: MediaQuery.of(context).size.width * 0.87, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemBuilder: (context, idx) { + return StarredTopics( + idx: topicController.allFavourites[idx].id!, + topic: + topicController.allFavourites[idx].name, + desc: + topicController.allFavourites[idx].desc, + topicController: topicController, + ); + }, + itemCount: topicController.allFavourites.length, + ), + ), + ), + Align( + alignment: Alignment.topRight, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: CreateTopicWidget( + topicController: topicController, + ), + ), + ), + // displays topicics + // displayed in a grid view if they are more than five + topicController.allTopics.length <= 5 + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + height: + MediaQuery.of(context).size.height * 0.35, + child: Lottie.asset( + "assets/lotties/empty.json", + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "Once you have more than five topics, they’ll pop into a cool grid view! Don’t forget to star your favorites for quick access—but heads up, you can only star five at a time. If you add a new one, the oldest star gets bumped! ⭐", + style: Theme.of(context).textTheme.titleSmall, + textAlign: TextAlign.center, + ), + ), + ], + ) + : Container( + height: MediaQuery.of(context).size.height * 0.52, + decoration: BoxDecoration( + color: lightColorScheme.primaryContainer, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(28), + topRight: Radius.circular(28), + ), + ), + child: Padding( + padding: const EdgeInsets.all(22.0), + child: GridView.builder( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + ), + itemBuilder: (context, idx) { + return GridViewTopic( + idx: idx, + topicId: topicController.allTopics[idx].id!, + topic: topicController.allTopics[idx].name, + topicDesc: + topicController.allTopics[idx].desc, + isFavourite: topicController + .allTopics[idx].isFavourite, + controller: topicController, + ); + }, + itemCount: topicController.allTopics.length, + scrollDirection: Axis.vertical, + ), + ), + ), + ], + ), + ), + ), + ); + } +} + +// widget for creating a topic +class CreateTopicWidget extends StatelessWidget { + const CreateTopicWidget({ + super.key, + required this.topicController, + }); + + final TopicController topicController; + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (context) { + final TextEditingController titleController = + TextEditingController(); + final TextEditingController descController = + TextEditingController(); + return AlertDialog( + title: const Text("Create Topic"), + content: SizedBox( + height: MediaQuery.of(context).size.height * 0.4, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text("Topic"), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 0, + vertical: 8.0, + ), + child: TextField( + controller: titleController, + maxLength: 10, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ), + const Text("Topic Description"), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 0, + vertical: 8.0, + ), + child: TextField( + controller: descController, + maxLength: 54, + maxLines: 2, + decoration: InputDecoration( + hintText: + "This Contains Flash Cards For [Topic Name]", + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ) + ], + ), + ), + actions: [ + ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + style: ElevatedButton.styleFrom( + foregroundColor: lightColorScheme.error), + child: const Text("Cancel"), + ), + ElevatedButton( + onPressed: () { + if (titleController.text.trim().isEmpty || + descController.text.trim().isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("All Above Fields are required"), + duration: Duration(seconds: 1), + ), + ); + } else if (titleController.text.trim().length <= 3) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + "Topic Length should be more than 2 characters"), + duration: Duration(seconds: 1), + ), + ); + } else if (descController.text.trim().length <= 12) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + "Desc Length should be more than 11 characters"), + duration: Duration(seconds: 1), + ), + ); + } else { + // create topic + AnkiTopic ankiTopic = AnkiTopic( + name: titleController.text, + desc: descController.text, + ); + // add to database + if (topicController.numTopics() <= 5) { + ankiTopic.isFavourite = true; + } + topicController.addTopic(ankiTopic); + + if (topicController.numTopics() < 5) { + // updating favourites + topicController.getAllFavourites(); + } + // updating topic list and favorites + topicController.getAllTopics(); + topicController.getAllFavourites(); + Navigator.of(context).pop(); + } + }, + child: const Text("create"), + ), + ], + ); + }, + ); + }, + style: ElevatedButton.styleFrom( + elevation: 0, + ), + child: const Text( + "Create Topic", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ); + } +} diff --git a/lib/tools/anki/pages/anki_swap.dart b/lib/tools/anki/pages/anki_swap.dart new file mode 100644 index 0000000..ddb6361 --- /dev/null +++ b/lib/tools/anki/pages/anki_swap.dart @@ -0,0 +1,165 @@ +import 'package:academia/tools/anki/models/ankicard_model.dart'; +import 'package:appinio_swiper/appinio_swiper.dart'; +import 'package:flutter/material.dart'; + +class AnkiSwap extends StatefulWidget { + const AnkiSwap({ + super.key, + required this.ankiCards, + }); + + final List ankiCards; + + @override + State createState() => _AnkiSwapState(); +} + +class _AnkiSwapState extends State { + // holds the mutable list + List cards = []; + bool showAnswer = false; + final AppinioSwiperController controller = AppinioSwiperController(); + + @override + void initState() { + cards.clear(); + cards.addAll(widget.ankiCards); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: true, + title: const Text( + "Academia Anki", + ), + centerTitle: true, + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text("Double Tap To Show Answer"), + Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.5, + child: AppinioSwiper( + backgroundCardOffset: Offset.zero, + backgroundCardCount: 0, + allowUnSwipe: false, + onSwipeBegin: (previousIndex, targetIndex, activity) { + setState(() { + showAnswer = false; + }); + if (activity.direction == AxisDirection.left) { + cards.insert(targetIndex + 2, cards[previousIndex]); + } else { + cards.add(cards[previousIndex]); + } + }, + maxAngle: 60, + swipeOptions: const SwipeOptions.symmetric(horizontal: true), + cardCount: cards.length, + cardBuilder: (context, index) { + var colorDet = index % 4; + return GestureDetector( + onDoubleTap: () { + setState(() { + showAnswer = true; + }); + }, + child: Container( + height: MediaQuery.of(context).size.height * 0.45, + decoration: BoxDecoration( + color: colorDet == 0 + ? const Color(0xff8999aa) + : colorDet == 1 + ? const Color(0xffffcdfe) + : colorDet == 2 + ? const Color(0xffffe7cd) + : const Color(0xffcdffce), + borderRadius: const BorderRadius.all( + Radius.circular(12), + ), + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + const Text( + "Question", + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + ), + ), + Text( + cards[index].question, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 18, + ), + ), + const Text( + "Answer", + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + ), + ), + showAnswer + ? Text( + cards[index].answer, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 18, + ), + ) + : const SizedBox(), + ], + ), + ), + ), + ); + }, + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Row( + children: [ + Image.asset( + "assets/images/left_arrow.png", + height: 70, + ), + const Padding( + padding: EdgeInsets.all(8.0), + child: Text("If Good"), + ), + ], + ), + Row( + children: [ + const Padding( + padding: EdgeInsets.all(8.0), + child: Text("If Bad"), + ), + Image.asset( + "assets/images/right_arrow.png", + height: 70, + ), + ], + ), + ], + ) + ], + ), + ), + ); + } +} diff --git a/lib/tools/anki/pages/create_ankicard.dart b/lib/tools/anki/pages/create_ankicard.dart new file mode 100644 index 0000000..9b96b14 --- /dev/null +++ b/lib/tools/anki/pages/create_ankicard.dart @@ -0,0 +1,181 @@ +import 'package:academia/tools/anki/controllers/controllers.dart'; +import 'package:academia/tools/anki/models/models.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class CreateAnkicard extends StatelessWidget { + const CreateAnkicard({super.key}); + + @override + Widget build(BuildContext context) { + TextEditingController cardInfo = TextEditingController(); + TextEditingController cardAns = TextEditingController(); + AnswerCardController ansCardController = Get.put(AnswerCardController()); + AnkiCardController ankiCardController = Get.find(); + + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: true, + title: const Text("Create Card"), + centerTitle: true, + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + controller: cardInfo, + maxLines: 5, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onTapOutside: (event) { + ansCardController.ansCard.value = + cardInfo.selection.textInside(cardInfo.text); + cardAns.text = ansCardController.ansCard.value; + }, + ), + ), + Obx( + () => Padding( + padding: const EdgeInsets.all(8.0), + child: ansCardController.ansSwitch.value + ? const Text( + "Selected Answer", + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ) + : const Text( + "Type Answer", + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Obx( + () => ansCardController.ansSwitch.value + // TextField for Showing Highlighted Answer + ? Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + enabled: false, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + hintText: ansCardController.ansCard.isEmpty + ? "Your Answer To be Hidden" + : ansCardController.ansCard.value, + ), + ), + ) + // TextField For Writing An Answer + : Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + controller: cardAns, + maxLines: 3, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + hintText: "Your Answer To be Hidden", + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 12.0), + child: Center( + child: ElevatedButton( + onPressed: () { + ansCardController.ansSwitch.value = + !ansCardController.ansSwitch.value; + // clearing the text fields + cardInfo.clear(); + cardAns.clear(); + ansCardController.ansCard.value = ""; + }, + child: Obx( + () => Text( + "Switch To ${ansCardController.ansSwitch.value ? "Writing" : "Highlight"}", + ), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 12.0), + child: Center( + child: ElevatedButton( + onPressed: () async { + // check for user inputs + if (cardInfo.text.trim().isEmpty || + cardAns.text.trim().isEmpty) { + // tell user that all fields are required + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("All Fields Are Required"), + duration: Duration(seconds: 1), + ), + ); + } + // user chose to write the answer + else if (!ansCardController.ansSwitch.value) { + AnkiCard ankiCard = AnkiCard( + topicId: ankiCardController.topicId, + question: cardInfo.text.trim(), + answer: cardAns.text.trim()); + await ankiCardController.addAnkiCard(ankiCard); + // clearing the text fields + cardInfo.clear(); + cardAns.clear(); + ansCardController.ansCard.value = ""; + // reload the anki cards + await ankiCardController.getAllTopicCards(); + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("AnkiCard Created Successfully"), + duration: Duration(seconds: 1), + ), + ); + } else { + // remove highlighted answer from the question + String question = cardInfo.text.trim(); + question = question.replaceAll(cardAns.text.trim(), "_"); + AnkiCard ankiCard = AnkiCard( + topicId: ankiCardController.topicId, + question: question, + answer: cardAns.text.trim()); + await ankiCardController.addAnkiCard(ankiCard); + // clearing the text fields + cardInfo.clear(); + cardAns.clear(); + ansCardController.ansCard.value = ""; + // reload the anki cards + await ankiCardController.getAllTopicCards(); + + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("AnkiCard Created Successfully"), + duration: Duration(seconds: 1), + ), + ); + } + }, + child: const Text("Create Card"), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/tools/anki/pages/edit_anki_card.dart b/lib/tools/anki/pages/edit_anki_card.dart new file mode 100644 index 0000000..e573813 --- /dev/null +++ b/lib/tools/anki/pages/edit_anki_card.dart @@ -0,0 +1,233 @@ +import 'package:academia/exports/barrel.dart'; +import 'package:academia/tools/anki/controllers/controllers.dart'; +import 'package:academia/tools/anki/models/ankicard_model.dart'; +import 'package:academia/tools/anki/widgets/widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class EditAnkiCard extends StatefulWidget { + const EditAnkiCard({ + super.key, + required this.ankiCard, + }); + + final AnkiCard ankiCard; + + @override + State createState() => _EditAnkiCardState(); +} + +class _EditAnkiCardState extends State { + bool editQuestion = false; + bool editAnswer = false; + // true when the user is highlighting answer otherwise false + bool switchToWriting = false; + bool showAnswer = false; + late TextEditingController questionController; + late TextEditingController ansController; + late AnkiCardController ankiCardController; + + @override + void initState() { + // intializing controllers + ankiCardController = Get.find(); + questionController = TextEditingController(); + ansController = TextEditingController(); + // setting the correct value for switchToWriting + if (widget.ankiCard.question.contains("_")) { + switchToWriting = true; + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + "Edit Anki Card", + style: Theme.of(context).textTheme.headlineMedium, + ), + automaticallyImplyLeading: true, + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextButton( + onPressed: () { + setState(() { + switchToWriting = !switchToWriting; + // setting all enable edits to false + editAnswer = false; + editQuestion = false; + }); + }, + child: Text( + "Switch to ${switchToWriting ? "Writing" : "Highlighting"}", + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "Question", + style: Theme.of(context).textTheme.titleMedium, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width * 0.7, + child: TextField( + controller: questionController, + enabled: editQuestion, + maxLines: 5, + decoration: InputDecoration( + hintText: widget.ankiCard.question, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onTapOutside: (event) { + // checks whether user has highlighted new answers + if (switchToWriting && + questionController.selection + .textInside(questionController.text) + .trim() + .isNotEmpty) { + setState(() { + // remove previous _ + widget.ankiCard.question = widget.ankiCard.question + .replaceAll("_", widget.ankiCard.answer); + // changing answer + widget.ankiCard.answer = questionController.selection + .textInside(questionController.text); + // changing question + widget.ankiCard.question = widget.ankiCard.question + .replaceAll(widget.ankiCard.answer, "_"); + }); + } + }, + ), + ), + TextButton( + onPressed: () { + setState(() { + editQuestion = true; + // setting the text + if (widget.ankiCard.question.contains("_")) { + // answer was highlited + String newQuiz = widget.ankiCard.question + .replaceAll("_", widget.ankiCard.answer); + questionController.text = newQuiz; + } else { + questionController.text = widget.ankiCard.question; + } + }); + }, + child: const Text("Edit")) + ], + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "Answer", + style: Theme.of(context).textTheme.titleMedium, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: !switchToWriting + ? Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width * 0.7, + child: TextField( + controller: ansController, + enabled: editAnswer, + decoration: InputDecoration( + hintText: widget.ankiCard.answer, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onTapOutside: (event) { + // user has typed new answer + if (ansController.text.trim().isNotEmpty) { + setState(() { + widget.ankiCard.answer = ansController.text; + }); + } + }, + ), + ), + TextButton( + onPressed: () { + setState(() { + editAnswer = true; + }); + }, + child: const Text("Edit")) + ], + ) + : TextField( + enabled: false, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + hintText: widget.ankiCard.answer, + ), + ), + ), + Align( + alignment: Alignment.center, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + onPressed: () async { + // updating anki card + await ankiCardController.updateAnkiCard(widget.ankiCard); + await ankiCardController.getAllTopicCards(); + if (!mounted) return; + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Card Successfully editted"), + duration: Duration(seconds: 1), + ), + ); + }, + child: const Text("Update"), + ), + ), + ), + ), + Align( + alignment: Alignment.center, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (context) { + return PreviewAnkiCard(ankiCard: widget.ankiCard); + }, + ); + }, + child: const Text("Preview"), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/tools/anki/pages/flashcards.dart b/lib/tools/anki/pages/flashcards.dart new file mode 100644 index 0000000..f27cb36 --- /dev/null +++ b/lib/tools/anki/pages/flashcards.dart @@ -0,0 +1,311 @@ +import 'package:academia/exports/barrel.dart'; +import 'package:academia/tools/anki/pages/anki_swap.dart'; +import 'package:academia/tools/anki/pages/create_ankicard.dart'; +import 'package:academia/tools/anki/widgets/widgets.dart'; +import 'package:academia/tools/anki/controllers/controllers.dart'; +import 'package:get/get.dart'; +import 'package:lottie/lottie.dart'; + +class TopicFlashCards extends StatelessWidget { + const TopicFlashCards({ + super.key, + required this.topicId, + }); + + final int topicId; + + @override + Widget build(BuildContext context) { + // adding AnkiCardController + AnkiCardController ankiCardController = Get.put( + AnkiCardController( + topicId: topicId, + ), + ); + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + floatingActionButton: SizedBox( + height: MediaQuery.of(context).size.height * 0.17, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + // add card button + FloatingActionButton( + heroTag: "btn1", + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const CreateAnkicard(), + ), + ); + }, + child: const Icon(Icons.add), + ), + const Spacer(), + // play cards button + FloatingActionButton( + heroTag: "btn2", + onPressed: () { + if (ankiCardController.allCards.length >= 5) { + // opens the anki swap page + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + AnkiSwap(ankiCards: ankiCardController.allCards), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text("Anki Cards Should be More than 4, To Play"), + duration: Duration(seconds: 1), + ), + ); + } + }, + child: const Icon(Icons.play_arrow), + ), + ], + ), + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: MediaQuery.of(context).size.height * 0.26, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.24, + width: MediaQuery.of(context).size.width * 0.37, + child: Stack( + children: [ + Container( + padding: const EdgeInsets.all(0), + height: MediaQuery.of(context).size.height * 0.12, + width: MediaQuery.of(context).size.width * 0.37, + decoration: const BoxDecoration( + color: Color(0xffe5ffcd), + borderRadius: BorderRadius.all( + Radius.circular(20), + ), + ), + child: const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only( + left: 8, + top: 8, + ), + child: Text( + "Number Of", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + Padding( + padding: EdgeInsets.only( + left: 8, + ), + child: Text( + "Cards", + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + Positioned( + bottom: 30, + child: Container( + padding: const EdgeInsets.all(0), + height: + MediaQuery.of(context).size.height * 0.09, + width: MediaQuery.of(context).size.width * 0.37, + decoration: const BoxDecoration( + color: Color(0xffe5ffcd), + borderRadius: BorderRadius.all( + Radius.circular(20), + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Obx( + () => Text( + "${ankiCardController.allCards.length}", + style: const TextStyle( + fontSize: 20, + ), + ), + ), + )), + ), + ], + ), + ), + ), + const Spacer(), + Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.24, + width: MediaQuery.of(context).size.width * 0.54, + child: Stack( + children: [ + Container( + padding: const EdgeInsets.all(0), + height: MediaQuery.of(context).size.height * 0.12, + width: MediaQuery.of(context).size.width * 0.54, + decoration: const BoxDecoration( + color: Color(0xffffe7cd), + borderRadius: BorderRadius.all( + Radius.circular(21), + ), + ), + child: const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only( + left: 8, + top: 8, + ), + child: Text( + "Good", + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + Padding( + padding: EdgeInsets.only( + left: 8, + ), + child: Text( + "Frequencies", + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + Positioned( + bottom: 30, + child: Container( + padding: const EdgeInsets.all(0), + height: MediaQuery.of(context).size.height * 0.09, + width: MediaQuery.of(context).size.width * 0.54, + decoration: const BoxDecoration( + color: Color(0xffffe7cd), + borderRadius: BorderRadius.all( + Radius.circular(21), + ), + ), + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Text( + "1000", + style: TextStyle( + fontSize: 20, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + const Padding( + padding: EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 0.0, + ), + child: Text( + "All Flash Cards", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ), + Obx( + () => ankiCardController.allCards.isEmpty + ? Align( + alignment: Alignment.center, + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.64, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: MediaQuery.of(context).size.height * 0.4, + child: Lottie.asset( + "assets/lotties/study.json", + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "Your card deck is blank! 🎴", + style: Theme.of(context).textTheme.titleSmall, + textAlign: TextAlign.center, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "Add your first card to start", + style: Theme.of(context).textTheme.titleSmall, + textAlign: TextAlign.center, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "mastering your topics!", + style: Theme.of(context).textTheme.titleSmall, + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + ) + : SizedBox( + height: MediaQuery.of(context).size.height * 0.64, + child: ListView.builder( + itemBuilder: (context, idx) { + return FlashCardTile( + ankiCard: ankiCardController.allCards[idx], + ); + }, + itemCount: ankiCardController.allCards.length, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/tools/anki/widgets/eclipses.dart b/lib/tools/anki/widgets/eclipses.dart new file mode 100644 index 0000000..7fb4856 --- /dev/null +++ b/lib/tools/anki/widgets/eclipses.dart @@ -0,0 +1,87 @@ +import 'package:academia/exports/barrel.dart'; + +Widget blackcircle = Padding( + padding: const EdgeInsets.all(1.0), + child: Container( + width: 15, + height: 15, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Color(0xff444c55), + ), + ), +); + +Widget bluecircle = Padding( + padding: const EdgeInsets.all(1.0), + child: Container( + width: 15, + height: 15, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: lightColorScheme.primaryContainer, + ), + ), +); + +Widget redcircle = Padding( + padding: const EdgeInsets.all(1.0), + child: Container( + width: 15, + height: 15, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Color(0xffffcde5), + ), + ), +); + +Widget greencircle = Padding( + padding: const EdgeInsets.all(1.0), + child: Container( + width: 15, + height: 15, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Color(0xffe5ffcd), + ), + ), +); + +class CustomEclipse extends StatelessWidget { + const CustomEclipse({super.key}); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: MediaQuery.of(context).size.width * 0.2, + height: MediaQuery.of(context).size.height * 0.05, + child: Row( + children: [ + blackcircle, + blackcircle, + blackcircle, + ], + ), + ); + } +} + +class CustomMixEclipse extends StatelessWidget { + const CustomMixEclipse({super.key}); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: MediaQuery.of(context).size.width * 0.2, + height: MediaQuery.of(context).size.height * 0.05, + child: Row( + children: [ + bluecircle, + redcircle, + greencircle, + ], + ), + ); + } +} diff --git a/lib/tools/anki/widgets/grid_topics.dart b/lib/tools/anki/widgets/grid_topics.dart new file mode 100644 index 0000000..28b595d --- /dev/null +++ b/lib/tools/anki/widgets/grid_topics.dart @@ -0,0 +1,170 @@ +import 'package:academia/exports/barrel.dart'; +import 'package:academia/tools/anki/controllers/controllers.dart'; +import 'package:academia/tools/anki/models/models.dart'; +import 'package:academia/tools/anki/pages/flashcards.dart'; + +class GridViewTopic extends StatelessWidget { + const GridViewTopic({ + super.key, + required this.idx, + required this.topicId, + required this.topic, + required this.topicDesc, + required this.isFavourite, + this.controller, + }); + + final int idx; + final int topicId; + final String topic; + final String topicDesc; + final bool isFavourite; + final TopicController? controller; + + @override + Widget build(BuildContext context) { + var colorDet = idx % 4; + return GestureDetector( + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => TopicFlashCards( + topicId: idx, + ), + ), + ), + child: Container( + decoration: BoxDecoration( + color: colorDet == 0 + ? const Color(0xff8999aa) + : colorDet == 1 + ? const Color(0xffffcdfe) + : colorDet == 2 + ? const Color(0xffffe7cd) + : const Color(0xffcdffce), + borderRadius: const BorderRadius.all( + Radius.circular(12), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + topic, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + topicDesc, + ), + ), + const Spacer(), + Align( + alignment: Alignment.bottomRight, + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.05, + width: MediaQuery.of(context).size.width * 0.2, + child: Row( + children: [ + // favourite a topic + GestureDetector( + onTap: () async { + // create a topic object + AnkiTopic topic = AnkiTopic( + id: idx, + name: this.topic, + desc: topicDesc, + isFavourite: isFavourite, + ); + await controller?.favouriteTopic(topic); + // update favourites and all topics + await controller?.getAllFavourites(); + await controller?.getAllTopics(); + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: !isFavourite + ? const Text("Topic Successfully Favourited") + : const Text("Topic Successfully Unfavourited"), + duration: const Duration(seconds: 1), + ), + ); + }, + child: Icon( + isFavourite ? Icons.star : Icons.star_border_outlined, + size: MediaQuery.of(context).size.height * 0.035, + ), + ), + // delete for topics + GestureDetector( + onTap: () => showDialog( + context: context, + builder: (context) => AlertDialog( + content: const Text( + "Are You Sure You Want To Delete Topic?", + ), + actions: [ + TextButton( + style: ButtonStyle( + foregroundColor: WidgetStatePropertyAll( + lightColorScheme.error, + ), + ), + onPressed: () async { + AnkiTopic topic = AnkiTopic( + id: idx, + name: this.topic, + desc: topicDesc, + ); + bool? deleted = + await controller?.deleteTopic(topic); + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: (deleted != null && deleted) + ? const Text( + "Topic Successfully Deleted") + : const Text( + "Something happened! Kindly Retry!!"), + duration: const Duration(seconds: 1), + ), + ); + // update favourites and all topics + await controller?.getAllFavourites(); + await controller?.getAllTopics(); + // ignore: use_build_context_synchronously + Navigator.of(context).pop(); + }, + child: const Text("Yes"), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text("No"), + ), + ], + ), + ), + child: Icon( + Icons.delete, + size: MediaQuery.of(context).size.height * 0.035, + ), + ), + ], + ), + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/tools/anki/widgets/preview_card.dart b/lib/tools/anki/widgets/preview_card.dart new file mode 100644 index 0000000..bda4db3 --- /dev/null +++ b/lib/tools/anki/widgets/preview_card.dart @@ -0,0 +1,69 @@ +import 'package:academia/exports/barrel.dart'; +import 'package:academia/tools/anki/models/models.dart'; + +class PreviewAnkiCard extends StatefulWidget { + const PreviewAnkiCard({ + super.key, + required this.ankiCard, + }); + + final AnkiCard ankiCard; + + @override + State createState() => _PreviewAnkiCardState(); +} + +class _PreviewAnkiCardState extends State { + bool showAnswer = false; + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text( + "Anki Card Preview", + ), + content: IntrinsicHeight( + child: Column( + children: [ + const Padding( + padding: EdgeInsets.all(8.0), + child: Text( + "Question", + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + widget.ankiCard.question, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: TextButton( + onPressed: () { + setState(() { + showAnswer = !showAnswer; + }); + }, + child: showAnswer + ? const Text( + "hide answer", + ) + : const Text( + "show answer", + ), + ), + ), + showAnswer + ? Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + widget.ankiCard.answer, + ), + ) + : const SizedBox(), + ], + ), + ), + ); + } +} diff --git a/lib/tools/anki/widgets/starred_topic.dart b/lib/tools/anki/widgets/starred_topic.dart new file mode 100644 index 0000000..882434d --- /dev/null +++ b/lib/tools/anki/widgets/starred_topic.dart @@ -0,0 +1,198 @@ +import 'package:academia/exports/barrel.dart'; +import 'package:academia/tools/anki/controllers/controllers.dart'; +import 'package:academia/tools/anki/pages/flashcards.dart'; +import 'package:academia/tools/anki/widgets/widgets.dart'; +import '../models/models.dart'; + +class StarredTopics extends StatelessWidget { + const StarredTopics({ + super.key, + required this.idx, + required this.topic, + required this.desc, + this.topicController, + }); + + final int idx; + final String topic; + final String desc; + final TopicController? topicController; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => TopicFlashCards( + topicId: idx, + ), + ), + ), + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.27, + width: MediaQuery.of(context).size.width * 0.87, + child: Stack( + clipBehavior: Clip.antiAlias, + children: [ + Positioned( + top: 9, + left: 3, + right: 4, + child: Container( + height: MediaQuery.of(context).size.height * 0.267, + decoration: const BoxDecoration( + color: Color(0xffe5ffcd), + borderRadius: BorderRadius.all( + Radius.circular(16), + ), + ), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "Topic", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + Text(topic), + const Text( + "Description", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + Text(desc), + ], + ), + ), + ), + ), + Positioned( + top: 9, + right: 5, + left: 150, + child: Container( + height: 95, + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: Color(0xff444c55), + width: 10, + style: BorderStyle.solid, + ), + left: BorderSide( + color: Color(0xff444c55), + width: 10, + style: BorderStyle.solid, + ), + ), + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(500), + ), + ), + ), + ), + Positioned( + bottom: 19, + right: 0, + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.05, + width: MediaQuery.of(context).size.width * 0.2, + child: Row( + children: [ + GestureDetector( + onTap: () async { + AnkiTopic topic = AnkiTopic( + id: idx, + name: this.topic, + desc: desc, + isFavourite: true, + ); + await topicController?.favouriteTopic(topic); + // update favourites and all topics + await topicController?.getAllFavourites(); + await topicController?.getAllTopics(); + }, + child: Icon( + Icons.star, + size: MediaQuery.of(context).size.height * 0.035, + ), + ), + // delete for topics + GestureDetector( + onTap: () => showDialog( + context: context, + builder: (context) => AlertDialog( + content: const Text( + "Are You Sure You Want To Delete Topic?", + ), + actions: [ + TextButton( + style: ButtonStyle( + foregroundColor: WidgetStatePropertyAll( + lightColorScheme.error, + ), + ), + onPressed: () async { + AnkiTopic topic = AnkiTopic( + id: idx, + name: this.topic, + desc: desc, + ); + bool? deleted = + await topicController?.deleteTopic(topic); + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: (deleted != null && deleted) + ? const Text( + "Topic Successfully Deleted") + : const Text( + "Something happened! Kindly Retry!!"), + duration: const Duration(seconds: 1), + ), + ); + // update favourites and all topics + await topicController?.getAllFavourites(); + await topicController?.getAllTopics(); + // ignore: use_build_context_synchronously + Navigator.of(context).pop(); + }, + child: const Text("Yes"), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text("No"), + ), + ], + ), + ), + child: Icon( + Icons.delete, + size: MediaQuery.of(context).size.height * 0.035, + ), + ) + ], + ), + ), + ), + const Positioned( + top: 10, + left: 16, + child: CustomEclipse(), + ), + ], + ), + ), + ); + } +} diff --git a/lib/tools/anki/widgets/topic_flashcard.dart b/lib/tools/anki/widgets/topic_flashcard.dart new file mode 100644 index 0000000..8ab3cd5 --- /dev/null +++ b/lib/tools/anki/widgets/topic_flashcard.dart @@ -0,0 +1,110 @@ +import 'package:academia/tools/anki/controllers/controllers.dart'; +import 'package:academia/tools/anki/models/ankicard_model.dart'; +import 'package:academia/tools/anki/pages/edit_anki_card.dart'; +import 'package:academia/tools/anki/widgets/eclipses.dart'; +import 'package:academia/tools/anki/widgets/preview_card.dart'; +import 'package:academia/exports/barrel.dart'; +import 'package:get/get.dart'; + +class FlashCardTile extends StatelessWidget { + const FlashCardTile({ + super.key, + required this.ankiCard, + }); + + final AnkiCard ankiCard; + + @override + Widget build(BuildContext context) { + AnkiCardController ankiCardController = Get.find(); + + return Padding( + padding: const EdgeInsets.all(8.0), + child: GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (context) { + return PreviewAnkiCard(ankiCard: ankiCard); + }, + ); + }, + onDoubleTap: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + content: const Text( + "Are You Sure You Want To Delete AnkiCard?", + ), + actions: [ + TextButton( + style: ButtonStyle( + foregroundColor: WidgetStatePropertyAll( + lightColorScheme.error, + ), + ), + onPressed: () async { + bool? deleted = + await ankiCardController.deleteCard(ankiCard); + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: deleted + ? const Text("AnkiCard Successfully Deleted") + : const Text("Something happened! Kindly Retry!!"), + duration: const Duration(seconds: 1), + ), + ); + // update favourites and all topics + await ankiCardController.getAllTopicCards(); + // ignore: use_build_context_synchronously + Navigator.of(context).pop(); + }, + child: const Text("Yes"), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text("No"), + ), + ], + ), + ); + }, + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: const Color(0xff444c55), + ), + borderRadius: const BorderRadius.all( + Radius.circular(12), + ), + ), + child: ListTile( + leading: const CustomMixEclipse(), + title: ankiCard.question.length > 31 + ? Text( + "${ankiCard.question.substring(0, 29)}...", + ) + : Text("${ankiCard.question}..."), + dense: false, + trailing: IconButton( + onPressed: () => Navigator.push( + context, + MaterialPageRoute( + builder: (builder) => EditAnkiCard( + ankiCard: ankiCard, + ), + ), + ), + icon: const Icon( + Icons.edit_note, + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/tools/anki/widgets/widgets.dart b/lib/tools/anki/widgets/widgets.dart new file mode 100644 index 0000000..66165af --- /dev/null +++ b/lib/tools/anki/widgets/widgets.dart @@ -0,0 +1,5 @@ +export 'grid_topics.dart'; +export 'starred_topic.dart'; +export 'eclipses.dart'; +export 'topic_flashcard.dart'; +export 'preview_card.dart'; diff --git a/lib/tools/tools.dart b/lib/tools/tools.dart index 9b7bf06..0c9f384 100644 --- a/lib/tools/tools.dart +++ b/lib/tools/tools.dart @@ -1,4 +1,5 @@ export 'leaderboard/leaderboard.dart'; +export 'anki/anki.dart'; // export 'stories/stories.dart'; export 'events/events.dart'; export 'fees/fees.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index aecec8e..1d140ac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -72,6 +72,7 @@ dependencies: timeago: ^3.7.0 swipe_to: ^1.0.6 file_picker: ^8.0.5 + appinio_swiper: ^2.1.1 flutter_html: ^3.0.0-beta.2