diff --git a/app/schemas/io.github.wulkanowy.data.db.AppDatabase/43.json b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/43.json new file mode 100644 index 000000000..22c0d8125 --- /dev/null +++ b/app/schemas/io.github.wulkanowy.data.db.AppDatabase/43.json @@ -0,0 +1,2408 @@ +{ + "formatVersion": 1, + "database": { + "version": 43, + "identityHash": "66946510bb620ae82686a5a1a31aba18", + "entities": [ + { + "tableName": "Students", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`scrapper_base_url` TEXT NOT NULL, `mobile_base_url` TEXT NOT NULL, `login_type` TEXT NOT NULL, `login_mode` TEXT NOT NULL, `certificate_key` TEXT NOT NULL, `private_key` TEXT NOT NULL, `is_parent` INTEGER NOT NULL, `email` TEXT NOT NULL, `password` TEXT NOT NULL, `symbol` TEXT NOT NULL, `student_id` INTEGER NOT NULL, `user_login_id` INTEGER NOT NULL, `user_name` TEXT NOT NULL, `student_name` TEXT NOT NULL, `school_id` TEXT NOT NULL, `school_short` TEXT NOT NULL, `school_name` TEXT NOT NULL, `class_name` TEXT NOT NULL, `class_id` INTEGER NOT NULL, `is_current` INTEGER NOT NULL, `registration_date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `nick` TEXT NOT NULL, `avatar_color` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "scrapperBaseUrl", + "columnName": "scrapper_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mobileBaseUrl", + "columnName": "mobile_base_url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginType", + "columnName": "login_type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginMode", + "columnName": "login_mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "certificateKey", + "columnName": "certificate_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "privateKey", + "columnName": "private_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isParent", + "columnName": "is_parent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userLoginId", + "columnName": "user_login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userName", + "columnName": "user_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentName", + "columnName": "student_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolSymbol", + "columnName": "school_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolShortName", + "columnName": "school_short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolName", + "columnName": "school_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrent", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "registrationDate", + "columnName": "registration_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nick", + "columnName": "nick", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarColor", + "columnName": "avatar_color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Students_email_symbol_student_id_school_id_class_id", + "unique": true, + "columnNames": [ + "email", + "symbol", + "student_id", + "school_id", + "class_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Students_email_symbol_student_id_school_id_class_id` ON `${TABLE_NAME}` (`email`, `symbol`, `student_id`, `school_id`, `class_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Semesters", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `diary_name` TEXT NOT NULL, `school_year` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `semester_name` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_current` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryName", + "columnName": "diary_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "schoolYear", + "columnName": "school_year", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterName", + "columnName": "semester_name", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "current", + "columnName": "is_current", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_Semesters_student_id_diary_id_semester_id", + "unique": true, + "columnNames": [ + "student_id", + "diary_id", + "semester_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Semesters_student_id_diary_id_semester_id` ON `${TABLE_NAME}` (`student_id`, `diary_id`, `semester_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Exams", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `group` TEXT NOT NULL, `type` TEXT NOT NULL, `description` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Timetable", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `number` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `subjectOld` TEXT NOT NULL, `group` TEXT NOT NULL, `room` TEXT NOT NULL, `roomOld` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacherOld` TEXT NOT NULL, `info` TEXT NOT NULL, `student_plan` INTEGER NOT NULL, `changes` INTEGER NOT NULL, `canceled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subjectOld", + "columnName": "subjectOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roomOld", + "columnName": "roomOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherOld", + "columnName": "teacherOld", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "info", + "columnName": "info", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isStudentPlan", + "columnName": "student_plan", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "changes", + "columnName": "changes", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canceled", + "columnName": "canceled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Attendance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `time_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `excused` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `excusable` INTEGER NOT NULL, `excuse_status` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeId", + "columnName": "time_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excused", + "columnName": "excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excusable", + "columnName": "excusable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "excuseStatus", + "columnName": "excuse_status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AttendanceSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `subject_id` INTEGER NOT NULL, `month` INTEGER NOT NULL, `presence` INTEGER NOT NULL, `absence` INTEGER NOT NULL, `absence_excused` INTEGER NOT NULL, `absence_for_school_reasons` INTEGER NOT NULL, `lateness` INTEGER NOT NULL, `lateness_excused` INTEGER NOT NULL, `exemption` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subjectId", + "columnName": "subject_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "month", + "columnName": "month", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "presence", + "columnName": "presence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceExcused", + "columnName": "absence_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "absenceForSchoolReasons", + "columnName": "absence_for_school_reasons", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lateness", + "columnName": "lateness", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "latenessExcused", + "columnName": "lateness_excused", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "exemption", + "columnName": "exemption", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Grades", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `entry` TEXT NOT NULL, `value` REAL NOT NULL, `modifier` REAL NOT NULL, `comment` TEXT NOT NULL, `color` TEXT NOT NULL, `grade_symbol` TEXT NOT NULL, `description` TEXT NOT NULL, `weight` TEXT NOT NULL, `weightValue` REAL NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "entry", + "columnName": "entry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gradeSymbol", + "columnName": "grade_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weightValue", + "columnName": "weightValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesSummary", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `position` INTEGER NOT NULL, `subject` TEXT NOT NULL, `predicted_grade` TEXT NOT NULL, `final_grade` TEXT NOT NULL, `proposed_points` TEXT NOT NULL, `final_points` TEXT NOT NULL, `points_sum` TEXT NOT NULL, `average` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_predicted_grade_notified` INTEGER NOT NULL, `is_final_grade_notified` INTEGER NOT NULL, `predicted_grade_last_change` INTEGER NOT NULL, `final_grade_last_change` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "predictedGrade", + "columnName": "predicted_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalGrade", + "columnName": "final_grade", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "proposedPoints", + "columnName": "proposed_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "finalPoints", + "columnName": "final_points", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pointsSum", + "columnName": "points_sum", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "average", + "columnName": "average", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPredictedGradeNotified", + "columnName": "is_predicted_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFinalGradeNotified", + "columnName": "is_final_grade_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "predictedGradeLastChange", + "columnName": "predicted_grade_last_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "finalGradeLastChange", + "columnName": "final_grade_last_change", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradePartialStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `class_average` TEXT NOT NULL, `student_average` TEXT NOT NULL, `class_amounts` TEXT NOT NULL, `student_amounts` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAverage", + "columnName": "class_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAverage", + "columnName": "student_average", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "classAmounts", + "columnName": "class_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentAmounts", + "columnName": "student_amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradesPointsStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `others` REAL NOT NULL, `student` REAL NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "others", + "columnName": "others", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "student", + "columnName": "student", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GradeSemesterStatistics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `semester_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `amounts` TEXT NOT NULL, `student_grade` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amounts", + "columnName": "amounts", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "studentGrade", + "columnName": "student_grade", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `recipient_name` TEXT NOT NULL, `subject` TEXT NOT NULL, `date` INTEGER NOT NULL, `folder_id` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `removed` INTEGER NOT NULL, `has_attachments` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL, `unread_by` INTEGER NOT NULL, `read_by` INTEGER NOT NULL, `content` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sender", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipient", + "columnName": "recipient_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "removed", + "columnName": "removed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasAttachments", + "columnName": "has_attachments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadBy", + "columnName": "unread_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readBy", + "columnName": "read_by", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MessageAttachments", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`real_id` INTEGER NOT NULL, `message_id` INTEGER NOT NULL, `one_drive_id` TEXT NOT NULL, `url` TEXT NOT NULL, `filename` TEXT NOT NULL, PRIMARY KEY(`real_id`))", + "fields": [ + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageId", + "columnName": "message_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "oneDriveId", + "columnName": "one_drive_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filename", + "columnName": "filename", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "real_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `category` TEXT NOT NULL, `category_type` INTEGER NOT NULL, `is_points_show` INTEGER NOT NULL, `points` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_read` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "categoryType", + "columnName": "category_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPointsShow", + "columnName": "is_points_show", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "points", + "columnName": "points", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Homework", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`semester_id` INTEGER NOT NULL, `student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `entry_date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `attachments` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_done` INTEGER NOT NULL, `is_notified` INTEGER NOT NULL, `is_added_by_user` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "semesterId", + "columnName": "semester_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "entryDate", + "columnName": "entry_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isDone", + "columnName": "is_done", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isAddedByUser", + "columnName": "is_added_by_user", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LuckyNumbers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `lucky_number` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "luckyNumber", + "columnName": "lucky_number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CompletedLesson", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `number` INTEGER NOT NULL, `subject` TEXT NOT NULL, `topic` TEXT NOT NULL, `teacher` TEXT NOT NULL, `teacher_symbol` TEXT NOT NULL, `substitution` TEXT NOT NULL, `absence` TEXT NOT NULL, `resources` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "topic", + "columnName": "topic", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacher", + "columnName": "teacher", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "teacherSymbol", + "columnName": "teacher_symbol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "substitution", + "columnName": "substitution", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "absence", + "columnName": "absence", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resources", + "columnName": "resources", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ReportingUnits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` INTEGER NOT NULL, `short` TEXT NOT NULL, `sender_id` INTEGER NOT NULL, `sender_name` TEXT NOT NULL, `roles` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "real_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "senderId", + "columnName": "sender_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "senderName", + "columnName": "sender_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "roles", + "columnName": "roles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Recipients", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `real_id` TEXT NOT NULL, `name` TEXT NOT NULL, `real_name` TEXT NOT NULL, `login_id` INTEGER NOT NULL, `unit_id` INTEGER NOT NULL, `role` INTEGER NOT NULL, `hash` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "realId", + "columnName": "real_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "realName", + "columnName": "real_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "loginId", + "columnName": "login_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unitId", + "columnName": "unit_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hash", + "columnName": "hash", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "MobileDevices", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `device_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "userLoginId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceId", + "columnName": "device_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Teachers", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `subject` TEXT NOT NULL, `name` TEXT NOT NULL, `short_name` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "School", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `class_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `address` TEXT NOT NULL, `contact` TEXT NOT NULL, `headmaster` TEXT NOT NULL, `pedagogue` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "classId", + "columnName": "class_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contact", + "columnName": "contact", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "headmaster", + "columnName": "headmaster", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pedagogue", + "columnName": "pedagogue", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conferences", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `subject` TEXT NOT NULL, `agenda` TEXT NOT NULL, `present_on_conference` TEXT NOT NULL, `conference_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "agenda", + "columnName": "agenda", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "presentOnConference", + "columnName": "present_on_conference", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "conferenceId", + "columnName": "conference_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableAdditional", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `start` INTEGER NOT NULL, `end` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StudentInfo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `full_name` TEXT NOT NULL, `first_name` TEXT NOT NULL, `second_name` TEXT NOT NULL, `surname` TEXT NOT NULL, `birth_date` INTEGER NOT NULL, `birth_place` TEXT NOT NULL, `gender` TEXT NOT NULL, `has_polish_citizenship` INTEGER NOT NULL, `family_name` TEXT NOT NULL, `parents_names` TEXT NOT NULL, `address` TEXT NOT NULL, `registered_address` TEXT NOT NULL, `correspondence_address` TEXT NOT NULL, `phone_number` TEXT NOT NULL, `cell_phone_number` TEXT NOT NULL, `email` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_guardian_full_name` TEXT, `first_guardian_kinship` TEXT, `first_guardian_address` TEXT, `first_guardian_phones` TEXT, `first_guardian_email` TEXT, `second_guardian_full_name` TEXT, `second_guardian_kinship` TEXT, `second_guardian_address` TEXT, `second_guardian_phones` TEXT, `second_guardian_email` TEXT)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "first_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "secondName", + "columnName": "second_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "surname", + "columnName": "surname", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "birthDate", + "columnName": "birth_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "birthPlace", + "columnName": "birth_place", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasPolishCitizenship", + "columnName": "has_polish_citizenship", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "familyName", + "columnName": "family_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentsNames", + "columnName": "parents_names", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "registeredAddress", + "columnName": "registered_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "correspondenceAddress", + "columnName": "correspondence_address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cellPhoneNumber", + "columnName": "cell_phone_number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstGuardian.fullName", + "columnName": "first_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.kinship", + "columnName": "first_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.address", + "columnName": "first_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.phones", + "columnName": "first_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstGuardian.email", + "columnName": "first_guardian_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.fullName", + "columnName": "second_guardian_full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.kinship", + "columnName": "second_guardian_kinship", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.address", + "columnName": "second_guardian_address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.phones", + "columnName": "second_guardian_phones", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "secondGuardian.email", + "columnName": "second_guardian_email", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TimetableHeaders", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `diary_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "diaryId", + "columnName": "diary_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SchoolAnnouncements", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `date` INTEGER NOT NULL, `subject` TEXT NOT NULL, `content` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_notified` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotified", + "columnName": "is_notified", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Notifications", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`student_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `type` TEXT NOT NULL, `date` INTEGER NOT NULL, `data` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "studentId", + "columnName": "student_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "AdminMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `version_name` INTEGER, `version_max` INTEGER, `target_register_host` TEXT, `target_flavor` TEXT, `destination_url` TEXT, `priority` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "versionMin", + "columnName": "version_name", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "versionMax", + "columnName": "version_max", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "targetRegisterHost", + "columnName": "target_register_host", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "targetFlavor", + "columnName": "target_flavor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "destinationUrl", + "columnName": "destination_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '66946510bb620ae82686a5a1a31aba18')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt index d96856068..7e6d609f7 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt @@ -102,6 +102,7 @@ import io.github.wulkanowy.data.db.migrations.Migration4 import io.github.wulkanowy.data.db.migrations.Migration40 import io.github.wulkanowy.data.db.migrations.Migration41 import io.github.wulkanowy.data.db.migrations.Migration42 +import io.github.wulkanowy.data.db.migrations.Migration43 import io.github.wulkanowy.data.db.migrations.Migration5 import io.github.wulkanowy.data.db.migrations.Migration6 import io.github.wulkanowy.data.db.migrations.Migration7 @@ -151,7 +152,7 @@ import javax.inject.Singleton abstract class AppDatabase : RoomDatabase() { companion object { - const val VERSION_SCHEMA = 42 + const val VERSION_SCHEMA = 43 fun getMigrations(sharedPrefProvider: SharedPrefProvider, appInfo: AppInfo) = arrayOf( Migration2(), @@ -194,7 +195,8 @@ abstract class AppDatabase : RoomDatabase() { Migration39(), Migration40(), Migration41(sharedPrefProvider), - Migration42() + Migration42(), + Migration43() ) fun newInstance( diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt index 8ef3fd446..c6c255a1f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt @@ -11,6 +11,11 @@ import javax.inject.Singleton @Dao interface AttendanceDao : BaseDao { - @Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") - fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow> + @Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :start AND date <= :end") + fun loadAll( + diaryId: Int, + studentId: Int, + start: LocalDate, + end: LocalDate + ): Flow> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt index f141d5d52..b40dd52e5 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt @@ -47,4 +47,7 @@ data class Attendance( @PrimaryKey(autoGenerate = true) var id: Long = 0 + + @ColumnInfo(name = "is_notified") + var isNotified: Boolean = true } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt index 1bf159efd..29b3737bc 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt @@ -50,4 +50,7 @@ data class Timetable( @PrimaryKey(autoGenerate = true) var id: Long = 0 + + @ColumnInfo(name = "is_notified") + var isNotified: Boolean = true } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt new file mode 100644 index 000000000..68c2834d6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/migrations/Migration43.kt @@ -0,0 +1,12 @@ +package io.github.wulkanowy.data.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration43 : Migration(42, 43) { + + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE Timetable ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") + database.execSQL("ALTER TABLE Attendance ADD COLUMN is_notified INTEGER NOT NULL DEFAULT 1") + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/pojos/NotificationData.kt b/app/src/main/java/io/github/wulkanowy/data/pojos/NotificationData.kt index 0b4603ef7..0748ba647 100644 --- a/app/src/main/java/io/github/wulkanowy/data/pojos/NotificationData.kt +++ b/app/src/main/java/io/github/wulkanowy/data/pojos/NotificationData.kt @@ -1,36 +1,19 @@ package io.github.wulkanowy.data.pojos -import androidx.annotation.DrawableRes -import androidx.annotation.PluralsRes -import androidx.annotation.StringRes +import android.content.Intent import io.github.wulkanowy.services.sync.notifications.NotificationType -import io.github.wulkanowy.ui.modules.main.MainView -sealed interface NotificationData { +data class NotificationData( + val intentToStart: Intent, + val title: String, + val content: String +) + +data class GroupNotificationData( + val notificationDataList: List, + val title: String, + val content: String, + val intentToStart: Intent, val type: NotificationType - val startMenu: MainView.Section - val icon: Int - val titleStringRes: Int - val contentStringRes: Int -} +) -data class MultipleNotificationsData( - override val type: NotificationType, - override val startMenu: MainView.Section, - @DrawableRes override val icon: Int, - @PluralsRes override val titleStringRes: Int, - @PluralsRes override val contentStringRes: Int, - - @PluralsRes val summaryStringRes: Int, - val lines: List, -) : NotificationData - -data class OneNotificationData( - override val type: NotificationType, - override val startMenu: MainView.Section, - @DrawableRes override val icon: Int, - @StringRes override val titleStringRes: Int, - @StringRes override val contentStringRes: Int, - - val contentValues: List, -) : NotificationData diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt index d21ffb5fb..ec9198175 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt @@ -14,6 +14,7 @@ import io.github.wulkanowy.utils.monday import io.github.wulkanowy.utils.networkBoundResource import io.github.wulkanowy.utils.sunday import io.github.wulkanowy.utils.uniqueSubtract +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex import java.time.LocalDate import java.time.LocalDateTime @@ -38,6 +39,7 @@ class AttendanceRepository @Inject constructor( start: LocalDate, end: LocalDate, forceRefresh: Boolean, + notify: Boolean = false, ) = networkBoundResource( mutex = saveFetchResultMutex, shouldFetch = { @@ -56,13 +58,28 @@ class AttendanceRepository @Inject constructor( }, saveFetchResult = { old, new -> attendanceDb.deleteAll(old uniqueSubtract new) - attendanceDb.insertAll(new uniqueSubtract old) + val attendanceToAdd = (new uniqueSubtract old).map { newAttendance -> + newAttendance.apply { if (notify) isNotified = false } + } + attendanceDb.insertAll(attendanceToAdd) refreshHelper.updateLastRefreshTimestamp(getRefreshKey(cacheKey, semester, start, end)) }, filterResult = { it.filter { item -> item.date in start..end } } ) + fun getAttendanceFromDatabase( + semester: Semester, + start: LocalDate, + end: LocalDate + ): Flow> { + return attendanceDb.loadAll(semester.diaryId, semester.studentId, start, end) + } + + suspend fun updateTimetable(timetable: List) { + return attendanceDb.updateAll(timetable) + } + suspend fun excuseForAbsence( student: Student, semester: Semester, absenceList: List, reason: String? = null diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt index 769fa0f0d..8be621122 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt @@ -47,6 +47,7 @@ class TimetableRepository @Inject constructor( end: LocalDate, forceRefresh: Boolean, refreshAdditional: Boolean = false, + notify: Boolean = false ) = networkBoundResource( mutex = saveFetchResultMutex, shouldFetch = { (timetable, additional, headers) -> @@ -67,7 +68,7 @@ class TimetableRepository @Inject constructor( timetableFull.mapToEntities(semester) }, saveFetchResult = { timetableOld, timetableNew -> - refreshTimetable(student, timetableOld.lessons, timetableNew.lessons) + refreshTimetable(student, timetableOld.lessons, timetableNew.lessons, notify) refreshAdditional(timetableOld.additional, timetableNew.additional) refreshDayHeaders(timetableOld.headers, timetableNew.headers) @@ -117,13 +118,28 @@ class TimetableRepository @Inject constructor( } } + fun getTimetableFromDatabase( + semester: Semester, + from: LocalDate, + end: LocalDate + ): Flow> { + return timetableDb.loadAll(semester.diaryId, semester.studentId, from, end) + } + + suspend fun updateTimetable(timetable: List) { + return timetableDb.updateAll(timetable) + } + private suspend fun refreshTimetable( student: Student, lessonsOld: List, lessonsNew: List, + notify: Boolean ) { val lessonsToRemove = lessonsOld uniqueSubtract lessonsNew - val lessonsToAdd = lessonsNew uniqueSubtract lessonsOld + val lessonsToAdd = (lessonsNew uniqueSubtract lessonsOld).map { new -> + new.apply { if (notify) isNotified = false } + } timetableDb.deleteAll(lessonsToRemove) timetableDb.insertAll(lessonsToAdd) diff --git a/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt b/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt index cdf0c26a9..1729f1006 100644 --- a/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt +++ b/app/src/main/java/io/github/wulkanowy/services/ServicesModule.kt @@ -15,6 +15,7 @@ import dagger.multibindings.IntoSet import io.github.wulkanowy.services.sync.channels.Channel import io.github.wulkanowy.services.sync.channels.DebugChannel import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel +import io.github.wulkanowy.services.sync.channels.NewAttendanceChannel import io.github.wulkanowy.services.sync.channels.NewConferencesChannel import io.github.wulkanowy.services.sync.channels.NewExamChannel import io.github.wulkanowy.services.sync.channels.NewGradesChannel @@ -23,6 +24,7 @@ import io.github.wulkanowy.services.sync.channels.NewMessagesChannel import io.github.wulkanowy.services.sync.channels.NewNotesChannel import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel import io.github.wulkanowy.services.sync.channels.PushChannel +import io.github.wulkanowy.services.sync.channels.TimetableChangeChannel import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel import io.github.wulkanowy.services.sync.works.AttendanceSummaryWork import io.github.wulkanowy.services.sync.works.AttendanceWork @@ -167,4 +169,12 @@ abstract class ServicesModule { @Binds @IntoSet abstract fun provideUpcomingLessonsChannel(channel: UpcomingLessonsChannel): Channel + + @Binds + @IntoSet + abstract fun provideChangeTimetableChannel(channel: TimetableChangeChannel): Channel + + @Binds + @IntoSet + abstract fun provideNewAttendanceChannel(channel: NewAttendanceChannel): Channel } diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt index 9ce96ef3f..38ae7884c 100644 --- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationReceiver.kt @@ -15,8 +15,8 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.services.HiltBroadcastReceiver import io.github.wulkanowy.services.sync.channels.UpcomingLessonsChannel.Companion.CHANNEL_ID +import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.main.MainActivity -import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.flowWithResource import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.toLocalDateTime @@ -41,7 +41,7 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() { const val NOTIFICATION_TYPE_UPCOMING = 2 const val NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION = 3 - const val NOTIFICATION_ID = "id" + const val NOTIFICATION_ID = 2137 const val STUDENT_NAME = "student_name" const val STUDENT_ID = "student_id" @@ -71,11 +71,10 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() { private fun prepareNotification(context: Context, intent: Intent) { val type = intent.getIntExtra(LESSON_TYPE, 0) - val notificationId = intent.getIntExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id) val isPersistent = preferencesRepository.isUpcomingLessonsNotificationsPersistent if (type == NOTIFICATION_TYPE_LAST_LESSON_CANCELLATION) { - return NotificationManagerCompat.from(context).cancel(notificationId) + return NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID) } val studentId = intent.getIntExtra(STUDENT_ID, 0) @@ -92,7 +91,8 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() { Timber.d("TimetableNotification receive: type: $type, subject: $subject, start: ${start.toLocalDateTime()}, student: $studentId") - showNotification(context, notificationId, isPersistent, studentName, + showNotification( + context, isPersistent, studentName, if (type == NOTIFICATION_TYPE_CURRENT) end else start, end - start, context.getString( if (type == NOTIFICATION_TYPE_CURRENT) R.string.timetable_now else R.string.timetable_next, @@ -109,7 +109,6 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() { private fun showNotification( context: Context, - notificationId: Int, isPersistent: Boolean, studentName: String?, countDown: Long, @@ -118,7 +117,7 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() { next: String? ) { NotificationManagerCompat.from(context) - .notify(notificationId, NotificationCompat.Builder(context, CHANNEL_ID) + .notify(NOTIFICATION_ID, NotificationCompat.Builder(context, CHANNEL_ID) .setContentTitle(title) .setContentText(next) .setAutoCancel(false) @@ -138,8 +137,8 @@ class TimetableNotificationReceiver : HiltBroadcastReceiver() { .setContentIntent( PendingIntent.getActivity( context, - MainView.Section.TIMETABLE.id, - MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), + NOTIFICATION_ID, + MainActivity.getStartIntent(context, Destination.Timetable(), true), FLAG_UPDATE_CURRENT ) ) diff --git a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt index 2ec4e527b..28a9b3737 100644 --- a/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt +++ b/app/src/main/java/io/github/wulkanowy/services/alarm/TimetableNotificationSchedulerHelper.kt @@ -25,7 +25,6 @@ import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companio import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.NOTIFICATION_TYPE_UPCOMING import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_ID import io.github.wulkanowy.services.alarm.TimetableNotificationReceiver.Companion.STUDENT_NAME -import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.DispatchersProvider import io.github.wulkanowy.utils.nickOrName import io.github.wulkanowy.utils.toTimestamp @@ -79,7 +78,7 @@ class TimetableNotificationSchedulerHelper @Inject constructor( } fun cancelNotification() = - NotificationManagerCompat.from(context).cancel(MainView.Section.TIMETABLE.id) + NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID) suspend fun scheduleNotifications(lessons: List, student: Student) { if (!preferencesRepository.isUpcomingLessonsNotificationsEnable) { @@ -156,7 +155,6 @@ class TimetableNotificationSchedulerHelper @Inject constructor( AlarmManagerCompat.setExactAndAllowWhileIdle( alarmManager, RTC_WAKEUP, time.toTimestamp(), PendingIntent.getBroadcast(context, getRequestCode(time, studentId), intent.also { - it.putExtra(NOTIFICATION_ID, MainView.Section.TIMETABLE.id) it.putExtra(LESSON_TYPE, notificationType) }, FLAG_UPDATE_CURRENT) ) diff --git a/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt b/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt new file mode 100644 index 000000000..d1c215f26 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/shortcuts/ShortcutsHelper.kt @@ -0,0 +1,90 @@ +package io.github.wulkanowy.services.shortcuts + +import android.content.Context +import android.content.Intent +import androidx.core.content.pm.ShortcutInfoCompat +import androidx.core.content.pm.ShortcutManagerCompat +import androidx.core.graphics.drawable.IconCompat +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.main.MainActivity +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ShortcutsHelper @Inject constructor(@ApplicationContext private val context: Context) { + + private val destinations = mapOf( + "grade" to Destination.Grade, + "attendance" to Destination.Attendance, + "exam" to Destination.Exam, + "timetable" to Destination.Timetable() + ) + + init { + initializeShortcuts() + } + + fun getDestination(intent: Intent) = + destinations[intent.getStringExtra(EXTRA_SHORTCUT_DESTINATION_ID)] + + private fun initializeShortcuts() { + val shortcutsInfo = listOf( + ShortcutInfoCompat.Builder(context, "grade_shortcut") + .setShortLabel(context.getString(R.string.grade_title)) + .setLongLabel(context.getString(R.string.grade_title)) + .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_grade)) + .setIntent(MainActivity.getStartIntent(context, startNewTask = true) + .apply { + action = Intent.ACTION_VIEW + putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "grade") + } + ) + .build(), + + ShortcutInfoCompat.Builder(context, "attendance_shortcut") + .setShortLabel(context.getString(R.string.attendance_title)) + .setLongLabel(context.getString(R.string.attendance_title)) + .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_attendance)) + .setIntent(MainActivity.getStartIntent(context, startNewTask = true) + .apply { + action = Intent.ACTION_VIEW + putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "attendance") + } + ) + .build(), + + ShortcutInfoCompat.Builder(context, "exam_shortcut") + .setShortLabel(context.getString(R.string.exam_title)) + .setLongLabel(context.getString(R.string.exam_title)) + .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_exam)) + .setIntent(MainActivity.getStartIntent(context, startNewTask = true) + .apply { + action = Intent.ACTION_VIEW + putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "exam") + } + ) + .build(), + + ShortcutInfoCompat.Builder(context, "timetable_shortcut") + .setShortLabel(context.getString(R.string.timetable_title)) + .setLongLabel(context.getString(R.string.timetable_title)) + .setIcon(IconCompat.createWithResource(context, R.drawable.ic_shortcut_timetable)) + .setIntent(MainActivity.getStartIntent(context, startNewTask = true) + .apply { + action = Intent.ACTION_VIEW + putExtra(EXTRA_SHORTCUT_DESTINATION_ID, "timetable") + } + ) + .build() + ) + + shortcutsInfo.forEach { ShortcutManagerCompat.pushDynamicShortcut(context, it) } + } + + private companion object { + + private const val EXTRA_SHORTCUT_DESTINATION_ID = "shortcut_destination_id" + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewAttendanceChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewAttendanceChannel.kt new file mode 100644 index 000000000..3110099e5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/NewAttendanceChannel.kt @@ -0,0 +1,36 @@ +package io.github.wulkanowy.services.sync.channels + +import android.annotation.TargetApi +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import javax.inject.Inject + +@TargetApi(26) +class NewAttendanceChannel @Inject constructor( + private val notificationManager: NotificationManagerCompat, + @ApplicationContext private val context: Context +) : Channel { + + companion object { + const val CHANNEL_ID = "new_attendance_channel" + } + + override fun create() { + notificationManager.createNotificationChannel( + NotificationChannel( + CHANNEL_ID, + context.getString(R.string.channel_new_attendance), + NotificationManager.IMPORTANCE_HIGH + ) + .apply { + enableLights(true) + enableVibration(true) + lockscreenVisibility = Notification.VISIBILITY_PUBLIC + }) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/channels/TimetableChangeChannel.kt b/app/src/main/java/io/github/wulkanowy/services/sync/channels/TimetableChangeChannel.kt new file mode 100644 index 000000000..10dd3e004 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/channels/TimetableChangeChannel.kt @@ -0,0 +1,36 @@ +package io.github.wulkanowy.services.sync.channels + +import android.annotation.TargetApi +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import javax.inject.Inject + +@TargetApi(26) +class TimetableChangeChannel @Inject constructor( + private val notificationManager: NotificationManagerCompat, + @ApplicationContext private val context: Context +) : Channel { + + companion object { + const val CHANNEL_ID = "change_timetable_channel" + } + + override fun create() { + notificationManager.createNotificationChannel( + NotificationChannel( + CHANNEL_ID, + context.getString(R.string.channel_change_timetable), + NotificationManager.IMPORTANCE_HIGH + ) + .apply { + enableLights(true) + enableVibration(true) + lockscreenVisibility = Notification.VISIBILITY_PUBLIC + }) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt index ddad9bf27..542b9346b 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/AppNotificationManager.kt @@ -4,19 +4,15 @@ import android.annotation.SuppressLint import android.app.PendingIntent import android.content.Context import android.os.Build -import androidx.annotation.PluralsRes import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Notification import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.pojos.MultipleNotificationsData +import io.github.wulkanowy.data.pojos.GroupNotificationData import io.github.wulkanowy.data.pojos.NotificationData -import io.github.wulkanowy.data.pojos.OneNotificationData import io.github.wulkanowy.data.repositories.NotificationRepository -import io.github.wulkanowy.ui.modules.main.MainActivity -import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.getCompatBitmap import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.nickOrName @@ -27,102 +23,17 @@ import kotlin.random.Random class AppNotificationManager @Inject constructor( private val notificationManager: NotificationManagerCompat, @ApplicationContext private val context: Context, - private val appInfo: AppInfo, private val notificationRepository: NotificationRepository ) { - suspend fun sendNotification(notificationData: NotificationData, student: Student) = - when (notificationData) { - is OneNotificationData -> sendOneNotification(notificationData, student) - is MultipleNotificationsData -> sendMultipleNotifications(notificationData, student) - } - - private suspend fun sendOneNotification( - notificationData: OneNotificationData, - student: Student - ) { - val content = context.getString( - notificationData.contentStringRes, - *notificationData.contentValues.toTypedArray() - ) - - val title = context.getString(notificationData.titleStringRes) - - val notification = getDefaultNotificationBuilder(notificationData) - .setContentTitle(title) - .setContentText(content) - .setStyle( - NotificationCompat.BigTextStyle() - .setSummaryText(student.nickOrName) - .bigText(content) - ) - .build() - - notificationManager.notify(Random.nextInt(Int.MAX_VALUE), notification) - - saveNotification(title, content, notificationData, student) - } - - private suspend fun sendMultipleNotifications( - notificationData: MultipleNotificationsData, - student: Student - ) { - val groupType = notificationData.type.group ?: return - val group = "${groupType}_${student.id}" - - notificationData.sendSummaryNotification(group, student) - - notificationData.lines.forEach { item -> - val title = context.resources.getQuantityString(notificationData.titleStringRes, 1) - - val notification = getDefaultNotificationBuilder(notificationData) - .setContentTitle(title) - .setContentText(item) - .setStyle( - NotificationCompat.BigTextStyle() - .setSummaryText(student.nickOrName) - .bigText(item) - ) - .setGroup(group) - .build() - - notificationManager.notify(Random.nextInt(Int.MAX_VALUE), notification) - - saveNotification(title, item, notificationData, student) - } - } - - private fun MultipleNotificationsData.sendSummaryNotification(group: String, student: Student) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return - - val summaryNotification = getDefaultNotificationBuilder(this) - .setSmallIcon(icon) - .setContentTitle(getQuantityString(titleStringRes, lines.size)) - .setContentText(getQuantityString(contentStringRes, lines.size)) - .setStyle( - NotificationCompat.InboxStyle() - .setSummaryText(student.nickOrName) - .also { builder -> lines.forEach { builder.addLine(it) } } - ) - .setLocalOnly(true) - .setGroup(group) - .setGroupSummary(true) - .build() - - val groupId = student.id * 100 + type.ordinal - notificationManager.notify(groupId.toInt(), summaryNotification) - } - @SuppressLint("InlinedApi") - private fun getDefaultNotificationBuilder(notificationData: NotificationData): NotificationCompat.Builder { - val pendingIntentsFlags = if (appInfo.systemVersion >= Build.VERSION_CODES.M) { - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - } else { - PendingIntent.FLAG_UPDATE_CURRENT - } - - return NotificationCompat.Builder(context, notificationData.type.channel) - .setLargeIcon(context.getCompatBitmap(notificationData.icon, R.color.colorPrimary)) + suspend fun sendSingleNotification( + notificationData: NotificationData, + notificationType: NotificationType, + student: Student + ) { + val notification = NotificationCompat.Builder(context, notificationType.channel) + .setLargeIcon(context.getCompatBitmap(notificationType.icon, R.color.colorPrimary)) .setSmallIcon(R.drawable.ic_stat_all) .setAutoCancel(true) .setDefaults(NotificationCompat.DEFAULT_ALL) @@ -132,31 +43,122 @@ class AppNotificationManager @Inject constructor( .setContentIntent( PendingIntent.getActivity( context, - notificationData.startMenu.id, - MainActivity.getStartIntent(context, notificationData.startMenu, true), - pendingIntentsFlags + Random.nextInt(), + notificationData.intentToStart, + PendingIntent.FLAG_UPDATE_CURRENT ) ) + .setContentTitle(notificationData.title) + .setContentText(notificationData.content) + .setStyle( + NotificationCompat.BigTextStyle() + .setSummaryText(student.nickOrName) + .bigText(notificationData.content) + ) + .build() + + notificationManager.notify(Random.nextInt(), notification) + saveNotification(notificationData, notificationType, student) + } + + @SuppressLint("InlinedApi") + suspend fun sendMultipleNotifications( + groupNotificationData: GroupNotificationData, + student: Student + ) { + val notificationType = groupNotificationData.type + val groupType = notificationType.group ?: return + val group = "${groupType}_${student.id}" + + sendSummaryNotification(groupNotificationData, group, student) + + groupNotificationData.notificationDataList.forEach { notificationData -> + val notification = NotificationCompat.Builder(context, notificationType.channel) + .setLargeIcon(context.getCompatBitmap(notificationType.icon, R.color.colorPrimary)) + .setSmallIcon(R.drawable.ic_stat_all) + .setAutoCancel(true) + .setDefaults(NotificationCompat.DEFAULT_ALL) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setColor(context.getCompatColor(R.color.colorPrimary)) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) + .setContentIntent( + PendingIntent.getActivity( + context, + Random.nextInt(), + notificationData.intentToStart, + PendingIntent.FLAG_UPDATE_CURRENT + ) + ) + .setContentTitle(notificationData.title) + .setContentText(notificationData.content) + .setStyle( + NotificationCompat.BigTextStyle() + .setSummaryText(student.nickOrName) + .bigText(notificationData.content) + ) + .setGroup(group) + .build() + + notificationManager.notify(Random.nextInt(), notification) + saveNotification(notificationData, groupNotificationData.type, student) + } + } + + private fun sendSummaryNotification( + groupNotificationData: GroupNotificationData, + group: String, + student: Student + ) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return + + val summaryNotification = + NotificationCompat.Builder(context, groupNotificationData.type.channel) + .setContentTitle(groupNotificationData.title) + .setContentText(groupNotificationData.content) + .setSmallIcon(groupNotificationData.type.icon) + .setAutoCancel(true) + .setDefaults(NotificationCompat.DEFAULT_ALL) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setColor(context.getCompatColor(R.color.colorPrimary)) + .setStyle( + NotificationCompat.InboxStyle() + .setSummaryText(student.nickOrName) + .also { builder -> + groupNotificationData.notificationDataList.forEach { + builder.addLine(it.content) + } + } + ) + .setContentIntent( + PendingIntent.getActivity( + context, + Random.nextInt(), + groupNotificationData.intentToStart, + PendingIntent.FLAG_UPDATE_CURRENT + ) + ) + .setLocalOnly(true) + .setGroup(group) + .setGroupSummary(true) + .build() + + val groupId = student.id * 100 + groupNotificationData.type.ordinal + notificationManager.notify(groupId.toInt(), summaryNotification) } private suspend fun saveNotification( - title: String, - content: String, notificationData: NotificationData, + notificationType: NotificationType, student: Student ) { val notificationEntity = Notification( studentId = student.id, - title = title, - content = content, - type = notificationData.type, + title = notificationData.title, + content = notificationData.content, + type = notificationType, date = LocalDateTime.now() ) notificationRepository.saveNotification(notificationEntity) } - - private fun getQuantityString(@PluralsRes res: Int, arg: Int): String { - return context.resources.getQuantityString(res, arg, arg) - } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/ChangeTimetableNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/ChangeTimetableNotification.kt new file mode 100644 index 000000000..6d2d3a59f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/ChangeTimetableNotification.kt @@ -0,0 +1,125 @@ +package io.github.wulkanowy.services.sync.notifications + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.Timetable +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.utils.getPlural +import io.github.wulkanowy.utils.toFormattedString +import java.time.LocalDate +import java.time.LocalDateTime +import javax.inject.Inject + +class ChangeTimetableNotification @Inject constructor( + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context, +) { + + suspend fun notify(items: List, student: Student) { + val currentTime = LocalDateTime.now() + val changedLessons = items.filter { (it.canceled || it.changes) && it.start > currentTime } + val notificationDataList = changedLessons.groupBy { it.date } + .map { (date, lessons) -> + getNotificationContents(date, lessons).map { + NotificationData( + title = context.getPlural( + R.plurals.timetable_notify_new_items_title, + 1 + ), + content = it, + intentToStart = MainActivity.getStartIntent( + context = context, + destination = Destination.Timetable(date), + startNewTask = true + ) + ) + } + } + .flatten() + .ifEmpty { return } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural( + R.plurals.timetable_notify_new_items_title, + changedLessons.size + ), + content = context.getPlural( + R.plurals.timetable_notify_new_items_group, + changedLessons.size, + changedLessons.size + ), + intentToStart = MainActivity.getStartIntent(context, Destination.Timetable(), true), + type = NotificationType.CHANGE_TIMETABLE + ) + + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) + } + + private fun getNotificationContents(date: LocalDate, lessons: List): List { + val formattedDate = date.toFormattedString("EEE dd.MM") + + return if (lessons.size > 2) { + listOf( + context.getPlural( + R.plurals.timetable_notify_new_items, + lessons.size, + formattedDate, + lessons.size, + ) + ) + } else { + lessons.map { + buildString { + append( + context.getString( + R.string.timetable_notify_lesson, + formattedDate, + it.number, + it.subject + ) + ) + if (it.roomOld.isNotBlank()) { + appendLine() + append( + context.getString( + R.string.timetable_notify_change_room, + it.roomOld, + it.room + ) + ) + } + if (it.teacherOld.isNotBlank() && it.teacher != it.teacherOld) { + appendLine() + append( + context.getString( + R.string.timetable_notify_change_teacher, + it.teacherOld, + it.teacher + ) + ) + } + if (it.subjectOld.isNotBlank()) { + appendLine() + append( + context.getString( + R.string.timetable_notify_change_subject, + it.subjectOld, + it.subject + ) + ) + } + if (it.info.isNotBlank()) { + appendLine() + append(it.info) + } + } + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewAttendanceNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewAttendanceNotification.kt new file mode 100644 index 000000000..695552721 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewAttendanceNotification.kt @@ -0,0 +1,55 @@ +package io.github.wulkanowy.services.sync.notifications + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.Attendance +import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.utils.descriptionRes +import io.github.wulkanowy.utils.getPlural +import io.github.wulkanowy.utils.toFormattedString +import javax.inject.Inject + +class NewAttendanceNotification @Inject constructor( + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context +) { + + suspend fun notify(items: List, student: Student) { + val lines = items.filterNot { it.presence || it.name == "UNKNOWN" } + .map { + val description = context.getString(it.descriptionRes) + "${it.date.toFormattedString("dd.MM")} - ${it.subject}: $description" + } + .ifEmpty { return } + + val notificationDataList = lines.map { + NotificationData( + title = context.getPlural(R.plurals.attendance_notify_new_items_title, 1), + content = it, + intentToStart = MainActivity.getStartIntent(context, Destination.Attendance, true) + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural( + R.plurals.attendance_notify_new_items_title, + notificationDataList.size + ), + content = context.getPlural( + R.plurals.attendance_notify_new_items, + notificationDataList.size, + notificationDataList.size + ), + intentToStart = MainActivity.getStartIntent(context, Destination.Attendance, true), + type = NotificationType.NEW_ATTENDANCE + ) + + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt index 994cb8d4b..97b1332d8 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewConferenceNotification.kt @@ -1,34 +1,52 @@ package io.github.wulkanowy.services.sync.notifications +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Conference import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.pojos.MultipleNotificationsData -import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.utils.getPlural import io.github.wulkanowy.utils.toFormattedString import java.time.LocalDateTime import javax.inject.Inject class NewConferenceNotification @Inject constructor( - private val appNotificationManager: AppNotificationManager + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context ) { suspend fun notify(items: List, student: Student) { val today = LocalDateTime.now() - val lines = items.filter { !it.date.isBefore(today) }.map { - "${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}" - }.ifEmpty { return } + val lines = items.filter { !it.date.isBefore(today) } + .map { + "${it.date.toFormattedString("dd.MM")} - ${it.title}: ${it.subject}" + } + .ifEmpty { return } - val notification = MultipleNotificationsData( - type = NotificationType.NEW_CONFERENCE, - icon = R.drawable.ic_more_conferences, - titleStringRes = R.plurals.conference_notify_new_item_title, - contentStringRes = R.plurals.conference_notify_new_items, - summaryStringRes = R.plurals.conference_number_item, - startMenu = MainView.Section.CONFERENCE, - lines = lines + val notificationDataList = lines.map { + NotificationData( + title = context.getPlural(R.plurals.conference_notify_new_item_title, 1), + content = it, + intentToStart = MainActivity.getStartIntent(context, Destination.Conference, true) + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural(R.plurals.conference_notify_new_item_title, lines.size), + content = context.getPlural( + R.plurals.conference_notify_new_items, + lines.size, + lines.size + ), + intentToStart = MainActivity.getStartIntent(context, Destination.Conference, true), + type = NotificationType.NEW_CONFERENCE ) - appNotificationManager.sendNotification(notification, student) + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt index f148fa34f..6f8ed8961 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewExamNotification.kt @@ -1,34 +1,52 @@ package io.github.wulkanowy.services.sync.notifications +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.pojos.MultipleNotificationsData -import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.utils.getPlural import io.github.wulkanowy.utils.toFormattedString import java.time.LocalDate import javax.inject.Inject class NewExamNotification @Inject constructor( - private val appNotificationManager: AppNotificationManager + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context ) { suspend fun notify(items: List, student: Student) { val today = LocalDate.now() - val lines = items.filter { !it.date.isBefore(today) }.map { - "${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}" - }.ifEmpty { return } + val lines = items.filter { !it.date.isBefore(today) } + .map { + "${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.description}" + } + .ifEmpty { return } - val notification = MultipleNotificationsData( - type = NotificationType.NEW_EXAM, - icon = R.drawable.ic_main_exam, - titleStringRes = R.plurals.exam_notify_new_item_title, - contentStringRes = R.plurals.exam_notify_new_item_content, - summaryStringRes = R.plurals.exam_number_item, - startMenu = MainView.Section.EXAM, - lines = lines + val notificationDataList = lines.map { + NotificationData( + title = context.getPlural(R.plurals.exam_notify_new_item_title, 1), + content = it, + intentToStart = MainActivity.getStartIntent(context, Destination.Exam, true), + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural(R.plurals.exam_notify_new_item_title, lines.size), + content = context.getPlural( + R.plurals.exam_notify_new_item_content, + lines.size, + lines.size + ), + intentToStart = MainActivity.getStartIntent(context, Destination.Exam, true), + type = NotificationType.NEW_EXAM ) - appNotificationManager.sendNotification(notification, student) + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt index 52bdff588..09692fb7c 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewGradeNotification.kt @@ -1,62 +1,88 @@ package io.github.wulkanowy.services.sync.notifications +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.pojos.MultipleNotificationsData -import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.utils.getPlural import javax.inject.Inject class NewGradeNotification @Inject constructor( - private val appNotificationManager: AppNotificationManager + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context ) { suspend fun notifyDetails(items: List, student: Student) { - val notification = MultipleNotificationsData( - type = NotificationType.NEW_GRADE_DETAILS, - icon = R.drawable.ic_stat_grade, - titleStringRes = R.plurals.grade_new_items, - contentStringRes = R.plurals.grade_notify_new_items, - summaryStringRes = R.plurals.grade_number_item, - startMenu = MainView.Section.GRADE, - lines = items.map { - "${it.subject}: ${it.entry}" - } + val notificationDataList = items.map { + NotificationData( + title = context.getPlural(R.plurals.grade_new_items, 1), + content = "${it.subject}: ${it.entry}", + intentToStart = MainActivity.getStartIntent(context, Destination.Grade, true), + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural(R.plurals.grade_new_items, items.size), + content = context.getPlural(R.plurals.grade_notify_new_items, items.size, items.size), + intentToStart = MainActivity.getStartIntent(context, Destination.Grade, true), + type = NotificationType.NEW_GRADE_DETAILS ) - appNotificationManager.sendNotification(notification, student) + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) } suspend fun notifyPredicted(items: List, student: Student) { - val notification = MultipleNotificationsData( - type = NotificationType.NEW_GRADE_PREDICTED, - icon = R.drawable.ic_stat_grade, - titleStringRes = R.plurals.grade_new_items_predicted, - contentStringRes = R.plurals.grade_notify_new_items_predicted, - summaryStringRes = R.plurals.grade_number_item, - startMenu = MainView.Section.GRADE, - lines = items.map { - "${it.subject}: ${it.predictedGrade}" - } + val notificationDataList = items.map { + NotificationData( + title = context.getPlural(R.plurals.grade_new_items_predicted, 1), + content = "${it.subject}: ${it.predictedGrade}", + intentToStart = MainActivity.getStartIntent(context, Destination.Grade, true), + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural(R.plurals.grade_new_items_predicted, items.size), + content = context.getPlural( + R.plurals.grade_notify_new_items_predicted, + items.size, + items.size + ), + intentToStart = MainActivity.getStartIntent(context, Destination.Grade, true), + type = NotificationType.NEW_GRADE_PREDICTED ) - appNotificationManager.sendNotification(notification, student) + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) } suspend fun notifyFinal(items: List, student: Student) { - val notification = MultipleNotificationsData( - type = NotificationType.NEW_GRADE_FINAL, - icon = R.drawable.ic_stat_grade, - titleStringRes = R.plurals.grade_new_items_final, - contentStringRes = R.plurals.grade_notify_new_items_final, - summaryStringRes = R.plurals.grade_number_item, - startMenu = MainView.Section.GRADE, - lines = items.map { - "${it.subject}: ${it.finalGrade}" - } + val notificationDataList = items.map { + NotificationData( + title = context.getPlural(R.plurals.grade_new_items_final, 1), + content = "${it.subject}: ${it.finalGrade}", + intentToStart = MainActivity.getStartIntent(context, Destination.Grade, true), + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural(R.plurals.grade_new_items_final, items.size), + content = context.getPlural( + R.plurals.grade_notify_new_items_final, + items.size, + items.size + ), + intentToStart = MainActivity.getStartIntent(context, Destination.Grade, true), + type = NotificationType.NEW_GRADE_FINAL ) - appNotificationManager.sendNotification(notification, student) + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt index 4c34cb8ff..cdada844e 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewHomeworkNotification.kt @@ -1,34 +1,52 @@ package io.github.wulkanowy.services.sync.notifications +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.pojos.MultipleNotificationsData -import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.utils.getPlural import io.github.wulkanowy.utils.toFormattedString import java.time.LocalDate import javax.inject.Inject class NewHomeworkNotification @Inject constructor( - private val appNotificationManager: AppNotificationManager + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context ) { suspend fun notify(items: List, student: Student) { val today = LocalDate.now() - val lines = items.filter { !it.date.isBefore(today) }.map { - "${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}" - }.ifEmpty { return } + val lines = items.filter { !it.date.isBefore(today) } + .map { + "${it.date.toFormattedString("dd.MM")} - ${it.subject}: ${it.content}" + } + .ifEmpty { return } - val notification = MultipleNotificationsData( + val notificationDataList = lines.map { + NotificationData( + title = context.getPlural(R.plurals.homework_notify_new_item_title, 1), + content = it, + intentToStart = MainActivity.getStartIntent(context, Destination.Homework, true), + ) + } + + val groupNotificationData = GroupNotificationData( + title = context.getPlural(R.plurals.homework_notify_new_item_title, lines.size), + content = context.getPlural( + R.plurals.homework_notify_new_item_content, + lines.size, + lines.size + ), + intentToStart = MainActivity.getStartIntent(context, Destination.Homework, true), type = NotificationType.NEW_HOMEWORK, - icon = R.drawable.ic_more_homework, - titleStringRes = R.plurals.homework_notify_new_item_title, - contentStringRes = R.plurals.homework_notify_new_item_content, - summaryStringRes = R.plurals.homework_number_item, - startMenu = MainView.Section.HOMEWORK, - lines = lines + notificationDataList = notificationDataList ) - appNotificationManager.sendNotification(notification, student) + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewLuckyNumberNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewLuckyNumberNotification.kt index 08c985106..d9f138b5c 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewLuckyNumberNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewLuckyNumberNotification.kt @@ -1,26 +1,34 @@ package io.github.wulkanowy.services.sync.notifications +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.LuckyNumber import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.pojos.OneNotificationData -import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.main.MainActivity import javax.inject.Inject class NewLuckyNumberNotification @Inject constructor( - private val appNotificationManager: AppNotificationManager + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context ) { - suspend fun notify(item: LuckyNumber, student: Student) { - val notification = OneNotificationData( - type = NotificationType.NEW_LUCKY_NUMBER, - icon = R.drawable.ic_stat_luckynumber, - titleStringRes = R.string.lucky_number_notify_new_item_title, - contentStringRes = R.string.lucky_number_notify_new_item, - startMenu = MainView.Section.LUCKY_NUMBER, - contentValues = listOf(item.luckyNumber.toString()) - ) + suspend fun notify(item: LuckyNumber, student: Student) { + val notificationData = NotificationData( + title = context.getString(R.string.lucky_number_notify_new_item_title), + content = context.getString( + R.string.lucky_number_notify_new_item, + item.luckyNumber.toString() + ), + intentToStart = MainActivity.getStartIntent(context, Destination.LuckyNumber, true) + ) - appNotificationManager.sendNotification(notification, student) + appNotificationManager.sendSingleNotification( + notificationData = notificationData, + notificationType = NotificationType.NEW_LUCKY_NUMBER, + student = student + ) } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt index a6d503aa0..cdb5ab9c8 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewMessageNotification.kt @@ -1,29 +1,39 @@ package io.github.wulkanowy.services.sync.notifications +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.pojos.MultipleNotificationsData -import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.utils.getPlural import javax.inject.Inject class NewMessageNotification @Inject constructor( - private val appNotificationManager: AppNotificationManager + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context ) { suspend fun notify(items: List, student: Student) { - val notification = MultipleNotificationsData( - type = NotificationType.NEW_MESSAGE, - icon = R.drawable.ic_stat_message, - titleStringRes = R.plurals.message_new_items, - contentStringRes = R.plurals.message_notify_new_items, - summaryStringRes = R.plurals.message_number_item, - startMenu = MainView.Section.MESSAGE, - lines = items.map { - "${it.sender}: ${it.subject}" - } + val notificationDataList = items.map { + NotificationData( + title = context.getPlural(R.plurals.message_new_items, 1), + content = "${it.sender}: ${it.subject}", + intentToStart = MainActivity.getStartIntent(context, Destination.Message, true), + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + title = context.getPlural(R.plurals.message_new_items, items.size), + content = context.getPlural(R.plurals.message_notify_new_items, items.size, items.size), + intentToStart = MainActivity.getStartIntent(context, Destination.Message, true), + type = NotificationType.NEW_MESSAGE ) - appNotificationManager.sendNotification(notification, student) + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewNoteNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewNoteNotification.kt index ffa3cc9cb..16be1ca50 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewNoteNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewNoteNotification.kt @@ -1,42 +1,46 @@ package io.github.wulkanowy.services.sync.notifications +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.pojos.MultipleNotificationsData +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData import io.github.wulkanowy.sdk.scrapper.notes.NoteCategory -import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.utils.getPlural import javax.inject.Inject class NewNoteNotification @Inject constructor( - private val appNotificationManager: AppNotificationManager + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context ) { suspend fun notify(items: List, student: Student) { - val notification = MultipleNotificationsData( - type = NotificationType.NEW_NOTE, - icon = R.drawable.ic_stat_note, - titleStringRes = when (NoteCategory.getByValue(items.first().categoryType)) { + val notificationDataList = items.map { + val titleRes = when (NoteCategory.getByValue(it.categoryType)) { NoteCategory.POSITIVE -> R.plurals.praise_new_items NoteCategory.NEUTRAL -> R.plurals.neutral_note_new_items else -> R.plurals.note_new_items - }, - contentStringRes = when (NoteCategory.getByValue(items.first().categoryType)) { - NoteCategory.POSITIVE -> R.plurals.praise_notify_new_items - NoteCategory.NEUTRAL -> R.plurals.neutral_note_notify_new_items - else -> R.plurals.note_notify_new_items - }, - summaryStringRes = when (NoteCategory.getByValue(items.first().categoryType)) { - NoteCategory.POSITIVE -> R.plurals.praise_number_item - NoteCategory.NEUTRAL -> R.plurals.neutral_note_number_item - else -> R.plurals.note_number_item - }, - startMenu = MainView.Section.NOTE, - lines = items.map { - "${it.teacher}: ${it.category}" } + + NotificationData( + title = context.getPlural(titleRes, 1), + content = "${it.teacher}: ${it.category}", + intentToStart = MainActivity.getStartIntent(context, Destination.Note, true), + ) + } + + val groupNotificationData = GroupNotificationData( + notificationDataList = notificationDataList, + intentToStart = MainActivity.getStartIntent(context, Destination.Note, true), + title = context.getPlural(R.plurals.note_new_items, items.size), + content = context.getPlural(R.plurals.note_notify_new_items, items.size, items.size), + type = NotificationType.NEW_NOTE ) - appNotificationManager.sendNotification(notification, student) + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewSchoolAnnouncementNotification.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewSchoolAnnouncementNotification.kt index 990a950b1..1f603624b 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewSchoolAnnouncementNotification.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NewSchoolAnnouncementNotification.kt @@ -1,29 +1,56 @@ package io.github.wulkanowy.services.sync.notifications +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.SchoolAnnouncement import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.data.pojos.MultipleNotificationsData -import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.data.pojos.GroupNotificationData +import io.github.wulkanowy.data.pojos.NotificationData +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.main.MainActivity +import io.github.wulkanowy.utils.getPlural import javax.inject.Inject class NewSchoolAnnouncementNotification @Inject constructor( - private val appNotificationManager: AppNotificationManager + private val appNotificationManager: AppNotificationManager, + @ApplicationContext private val context: Context ) { - suspend fun notify(items: List, student: Student) { - val notification = MultipleNotificationsData( - type = NotificationType.NEW_ANNOUNCEMENT, - icon = R.drawable.ic_all_about, - titleStringRes = R.plurals.school_announcement_notify_new_item_title, - contentStringRes = R.plurals.school_announcement_notify_new_items, - summaryStringRes = R.plurals.school_announcement_number_item, - startMenu = MainView.Section.SCHOOL_ANNOUNCEMENT, - lines = items.map { - "${it.subject}: ${it.content}" - } + suspend fun notify(items: List, student: Student) { + val notificationDataList = items.map { + NotificationData( + intentToStart = MainActivity.getStartIntent( + context = context, + destination = Destination.SchoolAnnouncement, + startNewTask = true + ), + title = context.getPlural( + R.plurals.school_announcement_notify_new_item_title, + 1 + ), + content = "${it.subject}: ${it.content}" + ) + } + val groupNotificationData = GroupNotificationData( + type = NotificationType.NEW_ANNOUNCEMENT, + intentToStart = MainActivity.getStartIntent( + context = context, + destination = Destination.SchoolAnnouncement, + startNewTask = true + ), + title = context.getPlural( + R.plurals.school_announcement_notify_new_item_title, + items.size + ), + content = context.getPlural( + R.plurals.school_announcement_notify_new_items, + items.size, + items.size + ), + notificationDataList = notificationDataList ) - appNotificationManager.sendNotification(notification, student) + appNotificationManager.sendMultipleNotifications(groupNotificationData, student) } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt index 49cbcfe9e..af79fcd2a 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/notifications/NotificationType.kt @@ -1,6 +1,8 @@ package io.github.wulkanowy.services.sync.notifications +import io.github.wulkanowy.R import io.github.wulkanowy.services.sync.channels.LuckyNumberChannel +import io.github.wulkanowy.services.sync.channels.NewAttendanceChannel import io.github.wulkanowy.services.sync.channels.NewConferencesChannel import io.github.wulkanowy.services.sync.channels.NewExamChannel import io.github.wulkanowy.services.sync.channels.NewGradesChannel @@ -9,17 +11,76 @@ import io.github.wulkanowy.services.sync.channels.NewMessagesChannel import io.github.wulkanowy.services.sync.channels.NewNotesChannel import io.github.wulkanowy.services.sync.channels.NewSchoolAnnouncementsChannel import io.github.wulkanowy.services.sync.channels.PushChannel +import io.github.wulkanowy.services.sync.channels.TimetableChangeChannel -enum class NotificationType(val group: String?, val channel: String) { - NEW_CONFERENCE("new_conferences_group", NewConferencesChannel.CHANNEL_ID), - NEW_EXAM("new_exam_group", NewExamChannel.CHANNEL_ID), - NEW_GRADE_DETAILS("new_grade_details_group", NewGradesChannel.CHANNEL_ID), - NEW_GRADE_PREDICTED("new_grade_predicted_group", NewGradesChannel.CHANNEL_ID), - NEW_GRADE_FINAL("new_grade_final_group", NewGradesChannel.CHANNEL_ID), - NEW_HOMEWORK("new_homework_group", NewHomeworkChannel.CHANNEL_ID), - NEW_LUCKY_NUMBER("lucky_number_group", LuckyNumberChannel.CHANNEL_ID), - NEW_MESSAGE("new_message_group", NewMessagesChannel.CHANNEL_ID), - NEW_NOTE("new_notes_group", NewNotesChannel.CHANNEL_ID), - NEW_ANNOUNCEMENT("new_school_announcements_group", NewSchoolAnnouncementsChannel.CHANNEL_ID), - PUSH(null, PushChannel.CHANNEL_ID) +enum class NotificationType( + val group: String?, + val channel: String, + val icon: Int +) { + NEW_CONFERENCE( + group = "new_conferences_group", + channel = NewConferencesChannel.CHANNEL_ID, + icon = R.drawable.ic_more_conferences, + ), + NEW_EXAM( + group = "new_exam_group", + channel = NewExamChannel.CHANNEL_ID, + icon = R.drawable.ic_main_exam + ), + NEW_GRADE_DETAILS( + group = "new_grade_details_group", + channel = NewGradesChannel.CHANNEL_ID, + icon = R.drawable.ic_stat_grade, + ), + NEW_GRADE_PREDICTED( + group = "new_grade_predicted_group", + channel = NewGradesChannel.CHANNEL_ID, + icon = R.drawable.ic_stat_grade, + ), + NEW_GRADE_FINAL( + group = "new_grade_final_group", + channel = NewGradesChannel.CHANNEL_ID, + icon = R.drawable.ic_stat_grade, + ), + NEW_HOMEWORK( + group = "new_homework_group", + channel = NewHomeworkChannel.CHANNEL_ID, + icon = R.drawable.ic_more_homework, + ), + NEW_LUCKY_NUMBER( + group = null, + channel = LuckyNumberChannel.CHANNEL_ID, + icon = R.drawable.ic_stat_luckynumber, + ), + NEW_MESSAGE( + group = "new_message_group", + channel = NewMessagesChannel.CHANNEL_ID, + icon = R.drawable.ic_stat_message, + ), + NEW_NOTE( + group = "new_notes_group", + channel = NewNotesChannel.CHANNEL_ID, + icon = R.drawable.ic_stat_note + ), + NEW_ANNOUNCEMENT( + group = "new_school_announcements_group", + channel = NewSchoolAnnouncementsChannel.CHANNEL_ID, + icon = R.drawable.ic_all_about + ), + CHANGE_TIMETABLE( + group = "change_timetable_group", + channel = TimetableChangeChannel.CHANNEL_ID, + icon = R.drawable.ic_main_timetable + ), + NEW_ATTENDANCE( + group = "new_attendance_group", + channel = NewAttendanceChannel.CHANNEL_ID, + icon = R.drawable.ic_main_attendance + ), + PUSH( + group = null, + channel = PushChannel.CHANNEL_ID, + icon = R.drawable.ic_stat_all + ) } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt index 4823b2b5b..f7b680e31 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/AttendanceWork.kt @@ -3,18 +3,40 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.AttendanceRepository -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.sunday +import io.github.wulkanowy.data.repositories.PreferencesRepository +import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification +import io.github.wulkanowy.utils.previousOrSameSchoolDay import io.github.wulkanowy.utils.waitForResult +import kotlinx.coroutines.flow.first import java.time.LocalDate.now import javax.inject.Inject class AttendanceWork @Inject constructor( - private val attendanceRepository: AttendanceRepository + private val attendanceRepository: AttendanceRepository, + private val newAttendanceNotification: NewAttendanceNotification, + private val preferencesRepository: PreferencesRepository ) : Work { override suspend fun doWork(student: Student, semester: Semester) { - attendanceRepository.getAttendance(student, semester, now().monday, now().sunday, true) + attendanceRepository.getAttendance( + student = student, + semester = semester, + start = now().previousOrSameSchoolDay, + end = now().previousOrSameSchoolDay, + forceRefresh = true, + notify = preferencesRepository.isNotificationsEnable + ) .waitForResult() + + attendanceRepository.getAttendanceFromDatabase(semester, now().minusDays(7), now()) + .first() + .filterNot { it.isNotified } + .let { + if (it.isNotEmpty()) newAttendanceNotification.notify(it, student) + + attendanceRepository.updateTimetable(it.onEach { attendance -> + attendance.isNotified = true + }) + } } } diff --git a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt index 2df2c9dcb..fcc330638 100644 --- a/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt +++ b/app/src/main/java/io/github/wulkanowy/services/sync/works/TimetableWork.kt @@ -2,18 +2,41 @@ package io.github.wulkanowy.services.sync.works import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.TimetableRepository -import io.github.wulkanowy.utils.monday -import io.github.wulkanowy.utils.sunday +import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification +import io.github.wulkanowy.utils.nextOrSameSchoolDay import io.github.wulkanowy.utils.waitForResult +import kotlinx.coroutines.flow.first import java.time.LocalDate.now import javax.inject.Inject class TimetableWork @Inject constructor( - private val timetableRepository: TimetableRepository + private val timetableRepository: TimetableRepository, + private val changeTimetableNotification: ChangeTimetableNotification, + private val preferencesRepository: PreferencesRepository ) : Work { override suspend fun doWork(student: Student, semester: Semester) { - timetableRepository.getTimetable(student, semester, now().monday, now().sunday, true).waitForResult() + timetableRepository.getTimetable( + student = student, + semester = semester, + start = now().nextOrSameSchoolDay, + end = now().nextOrSameSchoolDay, + forceRefresh = true, + notify = preferencesRepository.isNotificationsEnable + ) + .waitForResult() + + timetableRepository.getTimetableFromDatabase(semester, now(), now().plusDays(7)) + .first() + .filterNot { it.isNotified } + .let { + if (it.isNotEmpty()) changeTimetableNotification.notify(it, student) + + timetableRepository.updateTimetable(it.onEach { timetable -> + timetable.isNotified = true + }) + } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt new file mode 100644 index 000000000..0406afa41 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/Destination.kt @@ -0,0 +1,132 @@ +package io.github.wulkanowy.ui.modules + +import androidx.fragment.app.Fragment +import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment +import io.github.wulkanowy.ui.modules.conference.ConferenceFragment +import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment +import io.github.wulkanowy.ui.modules.exam.ExamFragment +import io.github.wulkanowy.ui.modules.grade.GradeFragment +import io.github.wulkanowy.ui.modules.homework.HomeworkFragment +import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment +import io.github.wulkanowy.ui.modules.message.MessageFragment +import io.github.wulkanowy.ui.modules.more.MoreFragment +import io.github.wulkanowy.ui.modules.note.NoteFragment +import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolFragment +import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment +import io.github.wulkanowy.ui.modules.timetable.TimetableFragment +import java.io.Serializable +import java.time.LocalDate + +sealed interface Destination : Serializable { + + val type: Type + + val fragment: Fragment + + enum class Type(val defaultDestination: Destination) { + DASHBOARD(Dashboard), + GRADE(Grade), + ATTENDANCE(Attendance), + EXAM(Exam), + TIMETABLE(Timetable()), + HOMEWORK(Homework), + NOTE(Note), + CONFERENCE(Conference), + SCHOOL_ANNOUNCEMENT(SchoolAnnouncement), + SCHOOL(School), + LUCKY_NUMBER(More), + MORE(More), + MESSAGE(Message); + } + + object Dashboard : Destination { + + override val type = Type.DASHBOARD + + override val fragment get() = DashboardFragment.newInstance() + } + + object Grade : Destination { + + override val type = Type.GRADE + + override val fragment get() = GradeFragment.newInstance() + } + + object Attendance : Destination { + + override val type = Type.ATTENDANCE + + override val fragment get() = AttendanceFragment.newInstance() + } + + object Exam : Destination { + + override val type = Type.EXAM + + override val fragment get() = ExamFragment.newInstance() + } + + data class Timetable(val date: LocalDate? = null) : Destination { + + override val type = Type.TIMETABLE + + override val fragment get() = TimetableFragment.newInstance(date) + } + + object Homework : Destination { + + override val type = Type.HOMEWORK + + override val fragment get() = HomeworkFragment.newInstance() + } + + object Note : Destination { + + override val type = Type.NOTE + + override val fragment get() = NoteFragment.newInstance() + } + + object Conference : Destination { + + override val type = Type.CONFERENCE + + override val fragment get() = ConferenceFragment.newInstance() + } + + object SchoolAnnouncement : Destination { + + override val type = Type.SCHOOL_ANNOUNCEMENT + + override val fragment get() = SchoolAnnouncementFragment.newInstance() + } + + object School : Destination { + + override val type = Type.SCHOOL + + override val fragment get() = SchoolFragment.newInstance() + } + + object LuckyNumber : Destination { + + override val type = Type.LUCKY_NUMBER + + override val fragment get() = LuckyNumberFragment.newInstance() + } + + object More : Destination { + + override val type = Type.MORE + + override val fragment get() = MoreFragment.newInstance() + } + + object Message : Destination { + + override val type = Type.MESSAGE + + override val fragment get() = MessageFragment.newInstance() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt index bb4f70224..5d5ed504c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceAdapter.kt @@ -9,7 +9,7 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.enums.SentExcuseStatus import io.github.wulkanowy.databinding.ItemAttendanceBinding -import io.github.wulkanowy.utils.description +import io.github.wulkanowy.utils.descriptionRes import io.github.wulkanowy.utils.isExcusableOrNotExcused import javax.inject.Inject @@ -36,7 +36,7 @@ class AttendanceAdapter @Inject constructor() : with(holder.binding) { attendanceItemNumber.text = item.number.toString() attendanceItemSubject.text = item.subject - attendanceItemDescription.setText(item.description) + attendanceItemDescription.setText(item.descriptionRes) attendanceItemAlert.visibility = item.run { if (absence && !excused) View.VISIBLE else View.INVISIBLE } attendanceItemNumber.visibility = View.GONE attendanceItemExcuseInfo.visibility = View.GONE diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt index d816d8f00..9b5c63e4c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceDialog.kt @@ -7,7 +7,7 @@ import android.view.ViewGroup import androidx.fragment.app.DialogFragment import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.databinding.DialogAttendanceBinding -import io.github.wulkanowy.utils.description +import io.github.wulkanowy.utils.descriptionRes import io.github.wulkanowy.utils.lifecycleAwareVariable import io.github.wulkanowy.utils.toFormattedString @@ -45,7 +45,7 @@ class AttendanceDialog : DialogFragment() { with(binding) { attendanceDialogSubjectValue.text = attendance.subject - attendanceDialogDescriptionValue.setText(attendance.description) + attendanceDialogDescriptionValue.setText(attendance.descriptionRes) attendanceDialogDateValue.text = attendance.date.toFormattedString() attendanceDialogNumberValue.text = attendance.number.toString() attendanceDialogClose.setOnClickListener { dismiss() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt index 26eca18fd..d0dfcd696 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/NotificationDebugPresenter.kt @@ -3,6 +3,8 @@ package io.github.wulkanowy.ui.modules.debug.notification import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.services.sync.notifications.ChangeTimetableNotification +import io.github.wulkanowy.services.sync.notifications.NewAttendanceNotification import io.github.wulkanowy.services.sync.notifications.NewConferenceNotification import io.github.wulkanowy.services.sync.notifications.NewExamNotification import io.github.wulkanowy.services.sync.notifications.NewGradeNotification @@ -13,6 +15,7 @@ import io.github.wulkanowy.services.sync.notifications.NewNoteNotification import io.github.wulkanowy.services.sync.notifications.NewSchoolAnnouncementNotification import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.ErrorHandler +import io.github.wulkanowy.ui.modules.debug.notification.mock.debugAttendanceItems import io.github.wulkanowy.ui.modules.debug.notification.mock.debugConferenceItems import io.github.wulkanowy.ui.modules.debug.notification.mock.debugExamItems import io.github.wulkanowy.ui.modules.debug.notification.mock.debugGradeDetailsItems @@ -22,6 +25,7 @@ import io.github.wulkanowy.ui.modules.debug.notification.mock.debugLuckyNumber import io.github.wulkanowy.ui.modules.debug.notification.mock.debugMessageItems import io.github.wulkanowy.ui.modules.debug.notification.mock.debugNoteItems import io.github.wulkanowy.ui.modules.debug.notification.mock.debugSchoolAnnouncementItems +import io.github.wulkanowy.ui.modules.debug.notification.mock.debugTimetableItems import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -37,6 +41,8 @@ class NotificationDebugPresenter @Inject constructor( private val newNoteNotification: NewNoteNotification, private val newSchoolAnnouncementNotification: NewSchoolAnnouncementNotification, private val newLuckyNumberNotification: NewLuckyNumberNotification, + private val changeTimetableNotification: ChangeTimetableNotification, + private val newAttendanceNotification: NewAttendanceNotification, ) : BasePresenter(errorHandler, studentRepository) { private val items = listOf( @@ -64,6 +70,12 @@ class NotificationDebugPresenter @Inject constructor( NotificationDebugItem(R.string.note_title) { n -> withStudent { newNoteNotification.notify(debugNoteItems.take(n), it) } }, + NotificationDebugItem(R.string.attendance_title) { n -> + withStudent { newAttendanceNotification.notify(debugAttendanceItems.take(n), it) } + }, + NotificationDebugItem(R.string.timetable_title) { n -> + withStudent { changeTimetableNotification.notify(debugTimetableItems.take(n), it) } + }, NotificationDebugItem(R.string.school_announcement_title) { n -> withStudent { newSchoolAnnouncementNotification.notify(debugSchoolAnnouncementItems.take(n), it) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/attendance.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/attendance.kt new file mode 100644 index 000000000..042cf07e7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/attendance.kt @@ -0,0 +1,35 @@ +package io.github.wulkanowy.ui.modules.debug.notification.mock + +import io.github.wulkanowy.data.db.entities.Attendance +import java.time.LocalDate + +val debugAttendanceItems = listOf( + generateAttendance("Matematyka", "PRESENCE"), + generateAttendance("Język angielski", "UNEXCUSED_LATENESS"), + generateAttendance("Geografia", "ABSENCE_UNEXCUSED"), + generateAttendance("Sieci komputerowe", "ABSENCE_EXCUSED"), + generateAttendance("Systemy operacyjne", "EXCUSED_LATENESS"), + generateAttendance("Język niemiecki", "ABSENCE_UNEXCUSED"), + generateAttendance("Biologia", "ABSENCE_UNEXCUSED"), + generateAttendance("Chemia", "ABSENCE_EXCUSED"), + generateAttendance("Fizyka", "ABSENCE_UNEXCUSED"), + generateAttendance("Matematyka", "ABSENCE_EXCUSED"), +) + +private fun generateAttendance(subject: String, name: String) = Attendance( + subject = subject, + studentId = 0, + diaryId = 0, + date = LocalDate.now(), + timeId = 0, + number = 1, + name = name, + presence = false, + absence = false, + exemption = false, + lateness = false, + excused = false, + deleted = false, + excusable = false, + excuseStatus = "" +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/timetable.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/timetable.kt new file mode 100644 index 000000000..428c001d6 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/debug/notification/mock/timetable.kt @@ -0,0 +1,39 @@ +package io.github.wulkanowy.ui.modules.debug.notification.mock + +import io.github.wulkanowy.data.db.entities.Timetable +import java.time.LocalDate +import java.time.LocalDateTime +import kotlin.random.Random + +val debugTimetableItems = listOf( + generateTimetable("Matematyka", "12", "01"), + generateTimetable("Język angielski", "23", "12"), + generateTimetable("Geografia", "34", "23"), + generateTimetable("Sieci komputerowe", "45", "34"), + generateTimetable("Systemy operacyjne", "56", "45"), + generateTimetable("Język niemiecki", "67", "56"), + generateTimetable("Biologia", "78", "67"), + generateTimetable("Chemia", "89", "78"), + generateTimetable("Fizyka", "90", "89"), + generateTimetable("Matematyka", "01", "90"), +) + +private fun generateTimetable(subject: String, room: String, roomOld: String) = Timetable( + subject = subject, + studentId = 0, + diaryId = 0, + date = LocalDate.now().minusDays(Random.nextLong(0, 8)), + number = 1, + start = LocalDateTime.now().plusHours(1), + end = LocalDateTime.now(), + subjectOld = "", + group = "", + room = room, + roomOld = roomOld, + teacher = "Wtorkowska Renata", + teacherOld = "", + info = "", + isStudentPlan = true, + changes = true, + canceled = true +) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt index e71fc0f6f..04e35d318 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/login/studentselect/LoginStudentSelectFragment.kt @@ -66,7 +66,14 @@ class LoginStudentSelectFragment : } override fun openMainView() { - activity?.let { startActivity(MainActivity.getStartIntent(context = it, clear = true)) } + activity?.let { + startActivity( + MainActivity.getStartIntent( + context = it, + startNewTask = true + ) + ) + } } override fun showProgress(show: Boolean) { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt index 49a199431..51585ac58 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/luckynumberwidget/LuckyNumberWidgetProvider.kt @@ -18,8 +18,8 @@ import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.data.repositories.LuckyNumberRepository import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.main.MainActivity -import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.toFirstResult import kotlinx.coroutines.runBlocking import timber.log.Timber @@ -39,6 +39,8 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { companion object { + const val LUCKY_NUMBER_PENDING_INTENT_ID = 200 + fun getStudentWidgetKey(appWidgetId: Int) = "lucky_number_widget_student_$appWidgetId" fun getThemeWidgetKey(appWidgetId: Int) = "lucky_number_widget_theme_$appWidgetId" @@ -48,18 +50,31 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { fun getWidthWidgetKey(appWidgetId: Int) = "lucky_number_widget_width_$appWidgetId" } - override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray?) { + override fun onUpdate( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetIds: IntArray? + ) { super.onUpdate(context, appWidgetManager, appWidgetIds) appWidgetIds?.forEach { appWidgetId -> + val luckyNumber = + getLuckyNumber(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId) + val appIntent = PendingIntent.getActivity( + context, + LUCKY_NUMBER_PENDING_INTENT_ID, + MainActivity.getStartIntent(context, Destination.LuckyNumber, true), + FLAG_UPDATE_CURRENT + ) - val luckyNumber = getLuckyNumber(sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0), appWidgetId) - val appIntent = PendingIntent.getActivity(context, MainView.Section.LUCKY_NUMBER.id, - MainActivity.getStartIntent(context, MainView.Section.LUCKY_NUMBER, true), FLAG_UPDATE_CURRENT) - - val remoteView = RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context)).apply { - setTextViewText(R.id.luckyNumberWidgetNumber, luckyNumber?.luckyNumber?.toString() ?: "#") - setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent) - } + val remoteView = + RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context)) + .apply { + setTextViewText( + R.id.luckyNumberWidgetNumber, + luckyNumber?.luckyNumber?.toString() ?: "#" + ) + setOnClickPendingIntent(R.id.luckyNumberWidgetContainer, appIntent) + } setStyles(remoteView, appWidgetId) appWidgetManager.updateAppWidget(appWidgetId, remoteView) @@ -78,7 +93,12 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { } } - override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle?) { + override fun onAppWidgetOptionsChanged( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetId: Int, + newOptions: Bundle? + ) { super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) val remoteView = RemoteViews(context.packageName, getCorrectLayoutId(appWidgetId, context)) @@ -88,8 +108,12 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { } private fun setStyles(views: RemoteViews, appWidgetId: Int, options: Bundle? = null) { - val width = options?.getInt(OPTION_APPWIDGET_MIN_WIDTH) ?: sharedPref.getLong(getWidthWidgetKey(appWidgetId), 74).toInt() - val height = options?.getInt(OPTION_APPWIDGET_MAX_HEIGHT) ?: sharedPref.getLong(getHeightWidgetKey(appWidgetId), 74).toInt() + val width = options?.getInt(OPTION_APPWIDGET_MIN_WIDTH) ?: sharedPref.getLong( + getWidthWidgetKey(appWidgetId), 74 + ).toInt() + val height = options?.getInt(OPTION_APPWIDGET_MAX_HEIGHT) ?: sharedPref.getLong( + getHeightWidgetKey(appWidgetId), 74 + ).toInt() with(sharedPref) { putLong(getWidthWidgetKey(appWidgetId), width.toLong()) @@ -112,7 +136,11 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { } } - private fun RemoteViews.setVisibility(imageTop: Boolean, imageLeft: Boolean, title: Boolean = false) { + private fun RemoteViews.setVisibility( + imageTop: Boolean, + imageLeft: Boolean, + title: Boolean = false + ) { setViewVisibility(R.id.luckyNumberWidgetImageTop, if (imageTop) VISIBLE else GONE) setViewVisibility(R.id.luckyNumberWidgetImageLeft, if (imageLeft) VISIBLE else GONE) setViewVisibility(R.id.luckyNumberWidgetTitle, if (title) VISIBLE else GONE) @@ -152,7 +180,8 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() { private fun getCorrectLayoutId(appWidgetId: Int, context: Context): Int { val savedTheme = sharedPref.getLong(getThemeWidgetKey(appWidgetId), 0) - val isSystemDarkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES + val isSystemDarkMode = + context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES return if (savedTheme == 1L || (savedTheme == 2L && isSystemDarkMode)) { R.layout.widget_luckynumber_dark diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt index d758ac0da..92bc36bc7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainActivity.kt @@ -5,16 +5,10 @@ import android.content.Context import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK -import android.content.pm.ShortcutInfo -import android.content.pm.ShortcutManager -import android.graphics.drawable.Icon -import android.os.Build import android.os.Build.VERSION_CODES.P import android.os.Bundle import android.view.Menu import android.view.MenuItem -import androidx.annotation.RequiresApi -import androidx.core.content.getSystemService import androidx.core.view.ViewCompat import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment @@ -29,20 +23,10 @@ import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.databinding.ActivityMainBinding +import io.github.wulkanowy.services.shortcuts.ShortcutsHelper import io.github.wulkanowy.ui.base.BaseActivity +import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.account.accountquick.AccountQuickDialog -import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment -import io.github.wulkanowy.ui.modules.conference.ConferenceFragment -import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment -import io.github.wulkanowy.ui.modules.exam.ExamFragment -import io.github.wulkanowy.ui.modules.grade.GradeFragment -import io.github.wulkanowy.ui.modules.homework.HomeworkFragment -import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment -import io.github.wulkanowy.ui.modules.message.MessageFragment -import io.github.wulkanowy.ui.modules.more.MoreFragment -import io.github.wulkanowy.ui.modules.note.NoteFragment -import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment -import io.github.wulkanowy.ui.modules.timetable.TimetableFragment import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.InAppReviewHelper @@ -75,6 +59,9 @@ class MainActivity : BaseActivity(), MainVie @Inject lateinit var appInfo: AppInfo + @Inject + lateinit var shortcutsHelper: ShortcutsHelper + private var accountMenu: MenuItem? = null private val overlayProvider by lazy { ElevationOverlayProvider(this) } @@ -83,15 +70,19 @@ class MainActivity : BaseActivity(), MainVie FragNavController(supportFragmentManager, R.id.main_fragment_container) companion object { - const val EXTRA_START_MENU = "extraStartMenu" + + private const val EXTRA_START_DESTINATION = "start_destination" fun getStartIntent( context: Context, - startMenu: MainView.Section? = null, - clear: Boolean = false + destination: Destination? = null, + startNewTask: Boolean = false ) = Intent(context, MainActivity::class.java).apply { - if (clear) flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK - startMenu?.let { putExtra(EXTRA_START_MENU, it.id) } + putExtra(EXTRA_START_DESTINATION, destination) + + if (startNewTask) { + flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK + } } } @@ -106,42 +97,21 @@ class MainActivity : BaseActivity(), MainVie override val currentViewSubtitle get() = (navController.currentFrag as? MainView.TitledView)?.subtitleString - override var startMenuIndex = 0 - - override var startMenuMoreIndex = -1 - - private val moreMenuFragments = mapOf( - MainView.Section.MESSAGE.id to MessageFragment.newInstance(), - MainView.Section.EXAM.id to ExamFragment.newInstance(), - MainView.Section.HOMEWORK.id to HomeworkFragment.newInstance(), - MainView.Section.NOTE.id to NoteFragment.newInstance(), - MainView.Section.CONFERENCE.id to ConferenceFragment.newInstance(), - MainView.Section.SCHOOL_ANNOUNCEMENT.id to SchoolAnnouncementFragment.newInstance(), - MainView.Section.LUCKY_NUMBER.id to LuckyNumberFragment.newInstance(), - ) + private var savedInstanceState: Bundle? = null @SuppressLint("NewApi") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(ActivityMainBinding.inflate(layoutInflater).apply { binding = this }.root) setSupportActionBar(binding.mainToolbar) + this.savedInstanceState = savedInstanceState messageContainer = binding.mainMessageContainer updateHelper.messageContainer = binding.mainFragmentContainer - val section = MainView.Section.values() - .singleOrNull { it.id == intent.getIntExtra(EXTRA_START_MENU, -1) } - - presenter.onAttachView(this, section) - - with(navController) { - initialize(startMenuIndex, savedInstanceState) - pushFragment(moreMenuFragments[startMenuMoreIndex]) - } - - if (appInfo.systemVersion >= Build.VERSION_CODES.N_MR1) { - initShortcuts() - } + val destination = intent.getSerializableExtra(EXTRA_START_DESTINATION) as Destination? + ?: shortcutsHelper.getDestination(intent) + presenter.onAttachView(this, destination) updateHelper.checkAndInstallUpdates(this) } @@ -157,54 +127,6 @@ class MainActivity : BaseActivity(), MainVie updateHelper.onActivityResult(requestCode, resultCode) } - @RequiresApi(Build.VERSION_CODES.N_MR1) - fun initShortcuts() { - val shortcutsList = mutableListOf() - - listOf( - Triple( - getString(R.string.grade_title), - R.drawable.ic_shortcut_grade, - MainView.Section.GRADE - ), - Triple( - getString(R.string.attendance_title), - R.drawable.ic_shortcut_attendance, - MainView.Section.ATTENDANCE - ), - Triple( - getString(R.string.exam_title), - R.drawable.ic_shortcut_exam, - MainView.Section.EXAM - ), - Triple( - getString(R.string.timetable_title), - R.drawable.ic_shortcut_timetable, - MainView.Section.TIMETABLE - ) - ).forEach { (title, icon, enum) -> - shortcutsList.add( - ShortcutInfo.Builder(applicationContext, title) - .setShortLabel(title) - .setLongLabel(title) - .setIcon(Icon.createWithResource(applicationContext, icon)) - .setIntents( - arrayOf( - Intent(applicationContext, MainActivity::class.java) - .setAction(Intent.ACTION_VIEW), - Intent(applicationContext, MainActivity::class.java) - .putExtra(EXTRA_START_MENU, enum.id) - .setAction(Intent.ACTION_VIEW) - .addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK) - ) - ) - .build() - ) - } - - getSystemService()?.dynamicShortcuts = shortcutsList - } - override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.action_menu_main, menu) accountMenu = menu?.findItem(R.id.mainMenuAccount) @@ -213,15 +135,38 @@ class MainActivity : BaseActivity(), MainVie return true } - @SuppressLint("NewApi") - override fun initView() { + override fun initView(startMenuIndex: Int, rootDestinations: List) { + initializeToolbar() + initializeBottomNavigation(startMenuIndex) + initializeNavController(startMenuIndex, rootDestinations) + } + + private fun initializeNavController(startMenuIndex: Int, rootDestinations: List) { + with(navController) { + setOnViewChangeListener { destinationView -> + presenter.onViewChange(destinationView) + analytics.setCurrentScreen( + this@MainActivity, + destinationView::class.java.simpleName + ) + } + fragmentHideStrategy = HIDE + rootFragments = rootDestinations.map { it.fragment } + + initialize(startMenuIndex, savedInstanceState) + } + } + + private fun initializeToolbar() { with(binding.mainToolbar) { stateListAnimator = null setBackgroundColor( overlayProvider.compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(4f)) ) } + } + private fun initializeBottomNavigation(startMenuIndex: Int) { with(binding.mainBottomNav) { with(menu) { add(Menu.NONE, 0, Menu.NONE, R.string.dashboard_title) @@ -239,36 +184,6 @@ class MainActivity : BaseActivity(), MainVie setOnItemSelectedListener { presenter.onTabSelected(it.itemId, false) } setOnItemReselectedListener { presenter.onTabSelected(it.itemId, true) } } - - with(navController) { - setOnViewChangeListener { section, name -> - if (section == MainView.Section.ACCOUNT || section == MainView.Section.STUDENT_INFO) { - binding.mainBottomNav.isVisible = false - - if (appInfo.systemVersion >= P) { - window.navigationBarColor = getThemeAttrColor(R.attr.colorSurface) - } - } else { - binding.mainBottomNav.isVisible = true - - if (appInfo.systemVersion >= P) { - window.navigationBarColor = - getThemeAttrColor(android.R.attr.navigationBarColor) - } - } - - analytics.setCurrentScreen(this@MainActivity, name) - presenter.onViewChange(section) - } - fragmentHideStrategy = HIDE - rootFragments = listOf( - DashboardFragment.newInstance(), - GradeFragment.newInstance(), - AttendanceFragment.newInstance(), - TimetableFragment.newInstance(), - MoreFragment.newInstance() - ) - } } override fun onPreferenceStartFragment( @@ -317,6 +232,22 @@ class MainActivity : BaseActivity(), MainVie ViewCompat.setElevation(binding.mainToolbar, if (show) dpToPx(4f) else 0f) } + override fun showBottomNavigation(show: Boolean) { + binding.mainBottomNav.isVisible = show + + if (appInfo.systemVersion >= P) { + window.navigationBarColor = if (show) { + getThemeAttrColor(android.R.attr.navigationBarColor) + } else { + getThemeAttrColor(R.attr.colorSurface) + } + } + } + + override fun openMoreDestination(destination: Destination) { + pushView(destination.fragment) + } + override fun notifyMenuViewReselected() { (navController.currentStack?.getOrNull(0) as? MainView.MainChildView)?.onFragmentReselected() } @@ -373,6 +304,6 @@ class MainActivity : BaseActivity(), MainVie override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) navController.onSaveInstanceState(outState) - intent.removeExtra(EXTRA_START_MENU) + intent.removeExtra(EXTRA_START_DESTINATION) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt index 4805b5a1c..0aca51afe 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainPresenter.kt @@ -6,10 +6,15 @@ import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.ErrorHandler -import io.github.wulkanowy.ui.modules.main.MainView.Section.GRADE -import io.github.wulkanowy.ui.modules.main.MainView.Section.MESSAGE -import io.github.wulkanowy.ui.modules.main.MainView.Section.SCHOOL +import io.github.wulkanowy.ui.modules.Destination +import io.github.wulkanowy.ui.modules.account.AccountView +import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsView +import io.github.wulkanowy.ui.modules.grade.GradeView +import io.github.wulkanowy.ui.modules.message.MessageView +import io.github.wulkanowy.ui.modules.schoolandteachers.school.SchoolView +import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoView import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.flowWithResource import kotlinx.coroutines.flow.onEach @@ -27,19 +32,40 @@ class MainPresenter @Inject constructor( private var studentsWitSemesters: List? = null - fun onAttachView(view: MainView, initMenu: MainView.Section?) { - super.onAttachView(view) - view.apply { - getProperViewIndexes(initMenu).let { (main, more) -> - startMenuIndex = main - startMenuMoreIndex = more + private val rootDestinationTypeList = listOf( + Destination.Type.DASHBOARD, + Destination.Type.GRADE, + Destination.Type.ATTENDANCE, + Destination.Type.TIMETABLE, + Destination.Type.MORE + ) + + private val Destination?.startMenuIndex + get() = when { + this == null -> prefRepository.startMenuIndex + type in rootDestinationTypeList -> { + rootDestinationTypeList.indexOf(type) } - initView() - Timber.i("Main view was initialized with $startMenuIndex menu index and $startMenuMoreIndex more index") + else -> 4 + } + + fun onAttachView(view: MainView, initDestination: Destination?) { + super.onAttachView(view) + + val startMenuIndex = initDestination.startMenuIndex + val destinations = rootDestinationTypeList.map { + if (it == initDestination?.type) initDestination else it.defaultDestination + } + + view.initView(startMenuIndex, destinations) + if (initDestination != null && startMenuIndex == 4) { + view.openMoreDestination(initDestination) } syncManager.startPeriodicSyncWorker() - analytics.logEvent("app_open", "destination" to initMenu?.name) + + analytics.logEvent("app_open", "destination" to initDestination.toString()) + Timber.i("Main view was initialized with $initDestination") } fun onActionMenuCreated() { @@ -64,9 +90,10 @@ class MainPresenter @Inject constructor( }.launch("avatar") } - fun onViewChange(section: MainView.Section?) { + fun onViewChange(destinationView: BaseView) { view?.apply { - showActionBarElevation(section != GRADE && section != MESSAGE && section != SCHOOL) + showBottomNavigation(destinationView !is AccountView && destinationView !is StudentInfoView && destinationView !is AccountDetailsView) + showActionBarElevation(destinationView !is GradeView && destinationView !is MessageView && destinationView !is SchoolView) currentViewTitle?.let { setViewTitle(it) } currentViewSubtitle?.let { setViewSubTitle(it.ifBlank { null }) } currentStackSize?.let { @@ -134,10 +161,4 @@ class MainPresenter @Inject constructor( view?.showStudentAvatar(currentStudent) } - - private fun getProperViewIndexes(initMenu: MainView.Section?) = when (initMenu?.id) { - in 0..3 -> initMenu!!.id to -1 - in 4..100 -> 4 to initMenu!!.id - else -> prefRepository.startMenuIndex to -1 - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt index 8851f5878..3a57fcc6b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainView.kt @@ -3,13 +3,10 @@ package io.github.wulkanowy.ui.modules.main import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.StudentWithSemesters import io.github.wulkanowy.ui.base.BaseView +import io.github.wulkanowy.ui.modules.Destination interface MainView : BaseView { - var startMenuIndex: Int - - var startMenuMoreIndex: Int - val isRootView: Boolean val currentViewTitle: String? @@ -18,7 +15,7 @@ interface MainView : BaseView { val currentStackSize: Int? - fun initView() + fun initView(startMenuIndex: Int, rootDestinations: List) fun switchMenuView(position: Int) @@ -28,6 +25,8 @@ interface MainView : BaseView { fun showActionBarElevation(show: Boolean) + fun showBottomNavigation(show: Boolean) + fun notifyMenuViewReselected() fun notifyMenuViewChanged() @@ -42,6 +41,8 @@ interface MainView : BaseView { fun showInAppReview() + fun openMoreDestination(destination: Destination) + interface MainChildView { fun onFragmentReselected() @@ -57,25 +58,4 @@ interface MainView : BaseView { get() = "" set(_) {} } - - enum class Section { - DASHBOARD, - GRADE, - ATTENDANCE, - TIMETABLE, - MORE, - MESSAGE, - EXAM, - HOMEWORK, - NOTE, - CONFERENCE, - SCHOOL_ANNOUNCEMENT, - SCHOOL, - LUCKY_NUMBER, - ACCOUNT, - STUDENT_INFO, - SETTINGS; - - val id get() = ordinal - } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterAdapter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterAdapter.kt index 7ee326f8f..27b3637ad 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterAdapter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterAdapter.kt @@ -5,7 +5,6 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Notification import io.github.wulkanowy.databinding.ItemNotificationsCenterBinding import io.github.wulkanowy.services.sync.notifications.NotificationType @@ -28,26 +27,12 @@ class NotificationsCenterAdapter @Inject constructor() : notificationsCenterItemTitle.text = item.title notificationsCenterItemContent.text = item.content notificationsCenterItemDate.text = item.date.toFormattedString("HH:mm, d MMM") - notificationsCenterItemIcon.setImageResource(item.type.toDrawableResId()) + notificationsCenterItemIcon.setImageResource(item.type.icon) root.setOnClickListener { onItemClickListener(item.type) } } } - private fun NotificationType.toDrawableResId() = when (this) { - NotificationType.NEW_CONFERENCE -> R.drawable.ic_more_conferences - NotificationType.NEW_EXAM -> R.drawable.ic_main_exam - NotificationType.NEW_GRADE_DETAILS -> R.drawable.ic_stat_grade - NotificationType.NEW_GRADE_PREDICTED -> R.drawable.ic_stat_grade - NotificationType.NEW_GRADE_FINAL -> R.drawable.ic_stat_grade - NotificationType.NEW_HOMEWORK -> R.drawable.ic_more_homework - NotificationType.NEW_LUCKY_NUMBER -> R.drawable.ic_stat_luckynumber - NotificationType.NEW_MESSAGE -> R.drawable.ic_stat_message - NotificationType.NEW_NOTE -> R.drawable.ic_stat_note - NotificationType.NEW_ANNOUNCEMENT -> R.drawable.ic_all_about - NotificationType.PUSH -> R.drawable.ic_stat_all - } - class ViewHolder(val binding: ItemNotificationsCenterBinding) : RecyclerView.ViewHolder(binding.root) @@ -59,4 +44,4 @@ class NotificationsCenterAdapter @Inject constructor() : override fun areItemsTheSame(oldItem: Notification, newItem: Notification) = oldItem.id == newItem.id } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt index b9bfb447e..f3bbc42de 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/notificationscenter/NotificationsCenterFragment.kt @@ -11,6 +11,7 @@ import io.github.wulkanowy.data.db.entities.Notification import io.github.wulkanowy.databinding.FragmentNotificationsCenterBinding import io.github.wulkanowy.services.sync.notifications.NotificationType import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment import io.github.wulkanowy.ui.modules.conference.ConferenceFragment import io.github.wulkanowy.ui.modules.exam.ExamFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment @@ -21,6 +22,7 @@ import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment +import io.github.wulkanowy.ui.modules.timetable.TimetableFragment import javax.inject.Inject @AndroidEntryPoint @@ -104,5 +106,7 @@ class NotificationsCenterFragment : NotificationType.NEW_NOTE -> NoteFragment.newInstance() NotificationType.NEW_ANNOUNCEMENT -> SchoolAnnouncementFragment.newInstance() NotificationType.PUSH -> null + NotificationType.CHANGE_TIMETABLE -> TimetableFragment.newInstance() + NotificationType.NEW_ATTENDANCE -> AttendanceFragment.newInstance() } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt index 2612fab3a..d56cdfa7c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsFragment.kt @@ -6,7 +6,7 @@ import io.github.wulkanowy.R import io.github.wulkanowy.ui.modules.main.MainView import timber.log.Timber -class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView { +class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView, SettingsView { companion object { @@ -19,4 +19,16 @@ class SettingsFragment : PreferenceFragmentCompat(), MainView.TitledView { setPreferencesFromResource(R.xml.scheme_preferences, rootKey) Timber.i("Settings view was initialized") } + + override fun showError(text: String, error: Throwable) {} + + override fun showMessage(text: String) {} + + override fun showExpiredDialog() {} + + override fun openClearLoginView() {} + + override fun showErrorDetailsDialog(error: Throwable) {} + + override fun showChangePasswordSnackbar(redirectUrl: String) {} } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt new file mode 100644 index 000000000..79f91bc5d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/settings/SettingsView.kt @@ -0,0 +1,5 @@ +package io.github.wulkanowy.ui.modules.settings + +import io.github.wulkanowy.ui.base.BaseView + +interface SettingsView : BaseView \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt index f9079b5f9..63fc84962 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetablewidget/TimetableWidgetProvider.kt @@ -24,8 +24,8 @@ import io.github.wulkanowy.data.exceptions.NoCurrentStudentException import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.services.HiltBroadcastReceiver import io.github.wulkanowy.services.widgets.TimetableWidgetService +import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.main.MainActivity -import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.capitalise import io.github.wulkanowy.utils.createNameInitialsDrawable @@ -60,6 +60,8 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { companion object { + private const val TIMETABLE_PENDING_INTENT_ID = 201 + private const val EXTRA_TOGGLED_WIDGET_ID = "extraToggledWidget" private const val EXTRA_BUTTON_TYPE = "extraButtonType" @@ -174,8 +176,8 @@ class TimetableWidgetProvider : HiltBroadcastReceiver() { ) val appIntent = PendingIntent.getActivity( context, - MainView.Section.TIMETABLE.id, - MainActivity.getStartIntent(context, MainView.Section.TIMETABLE, true), + TIMETABLE_PENDING_INTENT_ID, + MainActivity.getStartIntent(context, Destination.Timetable(), true), FLAG_UPDATE_CURRENT ) diff --git a/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt index b89ad57d3..397c95953 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt @@ -29,7 +29,7 @@ private fun calculatePercentage(presence: Double, absence: Double): Double { return if ((presence + absence) == 0.0) 0.0 else (presence / (presence + absence)) * 100 } -inline val Attendance.description +inline val Attendance.descriptionRes get() = when (AttendanceCategory.getCategoryByName(name)) { AttendanceCategory.PRESENCE -> R.string.attendance_present AttendanceCategory.ABSENCE_UNEXCUSED -> R.string.attendance_absence_unexcused diff --git a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt index 2cd4459eb..ecd982a18 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/ContextExtension.kt @@ -18,6 +18,7 @@ import androidx.annotation.AttrRes import androidx.annotation.ColorInt import androidx.annotation.ColorRes import androidx.annotation.DrawableRes +import androidx.annotation.PluralsRes import androidx.core.content.ContextCompat import androidx.core.graphics.ColorUtils import androidx.core.graphics.applyCanvas @@ -57,6 +58,9 @@ fun Context.getCompatDrawable(@DrawableRes drawableRes: Int, @ColorRes colorRes: fun Context.getCompatBitmap(@DrawableRes drawableRes: Int, @ColorRes colorRes: Int) = getCompatDrawable(drawableRes, colorRes)?.toBitmap() +fun Context.getPlural(@PluralsRes pluralRes: Int, quantity: Int, vararg arguments: Any) = + resources.getQuantityString(pluralRes, quantity, *arguments) + fun Context.openInternetBrowser(uri: String, onActivityNotFound: (uri: String) -> Unit = {}) { Intent.parseUri(uri, 0).let { try { diff --git a/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt index 9dc1e18a0..01c876dd2 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/FragNavControlerExtension.kt @@ -2,16 +2,19 @@ package io.github.wulkanowy.utils import androidx.fragment.app.Fragment import com.ncapdevi.fragnav.FragNavController -import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.ui.base.BaseView -inline fun FragNavController.setOnViewChangeListener(crossinline listener: (section: MainView.Section?, name: String?) -> Unit) { +inline fun FragNavController.setOnViewChangeListener(crossinline listener: (view: BaseView) -> Unit) { transactionListener = object : FragNavController.TransactionListener { - override fun onFragmentTransaction(fragment: Fragment?, transactionType: FragNavController.TransactionType) { - listener(fragment?.toSection(), fragment?.let { it::class.java.simpleName }) + override fun onFragmentTransaction( + fragment: Fragment?, + transactionType: FragNavController.TransactionType + ) { + fragment?.let { listener(it as BaseView) } } override fun onTabTransaction(fragment: Fragment?, index: Int) { - listener(fragment?.toSection(), fragment?.let { it::class.java.simpleName }) + fragment?.let { listener(it as BaseView) } } } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt deleted file mode 100644 index 210a62090..000000000 --- a/app/src/main/java/io/github/wulkanowy/utils/FragmentExtension.kt +++ /dev/null @@ -1,44 +0,0 @@ -package io.github.wulkanowy.utils - -import androidx.fragment.app.Fragment -import io.github.wulkanowy.ui.modules.account.AccountFragment -import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment -import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment -import io.github.wulkanowy.ui.modules.conference.ConferenceFragment -import io.github.wulkanowy.ui.modules.dashboard.DashboardFragment -import io.github.wulkanowy.ui.modules.exam.ExamFragment -import io.github.wulkanowy.ui.modules.grade.GradeFragment -import io.github.wulkanowy.ui.modules.homework.HomeworkFragment -import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment -import io.github.wulkanowy.ui.modules.main.MainView -import io.github.wulkanowy.ui.modules.message.MessageFragment -import io.github.wulkanowy.ui.modules.more.MoreFragment -import io.github.wulkanowy.ui.modules.note.NoteFragment -import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment -import io.github.wulkanowy.ui.modules.schoolannouncement.SchoolAnnouncementFragment -import io.github.wulkanowy.ui.modules.settings.SettingsFragment -import io.github.wulkanowy.ui.modules.studentinfo.StudentInfoFragment -import io.github.wulkanowy.ui.modules.timetable.TimetableFragment - -fun Fragment.toSection(): MainView.Section? { - return when (this) { - is GradeFragment -> MainView.Section.GRADE - is AttendanceFragment -> MainView.Section.ATTENDANCE - is ExamFragment -> MainView.Section.EXAM - is TimetableFragment -> MainView.Section.TIMETABLE - is MoreFragment -> MainView.Section.MORE - is MessageFragment -> MainView.Section.MESSAGE - is HomeworkFragment -> MainView.Section.HOMEWORK - is NoteFragment -> MainView.Section.NOTE - is LuckyNumberFragment -> MainView.Section.LUCKY_NUMBER - is SettingsFragment -> MainView.Section.SETTINGS - is SchoolAndTeachersFragment -> MainView.Section.SCHOOL - is AccountFragment -> MainView.Section.ACCOUNT - is AccountDetailsFragment -> MainView.Section.ACCOUNT - is StudentInfoFragment -> MainView.Section.STUDENT_INFO - is ConferenceFragment -> MainView.Section.CONFERENCE - is SchoolAnnouncementFragment -> MainView.Section.SCHOOL_ANNOUNCEMENT - is DashboardFragment -> MainView.Section.DASHBOARD - else -> null - } -} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1e88ee1ad..008069562 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -163,6 +163,26 @@ Now: %s Next: %s Later: %s + %1$s lesson %2$d - %3$s + Change of room from %1$s to %2$s + Change of teacher from %1$s to %2$s + Change of subject from %1$s to %2$s + + Timetable change + Timetable changes + + + %1$s - %2$d change in timetable + %1$s - %2$d changes in timetable + + + %1$d change in timetable + %1$d changes in timetable + + + %d change + %d changes + @@ -200,6 +220,18 @@ Excuse z powodu Dzień dobry,\nProszę o usprawiedliwienie mojego dziecka w dniu %s z lekcji %s%s%s.\n\nPozdrawiam. + + New attendance + New attendance + + + %1$d new attendance + %1$d attendance + + + %d attendance + %d attendance + @@ -706,6 +738,8 @@ Push notifications Upcoming lessons Debug + Timetable change + New attendance diff --git a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt index 2c44e9fad..7557d745a 100644 --- a/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt +++ b/app/src/test/java/io/github/wulkanowy/ui/modules/main/MainPresenterTest.kt @@ -42,20 +42,12 @@ class MainPresenterTest { MockKAnnotations.init(this) clearMocks(mainView) - every { mainView.startMenuIndex = any() } just Runs - every { mainView.startMenuMoreIndex = any() } just Runs - every { mainView.startMenuIndex } returns 1 - every { mainView.startMenuMoreIndex } returns 1 - every { mainView.initView() } just Runs - presenter = MainPresenter(errorHandler, studentRepository, prefRepository, syncManager, analytics) + every { mainView.initView(any(), any()) } just Runs + presenter = + MainPresenter(errorHandler, studentRepository, prefRepository, syncManager, analytics) presenter.onAttachView(mainView, null) } - @Test - fun initMenuTest() { - verify { mainView.initView() } - } - @Test fun onTabSelectedTest() { every { mainView.notifyMenuViewChanged() } just Runs