diff --git a/po/POTFILES b/po/POTFILES index 68ed4dc..8b6e34f 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -4,6 +4,7 @@ resources/ui/create-card.blp src/flashcardsapp.c src/flashcardsappwin.c src/create-category.c +src/create-card.c src/database.c resources/li.sopht.Flashcards.desktop.in resources/li.sopht.Flashcards.metainfo.xml.in diff --git a/resources/ui/create-card.blp b/resources/ui/create-card.blp index 2c05e53..ee81c6a 100644 --- a/resources/ui/create-card.blp +++ b/resources/ui/create-card.blp @@ -10,17 +10,22 @@ template $FlashcardsCreateCardDialog : Adw.AlertDialog { ] response => $on_response(); - Adw.EntryRow titleEntry { - title: _("Title"); - styles [ - "card", - ] - } + Box { + orientation: vertical; + spacing: 12; - Adw.EntryRow answerEntry { - title: _("Answer"); - styles [ - "card", - ] + Adw.EntryRow titleEntry { + title: _("Title"); + styles [ + "card", + ] + } + + Adw.EntryRow answerEntry { + title: _("Answer"); + styles [ + "card", + ] + } } } \ No newline at end of file diff --git a/resources/ui/window.blp b/resources/ui/window.blp index 5483007..4f53e7f 100644 --- a/resources/ui/window.blp +++ b/resources/ui/window.blp @@ -29,10 +29,11 @@ template $FlashcardsAppWindow : Adw.ApplicationWindow { tooltip-text: _("Create category"); } [start] - Gtk.Button { + Gtk.Button delete_category_button { icon-name: "user-trash-symbolic"; clicked => $on_delete_category(); tooltip-text: _("Delete category"); + visible: false; } [end] Gtk.MenuButton { @@ -62,16 +63,18 @@ template $FlashcardsAppWindow : Adw.ApplicationWindow { [top] Adw.HeaderBar { [start] - Gtk.Button { + Gtk.Button add_card_button { icon-name: "list-add-symbolic"; clicked => $on_add_card(); tooltip-text: _("Add flashcard"); + visible: false; } [start] - Gtk.Button { + Gtk.Button delete_card_button { icon-name: "user-trash-symbolic"; clicked => $on_delete_card(); tooltip-text: _("Delete flashcard"); + visible: false; } [title] Adw.WindowTitle title { @@ -107,10 +110,11 @@ template $FlashcardsAppWindow : Adw.ApplicationWindow { margin-start: 12; margin-end: 12; - Box { + Button { styles ["card", "activatable"] halign: center; - Label { + clicked => $on_flip_card(); + Label card_title { styles ["title-4"] margin-top: 24; margin-bottom: 24; @@ -130,9 +134,9 @@ template $FlashcardsAppWindow : Adw.ApplicationWindow { styles ["linked"] Button { styles ["pill"] - label: _("Easy"); + label: _("Hard"); hexpand: true; - clicked => $on_answer_easy(); + clicked => $on_answer_hard(); } Button { styles ["pill"] @@ -142,9 +146,9 @@ template $FlashcardsAppWindow : Adw.ApplicationWindow { } Button { styles ["pill"] - label: _("Hard"); + label: _("Easy"); hexpand: true; - clicked => $on_answer_hard(); + clicked => $on_answer_easy(); } } } diff --git a/src/create-card.c b/src/create-card.c new file mode 100644 index 0000000..f7fc04a --- /dev/null +++ b/src/create-card.c @@ -0,0 +1,59 @@ +#include "create-card.h" + +struct _FlashcardsCreateCardDialog +{ + AdwAlertDialog parent; + + GtkEditable *titleEntry; + GtkEditable *answerEntry; + + FlashcardsAppWindow *win; +}; + +G_DEFINE_TYPE (FlashcardsCreateCardDialog, flashcards_create_card_dialog, + ADW_TYPE_ALERT_DIALOG); + +static void +on_response (__attribute__ ((unused)) AdwAlertDialog *dialog, + const gchar *response, gpointer user_data) +{ + FlashcardsCreateCardDialog *self = user_data; + + const gchar *title = gtk_editable_get_text (self->titleEntry); + const gchar *answer = gtk_editable_get_text (self->answerEntry); + + if (strcmp (response, "add") == 0 && strlen (title) > 0 + && strlen (answer) > 0) + flashcards_app_window_add_card (self->win, title, answer); +} + +static void +flashcards_create_card_dialog_init (FlashcardsCreateCardDialog *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +flashcards_create_card_dialog_class_init ( + FlashcardsCreateCardDialogClass *klass) +{ + gtk_widget_class_set_template_from_resource ( + GTK_WIDGET_CLASS (klass), "/li/sopht/flashcards/create-card.ui"); + + gtk_widget_class_bind_template_child ( + GTK_WIDGET_CLASS (klass), FlashcardsCreateCardDialog, titleEntry); + gtk_widget_class_bind_template_child ( + GTK_WIDGET_CLASS (klass), FlashcardsCreateCardDialog, answerEntry); + + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (klass), + on_response); +} + +FlashcardsCreateCardDialog * +flashcards_create_card_dialog_new (FlashcardsAppWindow *win) +{ + FlashcardsCreateCardDialog *self + = g_object_new (FLASHCARDS_CREATE_CARD_DIALOG_TYPE, nullptr); + self->win = win; + return self; +} \ No newline at end of file diff --git a/src/create-card.h b/src/create-card.h new file mode 100644 index 0000000..de14bb1 --- /dev/null +++ b/src/create-card.h @@ -0,0 +1,17 @@ +#ifndef __CREATECARDDIALOG_H +#define __CREATECARDDIALOG_H + +#include "flashcardsappwin.h" +#include +#include + +#define FLASHCARDS_CREATE_CARD_DIALOG_TYPE \ +(flashcards_create_card_dialog_get_type ()) +G_DECLARE_FINAL_TYPE (FlashcardsCreateCardDialog, + flashcards_create_card_dialog, FLASHCARDS, + CREATE_CARD_DIALOG, AdwAlertDialog) + +FlashcardsCreateCardDialog * +flashcards_create_card_dialog_new (FlashcardsAppWindow *win); + +#endif /* __CREATECARDDIALOG_H */ diff --git a/src/create-category.c b/src/create-category.c index a4e4173..93a4b01 100644 --- a/src/create-category.c +++ b/src/create-category.c @@ -1,8 +1,3 @@ -#include -#include - -#include - #include "create-category.h" struct _FlashcardsCreateCategoryDialog @@ -18,21 +13,21 @@ G_DEFINE_TYPE (FlashcardsCreateCategoryDialog, flashcards_create_category_dialog, ADW_TYPE_ALERT_DIALOG); static void -flashcards_create_category_dialog_init (FlashcardsCreateCategoryDialog *self) -{ - gtk_widget_init_template (GTK_WIDGET (self)); -} - -static void -on_response (__attribute__ ((unused)) AdwAlertDialog *dialog, gchar *response, - gpointer user_data) +on_response (__attribute__ ((unused)) AdwAlertDialog *dialog, + const gchar *response, gpointer user_data) { FlashcardsCreateCategoryDialog *self = user_data; const gchar *category = gtk_editable_get_text (self->entry); if (strcmp (response, "add") == 0 && strlen (category) > 0) - flashcards_app_window_test (self->win, category); + flashcards_app_window_add_category (self->win, category); +} + +static void +flashcards_create_category_dialog_init (FlashcardsCreateCategoryDialog *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); } static void diff --git a/src/create-category.h b/src/create-category.h index 61c50eb..ec1ffd5 100644 --- a/src/create-category.h +++ b/src/create-category.h @@ -1,7 +1,6 @@ #ifndef __CREATECATEGORYDIALOG_H #define __CREATECATEGORYDIALOG_H -#include "flashcardsapp.h" #include "flashcardsappwin.h" #include #include diff --git a/src/database.c b/src/database.c index 58249e2..533d974 100644 --- a/src/database.c +++ b/src/database.c @@ -35,10 +35,11 @@ database_create_tables (sqlite3 *db) sqlite3_stmt *stmt; const char *sql = "CREATE TABLE IF NOT EXISTS `cards` (" - "`id` INTEGER NOT NULL," - "`category` INTEGER NOT NULL," - "`task` TEXT NOT NULL," - "`solution` TEXT NOT NULL," + "`id` INTEGER NOT NULL," + "`category` INTEGER NOT NULL," + "`title` TEXT NOT NULL," + "`answer` TEXT NOT NULL," + "`next_time` INTEGER NOT NULL DEFAULT (unixepoch())," "PRIMARY KEY(`id` AUTOINCREMENT)" ")"; @@ -77,8 +78,11 @@ database_save_category (sqlite3 *db, const char *c) printf ("Failed to execute statement: %s\n", sqlite3_errmsg (db)); rc = sqlite3_step (stmt); - if (rc != SQLITE_OK) - fprintf (stderr, "Failed to execute statement: %s\n", sqlite3_errmsg (db)); + if (rc != SQLITE_DONE) + { + fprintf (stderr, "Failed to execute statement: %s\n", + sqlite3_errmsg (db)); + } sqlite3_finalize (stmt); } @@ -118,7 +122,7 @@ database_delete_category (sqlite3 *db, const int id) else fprintf (stderr, "Failed to execute statement: %s\n", sqlite3_errmsg (db)); rc = sqlite3_step (stmt); - if (rc != SQLITE_OK) + if (rc != SQLITE_DONE) fprintf (stderr, "Execution failed: %s\n", sqlite3_errmsg (db)); sqlite3_finalize (stmt); @@ -129,30 +133,34 @@ database_delete_category (sqlite3 *db, const int id) sqlite3_bind_int (stmt, 1, id); else fprintf (stderr, "Failed to execute statement: %s\n", sqlite3_errmsg (db)); + rc = sqlite3_step (stmt); - if (rc != SQLITE_OK) + if (rc != SQLITE_DONE) fprintf (stderr, "Execution failed: %s\n", sqlite3_errmsg (db)); sqlite3_finalize (stmt); } void -database_save_card (sqlite3 *db, const card c) +database_save_card (sqlite3 *db, const int category, const char *title, + const char *answer) { sqlite3_stmt *stmt; - fprintf (stdout, "%s: %s\n", c.task, c.solution); - int rc = sqlite3_prepare_v2 (db, "INSERT INTO cards VALUES(?, ?, ?)", -1, - &stmt, nullptr); + int rc = sqlite3_prepare_v2 ( + db, "INSERT INTO cards (category, title, answer) VALUES(?, ?, ?)", -1, + &stmt, nullptr); if (rc == SQLITE_OK) { - sqlite3_bind_int (stmt, 0, c.category); - sqlite3_bind_text (stmt, 1, c.task, -1, SQLITE_STATIC); - sqlite3_bind_text (stmt, 2, c.solution, -1, SQLITE_STATIC); + sqlite3_bind_int (stmt, 1, category); + sqlite3_bind_text (stmt, 2, title, -1, SQLITE_STATIC); + sqlite3_bind_text (stmt, 3, answer, -1, SQLITE_STATIC); } else fprintf (stderr, "Failed to execute statement: %s\n", sqlite3_errmsg (db)); - sqlite3_step (stmt); + rc = sqlite3_step (stmt); + if (rc != SQLITE_DONE) + fprintf (stderr, "Execution failed: %s\n", sqlite3_errmsg (db)); sqlite3_finalize (stmt); } @@ -162,8 +170,11 @@ database_load_cards (sqlite3 *db, const int category) GArray *cards = g_array_new (TRUE, FALSE, sizeof (card)); sqlite3_stmt *stmt; - int rc = sqlite3_prepare_v2 (db, "SELECT * FROM cards WHERE category = ?", - -1, &stmt, nullptr); + int rc + = sqlite3_prepare_v2 (db, + "SELECT * FROM cards WHERE category = ? AND " + "next_time <= unixepoch() ORDER BY next_time ASC", + -1, &stmt, nullptr); if (rc == SQLITE_OK) sqlite3_bind_int (stmt, 1, category); else @@ -174,8 +185,10 @@ database_load_cards (sqlite3 *db, const int category) card c; c.id = sqlite3_column_int (stmt, 0); c.category = sqlite3_column_int (stmt, 1); - c.task = strdup ((char *)sqlite3_column_text (stmt, 2)); - c.solution = strdup ((char *)sqlite3_column_text (stmt, 3)); + c.title = strdup ((char *)sqlite3_column_text (stmt, 2)); + c.answer = strdup ((char *)sqlite3_column_text (stmt, 3)); + c.next_time + = g_date_time_new_from_unix_utc (sqlite3_column_int64 (stmt, 4)); g_array_append_val (cards, c); } @@ -184,6 +197,26 @@ database_load_cards (sqlite3 *db, const int category) return cards; } +void +database_schedule_card (sqlite3 *db, const int id, GDateTime *next_time) +{ + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2 ( + db, "UPDATE cards SET next_time = ? WHERE id = ?", -1, &stmt, nullptr); + if (rc == SQLITE_OK) + { + sqlite3_bind_int64 (stmt, 1, g_date_time_to_unix (next_time)); + sqlite3_bind_int (stmt, 2, id); + } + else + fprintf (stderr, "Failed to execute statement: %s\n", sqlite3_errmsg (db)); + + rc = sqlite3_step (stmt); + if (rc != SQLITE_DONE) + fprintf (stderr, "Execution failed: %s\n", sqlite3_errmsg (db)); + sqlite3_finalize (stmt); +} + void database_delete_card (sqlite3 *db, const int id) { @@ -195,6 +228,8 @@ database_delete_card (sqlite3 *db, const int id) else fprintf (stderr, "Failed to execute statement: %s\n", sqlite3_errmsg (db)); - sqlite3_step (stmt); + rc = sqlite3_step (stmt); + if (rc != SQLITE_DONE) + fprintf (stderr, "Execution failed: %s\n", sqlite3_errmsg (db)); sqlite3_finalize (stmt); } diff --git a/src/database.h b/src/database.h index 1aa12c5..f7516bf 100644 --- a/src/database.h +++ b/src/database.h @@ -14,8 +14,9 @@ typedef struct card { int id; int category; - char *task; - char *solution; + char *title; + char *answer; + GDateTime *next_time; } card; sqlite3 *database_connect (const char *path); @@ -30,9 +31,12 @@ GArray *database_load_categories (sqlite3 *db); void database_delete_category (sqlite3 *db, int id); -void database_save_card (sqlite3 *db, card c); +void database_save_card (sqlite3 *db, int category, const char *title, + const char *answer); GArray *database_load_cards (sqlite3 *db, int category); +void database_schedule_card (sqlite3 *db, int id, GDateTime *next_time); + void database_delete_card (sqlite3 *db, int id); #endif /* DATABASE_H */ diff --git a/src/flashcardsapp.c b/src/flashcardsapp.c index 1bfc50c..3ffc027 100644 --- a/src/flashcardsapp.c +++ b/src/flashcardsapp.c @@ -1,9 +1,7 @@ -#include -#include +#include "flashcardsapp.h" #include -#include "flashcardsapp.h" #include "flashcardsappwin.h" struct _FlashcardsApp diff --git a/src/flashcardsappwin.c b/src/flashcardsappwin.c index 43659ae..2583e57 100644 --- a/src/flashcardsappwin.c +++ b/src/flashcardsappwin.c @@ -1,14 +1,12 @@ -#include -#include +#include "flashcardsappwin.h" #include -#include "create-category.h" -#include "flashcardsapp.h" -#include "flashcardsappwin.h" - #include "database.h" +#include "create-card.h" +#include "create-category.h" + struct _FlashcardsAppWindow { AdwApplicationWindow parent; @@ -16,10 +14,15 @@ struct _FlashcardsAppWindow sqlite3 *db; GArray *categories; GQueue *cards; + category current_category; card *current_card; AdwWindowTitle *title; + GtkButton *add_card_button; + GtkButton *delete_card_button; + GtkButton *delete_category_button; + AdwNavigationSplitView *split_view; AdwNavigationPage *sidebar; AdwNavigationPage *content; @@ -31,6 +34,8 @@ struct _FlashcardsAppWindow GtkListBox *topics; + GtkLabel *card_title; + bool started; }; @@ -60,6 +65,18 @@ load_categories (FlashcardsAppWindow *win) } } +static void +next_card (FlashcardsAppWindow *win) +{ + if (win->cards->length <= 0) + { + adw_view_stack_set_visible_child (win->main_view, win->placeholder_card); + return; + } + win->current_card = g_queue_pop_head (win->cards); + gtk_label_set_label (win->card_title, win->current_card->title); +} + static void load_cards (FlashcardsAppWindow *win, category c) { @@ -73,13 +90,7 @@ load_cards (FlashcardsAppWindow *win, category c) } printf ("Flashcards: %d\n", win->cards->length); - if (win->cards->length <= 0) - { - adw_view_stack_set_visible_child (win->main_view, win->placeholder_card); - return; - } - - win->current_card = g_queue_pop_head (win->cards); + next_card (win); } static void @@ -91,14 +102,19 @@ on_select_category (__attribute__ ((unused)) GtkListBox *box, adw_navigation_split_view_set_show_content ( ADW_NAVIGATION_SPLIT_VIEW (win->split_view), TRUE); - int id = gtk_list_box_row_get_index (row); - + const int id = gtk_list_box_row_get_index (row); category c = g_array_index (win->categories, category, id); - printf ("%d: %d\n", id, c.id); + + win->current_category = c; + adw_window_title_set_subtitle (ADW_WINDOW_TITLE (win->title), c.name); adw_view_stack_set_visible_child (win->main_view, win->flashcard); + gtk_widget_set_visible (GTK_WIDGET (win->add_card_button), TRUE); + gtk_widget_set_visible (GTK_WIDGET (win->delete_card_button), TRUE); + gtk_widget_set_visible (GTK_WIDGET (win->delete_category_button), TRUE); + load_cards (win, c); } @@ -115,56 +131,84 @@ on_delete_category (__attribute__ ((unused)) GtkButton *self, gpointer user_data) { FlashcardsAppWindow *win = user_data; - database_delete_category (win->db, 1); + database_delete_category (win->db, win->current_category.id); - printf ("Delete category\n"); gtk_list_box_unselect_all (win->topics); adw_view_stack_set_visible_child (win->main_view, win->placeholder_category); + + gtk_widget_set_visible (GTK_WIDGET (win->add_card_button), FALSE); + gtk_widget_set_visible (GTK_WIDGET (win->delete_card_button), FALSE); + gtk_widget_set_visible (GTK_WIDGET (win->delete_category_button), FALSE); + + load_categories (win); } static void on_add_card (__attribute__ ((unused)) GtkButton *self, gpointer user_data) { - /*FlashcardsAppWindow *win = user_data; - FlashcardsCreateCardDialog *dialog = flashcards_create_card_dialog_new(win); - adw_dialog_present(ADW_DIALOG(dialog), GTK_WIDGET(win));*/ + FlashcardsAppWindow *win = user_data; + FlashcardsCreateCardDialog *dialog = flashcards_create_card_dialog_new (win); + adw_dialog_present (ADW_DIALOG (dialog), GTK_WIDGET (win)); } static void on_delete_card (__attribute__ ((unused)) GtkButton *self, gpointer user_data) { - /*FlashcardsAppWindow *win = user_data; - database_delete_card(win->db, 1); - printf("Delete card\n");*/ + FlashcardsAppWindow *win = user_data; + database_delete_card (win->db, win->current_card->id); + next_card (win); } static void on_answer_easy (__attribute__ ((unused)) GtkButton *self, gpointer user_data) { FlashcardsAppWindow *win = user_data; - adw_view_stack_set_visible_child (win->main_view, win->placeholder_category); + card *c = win->current_card; + database_schedule_card ( + win->db, c->id, g_date_time_add_hours (g_date_time_new_now_utc (), 24)); + next_card (win); } static void on_answer_medium (__attribute__ ((unused)) GtkButton *self, gpointer user_data) { FlashcardsAppWindow *win = user_data; - adw_view_stack_set_visible_child (win->main_view, win->placeholder_category); + card *c = win->current_card; + database_schedule_card ( + win->db, c->id, g_date_time_add_hours (g_date_time_new_now_utc (), 12)); + next_card (win); } static void on_answer_hard (__attribute__ ((unused)) GtkButton *self, gpointer user_data) { FlashcardsAppWindow *win = user_data; - adw_view_stack_set_visible_child (win->main_view, win->placeholder_category); + card *c = win->current_card; + database_schedule_card ( + win->db, c->id, g_date_time_add_hours (g_date_time_new_now_utc (), 6)); + next_card (win); +} + +static void +on_flip_card (__attribute__ ((unused)) GtkWidget *self, gpointer user_data) +{ + FlashcardsAppWindow *win = user_data; + gtk_label_set_text (GTK_LABEL (win->card_title), win->current_card->answer); } void -flashcards_app_window_test (FlashcardsAppWindow *win, const gchar *test) +flashcards_app_window_add_card (FlashcardsAppWindow *win, const gchar *title, + const gchar *answer) { - GtkWidget *child; + database_save_card (win->db, win->current_category.id, title, answer); + printf ("Added card\n"); +} - database_save_category (win->db, test); +void +flashcards_app_window_add_category (FlashcardsAppWindow *win, + const gchar *title) +{ + database_save_category (win->db, title); load_categories (win); } @@ -194,6 +238,13 @@ flashcards_app_window_class_init (FlashcardsAppWindowClass *class) gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), FlashcardsAppWindow, title); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), + FlashcardsAppWindow, add_card_button); + gtk_widget_class_bind_template_child ( + GTK_WIDGET_CLASS (class), FlashcardsAppWindow, delete_card_button); + gtk_widget_class_bind_template_child ( + GTK_WIDGET_CLASS (class), FlashcardsAppWindow, delete_category_button); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), FlashcardsAppWindow, split_view); gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), @@ -213,6 +264,9 @@ flashcards_app_window_class_init (FlashcardsAppWindowClass *class) gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), FlashcardsAppWindow, topics); + gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), + FlashcardsAppWindow, card_title); + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), on_flashcards_app_window_show); @@ -233,6 +287,9 @@ flashcards_app_window_class_init (FlashcardsAppWindowClass *class) on_answer_medium); gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), on_answer_hard); + + gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), + on_flip_card); } FlashcardsAppWindow * diff --git a/src/flashcardsappwin.h b/src/flashcardsappwin.h index 1a3a175..5d0214f 100644 --- a/src/flashcardsappwin.h +++ b/src/flashcardsappwin.h @@ -10,6 +10,9 @@ G_DECLARE_FINAL_TYPE (FlashcardsAppWindow, flashcards_app_window, FLASHCARDS, APP_WINDOW, AdwApplicationWindow) FlashcardsAppWindow *flashcards_app_window_new (FlashcardsApp *app); -void flashcards_app_window_test (FlashcardsAppWindow *win, const gchar *test); +void flashcards_app_window_add_category (FlashcardsAppWindow *win, + const gchar *test); +void flashcards_app_window_add_card (FlashcardsAppWindow *win, + const gchar *title, const gchar *answer); #endif /* __FLASHCARDSAPPWIN_H */ diff --git a/src/meson.build b/src/meson.build index f4c09ff..42c219b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,6 +1,7 @@ sourcefiles = files('main.c', - 'flashcardsapp.c', - 'flashcardsappwin.c', - 'create-category.c', - 'database.c' + 'flashcardsapp.c', + 'flashcardsappwin.c', + 'create-category.c', + 'create-card.c', + 'database.c' ) \ No newline at end of file